diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 5ab7bf96..b2548fbc 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -18,6 +18,7 @@ except ImportError: from eccref import * from testcrypt import * from ssh import * +from ca import CertType, make_signature_preimage, sign_cert_via_testcrypt assert sys.version_info[:2] >= (3,0), "This is Python 3 code" @@ -2557,6 +2558,223 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b self.assertEqual(rsa1_save_sb(k2, comment, pp), input_encrypted_key) + def testOpenSSHCert(self): + def per_base_keytype_tests(alg, run_validation_tests=False, + ca_signflags=None): + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check the simple certificate methods + self.assertEqual(certified_key.cert_id_string(), b'id') + self.assertEqual(certified_key.ca_public_blob(), + ca_key.public_blob()) + recovered_base_key = certified_key.base_key() + self.assertEqual(recovered_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(recovered_base_key.private_blob(), + base_key.private_blob()) + + # Check that an ordinary key also supports base_key() + redundant_base_key = base_key.base_key() + self.assertEqual(redundant_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(redundant_base_key.private_blob(), + base_key.private_blob()) + + # Test signing and verifying using the certified key type + test_string = b'hello, world' + base_sig = base_key.sign(test_string, 0) + certified_sig = certified_key.sign(test_string, 0) + self.assertEqual(base_sig, certified_sig) + 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) + 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 + # only need to test this part once. + if not run_validation_tests: + return + + # Check cert verification at the other end of the valid + # time range + 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) + 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) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate\'s username list ["username"] ' + b'does not contain expected username "someoneelse"') + + # 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) + self.assertEqual(result, False) + self.assertEqual(err[:30], b'Certificate is not valid until') + result, err = certified_key.check_cert(False, b'someoneelse', 2000) + self.assertEqual(result, False) + self.assertEqual(err[:22], b'Certificate expired at') + + # Modify the certificate so that the signature doesn't validate + username_position = cert_pub.index(b'username') + bytelist = list(cert_pub) + 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) + self.assertEqual(result, False) + self.assertEqual(err, b"Certificate's signature is invalid") + + # Make a certificate containing a critical option, to test we + # reject it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 112, + principals = [b'username'], + 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) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate specifies an unsupported ' + b'critical option "unknown-option"') + + # Make a certificate containing a non-critical extension, to + # test we _accept_ it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 113, + principals = [b'username'], + 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) + self.assertEqual(result, True) + + # 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 + # host=True and not with host=False. + # + # Also, in this test, give two hostnames. + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.host, + keyid = b'id', + serial = 114, + principals = [b'hostname.example.com', + b'hostname2.example.com'], + valid_after = 1000, + valid_before = 2000), ca_key) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check certificate type + result, err = certified_key.check_cert( + True, b'hostname.example.com', 1000) + self.assertEqual(result, True) + result, err = certified_key.check_cert( + 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) + self.assertEqual(result, True) + result, err = certified_key.check_cert( + 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"] ' + b'does not contain expected hostname ' + b'"hostname3.example.com"') + + # And just for luck, try a totally unknown certificate type, + # making sure that it's rejected in both modes and gives the + # right error message + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = 12345, + keyid = b'id', + serial = 114, + principals = [b'username', b'hostname.example.com'], + valid_after = 1000, + valid_before = 2000), 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) + 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) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is unknown value 12345; ' + b'expected host') + + ca_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7'), b64('AAAAIK4STyaf63xHidqhvUop9/OKiYqSh/YEWLCp1lL5Vs4u')) + + base_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86i9bOHhMJYbDbxEJf'), b64('AAAAIB38jy02ZWYb4EXrJG9RIljEhqidrG5DdhZvMvoeOTZs')) + per_base_keytype_tests('ed25519', run_validation_tests=True) + + base_key = ssh_key_new_priv('p256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGc8VXplXScdWckJgAw6Hag5PP7g0JEVdLY5lP2ujvVxU5GwwquYLbX3yyj1zY5h2n9GoXrnRxzR5+5g8wsNjTA='), b64('AAAAICVRicPD5MyOHfKdnC/8IP84t+nQ4bqmMUyX7NHyCKjS')) + per_base_keytype_tests('p256') + + base_key = ssh_key_new_priv('p384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLITujAbKwHDEzVDFqWtA+CleAhN/Y+53mHbEoTpU0aof9L+2lHeUshXdxHDLxY69wO5+WfqWJCwSY58PuXIZzIisQkvIKq6LhpzK6C5JpWJ8Kbv7su+qZPf5sYoxx0xZg=='), b64('AAAAMHyQTQYcIA/bR4ZvWS86ohb5Lu0MhzjD8bUb3q8jnROOe3BrE9I8oJcx+l1lddPouA==')) + per_base_keytype_tests('p384') + + base_key = ssh_key_new_priv('p521', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADButwMRGdLkFhWcSDsLhRhgyrLQq1/A0M8x4GgEmesh4iydo4tGKZR14GhHvx150IWTE1Tre4wyH+1FsTfAlpUBgBDQjsZE0D3u3SLp4qjjhzyrJGhEUDd9J6lsr6JrXbTefz5+LkM9m5l86y9PoAgT+F25OiTYlfvR5qx/pzIPoCnpA=='), b64('AAAAQgFV8xBXC7XZNxdW1oWg6yCZjys2AX4beZVehE9A2R/4m11dHnfqoE1FzbRxj9xqwKvHZRhMOJ//DYuhtcG6+6yHsA==')) + per_base_keytype_tests('p521') + + base_key = ssh_key_new_priv('dsa', b64('AAAAB3NzaC1kc3MAAABCAXgDrF9Fw/Ty+QcoljAGjGL/Ph5+NBQqUYADm4wxF+aazjQXLuZ0VW9OdYBisgDZlYDj/w7y9NxCBgax2BSkhDNxAAAAFQC/YwnFzcom6cRRHPXtOUDLi2I29QAAAEIAqGOUYpfFPwzhgAmYXwWKdK8ouSUplNE29FOpv6NYjyf7k+tLSWF3b8oZdtw6XP8lr4vcKXC9Ik0YpKYKM7iKfb8AAABCAUDCcojlDLQmLHg8HhFCtT/CpayNh4OfmSrP8XOwJnFD/eBaSGuPB5EvGd+m6gr+Pc0RSAlWP1aIzUbYkQ33Yk58'), b64('AAAAFQChVuOTNrCwLSJygxlRQhDwHozwSg==')) + per_base_keytype_tests('dsa') + + base_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQDXLnqGPQLL9byoHFQWPiF5Uzcd0KedMRRJmuwyCAWprlh8EN43mL2F7q27Uv54m/ztqW4DsVtiCN6cDYvB9QPNYFR5npwsEAJ06Ro4s9ZpFsZVOvitqeoYIs+jkS8vq5V8X4hwLlJ8vXYPD6rHJhOz6HFpImHmVu40Mu5lq+MCQQ=='), b64('AAAAgH5dBwrJzVilKHK4oBCnz9SFr7pMjAHdjoJi/g2rdFfe0IubBEQ16CY8sb1t0Y5WXEPc2YRFpNp/RurxcX8nOWFPzgNJXEtkKpKO9Juqu5hL4xcf8QKC2aJFk3EXrn/M6dXEdjqN4UhsT6iFTsHKU4b8T6VTtgKzwkOdic/YotaBAAAAQQD6liDTlzTKzLhbypI6l+y2BGA3Kkzz71Y2o7XH/6bZ6HJOFgHuJeL3eNQptzd8Q+ctfvR0fa2PItYydDOlVUeZAAAAQQDb1IsO1/fkflDZhPQT2XOxtrjgQhotKjr6CSmJtDNmo1mOCN+mOgxtDfJ0PNEEM1P9CO2Ia3njtkxt4Ep2EpjpAAAAQQClRxLEHsRK9nMPZ4HW45iyw5dHhYar9pYUql2VnixWQxrHy13ZIaWxi6xwWjuPglrdBgEQfYwH9KGmlFmZXT/Z')) + per_base_keytype_tests('rsa') + + # 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) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext):