|
21 | 21 | algorithms,
|
22 | 22 | )
|
23 | 23 | from cryptography.utils import _check_byteslike
|
| 24 | +from cryptography.x509.oid import ExtendedKeyUsageOID |
| 25 | +from cryptography.x509.verification import ( |
| 26 | + Criticality, |
| 27 | + ExtensionPolicy, |
| 28 | + Policy, |
| 29 | +) |
24 | 30 |
|
25 | 31 | load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates
|
26 | 32 |
|
@@ -53,6 +59,120 @@ class PKCS7Options(utils.Enum):
|
53 | 59 | NoCerts = "Don't embed signer certificate"
|
54 | 60 |
|
55 | 61 |
|
| 62 | +def pkcs7_x509_extension_policies() -> tuple[ExtensionPolicy, ExtensionPolicy]: |
| 63 | + """ |
| 64 | + Gets the default X.509 extension policy for S/MIME, based on RFC 8550. |
| 65 | + Visit https://www.rfc-editor.org/rfc/rfc8550#section-4.4 for more info. |
| 66 | + """ |
| 67 | + # CA policy |
| 68 | + ca_policy = ExtensionPolicy.webpki_defaults_ca() |
| 69 | + |
| 70 | + # EE policy |
| 71 | + def _validate_basic_constraints( |
| 72 | + policy: Policy, cert: Certificate, bc: x509.BasicConstraints | None |
| 73 | + ) -> None: |
| 74 | + """ |
| 75 | + We check that Certificates used as EE (i.e., the cert used to sign |
| 76 | + a PKCS#7/SMIME message) must not have ca=true in their basic |
| 77 | + constraints extension. RFC 5280 doesn't impose this requirement, but we |
| 78 | + firmly agree about it being best practice. |
| 79 | + """ |
| 80 | + if bc is not None and bc.ca: |
| 81 | + raise ValueError("Basic Constraints CA must be False.") |
| 82 | + |
| 83 | + def _validate_key_usage( |
| 84 | + policy: Policy, cert: Certificate, ku: x509.KeyUsage | None |
| 85 | + ) -> None: |
| 86 | + """ |
| 87 | + Checks that the Key Usage extension, if present, has at least one of |
| 88 | + the digital signature or content commitment (formerly non-repudiation) |
| 89 | + bits set. |
| 90 | + """ |
| 91 | + if ( |
| 92 | + ku is not None |
| 93 | + and not ku.digital_signature |
| 94 | + and not ku.content_commitment |
| 95 | + ): |
| 96 | + raise ValueError( |
| 97 | + "Key Usage, if specified, must have at least one of the " |
| 98 | + "digital signature or content commitment (formerly non " |
| 99 | + "repudiation) bits set." |
| 100 | + ) |
| 101 | + |
| 102 | + def _validate_subject_alternative_name( |
| 103 | + policy: Policy, |
| 104 | + cert: Certificate, |
| 105 | + san: x509.SubjectAlternativeName, |
| 106 | + ) -> None: |
| 107 | + """ |
| 108 | + For each general name in the SAN, for those which are email addresses: |
| 109 | + - If it is an RFC822Name, general part must be ascii. |
| 110 | + - If it is an OtherName, general part must be non-ascii. |
| 111 | + """ |
| 112 | + for general_name in san: |
| 113 | + if ( |
| 114 | + isinstance(general_name, x509.RFC822Name) |
| 115 | + and "@" in general_name.value |
| 116 | + and not general_name.value.split("@")[0].isascii() |
| 117 | + ): |
| 118 | + raise ValueError( |
| 119 | + f"RFC822Name {general_name.value} contains non-ASCII " |
| 120 | + "characters." |
| 121 | + ) |
| 122 | + if ( |
| 123 | + isinstance(general_name, x509.OtherName) |
| 124 | + and "@" in general_name.value.decode() |
| 125 | + and general_name.value.decode().split("@")[0].isascii() |
| 126 | + ): |
| 127 | + raise ValueError( |
| 128 | + f"OtherName {general_name.value.decode()} is ASCII, " |
| 129 | + "so must be stored in RFC822Name." |
| 130 | + ) |
| 131 | + |
| 132 | + def _validate_extended_key_usage( |
| 133 | + policy: Policy, cert: Certificate, eku: x509.ExtendedKeyUsage | None |
| 134 | + ) -> None: |
| 135 | + """ |
| 136 | + Checks that the Extended Key Usage extension, if present, |
| 137 | + includes either emailProtection or anyExtendedKeyUsage bits. |
| 138 | + """ |
| 139 | + if ( |
| 140 | + eku is not None |
| 141 | + and ExtendedKeyUsageOID.EMAIL_PROTECTION not in eku |
| 142 | + and ExtendedKeyUsageOID.ANY_EXTENDED_KEY_USAGE not in eku |
| 143 | + ): |
| 144 | + raise ValueError( |
| 145 | + "Extended Key Usage, if specified, must include " |
| 146 | + "emailProtection or anyExtendedKeyUsage." |
| 147 | + ) |
| 148 | + |
| 149 | + ee_policy = ( |
| 150 | + ExtensionPolicy.webpki_defaults_ee() |
| 151 | + .may_be_present( |
| 152 | + x509.BasicConstraints, |
| 153 | + Criticality.AGNOSTIC, |
| 154 | + _validate_basic_constraints, |
| 155 | + ) |
| 156 | + .may_be_present( |
| 157 | + x509.KeyUsage, |
| 158 | + Criticality.CRITICAL, |
| 159 | + _validate_key_usage, |
| 160 | + ) |
| 161 | + .require_present( |
| 162 | + x509.SubjectAlternativeName, |
| 163 | + Criticality.AGNOSTIC, |
| 164 | + _validate_subject_alternative_name, |
| 165 | + ) |
| 166 | + .may_be_present( |
| 167 | + x509.ExtendedKeyUsage, |
| 168 | + Criticality.AGNOSTIC, |
| 169 | + _validate_extended_key_usage, |
| 170 | + ) |
| 171 | + ) |
| 172 | + |
| 173 | + return ca_policy, ee_policy |
| 174 | + |
| 175 | + |
56 | 176 | class PKCS7SignatureBuilder:
|
57 | 177 | def __init__(
|
58 | 178 | self,
|
|
0 commit comments