0

Question is simple:

What are the (private) key types and (their specification - format, syntax) accepted by current openSSH?

The vague answer "the keys generated by ssh-keygen" is not accepted - I know it. They are called PEM with such headers (RSA, DSA, ECDSA variations):

-----BEGIN RSA PRIVATE KEY-----

The other format is described in PROTOCOL.key and is extension by openssh. It is also used as a default for Ed25519 keys, as stated in manual and (regardless passphrase) marked with header:

-----BEGIN OPENSSH PRIVATE KEY-----

There are also legacy RSA1 keys that should be gone today with SSHv1 protocol, but can be identified by the header:

SSH PRIVATE KEY FILE FORMAT 1.1

Are there any others? I am searching for more verbose description of other keys. I didn't find it (so far) explicitly specified in any manual page nor in any RFC for SSH.

The bonus question (not needed to answer, but I would appreciate the insight):

What is the reason behind (openssh) decoding every dummy file and asking for a passphrase, even if it does not have the proper header?


Footnote: Question was originally posted on openssh-unix-dev list, but so far without any answer so I am trying this awesome community, if there is somebody able to answer it.

7
  • What do you mean by 'header'? The -----BEGIN and -----ENDstrings are merely encapsulation boundaries (EB) with an included comment. The actual info is found in the base64 serialized structure in between. Without having checked the source code, I'd guess the text in the EBs is ignored by openssh that only tries to decode whatever is in between. The ciphername and KDF info should indicate what are types of the actual keys stored in the structure. I'd also guess these are open to future values and there's no fixed list.
    – forcefsck
    Commented Dec 28, 2015 at 1:02
  • Yes. By header I meant the EB, as you corrected me. If I am right from my reading code in past, the first guess is based on the filename, then these EB's and if all the previous fails, it tries to decode it somehow. The details are still unknown for me because I was too lazy to go so deeper in the code, but there will not probably be any other option. The openssh key is documented, them PEM has also RFC specification and it is probably all. If openssh reads something that does not make sense or is not base64, it should probably error out, shouldn't it?
    – Jakuje
    Commented Dec 28, 2015 at 9:54
  • Could you add in the description exactly what your trying and how you test it? Details should appear in debug/verbose modes. - Anyway - You can take a look at sshkey.c. In sshkey_parse_private_fileblob_type(), you can see all the supported key types (for now). If the key type is unspecified, sshkey_parse_private2() will do all the checks you're expecting it to do, but if it fails and OpenSSL is enabled, sshkey_parse_private_pem_fileblob() is called and will use OpenSSL's PEM_read_bio_PrivateKey() to parse the data and if that fails too, it assumes the wrong passphrase was given.
    – forcefsck
    Commented Dec 28, 2015 at 13:00
  • I am most interested in the "bonus question" as posted above. Minimal testing case is just ssh-add <(), which repetitively asks for passphrase for empty file. It is at least weird. I got with my code inspection to the PEM_read_bio_PrivateKey(), which was for me black box. As I browse through man page I see the only result from this function is NULL, regardless the reason (parse error? or wrong passphrase, else?). This is probably the root cause of described behaviour. If I am right, there should be some way of getting the reason of failure through the api and decide regarding to that.
    – Jakuje
    Commented Dec 28, 2015 at 21:13
  • It all gets done here https://github.com/openssl/openssl/blob/master/crypto/pem/pem_lib.c, where PEM_read_bio() reads the file data and makes the necessary checks, for example it registers a failure reason PEM_R_NO_START_LINE when the first read returns no bytes. Is it possible to read this error in openssh::sshkey.c? Not sure. - Anyway, the current behaviour is explained. Is the question now if the implementation can change to return a failure reason, thus becoming a bit more user friendly?
    – forcefsck
    Commented Dec 29, 2015 at 22:39

1 Answer 1

2

TL;DR

The most helpful comments from @forcefsck. Unfortunately he didn't fill the answer so I was unable to award the bounty. In short, the answer is PEM + RSA1 + new openSSH format which is described in the question and the main problem was with PEM.

The long one & Bonus

OpenSSH is using parser from openSSL (PEM_read_bio_PrivateKey()), which has the only return value for all the failures (NULL) and if it fails, openSSH expects it was because of wrong passphrase.

Without OpenSSL

I just tried to build OpenSSH without OpenSSL support (--without-openssl configure option) and the behaviour is "correct":

# ./ssh-add <(echo "")
Error loading key "/dev/fd/63": invalid format

Fix with OpenSSL

The other thing is how to fix it. The poke comes from ERR_get_error() function and their friends which should allow us to distinguish between different errors.

Wrong passphrase errors

# ./ssh-add /tmp/rsa
140480353842840:error:0906A068:lib(9):func(106):reason(104):pem_lib.c:457:
Enter passphrase for /tmp/rsa: 
140480353842840:error:06065064:lib(6):func(101):reason(100):evp_enc.c:592:
140480353842840:error:0906A065:lib(9):func(106):reason(101):pem_lib.c:482:

Reasons: PEM_R_BAD_PASSWORD_READ, PEM_R_BAD_BASE64_DECODE, PEM_R_BAD_DECRYPT.

Parse error codes

# ./ssh-add <(echo "")
139656018548376:error:0906D06C:lib(9):func(109):reason(108):pem_lib.c:701:Expecting: ANY PRIVATE KEY

or this:

140654301202072:error:0906D066:lib(9):func(109):reason(102):pem_lib.c:809:

Reason: PEM_R_NO_START_LINE, PEM_R_BAD_END_LINE, but there might be more possibilities.

Solution?

Adding some more checks for OpenSSL errors should give us an option to choose which error we want to mark as "format" error and which is "bad passphrase". This is located in function sshkey_parse_private_pem_fileblob() in sshkey.c on line around 3800.

unsigned long e = ERR_get_error();
if (ERR_GET_REASON(e) == PEM_R_NO_START_LINE ||
    ERR_GET_REASON(e) == PEM_R_BAD_END_LINE) {
        r = SSH_ERR_INVALID_FORMAT;
} else {
        r = SSH_ERR_KEY_WRONG_PASSPHRASE;
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .