Paul Tagliamonte
2023-Feb-10 04:14 UTC
How should SSHSIG/ssh-keygen handle signatures by a Certificate
Heyya openssh-unix-dev, Apologies in advance for what may be the weirdest email of the year so far on this list (a low bar, to be fair). I've been playing a bit with OpenSSH SSHKEYs and operationalizing them with toy proofs of concepts, trying to better understand limits on how to properly use them and design real code accordingly. As such, I've hit a boundary condition that I would love to get some feedback from the list on. I've constructed an SSHSIG signature over a simple file ('text' namespace), but using an SSH Certificate, rather than a plain public key. Some example files can be found at the bottom[examples]. My questions going in: 1. Will it accept an SSH certificate at all? 2. If it does, will it accept the underlying Public Key too? 3. What happens if my principals in the SSHSIG principals file don't match in the ssh certificate 4. What happens if the certificate is expired? SSHSIG has no 'time of signature', so how do we even know? Now? The answers according to OpenSSH 9.1 / git 1. Yes! 2. No! 3/4. No validation on the Certificate is done I have *no* idea what the best way to solve this is, but I see a few possible options I'd love to get some feedback from the list on: 1. Can anyone think of a valid reason *at all* to accept SSH Certificates in an SSHSIG? IF YES: How are you validating? This would likely require custom validation to check the Certificate, or generate some sort of Principals file in a real-time way using all known Certificates issued by the CA. What is the use-case? Is ssh-keygen working in a way you're happy with? If I were forced to make it work, I'd dump all the issued Certificates by a CA, and list them in the principals file with matching principals and valid-* constraints matching the issued Certificate exactly. Maybe even do it by CA keys, and apend certs to the file as I see them? I'm not sure. I'm having a hard time with this one. How do you know what the signature time is? IF NO: Should we discourage this construction in the SSHSIG spec and add a note to implementers to avoid validating this pattern, or creating such signatures? I don't know if this is a MAY NOT, SHOULD NOT or MUST NOT situation if it becomes discouraged. My gut tells me I would likely do this is a way that looks like SHOULD NOT, since I guess it *is* possible to do this on your own if you implement the SSHSIG spec yourself, and validate without using the principals file, rather trusting signatures by seeding the trusted CAs. Either way, this brings up the next question: 2. Can anyone think of a valid reason *in ssh-keygen* to accept SSH Certificates in an SSHSIG? IF YES: How should the logic get cleaned up (if at all)? - at minimum, I'd expect the time to get validated since everything else was provided explicitly by the user in the principals file. I patched ssh-keygen locally[1] to play with how hard this would be, it wasn't bad but it did require some duplicate logic (as originally seen in 'sshkey_cert_check_authority') on the validity time. I don't like this patch and it shouldn't be accepted, as-is, mostly because I think there's a *lot* more work than this involved, and a few other codepaths for validation in 'sig_*' that aren't included with that diff. Just there for discussion more than anything else. Relatedly, I have *no* idea what the expected behavior becomes when you set valid-after=timestamp / valid-before=timestamp in the principals file *in addition* to the Certificate having a valid-before/valid-after. I suspect all constrants would have to be met (which is to say, it becomes the latest valid-after and earliest valid-before of all constraints in the cert and principals file combined) Even if we fix the valid time stuff, I have no idea what to do about the principals. The only thing that comes to mind is to ensure that the principal passed to 'sig_verify' is in the certificate principals in addition to the principals file ssh-genkey was given (so wildcards behave as expected, etc). I'm reluctant to validate the Certificate's signing key against the sshsig principals file (by some creative use of 'sshkey_cert_check_authority'), since they're not trusted SSH CAs, so trusting their Certificates seems like a mistake to me. Perhaps pulling the public key out of the Certificate in the SSHSIG and using that to validate? Smells like a dangerous pattern, since there's a lot of constrants on the key usage (principals, validity, usages, key type, extensions -- at minimum) that would get dropped for validation [intentionally this time]. What do others think here? IF NO: I patched ssh-keygen locally to play with this[2]. This would allow such constructions to exist, and even makes no effort to stop someone who's tricked ssh-keygen to create such signatures (I generated these signatures by hand, I'm not sure how to get ssh-keygen to create them yet - although I'm sure it's possible, I'm just not a smart man), but it will give the user an error when *validating* the Certificates. Hopefully this check wouldn't impact anyone in the wild? If so I would love to learn a bit more about that use case. Example CLI outputs: Certificate (as seen in 'ssh-keygen -L') Type: ssh-ed25519-cert-v01 at openssh.com user certificate Public key: ED25519-CERT SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU Signing CA: ED25519 SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU (using ssh-ed25519) Key ID: "test key" Serial: 1 Valid: from 2023-01-19T09:51:58 to 2023-01-19T09:52:28 Principals: me at mydomain.fqdn Critical Options: (none) Extensions: (none) Current OpenSSH as provided by my OS: ``` $ ssh-keygen -Y verify -I test2 -f principals -n text -s hello.asc < hello Good "text" signature for test2 with ED25519-CERT key SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU ``` As-is using the underlying ed25519 public key from the Certificate (which is to say the whole Certificate needs to be in the principals file, not the public key from the Certificate, tbh - as expected - this is likely the least surprising one). ``` $ ssh-keygen -Y verify -I test1 -f principals -n text -s hello.asc < hello Could not verify signature. ``` With the verify-time patch: ``` $ ssh-keygen -Y verify -O verify-time=20230119095000 -I test2 -f principals -n text -s hello.asc < hello Could not verify signature. $ ssh-keygen -Y verify -O verify-time=20230119095200 -I test2 -f principals -n text -s hello.asc < hello Good "text" signature for test2 with ED25519-CERT key SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU $ ssh-keygen -Y verify -O verify-time=20230119095900 -I test2 -f principals -n text -s hello.asc < hello Could not verify signature. ``` With the deny Certificates patch: ``` $ ssh-keygen -Y verify -I test2 -f principals -n text -s hello.asc < hello sig_verify: ssh certificates are not supported in sshsig Could not verify signature. ``` [1]: verify-time patch ``` diff --git a/ssh-keygen.c b/ssh-keygen.c index ae05440f..716e9486 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -2815,6 +2815,18 @@ sig_verify(const char *signature, const char *sig_namespace, } } + if (verify_time && sshkey_is_cert(sign_key)) { + /* If -O verify-time is set, validate the Certificate too */ + if (verify_time <= sign_key->cert->valid_after) { + debug3_fr(r, "Certificate invalid: not yet valid"); + goto done; + } + if (verify_time >= sign_key->cert->valid_before) { + debug3_fr(r, "Certificate invalid: expired"); + goto done; + } + } + if (allowed_keys != NULL && (r = sshsig_check_allowed_keys(allowed_keys, sign_key, principal, sig_namespace, verify_time)) != 0) { debug3_fr(r, "sshsig_check_allowed_keys"); ``` [2]: deny Certificates ``` diff --git a/ssh-keygen.c b/ssh-keygen.c index ae05440f..ce1e1c29 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -2815,6 +2815,11 @@ sig_verify(const char *signature, const char *sig_namespace, } } + if (sshkey_is_cert(sign_key)) { + error_f("ssh certificates are not supported in sshsig"); + goto done; + } + if (allowed_keys != NULL && (r = sshsig_check_allowed_keys(allowed_keys, sign_key, principal, sig_namespace, verify_time)) != 0) { debug3_fr(r, "sshsig_check_allowed_keys"); @@ -2883,6 +2888,10 @@ sig_find_principals(const char *signature, const char *allowed_keys, error_fr(r, "sshsig_get_pubkey"); goto done; } + if (sshkey_is_cert(sign_key)) { + error_f("ssh certificates are not supported in sshsig"); + goto done; + } if ((r = sshsig_find_principals(allowed_keys, sign_key, verify_time, &principals)) != 0) { if (r != SSH_ERR_KEY_NOT_FOUND) ``` [examples]: 'hello' file: ``` no one actually looks at this do they ``` 'hello.asc' file: ``` -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAUYAAAAgc3NoLWVkMjU1MTktY2VydC12MDFAb3BlbnNzaC5j b20AAAAgttJe6gKGMeH+MNY6L3Ip0BuJnZebPvkuW6Zl9ZJs35wAAAAggO3sKO3l Z/uhRpVlFNbxnoLG09Xx7PEcUKtootiXWm0AAAAAAAAAAQAAAAEAAAAIdGVzdCBr ZXkAAAAUAAAAEG1lQG15ZG9tYWluLmZxZG4AAAAAY8lZDgAAAABjyVksAAAAAAAA AAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCA7ewo7eVn+6FGlWUU1vGegsbT 1fHs8RxQq2ii2JdabQAAAFMAAAALc3NoLWVkMjU1MTkAAABAiAoeiwtDINpEZAWl tfqG+MaxBk4YLZcUiDQKblxgBy2bqc52tLQE24jn4LKj96ZxHNITNBdvY0fODMiA dBggAQAAAAR0ZXh0AAAAAAAAAAZzaGEyNTYAAABTAAAAC3NzaC1lZDI1NTE5AAAA QEwhhhboXmBwfFEh5+ztPUfjVtPTvzboCZ/VBQyUvBsEitSZuoC1MrDLCP54qkRX /uSGRXWwZEWLL+SJeeJWTQ8-----END SSH SIGNATURE----- ``` 'principals' file: ``` test1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIDt7Cjt5Wf7oUaVZRTW8Z6CxtPV8ezxHFCraKLYl1pt test2 ssh-ed25519-cert-v01 at openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILbSXuoChjHh/jDWOi9yKdAbiZ2Xmz75LlumZfWSbN+cAAAAIIDt7Cjt5Wf7oUaVZRTW8Z6CxtPV8ezxHFCraKLYl1ptAAAAAAAAAAEAAAABAAAACHRlc3Qga2V5AAAAFAAAABBtZUBteWRvbWFpbi5mcWRuAAAAAGPJWQ4AAAAAY8lZLAAAAAAAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAggO3sKO3lZ/uhRpVlFNbxnoLG09Xx7PEcUKtootiXWm0AAABTAAAAC3NzaC1lZDI1NTE5AAAAQIgKHosLQyDaRGQFpbX6hvjGsQZOGC2XFIg0Cm5cYActm6nOdrS0BNuI5+Cyo/emcRzSEzQXb2NHzgzIgHQYIAE``` paultag -- :wq
Brian Candler
2023-Feb-10 09:10 UTC
How should SSHSIG/ssh-keygen handle signatures by a Certificate
On 10/02/2023 04:14, Paul Tagliamonte wrote:> My questions going in: > > 1. Will it accept an SSH certificate at all? > 2. If it does, will it accept the underlying Public Key too? > 3. What happens if my principals in the SSHSIG principals file don't > match in the ssh certificate > 4. What happens if the certificate is expired? SSHSIG has no > 'time of signature', so how do we even know? Now? > > The answers according to OpenSSH 9.1 / git > > 1. Yes! > 2. No! > 3/4. No validation on the Certificate is doneYou didn't provide the "-Y sign" commands that you used, but let me demonstrate using OpenSSH_9.2p1.? For testing I have: ~/.ssh/id_rsa ~/.ssh/id_rsa.pub ~/.ssh/id_rsa-cert.pub??? # an expired certificate containing various principals, one of them is "brian at +nsrc" I do not have the CA private key available locally - the certificate was signed using Hashicorp Vault - but I do have its public key. As you described, plain signing works: $ echo "hello" >hello $ ssh-keygen -Y sign -f ~/.ssh/id_rsa.pub -n file hello Signing file hello Write signature to hello.sig $ mv hello.sig hello.sig1 $ echo "dontcare $(cat ~/.ssh/id_rsa.pub)" >hello.allowed1 $ ssh-keygen -Y verify -f hello.allowed1 -n file -s hello.sig1 -I dontcare <hello Good "file" signature for dontcare with RSA key SHA256:mVV81jWVCP/SDRFA7vRM/SDQniylCAcBoSERWyhAXEo I think you probably tried to do a certificate signature creation and verification like this: $ ssh-keygen -Y sign -f ~/.ssh/id_rsa-cert.pub -n file hello Signing file hello Write signature to hello.sig $ mv hello.sig hello.sig2 $ echo "brian at +nsrc $(cat ~/.ssh/id_rsa.pub)" >hello.allowed2 $ ssh-keygen -Y verify -f hello.allowed2 -n file -s hello.sig2 -I brian at +nsrc <hello Could not verify signature. Note that the *signing* process was successful.? This implies that it was using the private key id_rsa to make the signature (since the CA's private key is nowhere). Note also that the second signature is about twice the size of the first, although both have a single "BEGIN SSH SIGNATURE" block: $ ls -l hello.sig1 hello.sig2 -rw-r--r--? 1 brian? staff?? 866 10 Feb 08:30 hello.sig1 -rw-r--r--? 1 brian? staff? 1653 10 Feb 08:42 hello.sig2 In other words, it's a chain. You need to verify it using the CA's public key: $ echo "brian at +nsrc *cert-authority $(cat ca.pub)*" >hello.allowed3 $ ssh-keygen -Y verify -f hello.allowed3 -n file -s hello.sig2 -I brian at +nsrc <hello hello.allowed3:1: certificate not authorized: Certificate invalid: expired Could not verify signature. $ ssh-keygen -Y match-principals -f hello.allowed3 -n file -s hello.sig2 -I brian at +nsrc <hello brian at +nsrc To summarize, I believe the following is true: - to verify a detached signature made using a certificate, you must provide the public key of the certificate authority which originally signed that certificate - the time validity of the parent certificate is already being verified, without any code patches required I guess it might be more user friendly not to allow signing with an expired or not-yet-valid certificate, or at least to warn if you do this. Regarding your other question: "3. What happens if my principals in the SSHSIG principals file don't match in the ssh certificate" The answer is the manpage under "ALLOWED SIGNERS": ???? When verifying signatures made by certificates, the expected principal name must match ???? both the principals pattern in the allowed signers file and the principals embedded in ???? the certificate itself. Regards, Brian.