def _deserialize(self, value, attr, data): general_names = [] for name in value: if name['nameType'] == 'DNSName': general_names.append(x509.DNSName(name['value'])) elif name['nameType'] == 'IPAddress': general_names.append( x509.IPAddress(ipaddress.ip_address(name['value']))) elif name['nameType'] == 'IPNetwork': general_names.append( x509.IPAddress(ipaddress.ip_network(name['value']))) elif name['nameType'] == 'uniformResourceIdentifier': general_names.append( x509.UniformResourceIdentifier(name['value'])) elif name['nameType'] == 'directoryName': # TODO: Need to parse a string in name['value'] like: # 'CN=Common Name, O=Org Name, OU=OrgUnit Name, C=US, ST=ST, L=City/[email protected]' # or # 'CN=Common Name/O=Org Name/OU=OrgUnit Name/C=US/ST=NH/L=City/[email protected]' # and turn it into something like: # x509.Name([ # x509.NameAttribute(x509.OID_COMMON_NAME, "Common Name"), # x509.NameAttribute(x509.OID_ORGANIZATION_NAME, "Org Name"), # x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, "OrgUnit Name"), # x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), # x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, "NH"), # x509.NameAttribute(x509.OID_LOCALITY_NAME, "City"), # x509.NameAttribute(x509.OID_EMAIL_ADDRESS, "*****@*****.**") # ] # general_names.append(x509.DirectoryName(x509.Name(BLAH)))) pass elif name['nameType'] == 'rfc822Name': general_names.append(x509.RFC822Name(name['value'])) elif name['nameType'] == 'registeredID': general_names.append( x509.RegisteredID(x509.ObjectIdentifier(name['value']))) elif name['nameType'] == 'otherName': # This has two inputs (type and value), so it doesn't fit the mold of the rest of these GeneralName entities. # general_names.append(x509.OtherName(name['type'], bytes(name['value']), 'utf-8')) pass elif name['nameType'] == 'x400Address': # The Python Cryptography library doesn't support x400Address types (yet?) pass elif name['nameType'] == 'EDIPartyName': # The Python Cryptography library doesn't support EDIPartyName types (yet?) pass else: current_app.logger.warning( 'Unable to deserialize SubAltName with type: {name_type}'. format(name_type=name['nameType'])) return x509.SubjectAlternativeName(general_names)
def update_cert(self, csr, lifetime): """Given a CSR, look it up in the database, update it and present the new certificate. Args: csr (cryptography.x509.CertificateSigningRequest): A CSR. lifetime (int): Lifetime in seconds. Raises: CertProcessorMismatchedPublicKeyError: The public key from the new CSR does not match the in database Certificate. Returns: cryptography.x509.Certificate: A Signed Certificate for a user. """ common_name = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME) common_name = common_name[0].value bcert = bytes(str(self.storage.get_cert(common_name=common_name)[0]), "UTF-8") old_cert = x509.load_pem_x509_certificate(bcert, backend=default_backend()) old_cert_pub = (old_cert.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("UTF-8")) csr_pub = (csr.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("UTF-8")) if old_cert_pub != csr_pub: raise CertProcessorMismatchedPublicKeyError ca_pkey = self.get_ca_key() ca_cert = self.get_ca_cert(ca_pkey) now = datetime.datetime.utcnow() lifetime_delta = now + datetime.timedelta(seconds=int(lifetime)) alts = [] for alt in self.config.get("ca", "alternate_name").split(","): alts.append(x509.DNSName("{}".format(alt))) cert = (x509.CertificateBuilder().subject_name( old_cert.subject).issuer_name(ca_cert.subject).public_key( csr.public_key()).serial_number( old_cert.serial_number).not_valid_before( old_cert.not_valid_before).not_valid_after( lifetime_delta)) if len(alts) > 0: cert = cert.add_extension(x509.SubjectAlternativeName(alts), critical=False) crl_dp = x509.DistributionPoint( [ x509.UniformResourceIdentifier( "{protocol}://{server_url}/crl".format( protocol=self.PROTOCOL, server_url=self.SERVER_URL)) ], relative_name=None, reasons=None, crl_issuer=None, ) cert = cert.add_extension(x509.CRLDistributionPoints([crl_dp]), critical=False) cert = cert.sign( private_key=ca_pkey, algorithm=hashes.SHA256(), backend=default_backend(), ) self.storage.update_cert(cert=cert, serial_number=cert.serial_number) return cert
def test_uri(self): url = 'https://example.com' self.assertEqual(parse_general_name(url), x509.UniformResourceIdentifier(url)) self.assertEqual(parse_general_name('uri:%s' % url), x509.UniformResourceIdentifier(url))
class TestCertificateRevocationListBuilder(object): def test_issuer_name_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.issuer_name("notanx509name") def test_set_issuer_name_twice(self): builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) ) with pytest.raises(ValueError): builder.issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) ) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_aware_last_update(self, backend): last_time = datetime.datetime(2012, 1, 16, 22, 43) tz = pytz.timezone("US/Pacific") last_time = tz.localize(last_time) utc_last = datetime.datetime(2012, 1, 17, 6, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update(last_time).next_update(next_time) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.last_update == utc_last def test_last_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.last_update("notadatetime") def test_last_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.last_update(datetime.datetime(1940, 8, 10)) def test_set_last_update_twice(self): builder = x509.CertificateRevocationListBuilder().last_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_aware_next_update(self, backend): next_time = datetime.datetime(2022, 1, 16, 22, 43) tz = pytz.timezone("US/Pacific") next_time = tz.localize(next_time) utc_next = datetime.datetime(2022, 1, 17, 6, 43) last_time = datetime.datetime(2012, 1, 17, 6, 43) private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update(last_time).next_update(next_time) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.next_update == utc_next def test_next_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.next_update("notadatetime") def test_next_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.next_update(datetime.datetime(1940, 8, 10)) def test_set_next_update_twice(self): builder = x509.CertificateRevocationListBuilder().next_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) def test_last_update_after_next_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.next_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2003, 1, 1, 12, 1)) def test_next_update_after_last_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.last_update( datetime.datetime(2002, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2001, 1, 1, 12, 1)) def test_add_extension_checks_for_duplicates(self): builder = x509.CertificateRevocationListBuilder().add_extension( x509.CRLNumber(1), False ) with pytest.raises(ValueError): builder.add_extension(x509.CRLNumber(2), False) def test_add_invalid_extension(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_extension( object(), False ) def test_add_invalid_revoked_certificate(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_revoked_certificate(object()) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_issuer_name(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateRevocationListBuilder().last_update( datetime.datetime(2002, 1, 1, 12, 1) ).next_update( datetime.datetime(2030, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_last_update(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) ).next_update( datetime.datetime(2030, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_next_update(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) ).last_update( datetime.datetime(2030, 1, 1, 12, 1) ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_empty_list(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update(last_update).next_update(next_update) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert crl.last_update == last_update assert crl.next_update == next_update @pytest.mark.parametrize( "extension", [ x509.CRLNumber(13), x509.DeltaCRLIndicator(12345678901234567890), x509.AuthorityKeyIdentifier( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" b"\xcbY", None, None ), x509.AuthorityInformationAccess([ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DNSName(u"cryptography.io") ) ]), x509.IssuerAlternativeName([ x509.UniformResourceIdentifier(u"https://cryptography.io"), ]) ] ) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_extensions(self, backend, extension): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_extension( extension, False ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 1 ext = crl.extensions.get_extension_for_class(type(extension)) assert ext.critical is False assert ext.value == extension @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_multiple_extensions_critical(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) ian = x509.IssuerAlternativeName([ x509.UniformResourceIdentifier(u"https://cryptography.io"), ]) crl_number = x509.CRLNumber(13) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_extension( crl_number, False ).add_extension( ian, True ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 2 ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber) assert ext1.critical is False assert ext1.value == crl_number ext2 = crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ) assert ext2.critical is True assert ext2.value == ian @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_add_unsupported_extension(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_extension( x509.OCSPNoCheck(), False ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_rsa_key_too_small(self, backend): private_key = RSA_KEY_512.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ) with pytest.raises(TypeError): builder.sign(private_key, object(), backend) @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_dsa_key(self, backend): private_key = DSA_KEY_2048.private_key(backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName([ x509.UniformResourceIdentifier(u"https://cryptography.io"), ]) revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( 2 ).revocation_date( datetime.datetime(2012, 1, 1, 1, 1) ).add_extension( invalidity_date, False ).build(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_revoked_certificate( revoked_cert0 ).add_extension( ian, False ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ec_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) ian = x509.IssuerAlternativeName([ x509.UniformResourceIdentifier(u"https://cryptography.io"), ]) revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( 2 ).revocation_date( datetime.datetime(2012, 1, 1, 1, 1) ).add_extension( invalidity_date, False ).build(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_revoked_certificate( revoked_cert0 ).add_extension( ian, False ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.extensions.get_extension_for_class( x509.IssuerAlternativeName ).value == ian assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_dsa_key_sign_md5(self, backend): private_key = DSA_KEY_2048.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update(last_time).next_update(next_time) with pytest.raises(ValueError): builder.sign(private_key, hashes.MD5(), backend) @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_ec_key_sign_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = EC_KEY_SECP256R1.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update(last_time).next_update(next_time) with pytest.raises(ValueError): builder.sign(private_key, hashes.MD5(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_revoked_certificates(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0) ) revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( 38 ).revocation_date( datetime.datetime(2011, 1, 1, 1, 1) ).build(backend) revoked_cert1 = x509.RevokedCertificateBuilder().serial_number( 2 ).revocation_date( datetime.datetime(2012, 1, 1, 1, 1) ).add_extension( invalidity_date, False ).build(backend) builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") ]) ).last_update( last_update ).next_update( next_update ).add_revoked_certificate( revoked_cert0 ).add_revoked_certificate( revoked_cert1 ) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 2 assert crl.last_update == last_update assert crl.next_update == next_update assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 0 assert crl[1].serial_number == revoked_cert1.serial_number assert crl[1].revocation_date == revoked_cert1.revocation_date assert len(crl[1].extensions) == 1 ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date
def _decode_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: data = _asn1_string_to_bytes(backend, gn.d.dNSName) if not data: decoded = u"" elif data.startswith(b"*."): # This is a wildcard name. We need to remove the leading wildcard, # IDNA decode, then re-add the wildcard. Wildcard characters should # always be left-most (RFC 2595 section 2.4). decoded = u"*." + idna.decode(data[2:]) else: # Not a wildcard, decode away. If the string has a * in it anywhere # invalid this will raise an InvalidCodePoint decoded = idna.decode(data) if data.startswith(b"."): # idna strips leading periods. Name constraints can have that # so we need to re-add it. Sigh. decoded = u"." + decoded return x509.DNSName(decoded) elif gn.type == backend._lib.GEN_URI: data = _asn1_string_to_ascii(backend, gn.d.uniformResourceIdentifier) parsed = urllib_parse.urlparse(data) if parsed.hostname: hostname = idna.decode(parsed.hostname) else: hostname = "" if parsed.port: netloc = hostname + u":" + six.text_type(parsed.port) else: netloc = hostname # Note that building a URL in this fashion means it should be # semantically indistinguishable from the original but is not # guaranteed to be exactly the same. uri = urllib_parse.urlunparse( (parsed.scheme, netloc, parsed.path, parsed.params, parsed.query, parsed.fragment)) return x509.UniformResourceIdentifier(uri) elif gn.type == backend._lib.GEN_RID: oid = _obj2txt(backend, gn.d.registeredID) return x509.RegisteredID(x509.ObjectIdentifier(oid)) elif gn.type == backend._lib.GEN_IPADD: data = _asn1_string_to_bytes(backend, gn.d.iPAddress) data_len = len(data) if data_len == 8 or data_len == 32: # This is an IPv4 or IPv6 Network and not a single IP. This # type of data appears in Name Constraints. Unfortunately, # ipaddress doesn't support packed bytes + netmask. Additionally, # IPv6Network can only handle CIDR rather than the full 16 byte # netmask. To handle this we convert the netmask to integer, then # find the first 0 bit, which will be the prefix. If another 1 # bit is present after that the netmask is invalid. base = ipaddress.ip_address(data[:data_len // 2]) netmask = ipaddress.ip_address(data[data_len // 2:]) bits = bin(int(netmask))[2:] prefix = bits.find('0') # If no 0 bits are found it is a /32 or /128 if prefix == -1: prefix = len(bits) if "1" in bits[prefix:]: raise ValueError("Invalid netmask") ip = ipaddress.ip_network(base.exploded + u"/{0}".format(prefix)) else: ip = ipaddress.ip_address(data) return x509.IPAddress(ip) elif gn.type == backend._lib.GEN_DIRNAME: return x509.DirectoryName( _decode_x509_name(backend, gn.d.directoryName)) elif gn.type == backend._lib.GEN_EMAIL: data = _asn1_string_to_ascii(backend, gn.d.rfc822Name) name, address = parseaddr(data) parts = address.split(u"@") if name or not address: # parseaddr has found a name (e.g. Name <email>) or the entire # value is an empty string. raise ValueError("Invalid rfc822name value") elif len(parts) == 1: # Single label email name. This is valid for local delivery. No # IDNA decoding can be done since there is no domain component. return x509.RFC822Name(address) else: # A normal email of the form [email protected]. Let's attempt to # decode the domain component and return the entire address. return x509.RFC822Name(parts[0] + u"@" + idna.decode(parts[1])) elif gn.type == backend._lib.GEN_OTHERNAME: type_id = _obj2txt(backend, gn.d.otherName.type_id) value = _asn1_to_der(backend, gn.d.otherName.value) return x509.OtherName(x509.ObjectIdentifier(type_id), value) else: # x400Address or ediPartyName raise x509.UnsupportedGeneralNameType( "{0} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type)), gn.type)
def _decode_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: data = _asn1_string_to_bytes(backend, gn.d.dNSName) if data.startswith(b"*."): # This is a wildcard name. We need to remove the leading wildcard, # IDNA decode, then re-add the wildcard. Wildcard characters should # always be left-most (RFC 2595 section 2.4). decoded = u"*." + idna.decode(data[2:]) else: # Not a wildcard, decode away. If the string has a * in it anywhere # invalid this will raise an InvalidCodePoint decoded = idna.decode(data) if data.startswith(b"."): # idna strips leading periods. Name constraints can have that # so we need to re-add it. Sigh. decoded = u"." + decoded return x509.DNSName(decoded) elif gn.type == backend._lib.GEN_URI: data = _asn1_string_to_ascii(backend, gn.d.uniformResourceIdentifier) parsed = urllib_parse.urlparse(data) hostname = idna.decode(parsed.hostname) if parsed.port: netloc = hostname + u":" + six.text_type(parsed.port) else: netloc = hostname # Note that building a URL in this fashion means it should be # semantically indistinguishable from the original but is not # guaranteed to be exactly the same. uri = urllib_parse.urlunparse( (parsed.scheme, netloc, parsed.path, parsed.params, parsed.query, parsed.fragment)) return x509.UniformResourceIdentifier(uri) elif gn.type == backend._lib.GEN_RID: oid = _obj2txt(backend, gn.d.registeredID) return x509.RegisteredID(x509.ObjectIdentifier(oid)) elif gn.type == backend._lib.GEN_IPADD: return x509.IPAddress( ipaddress.ip_address(_asn1_string_to_bytes(backend, gn.d.iPAddress))) elif gn.type == backend._lib.GEN_DIRNAME: return x509.DirectoryName( _decode_x509_name(backend, gn.d.directoryName)) elif gn.type == backend._lib.GEN_EMAIL: data = _asn1_string_to_ascii(backend, gn.d.rfc822Name) name, address = parseaddr(data) parts = address.split(u"@") if name or len(parts) > 2 or not address: # parseaddr has found a name (e.g. Name <email>) or the split # has found more than 2 parts (which means more than one @ sign) # or the entire value is an empty string. raise ValueError("Invalid rfc822name value") elif len(parts) == 1: # Single label email name. This is valid for local delivery. No # IDNA decoding can be done since there is no domain component. return x509.RFC822Name(address) else: # A normal email of the form [email protected]. Let's attempt to # decode the domain component and return the entire address. return x509.RFC822Name(parts[0] + u"@" + idna.decode(parts[1])) else: # otherName, x400Address or ediPartyName raise x509.UnsupportedGeneralNameType( "{0} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type)), gn.type)
def uri(url): # just a shortcut """Shortcut to get a :py:class:`cg:cryptography.x509.UniformResourceIdentifier`.""" return x509.UniformResourceIdentifier(url)
def create_ca_issuer(url): return x509.AccessDescription( x509.AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier(url))
def action(self, act): if act == 'prep': img_name_tag = 'dtn-demo' LOGGER.info('Building image...') cmd = [ 'build', '-t', img_name_tag, '.', '-f', 'container/Dockerfile' ] if self.args.image_no_cache: cmd += ['--no-cache'] self.run_docker(cmd) LOGGER.info("Ensuring networks...") for (net_name, net_opts) in self._config['nets'].items(): try: self.run_docker(['network', 'inspect', net_name]) except subprocess.CalledProcessError: LOGGER.info("Creating network %s", net_name) cmd = ['network', 'create', net_name] if 'subnet4' in net_opts: cmd += ['--subnet', net_opts['subnet4']] if 'subnet6' in net_opts: cmd += ['--ipv6', '--subnet', net_opts['subnet6']] self.run_docker(cmd) nowtime = datetime.datetime.now(datetime.timezone.utc) # Private CA ca_key = generate_key({}) with open(os.path.join('container', 'workdir', 'ca.key'), 'wb') as outfile: outfile.write( ca_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), )) ca_name = x509.Name([ x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 'Certificate Authority'), ]) ca_cert = x509.CertificateBuilder().subject_name( ca_name).issuer_name(ca_name).public_key( ca_key.public_key()).serial_number( x509.random_serial_number() ).not_valid_before(nowtime).not_valid_after( nowtime + datetime.timedelta(days=10)).add_extension( x509.BasicConstraints(ca=True, path_length=1), critical=True, ).add_extension( x509.KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False, ), critical=False, ).add_extension( x509.SubjectKeyIdentifier.from_public_key( ca_key.public_key()), critical=False, ).add_extension( x509.AuthorityKeyIdentifier.from_issuer_public_key( ca_key.public_key()), critical=False, ).sign(ca_key, hashes.SHA256(), backend=default_backend()) for (node_name, node_opts) in self._config['nodes'].items(): fqdn = node_name + '.local' configPath = os.path.join('container', 'workdir', node_name, 'xdg', 'dtn') if not os.path.isdir(configPath): os.makedirs(configPath) with open(os.path.join(configPath, 'ca.crt'), 'wb') as outfile: outfile.write( ca_cert.public_bytes(serialization.Encoding.PEM)) # Generate node keys sans = [ x509.UniformResourceIdentifier( 'dtn://{}/'.format(node_name)), ] ekus = [ x509.oid.ObjectIdentifier( '1.3.6.1.5.5.7.3.35') # id-kp-bundleSecurity ] for key_name in ('transport', 'sign'): key_opts = node_opts.get('keys', {}).get(key_name, {}) node_key = generate_key(key_opts) with open(os.path.join(configPath, f'{key_name}.key'), 'wb') as outfile: outfile.write( node_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat. TraditionalOpenSSL, encryption_algorithm=serialization. NoEncryption(), )) if key_name == 'transport': sans += [ x509.DNSName(fqdn), ] ekus += [ x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH, x509.oid.ExtendedKeyUsageOID.SERVER_AUTH, ] node_cert = x509.CertificateBuilder().subject_name( x509.Name([ x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, node_name), ]), ).issuer_name(ca_cert.subject).public_key( node_key.public_key()).serial_number( x509.random_serial_number() ).not_valid_before(nowtime).not_valid_after( nowtime + datetime.timedelta(days=10)).add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ).add_extension( x509.SubjectAlternativeName(sans), critical=False, ).add_extension( x509.KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ), critical=False, ).add_extension( x509.ExtendedKeyUsage(ekus), critical=False, ).add_extension( x509.SubjectKeyIdentifier.from_public_key( node_key.public_key()), critical=False, ).add_extension( x509.AuthorityKeyIdentifier. from_issuer_public_key( ca_key.public_key()), critical=False, ).sign(ca_key, hashes.SHA256(), backend=default_backend()) with open(os.path.join(configPath, f'{key_name}.crt'), 'wb') as outfile: outfile.write( node_cert.public_bytes(serialization.Encoding.PEM)) extconfig = node_opts.get('config', {}) use_ipv4 = node_opts.get('use_ipv4', True) use_ipv6 = node_opts.get('use_ipv6', True) nodeid = 'dtn://{}/'.format(node_name) udpcl_listen = [] if extconfig.get('udpcl_listen', True): if use_ipv4: udpcl_listen.append({ 'address': '0.0.0.0', 'multicast_member': [ { 'addr': '224.0.0.1', }, ], }) if use_ipv6: udpcl_listen.append({ 'address': '::', 'multicast_member': [ { 'addr': 'FF02:0:0:0:0:0:0:1', 'iface': 'eth0', }, ], }) tcpcl_listen = [] if extconfig.get('tcpcl_listen', True): if use_ipv4: tcpcl_listen.append({ 'address': '0.0.0.0', }) if use_ipv6: tcpcl_listen.append({ 'address': '::', }) bp_rx_routes = extconfig.get('bp_rx_routes', []) bp_rx_routes += [ { 'eid_pattern': r'dtn://{{node_name}}/.*', 'action': 'deliver', }, { 'eid_pattern': '".*"', 'action': 'forward', }, ] bp_tx_routes = extconfig.get('bp_tx_routes', []) nodeconf = { 'udpcl': { 'log_level': 'debug', 'bus_addr': 'system', 'bus_service': 'org.ietf.dtn.node.udpcl', 'node_id': nodeid, 'dtls_enable_tx': False, 'dtls_ca_file': '/etc/xdg/dtn/ca.crt', 'dtls_cert_file': '/etc/xdg/dtn/transport.crt', 'dtls_key_file': '/etc/xdg/dtn/transport.key', 'polling': extconfig.get('udpcl_polling', []), 'init_listen': udpcl_listen, }, 'tcpcl': { 'log_level': 'debug', 'bus_addr': 'system', 'bus_service': 'org.ietf.dtn.node.tcpcl', 'node_id': nodeid, 'tls_enable': False, 'tls_ca_file': '/etc/xdg/dtn/ca.crt', 'tls_cert_file': '/etc/xdg/dtn/transport.crt', 'tls_key_file': '/etc/xdg/dtn/transport.key', 'init_listen': tcpcl_listen, }, 'bp': { 'log_level': 'debug', 'bus_addr': 'system', 'bus_service': 'org.ietf.dtn.node.bp', 'node_id': nodeid, 'verify_ca_file': '/etc/xdg/dtn/ca.crt', 'sign_cert_file': '/etc/xdg/dtn/sign.crt', 'sign_key_file': '/etc/xdg/dtn/sign.key', 'rx_route_table': bp_rx_routes, 'tx_route_table': bp_tx_routes, 'apps': extconfig.get('apps', {}) }, } with open(os.path.join(configPath, 'node.yaml'), 'w') as outfile: outfile.write(yaml.dump(nodeconf)) cmd = [ 'container', 'create', '--privileged', '-e', 'container=docker', '--mount', f'type=bind,src={SELFDIR}/workdir/{node_name}/xdg/dtn,dst=/etc/xdg/dtn', '--hostname', fqdn, '--name', node_name, ] cmd += [img_name_tag] self.run_docker(cmd) cmd = ['network', 'disconnect', 'bridge', node_name] self.run_docker(cmd) for net_name in node_opts['nets']: cmd = ['network', 'connect', net_name, node_name] self.run_docker(cmd) elif act == 'start': for node_name in self._config['nodes'].keys(): self.run_docker(['container', 'start', node_name]) elif act == 'stop': for node_name in self._config['nodes'].keys(): try: self.run_docker(['container', 'stop', node_name]) except subprocess.CalledProcessError: pass elif act == 'delete': for node_name in self._config['nodes'].keys(): try: self.run_docker(['container', 'rm', '-f', node_name]) except subprocess.CalledProcessError: pass for net_name in self._config['nets'].keys(): try: self.run_docker(['network', 'rm', net_name]) except subprocess.CalledProcessError: pass
def test_generate_root_ca(self): key = Key().create_key(2048) certificate = CertificateFactory(key=key.serialize()) certhandler = Certificate() certhandler.create_certificate(certificate) crt = certhandler.certificate self.assertEqual(crt.serial_number, int(certificate.serial)) self.assertEqual(crt.public_key().public_numbers(), key.key.public_key().public_numbers()) self.assertEqual( crt.not_valid_before, datetime.datetime(year=certificate.created_at.year, month=certificate.created_at.month, day=certificate.created_at.day)) self.assertEqual( crt.not_valid_after, datetime.datetime(year=certificate.expires_at.year, month=certificate.expires_at.month, day=certificate.expires_at.day)) # subject subject = crt.subject self.assertIsInstance(subject, x509.Name) self.assertListEqual(list(subject), [ x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName), x509.NameAttribute(NameOID.ORGANIZATION_NAME, certificate.dn.organizationName), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, certificate.dn.organizationalUnitName), x509.NameAttribute(NameOID.LOCALITY_NAME, certificate.dn.localityName), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, certificate.dn.stateOrProvinceName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, certificate.dn.emailAddress), x509.NameAttribute(NameOID.COUNTRY_NAME, str(certificate.dn.countryName)), ]) # issuer issuer = crt.issuer self.assertIsInstance(issuer, x509.Name) self.assertListEqual(list(subject), [ x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName), x509.NameAttribute(NameOID.ORGANIZATION_NAME, certificate.dn.organizationName), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, certificate.dn.organizationalUnitName), x509.NameAttribute(NameOID.LOCALITY_NAME, certificate.dn.localityName), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, certificate.dn.stateOrProvinceName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, certificate.dn.emailAddress), x509.NameAttribute(NameOID.COUNTRY_NAME, str(certificate.dn.countryName)), ]) # crlDistributionspoints ext = crt.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS) self.assertTrue(ext.critical) crl_dp = ext.value self.assertEqual( crl_dp[0].full_name[0].value, 'URI:{}{}.crl'.format(certificate.crl_distribution_url, certificate.shortname)) self.assertEqual( crl_dp[0].reasons, frozenset([ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.aa_compromise, x509.ReasonFlags.certificate_hold, ])) # keyUsage = basicConstraints = critical, CA:true ext = crt.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS) self.assertTrue(ext.critical) self.assertEqual(ext.value, x509.BasicConstraints(ca=True, path_length=None)) # keyUsage = critical, digitalSignature, cRLSign, keyCertSign ext = crt.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) self.assertTrue(ext.critical) self.assertEqual( ext.value, x509.KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False)) # OCSP # authorityInfoAccess = OCSP;URI:{{cert.ocsp_distribution_host}} ext = crt.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS) self.assertTrue(ext.critical) self.assertEqual( ext.value[0], x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier( certificate.ocsp_distribution_host))) # authorityKeyIdentifier = keyid:always, issuer ext = crt.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER) self.assertTrue(ext.critical) self.assertEqual(ext.value.key_identifier, _key_identifier_from_public_key(key.key.public_key())) # subjectKeyIdentifier = hash ext = crt.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER) self.assertTrue(ext.critical) self.assertEqual(ext.value.digest, _key_identifier_from_public_key(key.key.public_key()))
def test_generate_user_certificate(self): root_key = Key().create_key(2048) root_certificate = CertificateFactory(expires_at=arrow.get( timezone.now()).replace(days=+6).date(), key=root_key.serialize()) root_certhandler = Certificate() root_certhandler.create_certificate(root_certificate) subject = DistinguishedNameFactory( countryName=root_certificate.dn.countryName, stateOrProvinceName=root_certificate.dn.stateOrProvinceName, organizationName=root_certificate.dn.organizationName) int_key = Key().create_key(2048) int_certificate = CertificateFactory( expires_at=arrow.get(timezone.now()).replace(days=+5).date(), type=CertificateTypes.INTERMEDIATE, parent=root_certificate, dn=subject, key=int_key.serialize()) int_certhandler = Certificate() int_certhandler.create_certificate(int_certificate) key = Key().create_key(2048) server_subject = DistinguishedNameFactory( subjectAltNames=["jeroen", "*****@*****.**"]) certificate = CertificateFactory(type=CertificateTypes.CLIENT_CERT, parent=int_certificate, dn=server_subject, key=key.serialize()) certhandler = Certificate() certhandler.create_certificate(certificate) crt = certhandler.certificate self.assertEqual(crt.serial_number, int(certificate.serial)) self.assertEqual(crt.public_key().public_numbers(), key.key.public_key().public_numbers()) self.assertEqual( crt.not_valid_before, datetime.datetime(year=certificate.created_at.year, month=certificate.created_at.month, day=certificate.created_at.day)) self.assertEqual( crt.not_valid_after, datetime.datetime(year=certificate.expires_at.year, month=certificate.expires_at.month, day=certificate.expires_at.day)) # basicConstraints = CA:FALSE ext = crt.extensions.get_extension_for_oid( ExtensionOID.BASIC_CONSTRAINTS) self.assertFalse(ext.critical) self.assertEqual(ext.value, x509.BasicConstraints(ca=False, path_length=None)) # authorityKeyIdentifier = keyid:always, issuer ext = crt.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER) self.assertTrue(ext.critical) self.assertEqual( ext.value.key_identifier, _key_identifier_from_public_key(int_key.key.public_key())) # subjectKeyIdentifier = hash ext = crt.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_KEY_IDENTIFIER) self.assertTrue(ext.critical) self.assertEqual(ext.value.digest, _key_identifier_from_public_key(key.key.public_key())) # keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment ext = crt.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) self.assertTrue(ext.critical) self.assertEqual( ext.value, x509.KeyUsage(digital_signature=True, content_commitment=True, key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False)) # extendedKeyUsage = clientAuth, emailProtection ext = crt.extensions.get_extension_for_oid( ExtensionOID.EXTENDED_KEY_USAGE) self.assertFalse(ext.critical) self.assertEqual([x for x in ext.value], [ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.EMAIL_PROTECTION ]) # { % if cert.dn.subjectAltNames %} # subjectAltName = @alt_names ext = crt.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME) self.assertFalse(ext.critical) self.assertEqual([x.value for x in ext.value], ['jeroen', '*****@*****.**']) # crlDistributionPoints ext = crt.extensions.get_extension_for_oid( ExtensionOID.CRL_DISTRIBUTION_POINTS) self.assertTrue(ext.critical) crl_dp = ext.value self.assertEqual( crl_dp[0].full_name[0].value, 'URI:{}{}.crl'.format(int_certificate.crl_distribution_url, int_certificate.shortname)) self.assertEqual( crl_dp[0].reasons, frozenset([ x509.ReasonFlags.key_compromise, x509.ReasonFlags.ca_compromise, x509.ReasonFlags.affiliation_changed, x509.ReasonFlags.superseded, x509.ReasonFlags.privilege_withdrawn, x509.ReasonFlags.cessation_of_operation, x509.ReasonFlags.aa_compromise, x509.ReasonFlags.certificate_hold, ])) # OCSP # authorityInfoAccess = OCSP;URI:{{cert.ocsp_distribution_host}} ext = crt.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_INFORMATION_ACCESS) self.assertTrue(ext.critical) self.assertEqual( ext.value[0], x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier( int_certificate.ocsp_distribution_host))) # subject subject = crt.subject self.assertIsInstance(subject, x509.Name) self.assertListEqual(list(subject), [ x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName), x509.NameAttribute(NameOID.ORGANIZATION_NAME, certificate.dn.organizationName), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, certificate.dn.organizationalUnitName), x509.NameAttribute(NameOID.LOCALITY_NAME, certificate.dn.localityName), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, certificate.dn.stateOrProvinceName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, certificate.dn.emailAddress), x509.NameAttribute(NameOID.COUNTRY_NAME, str(certificate.dn.countryName)), ]) # issuer issuer = crt.issuer self.assertIsInstance(issuer, x509.Name) self.assertListEqual(list(issuer), [ x509.NameAttribute(NameOID.COMMON_NAME, int_certificate.dn.commonName), x509.NameAttribute(NameOID.ORGANIZATION_NAME, int_certificate.dn.organizationName), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, int_certificate.dn.organizationalUnitName), x509.NameAttribute(NameOID.LOCALITY_NAME, int_certificate.dn.localityName), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, int_certificate.dn.stateOrProvinceName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, int_certificate.dn.emailAddress), x509.NameAttribute(NameOID.COUNTRY_NAME, str(int_certificate.dn.countryName)), ])
def _build_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: data = backend._ffi.buffer(gn.d.dNSName.data, gn.d.dNSName.length)[:] return x509.DNSName(idna.decode(data)) elif gn.type == backend._lib.GEN_URI: data = backend._ffi.buffer( gn.d.uniformResourceIdentifier.data, gn.d.uniformResourceIdentifier.length )[:].decode("ascii") parsed = urllib_parse.urlparse(data) hostname = idna.decode(parsed.hostname) if parsed.port: netloc = hostname + u":" + six.text_type(parsed.port) else: netloc = hostname # Note that building a URL in this fashion means it should be # semantically indistinguishable from the original but is not # guaranteed to be exactly the same. uri = urllib_parse.urlunparse(( parsed.scheme, netloc, parsed.path, parsed.params, parsed.query, parsed.fragment )) return x509.UniformResourceIdentifier(uri) elif gn.type == backend._lib.GEN_RID: oid = _obj2txt(backend, gn.d.registeredID) return x509.RegisteredID(x509.ObjectIdentifier(oid)) elif gn.type == backend._lib.GEN_IPADD: return x509.IPAddress( ipaddress.ip_address( backend._ffi.buffer( gn.d.iPAddress.data, gn.d.iPAddress.length )[:] ) ) elif gn.type == backend._lib.GEN_DIRNAME: return x509.DirectoryName( _build_x509_name(backend, gn.d.directoryName) ) elif gn.type == backend._lib.GEN_EMAIL: data = backend._ffi.buffer( gn.d.rfc822Name.data, gn.d.rfc822Name.length )[:].decode("ascii") name, address = parseaddr(data) parts = address.split(u"@") if name or len(parts) > 2 or not address: # parseaddr has found a name (e.g. Name <email>) or the split # has found more than 2 parts (which means more than one @ sign) # or the entire value is an empty string. raise ValueError("Invalid rfc822name value") elif len(parts) == 1: # Single label email name. This is valid for local delivery. No # IDNA decoding can be done since there is no domain component. return x509.RFC822Name(address) else: # A normal email of the form [email protected]. Let's attempt to # decode the domain component and return the entire address. return x509.RFC822Name( parts[0] + u"@" + idna.decode(parts[1]) ) else: # otherName, x400Address or ediPartyName raise x509.UnsupportedGeneralNameType( "{0} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type) ), gn.type )
def parse_general_name(name: ParsableGeneralName) -> x509.GeneralName: """Parse a general name from user input. This function will do its best to detect the intended type of any value passed to it: >>> parse_general_name('example.com') <DNSName(value='example.com')> >>> parse_general_name('*.example.com') <DNSName(value='*.example.com')> >>> parse_general_name('.example.com') # Syntax used e.g. for NameConstraints: All levels of subdomains <DNSName(value='.example.com')> >>> parse_general_name('*****@*****.**') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('1.2.3.4') <IPAddress(value=1.2.3.4)> >>> parse_general_name('fd00::1') <IPAddress(value=fd00::1)> >>> parse_general_name('/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't work, an exception will be raised: >>> parse_general_name('foo..bar`*123') # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Could not parse name: foo..bar`*123 If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`: >>> parse_general_name('email:[email protected]') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('URI:https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('dirname:/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> Some more exotic values can only be generated by using this prefix: >>> parse_general_name('rid:2.5.4.3') <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)> >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com') <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')> If you give a prefixed value, this function is less forgiving of any typos and does not catch any exceptions: >>> parse_general_name('email:foo@bar com') Traceback (most recent call last): ... ValueError: Invalid domain: bar com """ # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements if isinstance(name, x509.GeneralName): return name if not isinstance(name, str): raise ValueError( f"Cannot parse general name {name}: Must be of type str (was: {type(name).__name__})." ) typ = None match = GENERAL_NAME_RE.match(name) if match is not None: typ, name = match.groups() typ = typ.lower() if typ is None: if re.match("[a-z0-9]{2,}://", name): # Looks like a URI try: return x509.UniformResourceIdentifier(encode_url(name)) except idna.IDNAError: pass if "@" in name: # Looks like an Email address try: return x509.RFC822Name(validate_email(name)) except ValueError: pass if name.strip().startswith("/"): # maybe it's a dirname? return x509.DirectoryName(x509_name(name)) # Try to parse this as IPAddress/Network try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass # Almost anything passes as DNS name, so this is our default fallback try: return x509.DNSName(encode_dns(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse name: {name}") from e if typ == "uri": try: return x509.UniformResourceIdentifier(encode_url(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse DNS name in URL: {name}") from e elif typ == "email": return x509.RFC822Name( validate_email(name)) # validate_email already raises ValueError elif typ == "ip": try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass raise ValueError("Could not parse IP address.") elif typ == "rid": return x509.RegisteredID(x509.ObjectIdentifier(name)) elif typ == "othername": match = re.match("(.*);(.*):(.*)", name) if match is not None: oid, asn_typ, val = match.groups() if asn_typ == "UTF8": parsed_value = val.encode("utf-8") elif asn_typ == "OctetString": parsed_value = OctetString(bytes( bytearray.fromhex(val))).dump() else: raise ValueError( f"Unsupported ASN type in otherName: {asn_typ}") return x509.OtherName(x509.ObjectIdentifier(oid), parsed_value) raise ValueError(f"Incorrect otherName format: {name}") elif typ == "dirname": return x509.DirectoryName(x509_name(name)) else: try: return x509.DNSName(encode_dns(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse DNS name: {name}") from e
def signing_publickey(self, user: User, service: Service, publickey: str, valid_time=DAY * 365): _public_key = serialization.load_pem_public_key( publickey.encode(), backend=default_backend()) ca_private_key, ca_cert = self._init_ca(service) ca_name = service.name username = str(user.username) config = service.pki_config #TODO use this config domain = self._domain not_valid_before = datetime.datetime.utcnow() ca_public_key = ca_private_key.public_key() end_entity_cert_builder = x509.CertificateBuilder().\ subject_name(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, config['cn'].format(username=username, domain=domain)), x509.NameAttribute(NameOID.EMAIL_ADDRESS, config['email'].format(username=username, domain=domain)), ])).\ issuer_name(ca_cert.subject).\ not_valid_before(not_valid_before).\ not_valid_after(not_valid_before + valid_time).\ serial_number(x509.random_serial_number()).\ public_key(_public_key).\ add_extension( x509.SubjectAlternativeName([ x509.DNSName(f'{username}'), ]), critical=False).\ add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True).\ add_extension( x509.KeyUsage(digital_signature=True, content_commitment=True, # False key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False), critical=True).\ add_extension( x509.ExtendedKeyUsage([ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH, ]), critical=False).\ add_extension( x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_public_key), critical=False).\ add_extension( x509.SubjectKeyIdentifier.from_public_key(_public_key), critical=False).\ add_extension( x509.CRLDistributionPoints([ x509.DistributionPoint( full_name=[x509.UniformResourceIdentifier(f'http://crl.{self._domain}/{ca_name}.crl')], relative_name=None, crl_issuer=None, reasons=None) ]), critical=False).\ add_extension( x509.AuthorityInformationAccess([ x509.AccessDescription( access_method=x509.AuthorityInformationAccessOID.CA_ISSUERS, access_location=x509.UniformResourceIdentifier(f'https://www.{self._domain}')), x509.AccessDescription( access_method=x509.AuthorityInformationAccessOID.OCSP, access_location=x509.UniformResourceIdentifier(f'http://ocsp.{self._domain}/{ca_name}/')) ]), critical=False) end_entity_cert = end_entity_cert_builder.\ sign( private_key=ca_private_key, algorithm=hashes.SHA256(), backend=default_backend() ) serial_number = f'{end_entity_cert.serial_number:X}' end_entity_cert_filename = self._pki_path / ca_name / \ f'{safe_filename(username)}-{serial_number}.crt.pem' # save cert with end_entity_cert_filename.open("wb") as end_entity_cert_file: end_entity_cert_file.write( end_entity_cert.public_bytes( encoding=serialization.Encoding.PEM)) return Certificate(user.username, service.name, end_entity_cert)
class TestCertificateRevocationListBuilder(object): def test_issuer_name_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.issuer_name("notanx509name") # type:ignore[arg-type] def test_set_issuer_name_twice(self): builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])) with pytest.raises(ValueError): builder.issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_aware_last_update(self, backend): last_time = datetime.datetime(2012, 1, 16, 22, 43) tz = pytz.timezone("US/Pacific") last_time = tz.localize(last_time) utc_last = datetime.datetime(2012, 1, 17, 6, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) private_key = RSA_KEY_2048.private_key(backend) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_time).next_update(next_time)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.last_update == utc_last def test_last_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.last_update("notadatetime") # type:ignore[arg-type] def test_last_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.last_update(datetime.datetime(1940, 8, 10)) def test_set_last_update_twice(self): builder = x509.CertificateRevocationListBuilder().last_update( datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_aware_next_update(self, backend): next_time = datetime.datetime(2022, 1, 16, 22, 43) tz = pytz.timezone("US/Pacific") next_time = tz.localize(next_time) utc_next = datetime.datetime(2022, 1, 17, 6, 43) last_time = datetime.datetime(2012, 1, 17, 6, 43) private_key = RSA_KEY_2048.private_key(backend) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_time).next_update(next_time)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert crl.next_update == utc_next def test_next_update_invalid(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.next_update("notadatetime") # type:ignore[arg-type] def test_next_update_before_1950(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(ValueError): builder.next_update(datetime.datetime(1940, 8, 10)) def test_set_next_update_twice(self): builder = x509.CertificateRevocationListBuilder().next_update( datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) def test_last_update_after_next_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.last_update(datetime.datetime(2003, 1, 1, 12, 1)) def test_next_update_after_last_update(self): builder = x509.CertificateRevocationListBuilder() builder = builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) with pytest.raises(ValueError): builder.next_update(datetime.datetime(2001, 1, 1, 12, 1)) def test_add_extension_checks_for_duplicates(self): builder = x509.CertificateRevocationListBuilder().add_extension( x509.CRLNumber(1), False) with pytest.raises(ValueError): builder.add_extension(x509.CRLNumber(2), False) def test_add_invalid_extension(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_extension(object(), False) # type:ignore[arg-type] def test_add_invalid_revoked_certificate(self): builder = x509.CertificateRevocationListBuilder() with pytest.raises(TypeError): builder.add_revoked_certificate(object()) # type:ignore[arg-type] @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_issuer_name(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = (x509.CertificateRevocationListBuilder().last_update( datetime.datetime(2002, 1, 1, 12, 1)).next_update( datetime.datetime(2030, 1, 1, 12, 1))) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_last_update(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US") ])).next_update(datetime.datetime(2030, 1, 1, 12, 1))) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_no_next_update(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US") ])).last_update(datetime.datetime(2030, 1, 1, 12, 1))) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_empty_list(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert crl.last_update == last_update assert crl.next_update == next_update @pytest.mark.parametrize( "extension", [ x509.CRLNumber(13), x509.DeltaCRLIndicator(12345678901234567890), x509.AuthorityKeyIdentifier( b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" b"\xcbY", None, None, ), x509.AuthorityInformationAccess([ x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.DNSName("cryptography.io"), ) ]), x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]), ], ) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_extensions(self, backend, extension): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update( next_update).add_extension(extension, False)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 1 ext = crl.extensions.get_extension_for_class(type(extension)) assert ext.critical is False assert ext.value == extension @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_multiple_extensions_critical(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]) crl_number = x509.CRLNumber(13) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update( next_update).add_extension(crl_number, False).add_extension(ian, True)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 2 ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber) assert ext1.critical is False assert ext1.value == crl_number ext2 = crl.extensions.get_extension_for_class( x509.IssuerAlternativeName) assert ext2.critical is True assert ext2.value == ian @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_freshestcrl_extension(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) freshest = x509.FreshestCRL([ x509.DistributionPoint( [x509.UniformResourceIdentifier("http://d.om/delta")], None, None, None, ) ]) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update( next_update).add_extension(freshest, False)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 0 assert len(crl.extensions) == 1 ext1 = crl.extensions.get_extension_for_class(x509.FreshestCRL) assert ext1.critical is False assert isinstance(ext1.value[0], x509.DistributionPoint) uri = ext1.value[0].full_name[0] assert isinstance(uri, x509.UniformResourceIdentifier) assert uri.value == "http://d.om/delta" @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_add_unsupported_extension(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update( next_update).add_extension(x509.OCSPNoCheck(), False)) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_rsa_key_too_small(self, backend): private_key = RSA_KEY_512.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update)) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA512(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update)) with pytest.raises(TypeError): builder.sign(private_key, object(), backend) @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash_ed25519(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update)) with pytest.raises(ValueError): builder.sign(private_key, object(), backend) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_invalid_hash_ed448(self, backend): private_key = ed448.Ed448PrivateKey.generate() last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update)) with pytest.raises(ValueError): builder.sign(private_key, object(), backend) with pytest.raises(ValueError): builder.sign(private_key, hashes.SHA256(), backend) @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_dsa_key(self, backend): private_key = DSA_KEY_2048.private_key(backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0)) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]) revoked_cert0 = ( x509.RevokedCertificateBuilder().serial_number(2).revocation_date( datetime.datetime(2012, 1, 1, 1, 1)).add_extension(invalidity_date, False).build(backend)) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update). add_revoked_certificate(revoked_cert0).add_extension( ian, False)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert (crl.extensions.get_extension_for_class( x509.IssuerAlternativeName).value == ian) assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ec_key(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = ec.generate_private_key(ec.SECP256R1(), backend) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0)) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]) revoked_cert0 = ( x509.RevokedCertificateBuilder().serial_number(2).revocation_date( datetime.datetime(2012, 1, 1, 1, 1)).add_extension(invalidity_date, False).build(backend)) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update). add_revoked_certificate(revoked_cert0).add_extension( ian, False)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert (crl.extensions.get_extension_for_class( x509.IssuerAlternativeName).value == ian) assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.supported( only_if=lambda backend: backend.ed25519_supported(), skip_message="Requires OpenSSL with Ed25519 support", ) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ed25519_key(self, backend): private_key = ed25519.Ed25519PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0)) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]) revoked_cert0 = ( x509.RevokedCertificateBuilder().serial_number(2).revocation_date( datetime.datetime(2012, 1, 1, 1, 1)).add_extension(invalidity_date, False).build(backend)) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update). add_revoked_certificate(revoked_cert0).add_extension( ian, False)) crl = builder.sign(private_key, None, backend) assert crl.signature_hash_algorithm is None assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED25519 assert (crl.extensions.get_extension_for_class( x509.IssuerAlternativeName).value == ian) assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.supported( only_if=lambda backend: backend.ed448_supported(), skip_message="Requires OpenSSL with Ed448 support", ) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_ed448_key(self, backend): private_key = ed448.Ed448PrivateKey.generate() invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0)) ian = x509.IssuerAlternativeName( [x509.UniformResourceIdentifier("https://cryptography.io")]) revoked_cert0 = ( x509.RevokedCertificateBuilder().serial_number(2).revocation_date( datetime.datetime(2012, 1, 1, 1, 1)).add_extension(invalidity_date, False).build(backend)) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update(next_update). add_revoked_certificate(revoked_cert0).add_extension( ian, False)) crl = builder.sign(private_key, None, backend) assert crl.signature_hash_algorithm is None assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED448 assert (crl.extensions.get_extension_for_class( x509.IssuerAlternativeName).value == ian) assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 1 ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_dsa_key_sign_md5(self, backend): private_key = DSA_KEY_2048.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_time).next_update(next_time)) with pytest.raises(ValueError): builder.sign(private_key, hashes.MD5(), backend) @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_ec_key_sign_md5(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) private_key = EC_KEY_SECP256R1.private_key(backend) last_time = datetime.datetime(2012, 1, 16, 22, 43) next_time = datetime.datetime(2022, 1, 17, 6, 43) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_time).next_update(next_time)) with pytest.raises(ValueError): builder.sign(private_key, hashes.MD5(), backend) @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_sign_with_revoked_certificates(self, backend): private_key = RSA_KEY_2048.private_key(backend) last_update = datetime.datetime(2002, 1, 1, 12, 1) next_update = datetime.datetime(2030, 1, 1, 12, 1) invalidity_date = x509.InvalidityDate( datetime.datetime(2002, 1, 1, 0, 0)) revoked_cert0 = ( x509.RevokedCertificateBuilder().serial_number(38).revocation_date( datetime.datetime(2011, 1, 1, 1, 1)).build(backend)) revoked_cert1 = ( x509.RevokedCertificateBuilder().serial_number(2).revocation_date( datetime.datetime(2012, 1, 1, 1, 1)).add_extension(invalidity_date, False).build(backend)) builder = (x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA") ])).last_update(last_update).next_update( next_update).add_revoked_certificate( revoked_cert0).add_revoked_certificate(revoked_cert1)) crl = builder.sign(private_key, hashes.SHA256(), backend) assert len(crl) == 2 assert crl.last_update == last_update assert crl.next_update == next_update assert crl[0].serial_number == revoked_cert0.serial_number assert crl[0].revocation_date == revoked_cert0.revocation_date assert len(crl[0].extensions) == 0 assert crl[1].serial_number == revoked_cert1.serial_number assert crl[1].revocation_date == revoked_cert1.revocation_date assert len(crl[1].extensions) == 1 ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) assert ext.critical is False assert ext.value == invalidity_date
def profile_ca(builder, ca_nick, ca): now = datetime.datetime.utcnow() builder = builder.not_valid_before(now) builder = builder.not_valid_after(now + 10 * YEAR) crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick)) builder = builder.add_extension( x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False, ), critical=True, ) builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True, ) builder = builder.add_extension( x509.CRLDistributionPoints([ x509.DistributionPoint( full_name=[x509.UniformResourceIdentifier(crl_uri)], relative_name=None, crl_issuer=None, reasons=None, ), ]), critical=False, ) public_key = builder._public_key builder = builder.add_extension( x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False, ) # here we get "ca" only for "ca1/subca" CA if not ca: builder = builder.add_extension( x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key), critical=False, ) else: ski_ext = ca.cert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier) auth_keyidentifier = ( x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier) ''' cryptography < 2.7 accepts only Extension object. Remove this workaround when all supported platforms update python-cryptography. ''' if (parse_version(cryptography_version) >= parse_version('2.7')): extension = auth_keyidentifier(ski_ext.value) else: extension = auth_keyidentifier(ski_ext) builder = builder.add_extension(extension, critical=False) return builder
def profile_kdc(builder, ca_nick, ca, warp=datetime.timedelta(days=0), dns_name=None, badusage=False): now = datetime.datetime.utcnow() + warp builder = builder.not_valid_before(now) builder = builder.not_valid_after(now + YEAR) crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick)) builder = builder.add_extension( x509.ExtendedKeyUsage([x509.ObjectIdentifier('1.3.6.1.5.2.3.5')]), critical=False, ) name = { 'realm': realm, 'principalName': { 'name-type': 2, 'name-string': ['krbtgt', realm], }, } name = native_decoder.decode(name, asn1Spec=KRB5PrincipalName()) name = der_encoder.encode(name) names = [x509.OtherName(x509.ObjectIdentifier('1.3.6.1.5.2.2'), name)] if dns_name is not None: names += [x509.DNSName(dns_name)] builder = builder.add_extension( x509.SubjectAlternativeName(names), critical=False, ) builder = builder.add_extension( x509.CRLDistributionPoints([ x509.DistributionPoint( full_name=[x509.UniformResourceIdentifier(crl_uri)], relative_name=None, crl_issuer=None, reasons=None, ), ]), critical=False, ) if badusage: builder = builder.add_extension(x509.KeyUsage(digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=True, key_agreement=True, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False), critical=False) return builder
def parse_general_name(name: ParsableGeneralName) -> x509.GeneralName: """Parse a general name from user input. This function will do its best to detect the intended type of any value passed to it: >>> parse_general_name('example.com') <DNSName(value='example.com')> >>> parse_general_name('*.example.com') <DNSName(value='*.example.com')> >>> parse_general_name('.example.com') # Syntax used e.g. for NameConstraints: All levels of subdomains <DNSName(value='.example.com')> >>> parse_general_name('*****@*****.**') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('1.2.3.4') <IPAddress(value=1.2.3.4)> >>> parse_general_name('fd00::1') <IPAddress(value=fd00::1)> >>> parse_general_name('/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't work, an exception will be raised: >>> parse_general_name('foo..bar`*123') # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Could not parse name: foo..bar`*123 If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`: >>> parse_general_name('email:[email protected]') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('URI:https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('dirname:/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> Some more exotic values can only be generated by using this prefix: >>> parse_general_name('rid:2.5.4.3') <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)> >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com') <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'\\x0c\\x0bexample.com')> If you give a prefixed value, this function is less forgiving of any typos and does not catch any exceptions: >>> parse_general_name('email:foo@bar com') Traceback (most recent call last): ... ValueError: Invalid domain: bar com """ # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements if isinstance(name, x509.GeneralName): return name if not isinstance(name, str): raise ValueError( f"Cannot parse general name {name}: Must be of type str (was: {type(name).__name__})." ) typ = None match = GENERAL_NAME_RE.match(name) if match is not None: typ, name = match.groups() typ = typ.lower() if typ is None: if re.match("[a-z0-9]{2,}://", name): # Looks like a URI try: return x509.UniformResourceIdentifier(encode_url(name)) except idna.IDNAError: pass if "@" in name: # Looks like an Email address try: return x509.RFC822Name(validate_email(name)) except ValueError: pass if name.strip().startswith("/"): # maybe it's a dirname? return x509.DirectoryName(x509_name(name)) # Try to parse this as IPAddress/Network try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass # Almost anything passes as DNS name, so this is our default fallback try: return x509.DNSName(encode_dns(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse name: {name}") from e if typ == "uri": try: return x509.UniformResourceIdentifier(encode_url(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse DNS name in URL: {name}") from e elif typ == "email": return x509.RFC822Name(validate_email(name)) # validate_email already raises ValueError elif typ == "ip": try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass raise ValueError("Could not parse IP address.") elif typ == "rid": return x509.RegisteredID(x509.ObjectIdentifier(name)) elif typ == "othername": match = re.match("(.*?);(.*?):(.*)", name) if match is not None: oid, asn_typ, val = match.groups() # Get DER representation of the value for x509.OtherName() if asn_typ in ("UTF8", "UTF8String"): parsed_value = asn1crypto.core.UTF8String(val).dump() elif asn_typ in ("UNIV", "UNIVERSALSTRING"): parsed_value = asn1crypto.core.UniversalString(val).dump() elif asn_typ in ("IA5", "IA5STRING"): parsed_value = asn1crypto.core.IA5String(val).dump() elif asn_typ in ("BOOL", "BOOLEAN"): # nconf allows for true, y, yes, false, n and no as valid values if val.lower() in ("true", "y", "yes"): parsed_value = asn1crypto.core.Boolean(True).dump() elif val.lower() in ("false", "n", "no"): parsed_value = asn1crypto.core.Boolean(False).dump() else: raise ValueError( f"Unsupported {asn_typ} specification for otherName: {val}: Must be TRUE or FALSE" ) elif asn_typ in ("UTC", "UTCTIME"): parsed_datetime = datetime.strptime(val, "%y%m%d%H%M%SZ").replace(tzinfo=timezone.utc) parsed_value = asn1crypto.core.UTCTime(parsed_datetime).dump() elif asn_typ in ("GENTIME", "GENERALIZEDTIME"): parsed_datetime = datetime.strptime(val, "%Y%m%d%H%M%SZ").replace(tzinfo=timezone.utc) parsed_value = asn1crypto.core.GeneralizedTime(parsed_datetime).dump() elif asn_typ == "NULL": if val: raise ValueError("Invalid NULL specification for otherName: Value must not be present") parsed_value = asn1crypto.core.Null().dump() elif asn_typ in ("INT", "INTEGER"): if val.startswith("0x"): parsed_value = asn1crypto.core.Integer(int(val, 16)).dump() else: parsed_value = asn1crypto.core.Integer(int(val)).dump() elif asn_typ == "OctetString": parsed_value = asn1crypto.core.OctetString(bytes(bytearray.fromhex(val))).dump() else: raise ValueError(f"Unsupported ASN type in otherName: {asn_typ}") # NOTE: cryptography docs are not really clear on what kind of bytes x509.OtherName() expects, but # the test suite explicitly use b"derdata" as value, indicating DER encoded data. return x509.OtherName(x509.ObjectIdentifier(oid), parsed_value) raise ValueError(f"Incorrect otherName format: {name}") elif typ == "dirname": return x509.DirectoryName(x509_name(name)) else: try: return x509.DNSName(encode_dns(name)) except idna.IDNAError as e: raise ValueError(f"Could not parse DNS name: {name}") from e
def get_crl(self, expires=86400, encoding=None, algorithm=None, password=None, scope=None, counter=None, **kwargs): """Generate a Certificate Revocation List (CRL). The ``full_name`` and ``relative_name`` parameters describe how to retrieve the CRL and are used in the `Issuing Distribution Point extension <https://tools.ietf.org/html/rfc5280.html#section-5.2.5>`_. The former defaults to the ``crl_url`` field, pass ``None`` to not include the value. At most one of the two may be set. Parameters ---------- expires : int The time in seconds when this CRL expires. Note that you should generate a new CRL until then. encoding : :py:class:`~cg:cryptography.hazmat.primitives.serialization.Encoding` or str, optional The encoding format for the CRL, passed to :py:func:`~django_ca.utils.parse_encoding`. The default value is ``"PEM"``. algorithm : :py:class:`~cg:cryptography.hazmat.primitives.hashes.Hash` or str, optional The hash algorithm to use, passed to :py:func:`~django_ca.utils.parse_hash_algorithm`. The default is to use :ref:`CA_DIGEST_ALGORITHM <settings-ca-digest-algorithm>`. password : bytes, optional Password used to load the private key of the certificate authority. If not passed, the private key is assumed to be unencrypted. scope : {None, 'ca', 'user', 'attribute'}, optional What to include in the CRL: Use ``"ca"`` to include only revoked certificate authorities and ``"user"`` to include only certificates or ``None`` (the default) to include both. ``"attribute"`` is reserved for future use and always produces an empty CRL. counter : str, optional Override the counter-variable for the CRL Number extension. Passing the same key to multiple invocations will yield a different sequence then what would ordinarily be returned. The default is to use the scope as the key. full_name : list of str or :py:class:`~cg:cryptography.x509.GeneralName`, optional List of general names to use in the Issuing Distribution Point extension. If not passed, use ``crl_url`` if set. relative_name : :py:class:`~cg:cryptography.x509.RelativeDistinguishedName`, optional Used in Issuing Distribution Point extension, retrieve the CRL relative to the issuer. Returns ------- bytes The CRL in the requested format. """ if scope is not None and scope not in ['ca', 'user', 'attribute']: raise ValueError( 'Scope must be either None, "ca", "user" or "attribute"') encoding = parse_encoding(encoding) now = now_builder = timezone.now() algorithm = parse_hash_algorithm(algorithm) if timezone.is_aware(now_builder): now_builder = timezone.make_naive(now, pytz.utc) else: now_builder = datetime.utcnow() builder = x509.CertificateRevocationListBuilder() builder = builder.issuer_name(self.x509.subject) builder = builder.last_update(now_builder) builder = builder.next_update(now_builder + timedelta(seconds=expires)) if 'full_name' in kwargs: full_name = kwargs['full_name'] full_name = [parse_general_name(n) for n in full_name] elif self.crl_url: crl_url = [url.strip() for url in self.crl_url.split()] full_name = [x509.UniformResourceIdentifier(c) for c in crl_url] else: full_name = None # Keyword arguments for the IssuingDistributionPoint extension idp_kwargs = { 'only_contains_ca_certs': False, 'only_contains_user_certs': False, 'indirect_crl': False, 'only_contains_attribute_certs': False, 'only_some_reasons': None, 'full_name': full_name, 'relative_name': kwargs.get('relative_name'), } ca_qs = self.children.filter(expires__gt=now).revoked() cert_qs = self.certificate_set.filter(expires__gt=now).revoked() if scope == 'ca': certs = ca_qs idp_kwargs['only_contains_ca_certs'] = True elif scope == 'user': certs = cert_qs idp_kwargs['only_contains_user_certs'] = True elif scope == 'attribute': # sorry, nothing we support right now certs = [] idp_kwargs['only_contains_attribute_certs'] = True else: certs = itertools.chain(ca_qs, cert_qs) for cert in certs: builder = builder.add_revoked_certificate(cert.get_revocation()) # We can only add the IDP extension if one of these properties is set, see RFC 5280, 5.2.5. add_idp = idp_kwargs['only_contains_attribute_certs'] or idp_kwargs['only_contains_user_certs'] \ or idp_kwargs['only_contains_ca_certs'] or idp_kwargs['full_name'] or idp_kwargs['relative_name'] if add_idp: # pragma: no branch builder = builder.add_extension( x509.IssuingDistributionPoint(**idp_kwargs), critical=True) # Add AuthorityKeyIdentifier from CA if present try: aki = self.x509.extensions.get_extension_for_oid( ExtensionOID.AUTHORITY_KEY_IDENTIFIER) builder = builder.add_extension(aki.value, critical=aki.critical) except x509.ExtensionNotFound: pass # Add the CRLNumber extension (RFC 5280, 5.2.3) if counter is None: counter = scope or 'all' crl_number_data = json.loads(self.crl_number) crl_number = int(crl_number_data['scope'].get(counter, 0)) builder = builder.add_extension(x509.CRLNumber(crl_number=crl_number), critical=False) # increase crl_number for the given scope and save crl_number_data['scope'][counter] = crl_number + 1 self.crl_number = json.dumps(crl_number_data) self.save() crl = builder.sign(private_key=self.key(password), algorithm=algorithm, backend=default_backend()) return crl.public_bytes(encoding)
def sign_csr(key, csr, caprofile, valid=30, isca=False, cacert=None, aia_issuers=None, ocsp_responders=None): global SETTINGS one_day = datetime.timedelta(1, 0, 0) builder = x509.CertificateBuilder() builder = builder.subject_name(csr.subject) if not cacert: builder = builder.issuer_name(x509.Name(construct_sn(caprofile))) else: builder = builder.issuer_name(cacert.subject) builder = builder.not_valid_before(datetime.datetime.today() - one_day) builder = builder.not_valid_after(datetime.datetime.today() + (one_day * valid)) #builder = builder.serial_number(x509.random_serial_number()) # too new to some systems builder = builder.serial_number( int.from_bytes(os.urandom(10), byteorder="big")) builder = builder.public_key(csr.public_key()) builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key( csr.public_key()), critical=False) # more info about issuer has_ski = False try: if cacert: ski = cacert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier) builder = builder.add_extension( x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ski), critical=False) has_ski = True except AttributeError: # this is workaround for older versions of python cryptography, not having from_issuer_subject_key_identifier # -> which throws AttributeError has_ski = False except x509.extensions.ExtensionNotFound: has_ski = False if not has_ski: builder = builder.add_extension( x509.AuthorityKeyIdentifier.from_issuer_public_key( key.public_key()), critical=False) all_aias = [] if aia_issuers: for loc in aia_issuers: aia_uri = x509.AccessDescription( AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier(loc)) all_aias.append(aia_uri) if ocsp_responders: for resp in ocsp_responders: aia_uri = x509.AccessDescription( AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier(resp)) all_aias.append(aia_uri) if all_aias: alist = x509.AuthorityInformationAccess(all_aias) builder = builder.add_extension(alist, critical=False) print("sign CSR: == extensions ==") for e in csr.extensions: if isinstance(e.value, x509.BasicConstraints): print("sign CSR: %s" % (e.oid, )) if e.value.ca: print(" CA=TRUE requested") if isca and not SETTINGS["ca"]["settings"]["grant_ca"]: print(" not allowed but overridden") elif not SETTINGS["ca"]["settings"]["grant_ca"]: print(" not allowed by rule") continue else: print(" allowed by rule") builder = builder.add_extension(e.value, e.critical) certificate = builder.sign(private_key=key, algorithm=hashes.SHA256(), backend=default_backend()) return certificate
def get_idp_full_name(self, ca): """Get the IDP full name for `ca`.""" crl_url = [url.strip() for url in ca.crl_url.split()] return [x509.UniformResourceIdentifier(c) for c in crl_url] or None
def get_idp_full_name( self, ca: CertificateAuthority ) -> typing.Optional[typing.List[x509.UniformResourceIdentifier]]: """Get the IDP full name for `ca`.""" crl_url = [url.strip() for url in ca.crl_url.split()] return [x509.UniformResourceIdentifier(c) for c in crl_url] or None
def parse_general_name(name): """Parse a general name from user input. This function will do its best to detect the intended type of any value passed to it: >>> parse_general_name('example.com') <DNSName(value='example.com')> >>> parse_general_name('*.example.com') <DNSName(value='*.example.com')> >>> parse_general_name('.example.com') # Syntax used e.g. for NameConstraints: All levels of subdomains <DNSName(value='.example.com')> >>> parse_general_name('*****@*****.**') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('1.2.3.4') <IPAddress(value=1.2.3.4)> >>> parse_general_name('fd00::1') <IPAddress(value=fd00::1)> >>> parse_general_name('/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't work, an exception will be raised: >>> parse_general_name('foo..bar`*123') # doctest: +ELLIPSIS Traceback (most recent call last): ... idna.core.IDNAError: ... If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`: >>> parse_general_name('email:[email protected]') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('URI:https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('dirname:/CN=example.com') <DirectoryName(value=<Name(CN=example.com)>)> Some more exotic values can only be generated by using this prefix: >>> parse_general_name('rid:2.5.4.3') <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)> >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com') <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')> If you give a prefixed value, this function is less forgiving of any typos and does not catch any exceptions: >>> parse_general_name('email:foo@bar com') Traceback (most recent call last): ... ValueError: Invalid domain: bar com """ name = force_text(name) typ = None match = GENERAL_NAME_RE.match(name) if match is not None: typ, name = match.groups() typ = typ.lower() if typ is None: if re.match('[a-z0-9]{2,}://', name): # Looks like a URI try: return x509.UniformResourceIdentifier(name) except Exception: # pragma: no cover - this really accepts anything pass if '@' in name: # Looks like an Email address try: return x509.RFC822Name(validate_email(name)) except Exception: pass if name.strip().startswith('/'): # maybe it's a dirname? return x509.DirectoryName(x509_name(name)) # Try to parse this as IPAddress/Network try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass # Try to encode as domain name. DNSName() does not validate the domain name, but this check will fail. if name.startswith('*.'): idna.encode(name[2:]) elif name.startswith('.'): idna.encode(name[1:]) else: idna.encode(name) # Almost anything passes as DNS name, so this is our default fallback return x509.DNSName(name) if typ == 'uri': return x509.UniformResourceIdentifier(name) elif typ == 'email': return x509.RFC822Name(validate_email(name)) elif typ == 'ip': try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass raise ValueError('Could not parse IP address.') elif typ == 'rid': return x509.RegisteredID(x509.ObjectIdentifier(name)) elif typ == 'othername': regex = "(.*);(.*):(.*)" if re.match(regex, name) is not None: oid, asn_typ, val = re.match(regex, name).groups() oid = x509.ObjectIdentifier(oid) if asn_typ == 'UTF8': val = val.encode('utf-8') elif asn_typ == 'OctetString': val = bytes(bytearray.fromhex(val)) val = OctetString(val).dump() else: raise ValueError('Unsupported ASN type in otherName: %s' % asn_typ) val = force_bytes(val) return x509.OtherName(oid, val) else: raise ValueError('Incorrect otherName format: %s' % name) elif typ == 'dirname': return x509.DirectoryName(x509_name(name)) else: # Try to encode the domain name. DNSName() does not validate the domain name, but this # check will fail. if name.startswith('*.'): idna.encode(name[2:]) elif name.startswith('.'): idna.encode(name[1:]) else: idna.encode(name) return x509.DNSName(name)
def parse_general_name(name): """Parse a general name from user input. This function will do its best to detect the intended type of any value passed to it: >>> parse_general_name('example.com') <DNSName(value='example.com')> >>> parse_general_name('*.example.com') <DNSName(value='*.example.com')> >>> parse_general_name('*****@*****.**') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('1.2.3.4') <IPAddress(value=1.2.3.4)> >>> parse_general_name('fd00::1') <IPAddress(value=fd00::1)> >>> parse_general_name('/CN=example.com') # doctest: +NORMALIZE_WHITESPACE <DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>)> The default fallback is to assume a :py:class:`~cryptography:cryptography.x509.DNSName`. If this doesn't work, an exception will be raised: >>> parse_general_name('foo..bar`*123') Traceback (most recent call last): ... idna.core.IDNAError: The label b'' is not a valid A-label >>> parse_general_name('foo bar') Traceback (most recent call last): ... idna.core.IDNAError: The label b'foo bar' is not a valid A-label If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`: >>> parse_general_name('email:[email protected]') <RFC822Name(value='*****@*****.**')> >>> parse_general_name('URI:https://example.com') <UniformResourceIdentifier(value='https://example.com')> >>> parse_general_name('dirname:/CN=example.com') # doctest: +NORMALIZE_WHITESPACE <DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value='example.com')>])>)> Some more exotic values can only be generated by using this prefix: >>> parse_general_name('rid:2.5.4.3') <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)> >>> parse_general_name('otherName:2.5.4.3,example.com') <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')> If you give a prefixed value, this function is less forgiving of any typos and does not catch any exceptions: >>> parse_general_name('email:foo@bar com') Traceback (most recent call last): ... ValueError: Invalid domain: bar com """ name = force_text(name) typ = None match = GENERAL_NAME_RE.match(name) if match is not None: typ, name = match.groups() typ = typ.lower() if typ is None: if re.match('[a-z0-9]{2,}://', name): # Looks like a URI try: return x509.UniformResourceIdentifier(name) except Exception: # pragma: no cover - this really accepts anything pass if '@' in name: # Looks like an Email address try: return x509.RFC822Name(validate_email(name)) except Exception: pass if name.strip().startswith('/'): # maybe it's a dirname? return x509.DirectoryName(x509_name(name)) # Try to parse this as IPAddress/Network try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass # Try to encode the domain name. DNSName() does not validate the domain name, but this # check will fail. if name.startswith('*.'): idna.encode(name[2:]) else: idna.encode(name) # Almost anything passes as DNS name, so this is our default fallback return x509.DNSName(name) if typ == 'uri': return x509.UniformResourceIdentifier(name) elif typ == 'email': return x509.RFC822Name(validate_email(name)) elif typ == 'ip': try: return x509.IPAddress(ip_address(name)) except ValueError: pass try: return x509.IPAddress(ip_network(name)) except ValueError: pass raise ValueError('Could not parse IP address.') elif typ == 'rid': return x509.RegisteredID(x509.ObjectIdentifier(name)) elif typ == 'othername': type_id, value = name.split(',', 1) type_id = x509.ObjectIdentifier(type_id) value = force_bytes(value) return x509.OtherName(type_id, value) elif typ == 'dirname': return x509.DirectoryName(x509_name(name)) else: # Try to encode the domain name. DNSName() does not validate the domain name, but this # check will fail. if name.startswith('*.'): idna.encode(name[2:]) else: idna.encode(name) return x509.DNSName(name)
def build_csr(self): if not self.private_key: self._gen_key() csr_builder = x509.CertificateSigningRequestBuilder() subject = [ x509.NameAttribute( NameOID.COMMON_NAME, self.common_name, ) ] if self.locality: subject.append( x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality)) if self.province: subject.append( x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.province)) if self.country: subject.append( x509.NameAttribute(NameOID.COUNTRY_NAME, self.country)) if self.organization: subject.append( x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization)) if self.organizational_unit: if isinstance(self.organizational_unit, string_types): subject.append( x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, self.organizational_unit)) elif isinstance(self.organizational_unit, list): for u in self.organizational_unit: subject.append( x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u)) csr_builder = csr_builder.subject_name(x509.Name(subject)) alt_names = [] if self.ip_addresses: for ip in self.ip_addresses: alt_names.append(x509.IPAddress(ipaddress.IPv4Address(ip))) if self.san_dns: for ns in self.san_dns: alt_names.append(x509.DNSName(ns)) if self.email_addresses: for mail in self.email_addresses: alt_names.append(x509.RFC822Name(mail)) if self.uniform_resource_identifiers: for uri in self.uniform_resource_identifiers: alt_names.append(x509.UniformResourceIdentifier(uri)) if self.user_principal_names: for upn in self.user_principal_names: # Python cryptography library doesn't include # OID for UPN in any of the OID constants # See http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3 # and https://docs.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-certificate-requirements-and-enumeration UPNOID = x509.ObjectIdentifier('1.3.6.1.4.1.311.20.2.3') # x509 library expects DER encoded string, so encode UPN into bytes # with ASN1 syntax for UTF8String, which is a type/length/value encoding # per https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-utf8string # Construct the array of bytes (note bytes are strings in python2) # by inserting the header consisting of the tag '0x0C' followed by the length of the upn if sys.version_info > (3, 0): # For python3, since we have native bytes available we'll convert the string into bytes. # However, for cases when this method is called with inputs extracted from existing certificates # & csrs, the input will already be encoded as bytes. So we'll check the input, if it is a # string we'll convert it into a bytes object then insert our header. Otherwise, we'll just # insert the header in the passed in bytes. if isinstance(upn, str): bupn = bytes(upn, 'utf-8') else: bupn = upn values = [12, len(upn)] header = bytes(values) data = header + bupn alt_names.append(x509.OtherName(UPNOID, data)) else: # For python2, since there is no native bytes method, we'll use the bytes method # in the future module to convert the string we constructed into bytes. bupn = ''.join(chr(x) for x in [12, len(upn)]) bupn = bupn + str(upn) alt_names.append( x509.OtherName(UPNOID, bytes(bupn, 'utf-8'))) csr_builder = csr_builder.add_extension( x509.SubjectAlternativeName(alt_names), critical=False) csr_builder = csr_builder.sign(self.private_key, hashes.SHA256(), default_backend()) self.csr = csr_builder.public_bytes( serialization.Encoding.PEM).decode() return
def generate_cert(self, csr, lifetime, fingerprint): """Generate a Certificate from a CSR. Args: csr: The CSR object lifetime: The lifetime of the certificate in seconds fingerprint: The fingerprint of the signer for the CSR. Raises: CertProcessorNotAdminUserError: When an admin request is made without and admin key CertProcessorInvalidSignatureError: When an invalid user attempts to sign a request for a certificate Returns: The certificates public bytes """ ca_pkey = self.get_ca_key() ca_cert = self.get_ca_cert(ca_pkey) now = datetime.datetime.utcnow() lifetime_delta = now + datetime.timedelta(seconds=int(lifetime)) alts = [] is_admin = self.is_admin(fingerprint) logger.info(f"generate_cert: getting gpg key for {fingerprint}") user_gpg_key = self.get_gpg_key_by_fingerprint(fingerprint, is_admin) if user_gpg_key is None: raise CertProcessorNoPGPKeyFoundError() email_in_key = self.check_subject_against_key(csr.subject, user_gpg_key) if not email_in_key and not is_admin: raise CertProcessorNotAdminUserError() for alt in self.config.get("ca", "alternate_name").split(","): alts.append(x509.DNSName("{}".format(alt))) cert = (x509.CertificateBuilder().subject_name( csr.subject).issuer_name(ca_cert.subject).public_key( csr.public_key()).serial_number( uuid.uuid4().int).not_valid_before(now).not_valid_after( lifetime_delta)) if len(alts) > 0: cert = cert.add_extension(x509.SubjectAlternativeName(alts), critical=False) crl_dp = x509.DistributionPoint( [ x509.UniformResourceIdentifier( "{protocol}://{server_url}/crl".format( protocol=self.PROTOCOL, server_url=self.SERVER_URL)) ], relative_name=None, reasons=None, crl_issuer=None, ) cert = cert.add_extension(x509.CRLDistributionPoints([crl_dp]), critical=False) logger.info(f"generate_cert: Signing certificate for {fingerprint}") cert = cert.sign( private_key=ca_pkey, algorithm=hashes.SHA256(), backend=default_backend(), ) try: logger.info(f"generate_cert: saving certificate for {fingerprint}") self.storage.save_cert(cert, fingerprint) except StorageEngineCertificateConflict: logger.info( f"generate_cert: updating certificate for {fingerprint}") cert = self.update_cert(csr, lifetime) return cert.public_bytes(serialization.Encoding.PEM)