Patching ssh-keygen in OpenSSH

Turns out removing a passphrase from a OpenSSH private key in PowerShell automation scripts on Windows is difficult.

I’m a fan of building golden images for deployment and I like to use Packer as one of my tools. When you want to build a Windows image you will most likely want to use Powershell for scripting.

I scripted all of the Windows image automation in Powershell and one of the steps required to remove a passphrase from a OpenSSH private key.

This is where I hit a snag. Consider the following snippet:

1Start-Process -FilePath "C:\Program Files\OpenSSH\ssh-keygen.exe" -ArgumentList ("-p", "-f", "C:\Users\Administrator\.ssh\id_rsa", "-P", "<current passphrase>", "-N", "")

The last argument, which is the new passphrase is empty.

Start-Process : Cannot validate argument on parameter 'ArgumentList'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command
again.

This command doesn’t work. I also tried other approaches - including an attempt to control the ssh-keygen stdin. Perhaps I could have also tried making a classic batch file and work around this limitation, but I really don’t like hacks like that.

The bottom of this issue is between what Powershell requires and what OpenSSH ssh-keygen expects.

Powershell simply doesn’t allow empty or null strings in the Start-Process (and others) ArgumentList parameter arrays. OpenSSH requires the ssh-keygen -N "" to remove the passphrase from the private key.

I went and searched for OpenSSH source code and found the main repository for OpenBSD and a GitHub repository for portable versions. I figured I should attempt to make the change in the portable repository.

I found the code responsible for the passphrase changes. The core of the issue is the code just checks if the pointer to the new passphrase is NULL. It can never pass if the parameter value isn’t there - the pointer will always be NULL.

Based on many previous interactions and experience I’ve determined the solution with least impact on existing functionality is introducing a new switch that will just use an empty string as the new passphrase.

Consider the following excerpt from the patch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -107,6 +107,9 @@ static char *identity_passphrase = NULL;
 /* This is set to the new passphrase if given on the command line. */
 static char *identity_new_passphrase = NULL;
 
+/* This is the empty passphrase. */
+static char *identity_empty_passphrase = "";
+

@@ -3444,6 +3447,9 @@ main(int argc, char **argv)
 		case 'N':
 			identity_new_passphrase = optarg;
 			break;
+		case 'G':
+			identity_new_passphrase = identity_empty_passphrase;
+			break;
 		case 'Q':
 			check_krl = 1;
 			break;
-- 

I introduced a new static variable identity_empty_passphrase initialized to an empty string. This is non-NULL by default. Next when the new ‘G’ option is invoked the pointer to this variable is used as the identity_new_passphrase value.

Now the following command will get the passphrase removed:

1Start-Process -FilePath "C:\Program Files\OpenSSH\ssh-keygen.exe" -ArgumentList ("-p", "-f", "C:\Users\Administrator\.ssh\id_rsa", "-P", "<current passphrase>", "-G")

I also updated the manual page with the new switch in the full patch. Here’s the pull request on GitHub.

I like solving real problems and open source is a great space for practice.