diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 78be4f15..c27118a4 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -726,11 +726,40 @@ static bool opensshcert_check_cert( ptrlen signature = ptrlen_from_strbuf(ck->signature); - ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + /* + * The OpenSSH certificate spec is one-layer only: it explicitly + * forbids using a certified key in turn as the CA. + * + * If it did not, then we'd also have to recursively verify + * everything up the CA chain until we reached the ultimate root, + * and then make sure _that_ was something we trusted. (Not to + * mention that there'd probably be an additional SSH_CERT_TYPE_CA + * or some such, and certificate options saying what kinds of + * certificate a CA was trusted to sign for, and ...) + */ + ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL); if (!ca_key) { put_fmt(error, "Certificate's signing key is invalid"); goto out; } + if (ssh_key_alg(ca_key)->is_certificate) { + put_fmt(error, "Certificate is signed with a certified key " + "(forbidden by OpenSSH certificate specification)"); + goto out; + } + + /* + * Now re-instantiate the key in a way that matches the signature + * (i.e. so that if the key is an RSA one we get the right subtype + * of RSA). + */ + ssh_key_free(ca_key); + ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + if (!ca_key) { + put_fmt(error, "Certificate's signing key does not match " + "signature type"); + goto out; + } /* Check which signature algorithm is actually in use, because * that might be a reason to reject the certificate (e.g. ssh-rsa diff --git a/test/cryptsuite.py b/test/cryptsuite.py index ff8cf1ed..c23bd00b 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2715,6 +2715,43 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b False, b'username', 1000, '') self.assertEqual(result, True) + # Make a certificate on the CA key, and re-sign the main + # key using that, to ensure that two-level certs are rejected + ca_self_certificate = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = ca_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b"doesn't matter"], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + import base64 + print(base64.b64encode(ca_self_certificate)) + self_signed_ca_key = ssh_key_new_pub( + alg + '-cert', ca_self_certificate) + print(self_signed_ca_key) + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = self_signed_ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + print(base64.b64encode(cert_pub)) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1500, '') + self.assertEqual(result, False) + self.assertEqual( + err, b'Certificate is signed with a certified key ' + b'(forbidden by OpenSSH certificate specification)') + # Now try a host certificate. We don't need to do _all_ the # checks over again, but at least make sure that setting # CertType.host leads to the certificate validating with