From 50ce8abf9fbff8504d5fd4a566c4b734b981ffa7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Tue, 22 Aug 2023 08:50:27 -0400 Subject: [PATCH] adapt the existing case to a multi-case structure --- src/allmydata/storage/http_common.py | 15 +++-- .../test/data/spki-hash-test-vectors.yaml | 26 ++++++++ src/allmydata/test/test_storage_https.py | 60 ++++++++++--------- 3 files changed, 70 insertions(+), 31 deletions(-) create mode 100644 src/allmydata/test/data/spki-hash-test-vectors.yaml diff --git a/src/allmydata/storage/http_common.py b/src/allmydata/storage/http_common.py index 650d905e9..d59cab541 100644 --- a/src/allmydata/storage/http_common.py +++ b/src/allmydata/storage/http_common.py @@ -54,6 +54,15 @@ class Secrets(Enum): WRITE_ENABLER = "write-enabler" +def get_spki(certificate: Certificate) -> bytes: + """ + Get the bytes making up the DER encoded representation of the + `SubjectPublicKeyInfo` (RFC 7469) for the given certificate. + """ + return certificate.public_key().public_bytes( + Encoding.DER, PublicFormat.SubjectPublicKeyInfo + ) + def get_spki_hash(certificate: Certificate) -> bytes: """ Get the public key hash, as per RFC 7469: base64 of sha256 of the public @@ -61,7 +70,5 @@ def get_spki_hash(certificate: Certificate) -> bytes: We use the URL-safe base64 variant, since this is typically found in NURLs. """ - public_key_bytes = certificate.public_key().public_bytes( - Encoding.DER, PublicFormat.SubjectPublicKeyInfo - ) - return urlsafe_b64encode(sha256(public_key_bytes).digest()).strip().rstrip(b"=") + spki_bytes = get_spki(certificate) + return urlsafe_b64encode(sha256(spki_bytes).digest()).strip().rstrip(b"=") diff --git a/src/allmydata/test/data/spki-hash-test-vectors.yaml b/src/allmydata/test/data/spki-hash-test-vectors.yaml new file mode 100644 index 000000000..6be6a9b71 --- /dev/null +++ b/src/allmydata/test/data/spki-hash-test-vectors.yaml @@ -0,0 +1,26 @@ +vector: +- expected-hash: >- + JIj6ezHkdSBlHhrnezAgIC_mrVQHy4KAFyL-8ZNPGPM + expected-spki: >- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9vqtA8Toy9D6xLGq41iUafSiAXnuirWxML2ct/LAcGJzATg6JctmJxxZQL7vkmaFFPBF6Y39bOGbbECM2iQYn2Qemj5fl3IzKTnYLqzryGM0ZwwnNbPyetSe/sksAIYRLzn49d6l+AHR+DjGyvoLzIyGUTn41MTDafMNtPgWx1i+65lFW3GHYpEmugu4bjeUPizNja2LrqwvwFuYXwmKxbIMdioCoRvDGX9SI3/euFstuR4rbOEUDxniYRF5g6reP8UMF30zJzF5j0kyDg8Z5b1XpKFNZAeyRYxcs9wJCqVlP6BLPDnvNVpMXodnWLeTK+r6YWvGadGVufkYNC1PwIDAQAB + certificate: | + -----BEGIN CERTIFICATE----- + MIIDWTCCAkECFCf+I+3oEhTfqt+6ruH4qQ4Wst1DMA0GCSqGSIb3DQEBCwUAMGkx + CzAJBgNVBAYTAlpaMRAwDgYDVQQIDAdOb3doZXJlMRQwEgYDVQQHDAtFeGFtcGxl + dG93bjEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEUMBIGA1UEAwwLZXhh + bXBsZS5jb20wHhcNMjIwMzAyMTUyNTQ3WhcNMjMwMzAyMTUyNTQ3WjBpMQswCQYD + VQQGEwJaWjEQMA4GA1UECAwHTm93aGVyZTEUMBIGA1UEBwwLRXhhbXBsZXRvd24x + HDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxFDASBgNVBAMMC2V4YW1wbGUu + Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9vqtA8Toy9D6xLG + q41iUafSiAXnuirWxML2ct/LAcGJzATg6JctmJxxZQL7vkmaFFPBF6Y39bOGbbEC + M2iQYn2Qemj5fl3IzKTnYLqzryGM0ZwwnNbPyetSe/sksAIYRLzn49d6l+AHR+Dj + GyvoLzIyGUTn41MTDafMNtPgWx1i+65lFW3GHYpEmugu4bjeUPizNja2LrqwvwFu + YXwmKxbIMdioCoRvDGX9SI3/euFstuR4rbOEUDxniYRF5g6reP8UMF30zJzF5j0k + yDg8Z5b1XpKFNZAeyRYxcs9wJCqVlP6BLPDnvNVpMXodnWLeTK+r6YWvGadGVufk + YNC1PwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQByrhn78GSS3dJ0pJ6czmhMX5wH + +fauCtt1+Wbn+ctTodTycS+pfULO4gG7wRzhl8KNoOqLmWMjyA2A3mon8kdkD+0C + i8McpoPaGS2wQcqC28Ud6kP9YO81YFyTl4nHVKQ0nmplT+eoLDTCIWMVxHHzxIgs + 2ybUluAc+THSjpGxB6kWSAJeg3N+f2OKr+07Yg9LiQ2b8y0eZarpiuuuXCzWeWrQ + PudP0aniyq/gbPhxq0tYF628IBvhDAnr/2kqEmVF2TDr2Sm/Y3PDBuPY6MeIxjnr + ox5zO3LrQmQw11OaIAs2/kviKAoKTFFxeyYcpS5RuKNDZfHQCXlLwt9bySxG + -----END CERTIFICATE----- diff --git a/src/allmydata/test/test_storage_https.py b/src/allmydata/test/test_storage_https.py index 0e0bbcc95..9e44b86b0 100644 --- a/src/allmydata/test/test_storage_https.py +++ b/src/allmydata/test/test_storage_https.py @@ -7,7 +7,9 @@ Protocol. """ from contextlib import asynccontextmanager +from base64 import b64decode +from yaml import safe_load from cryptography import x509 from twisted.internet.endpoints import serverFromString @@ -26,18 +28,27 @@ from .certs import ( private_key_to_file, cert_to_file, ) -from ..storage.http_common import get_spki_hash +from ..storage.http_common import get_spki, get_spki_hash from ..storage.http_client import _StorageClientHTTPSPolicy from ..storage.http_server import _TLSEndpointWrapper from ..util.deferredutil import async_to_deferred from .common_system import spin_until_cleanup_done +spki_test_vectors_path = FilePath(__file__).sibling("data").child("spki-hash-test-vectors.yaml") + class HTTPSNurlTests(SyncTestCase): """Tests for HTTPS NURLs.""" def test_spki_hash(self): - """The output of ``get_spki_hash()`` matches the semantics of RFC 7469. + """ + The output of ``get_spki_hash()`` matches the semantics of RFC + 7469. + + The test vector certificates were generated using the openssl command + line tool:: + + openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 The expected hash was generated using Appendix A instructions in the RFC:: @@ -45,32 +56,27 @@ class HTTPSNurlTests(SyncTestCase): openssl x509 -noout -in certificate.pem -pubkey | \ openssl asn1parse -noout -inform pem -out public.key openssl dgst -sha256 -binary public.key | openssl enc -base64 + + The expected SubjectPublicKeyInfo bytes were extracted from the implementation of `get_spki_hash` after its result matched the expected value generated by the command above. + """ - expected_hash = b"JIj6ezHkdSBlHhrnezAgIC_mrVQHy4KAFyL-8ZNPGPM" - certificate_text = b"""\ ------BEGIN CERTIFICATE----- -MIIDWTCCAkECFCf+I+3oEhTfqt+6ruH4qQ4Wst1DMA0GCSqGSIb3DQEBCwUAMGkx -CzAJBgNVBAYTAlpaMRAwDgYDVQQIDAdOb3doZXJlMRQwEgYDVQQHDAtFeGFtcGxl -dG93bjEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEUMBIGA1UEAwwLZXhh -bXBsZS5jb20wHhcNMjIwMzAyMTUyNTQ3WhcNMjMwMzAyMTUyNTQ3WjBpMQswCQYD -VQQGEwJaWjEQMA4GA1UECAwHTm93aGVyZTEUMBIGA1UEBwwLRXhhbXBsZXRvd24x -HDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxFDASBgNVBAMMC2V4YW1wbGUu -Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9vqtA8Toy9D6xLG -q41iUafSiAXnuirWxML2ct/LAcGJzATg6JctmJxxZQL7vkmaFFPBF6Y39bOGbbEC -M2iQYn2Qemj5fl3IzKTnYLqzryGM0ZwwnNbPyetSe/sksAIYRLzn49d6l+AHR+Dj -GyvoLzIyGUTn41MTDafMNtPgWx1i+65lFW3GHYpEmugu4bjeUPizNja2LrqwvwFu -YXwmKxbIMdioCoRvDGX9SI3/euFstuR4rbOEUDxniYRF5g6reP8UMF30zJzF5j0k -yDg8Z5b1XpKFNZAeyRYxcs9wJCqVlP6BLPDnvNVpMXodnWLeTK+r6YWvGadGVufk -YNC1PwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQByrhn78GSS3dJ0pJ6czmhMX5wH -+fauCtt1+Wbn+ctTodTycS+pfULO4gG7wRzhl8KNoOqLmWMjyA2A3mon8kdkD+0C -i8McpoPaGS2wQcqC28Ud6kP9YO81YFyTl4nHVKQ0nmplT+eoLDTCIWMVxHHzxIgs -2ybUluAc+THSjpGxB6kWSAJeg3N+f2OKr+07Yg9LiQ2b8y0eZarpiuuuXCzWeWrQ -PudP0aniyq/gbPhxq0tYF628IBvhDAnr/2kqEmVF2TDr2Sm/Y3PDBuPY6MeIxjnr -ox5zO3LrQmQw11OaIAs2/kviKAoKTFFxeyYcpS5RuKNDZfHQCXlLwt9bySxG ------END CERTIFICATE----- -""" - certificate = x509.load_pem_x509_certificate(certificate_text) - self.assertEqual(get_spki_hash(certificate), expected_hash) + spki_cases = safe_load(spki_test_vectors_path.getContent())["vector"] + for n, case in enumerate(spki_cases): + certificate_text = case["certificate"].encode("ascii") + expected_spki = b64decode(case["expected-spki"]) + expected_hash = case["expected-hash"].encode("ascii") + + certificate = x509.load_pem_x509_certificate(certificate_text) + self.assertEqual( + expected_spki, + get_spki(certificate), + f"case {n} spki data mismatch", + ) + self.assertEqual( + expected_hash, + get_spki_hash(certificate), + f"case {n} spki hash mismatch", + ) class PinningHTTPSValidation(AsyncTestCase):