1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-01 11:32:48 -05:00

Permit configuring RSA signature types in certificates.

As distinct from the type of signature generated by the SSH server
itself from the host key, this lets you exclude (and by default does
exclude) the old "ssh-rsa" SHA-1 signature type from the signature of
the CA on the certificate.
This commit is contained in:
Simon Tatham
2022-05-02 10:18:16 +01:00
parent e34e0220ab
commit dc7ba12253
12 changed files with 215 additions and 47 deletions

View File

@ -2560,7 +2560,7 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
def testOpenSSHCert(self):
def per_base_keytype_tests(alg, run_validation_tests=False,
ca_signflags=None):
run_ca_rsa_tests=False, ca_signflags=None):
cert_pub = sign_cert_via_testcrypt(
make_signature_preimage(
key_to_certify = base_key.public_blob(),
@ -2600,9 +2600,36 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
self.assertEqual(certified_key.verify(base_sig, test_string), True)
# Check a successful certificate verification
result, err = certified_key.check_cert(False, b'username', 1000)
result, err = certified_key.check_cert(
False, b'username', 1000, '')
self.assertEqual(result, True)
# If the key type is RSA, check that the validator rejects
# wrong kinds of CA signature
if run_ca_rsa_tests:
forbid_all = ",".join(["permit_rsa_sha1=false",
"permit_rsa_sha256=false,"
"permit_rsa_sha512=false"])
result, err = certified_key.check_cert(
False, b'username', 1000, forbid_all)
self.assertEqual(result, False)
algname = ("rsa-sha2-512" if ca_signflags == 4 else
"rsa-sha2-256" if ca_signflags == 2 else
"ssh-rsa")
self.assertEqual(err, (
"Certificate signature uses '{}' signature type "
"(forbidden by user configuration)".format(algname)
.encode("ASCII")))
permitflag = ("permit_rsa_sha512" if ca_signflags == 4 else
"permit_rsa_sha256" if ca_signflags == 2 else
"permit_rsa_sha1")
result, err = certified_key.check_cert(
False, b'username', 1000, "{},{}=true".format(
forbid_all, permitflag))
self.assertEqual(result, True)
# That's the end of the tests we need to repeat for all
# the key types. Now we move on to detailed tests of the
# validation, which are independent of key type, so we
@ -2612,16 +2639,19 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
# Check cert verification at the other end of the valid
# time range
result, err = certified_key.check_cert(False, b'username', 1999)
result, err = certified_key.check_cert(
False, b'username', 1999, '')
self.assertEqual(result, True)
# Oops, wrong certificate type
result, err = certified_key.check_cert(True, b'username', 1000)
result, err = certified_key.check_cert(
True, b'username', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate type is user; expected host')
# Oops, wrong username
result, err = certified_key.check_cert(False, b'someoneelse', 1000)
result, err = certified_key.check_cert(
False, b'someoneelse', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate\'s username list ["username"] '
b'does not contain expected username "someoneelse"')
@ -2629,10 +2659,12 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
# Oops, time is wrong. (But we can't check the full error
# message including the translated start/end times, because
# those vary with LC_TIME.)
result, err = certified_key.check_cert(False, b'someoneelse', 999)
result, err = certified_key.check_cert(
False, b'someoneelse', 999, '')
self.assertEqual(result, False)
self.assertEqual(err[:30], b'Certificate is not valid until')
result, err = certified_key.check_cert(False, b'someoneelse', 2000)
result, err = certified_key.check_cert(
False, b'someoneelse', 2000, '')
self.assertEqual(result, False)
self.assertEqual(err[:22], b'Certificate expired at')
@ -2642,7 +2674,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
bytelist[username_position] ^= 1
miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist),
base_key.private_blob())
result, err = miscertified_key.check_cert(False, b'username', 1000)
result, err = miscertified_key.check_cert(
False, b'username', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b"Certificate's signature is invalid")
@ -2659,7 +2692,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
critical_options = {b'unknown-option': b'yikes!'}), ca_key)
certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
base_key.private_blob())
result, err = certified_key.check_cert(False, b'username', 1000)
result, err = certified_key.check_cert(
False, b'username', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate specifies an unsupported '
b'critical option "unknown-option"')
@ -2677,7 +2711,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
extensions = {b'unknown-ext': b'whatever, dude'}), ca_key)
certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
base_key.private_blob())
result, err = certified_key.check_cert(False, b'username', 1000)
result, err = certified_key.check_cert(
False, b'username', 1000, '')
self.assertEqual(result, True)
# Now try a host certificate. We don't need to do _all_ the
@ -2703,19 +2738,19 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
# Check certificate type
result, err = certified_key.check_cert(
True, b'hostname.example.com', 1000)
True, b'hostname.example.com', 1000, '')
self.assertEqual(result, True)
result, err = certified_key.check_cert(
False, b'hostname.example.com', 1000)
False, b'hostname.example.com', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate type is host; expected user')
# Check the second hostname and an unknown one
result, err = certified_key.check_cert(
True, b'hostname2.example.com', 1000)
True, b'hostname2.example.com', 1000, '')
self.assertEqual(result, True)
result, err = certified_key.check_cert(
True, b'hostname3.example.com', 1000)
True, b'hostname3.example.com', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate\'s hostname list ['
b'"hostname.example.com", "hostname2.example.com"] '
@ -2738,12 +2773,12 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
base_key.private_blob())
result, err = certified_key.check_cert(
False, b'username', 1000)
False, b'username', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate type is unknown value 12345; '
b'expected user')
result, err = certified_key.check_cert(
True, b'hostname.example.com', 1000)
True, b'hostname.example.com', 1000, '')
self.assertEqual(result, False)
self.assertEqual(err, b'Certificate type is unknown value 12345; '
b'expected host')
@ -2771,9 +2806,9 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
# Now switch to an RSA certifying key, and test different RSA
# signature subtypes being used to sign the certificate
ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM='))
per_base_keytype_tests('rsa')
per_base_keytype_tests('rsa', ca_signflags=2)
per_base_keytype_tests('rsa', ca_signflags=4)
per_base_keytype_tests('rsa', run_ca_rsa_tests=True)
per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2)
per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4)
class standard_test_vectors(MyTestBase):
def testAES(self):

View File

@ -309,7 +309,8 @@ FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key),
ARG(out_val_string_binarysink, blob))
FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key),
ARG(boolean, host), ARG(val_string_ptrlen, principal),
ARG(uint, time), ARG(out_val_string_binarysink, error))
ARG(uint, time), ARG(val_string_ptrlen, options),
ARG(out_val_string_binarysink, error))
/*
* Accessors to retrieve the innards of a 'key_components'.

View File

@ -830,13 +830,37 @@ void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out)
}
static bool ssh_key_check_cert_wrapper(
ssh_key *key, bool host, ptrlen principal, uint64_t time,
ssh_key *key, bool host, ptrlen principal, uint64_t time, ptrlen optstr,
BinarySink *error)
{
/* Wrap to avoid null-pointer dereference */
if (!key->vt->is_certificate)
fatal_error("ssh_key_cert_id_string: needs a certificate");
return ssh_key_check_cert(key, host, principal, time, error);
ca_options opts;
opts.permit_rsa_sha1 = true;
opts.permit_rsa_sha256 = true;
opts.permit_rsa_sha512 = true;
while (optstr.len) {
ptrlen word = ptrlen_get_word(&optstr, ",");
ptrlen key = word, value = PTRLEN_LITERAL("");
const char *comma = memchr(word.ptr, '=', word.len);
if (comma) {
key.len = comma - (const char *)word.ptr;
value.ptr = comma + 1;
value.len = word.len - key.len - 1;
}
if (ptrlen_eq_string(key, "permit_rsa_sha1"))
opts.permit_rsa_sha1 = ptrlen_eq_string(value, "true");
if (ptrlen_eq_string(key, "permit_rsa_sha256"))
opts.permit_rsa_sha256 = ptrlen_eq_string(value, "true");
if (ptrlen_eq_string(key, "permit_rsa_sha512"))
opts.permit_rsa_sha512 = ptrlen_eq_string(value, "true");
}
return ssh_key_check_cert(key, host, principal, time, &opts, error);
}
bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f)