diff --git a/resources/cmputils.py b/resources/cmputils.py index 33f07c86..c198d105 100644 --- a/resources/cmputils.py +++ b/resources/cmputils.py @@ -40,6 +40,7 @@ certextractutils, certutils, convertutils, + copyasn1utils, cryptoutils, envdatautils, keyutils, @@ -562,12 +563,6 @@ def prepare_old_cert_id_control( if inc_serial_number: ser_num += 1 - if cert is not None: - cert_issuer = utils.get_openssl_name_notation(cert["tbsCertificate"]["issuer"]) # type: ignore - cert_issuer: Optional[str] - else: - cert_issuer = None - if bad_issuer and cert is None: raise ValueError("A valid certificate must be provided when `bad_issuer` is True.") @@ -577,15 +572,19 @@ def prepare_old_cert_id_control( issuer=True, ) - if issuer is None and cert_issuer is None: + if not issuer and cert is None: raise ValueError("Either a valid certificate or an issuer must be provided.") - _issuer_name = issuer or cert_issuer # type: ignore - _issuer_name: str - issuer_name = prepareutils.prepare_general_name( - name_type="directoryName", - name_str=_issuer_name, - ) + if issuer: + issuer_name = prepareutils.prepare_general_name( + name_type="directoryName", + name_str=issuer, + ) + else: + # Copy the `Name` object. A round trip through text reverses the RDN order (RFC 4514 Section 2.1), + # so the control would no longer identify the certificate. + cert_issuer = copyasn1utils.copy_name(target=rfc9480.Name(), filled_name=cert["tbsCertificate"]["issuer"]) # type: ignore + issuer_name = prepareutils.prepare_general_name_from_name(cert_issuer) old_cert_id = rfc4211.OldCertId() old_cert_id["issuer"] = issuer_name diff --git a/unit_tests/tests_prepare_cert_related_structures/test_prepare_controls_structure.py b/unit_tests/tests_prepare_cert_related_structures/test_prepare_controls_structure.py index 2ad5196d..b6e90873 100644 --- a/unit_tests/tests_prepare_cert_related_structures/test_prepare_controls_structure.py +++ b/unit_tests/tests_prepare_cert_related_structures/test_prepare_controls_structure.py @@ -7,8 +7,8 @@ from pyasn1.codec.der.decoder import decode from pyasn1.codec.der.encoder import encode from pyasn1_alt_modules import rfc4211, rfc5280 -from resources.certbuildutils import generate_certificate -from resources.cmputils import prepare_controls_structure +from resources.certbuildutils import build_certificate, generate_certificate +from resources.cmputils import prepare_controls_structure, prepare_old_cert_id_control from resources.keyutils import generate_key from resources.utils import get_openssl_name_notation @@ -51,6 +51,31 @@ def test_prepare_structure_controls(self): decoded_controls, rest = decode(encoded_data, asn1Spec=rfc4211.Controls()) self.assertEqual(rest, b"") + def test_old_cert_id_keeps_multi_rdn_issuer(self): + """ + GIVEN a certificate whose issuer has several RDNs + WHEN prepare_old_cert_id_control is called with the certificate, + THEN the issuer of the `OldCertId` must be DER-identical to the issuer of the certificate. + """ + cert, _ = build_certificate(common_name="CN=Test CA,O=Test Org,C=DE") + control = prepare_old_cert_id_control(cert=cert) + self.assertEqual( + encode(control["value"]["issuer"]["directoryName"]["rdnSequence"]), + encode(cert["tbsCertificate"]["issuer"]["rdnSequence"]), + ) + + def test_old_cert_id_does_not_share_issuer_with_cert(self): + """ + GIVEN a certificate + WHEN prepare_old_cert_id_control is called with the certificate and the issuer of the control is changed, + THEN the issuer of the certificate must stay unchanged. + """ + cert, _ = build_certificate(common_name="CN=Test CA,O=Test Org,C=DE") + issuer_der = encode(cert["tbsCertificate"]["issuer"]["rdnSequence"]) + control = prepare_old_cert_id_control(cert=cert) + control["value"]["issuer"]["directoryName"]["rdnSequence"].clear() + self.assertEqual(encode(cert["tbsCertificate"]["issuer"]["rdnSequence"]), issuer_der) + if __name__ == "__main__": unittest.main()