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 load_gnames(self, gname_list): """Converts list of prefixed strings to GeneralName list. """ gnames = [] for alt in gname_list: if ':' not in alt: die("Invalid gname: %s", alt) t, val = alt.split(':', 1) t = t.lower().strip() val = val.strip() if t == 'dn': gn = x509.DirectoryName(self.load_name(parse_dn(val))) elif t == 'dns': gn = x509.DNSName(val) elif t == 'email': gn = x509.RFC822Name(val) elif t == 'uri': gn = x509.UniformResourceIdentifier(val) elif t == 'ip': if val.find(':') >= 0: gn = x509.IPAddress(ipaddress.IPv6Address(val)) else: gn = x509.IPAddress(ipaddress.IPv4Address(val)) elif t == 'dn': gn = x509.DirectoryName(self.load_name(parse_dn(val))) elif t == 'net': if val.find(':') >= 0: gn = x509.IPAddress(ipaddress.IPv6Network(val)) else: gn = x509.IPAddress(ipaddress.IPv4Network(val)) else: raise Exception('Invalid GeneralName: ' + alt) gnames.append(gn) return gnames
def parse_general_name(name): typ, value = name['typ'], name['value'] if typ == 'DNS': return x509.DNSName(value) if typ == 'RFC822': return x509.RFC822Name(value) return x509.UniformResourceIdentifier(value)
def _encode_user_principals(principals): """Encode user principals as e-mail addresses""" if isinstance(principals, str): principals = [p.strip() for p in principals.split(',')] return [x509.RFC822Name(name) for name in principals]
def make(pkey_pem, emails, usage): private_key = serialization.load_pem_private_key(pkey_pem, password=None) csr = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, emails[0]), x509.NameAttribute(NameOID.EMAIL_ADDRESS, emails[0]), ])).add_extension( x509.SubjectAlternativeName([x509.RFC822Name(e) for e in emails] + [x509.DNSName(e) for e in emails]), critical=False, ) if (usage): data_encipherment, key_cert_sign, crl_sign, encipher_only, decipher_only = ( False, ) * 5 digital_signature = 'digitalSignature' in usage content_commitment = 'contentCommitment' in usage key_encipherment = 'keyEncipherment' in usage key_agreement = 'keyAgreement' in usage csr = csr.add_extension( x509.KeyUsage( digital_signature=digital_signature, content_commitment=content_commitment, key_encipherment=key_encipherment, data_encipherment=data_encipherment, key_agreement=key_agreement, key_cert_sign=key_cert_sign, crl_sign=crl_sign, encipher_only=encipher_only, decipher_only=decipher_only, ), critical=True, ) csr_pem = csr.sign(private_key, hashes.SHA256()).public_bytes( serialization.Encoding.PEM) return csr_pem
def build_name(key, value): key = key.lower() if key == "dns": return x509.DNSName(str(value)) if key == "email": return x509.RFC822Name(str(value)) if key == "uri": return x509.UniformResourceIdentifier(str(value)) if key == "dirname": return x509.DirectoryName(str(value)) if key in ("ip", "ip address"): import ipaddress try: value = ipaddress.ip_address(str(value)) except ValueError: value = ipaddress.ip_network(str(value)) return x509.IPAddress(value) raise ValueError(f"Unsupported alternative name: {key}")
def generate_x509_client_cert(email): key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Niedersachsen"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Hannover"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Leibniz Universitaet Hannover"), x509.NameAttribute(NameOID.COMMON_NAME, "ACME Toolkit"), ]) cert = (x509.CertificateBuilder().subject_name(subject).issuer_name( issuer).public_key(key.public_key()).serial_number( x509.random_serial_number()).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=2)).add_extension( x509.SubjectAlternativeName([x509.RFC822Name(email)]), critical=False, ).sign(key, hashes.SHA256())) data = cert.public_bytes(serialization.Encoding.PEM) return urllib.parse.quote(data)
def test_identity_variants(): ca = CA() for bad in [b"example.org", bytearray(b"example.org"), 123]: with pytest.raises(TypeError): ca.issue_cert(bad) cases = { # Traditional ascii hostname u"example.org": x509.DNSName(u"example.org"), # Wildcard u"*.example.org": x509.DNSName(u"*.example.org"), # IDN u"éxamplë.org": x509.DNSName(u"xn--xampl-9rat.org"), u"xn--xampl-9rat.org": x509.DNSName(u"xn--xampl-9rat.org"), # IDN + wildcard u"*.éxamplë.org": x509.DNSName(u"*.xn--xampl-9rat.org"), u"*.xn--xampl-9rat.org": x509.DNSName(u"*.xn--xampl-9rat.org"), # IDN that acts differently in IDNA-2003 vs IDNA-2008 u"faß.de": x509.DNSName(u"xn--fa-hia.de"), u"xn--fa-hia.de": x509.DNSName(u"xn--fa-hia.de"), # IDN with non-permissable character (uppercase K) # (example taken from idna package docs) u"Königsgäßchen.de": x509.DNSName(u"xn--knigsgchen-b4a3dun.de"), # IP addresses u"127.0.0.1": x509.IPAddress(IPv4Address(u"127.0.0.1")), u"::1": x509.IPAddress(IPv6Address(u"::1")), # Check normalization u"0000::1": x509.IPAddress(IPv6Address(u"::1")), # IP networks u"127.0.0.0/24": x509.IPAddress(IPv4Network(u"127.0.0.0/24")), u"2001::/16": x509.IPAddress(IPv6Network(u"2001::/16")), # Check normalization u"2001:0000::/16": x509.IPAddress(IPv6Network(u"2001::/16")), # Email address u"*****@*****.**": x509.RFC822Name(u"*****@*****.**"), } for hostname, expected in cases.items(): # Can't repr the got or expected values here, at least until # cryptography v2.1 is out, because in v2.0 on py2, DNSName.__repr__ # blows up on IDNs. print("testing: {!r}".format(hostname)) pem = ca.issue_cert(hostname).cert_chain_pems[0].bytes() cert = x509.load_pem_x509_certificate(pem, default_backend()) san = cert.extensions.get_extension_for_class( x509.SubjectAlternativeName ) assert_is_leaf(cert) got = san.value[0] assert got == expected
def cryptography_get_name(name, what='Subject Alternative Name'): ''' Given a name string, returns a cryptography x509.GeneralName object. Raises an OpenSSLObjectError if the name is unknown or cannot be parsed. ''' try: if name.startswith('DNS:'): return x509.DNSName(to_text(name[4:])) if name.startswith('IP:'): address = to_text(name[3:]) if '/' in address: return x509.IPAddress(ipaddress.ip_network(address)) return x509.IPAddress(ipaddress.ip_address(address)) if name.startswith('email:'): return x509.RFC822Name(to_text(name[6:])) if name.startswith('URI:'): return x509.UniformResourceIdentifier(to_text(name[4:])) if name.startswith('RID:'): m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:])) if not m: raise OpenSSLObjectError('Cannot parse {what} "{name}"'.format( name=name, what=what)) return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1))) if name.startswith('otherName:'): # otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with. m = re.match( r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:])) if m: return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2))) # See https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html - Subject Alternative Name for more # defailts on the format expected. name = to_text(name[10:], errors='surrogate_or_strict') if ';' not in name: raise OpenSSLObjectError( 'Cannot parse {what} otherName "{name}", must be in the ' 'format "otherName:<OID>;<ASN.1 OpenSSL Encoded String>" or ' '"otherName:<OID>;<hex string>"'.format(name=name, what=what)) oid, value = name.split(';', 1) b_value = serialize_asn1_string_as_der(value) return x509.OtherName(x509.ObjectIdentifier(oid), b_value) if name.startswith('dirName:'): return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:])))) except Exception as e: raise OpenSSLObjectError( 'Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e)) if ':' not in name: raise OpenSSLObjectError( 'Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'.format( name=name, what=what)) raise OpenSSLObjectError( 'Cannot parse {what} "{name}" (potentially unsupported by cryptography backend)' .format(name=name, what=what))
def _deserialize(self, value, attr, data): general_names = [] for name in value: if name['nameType'] == 'DNSName': validators.sensitive_domain(name['value']) 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 build_csr(self): if not self.private_key: if self.key_type == KeyTypes.RSA: self.private_key = rsa.generate_private_key( public_exponent=65537, key_size=self.key_length, backend=default_backend()) elif self.key_type == KeyTypes.ECDSA: if self.key_curve == "P521": curve = ec.SECP521R1() elif self.key_curve == "P384": curve = ec.SECP384R1() elif self.key_curve == "P256": curve = ec.SECP256R1() elif self.key_curve == "P224": curve = ec.SECP224R1() else: curve = ec.SECP521R1() self.private_key = ec.generate_private_key( curve, default_backend()) else: raise ClientBadData self.public_key_from_private() csr_builder = x509.CertificateSigningRequestBuilder() subject = [ x509.NameAttribute( NameOID.COMMON_NAME, self.common_name, ) ] 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)) 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 parse_general_name(name): if not isinstance(name, dict): raise ValueError('General name unprocessable. Object expected.') typ, value = name['type'], name['value'] if typ == 'DNS': return x509.DNSName(value) if typ == 'rfc822name': return x509.RFC822Name(value) if typ == 'URI': return x509.UniformResourceIdentifier(value) raise ValueError(f'General name {typ} not recognized or not supported.')
def _create_client_certificate(self, cert: Certificate_model, private_key: Key, issuer_key: Key) -> x509.Certificate: # TODO implement checks # countryName = match # stateOrProvinceName = match # localityName = match # organizationName = match # organizationalUnitName = optional # commonName = supplied # emailAddress = optional if cert.parent.type != CertificateTypes.INTERMEDIATE and cert.parent.type != CertificateTypes.ROOT: raise RuntimeError("A root or intermediate parent is expected ") self._builder = x509.CertificateBuilder() self._set_basic(cert, private_key, issuer_key) self._builder = self._builder.add_extension( 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), critical=True, ) self._builder = self._builder.add_extension( x509.ExtendedKeyUsage([ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.EMAIL_PROTECTION ]), critical=False, ) if cert.dn.subjectAltNames: alts = [] for altname in cert.dn.subjectAltNames: try: alt = x509.RFC822Name(altname) alts.append(alt) continue except: pass self._builder = self._builder.add_extension( x509.SubjectAlternativeName(alts), critical=False, ) return self._sign_certificate(issuer_key)
def _decode_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: data = _asn1_string_to_bytes(backend, gn.d.dNSName) return x509.DNSName(data) elif gn.type == backend._lib.GEN_URI: data = _asn1_string_to_bytes(backend, gn.d.uniformResourceIdentifier) return x509.UniformResourceIdentifier(data) 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_bytes(backend, gn.d.rfc822Name) return x509.RFC822Name(data) 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 san_general_names(self): """ Return SAN general names from a python-cryptography certificate object. If the SAN extension is not present, return an empty sequence. Because python-cryptography does not yet provide a way to handle unrecognised critical extensions (which may occur), we must parse the certificate and extract the General Names. For uniformity with other code, we manually construct values of python-crytography GeneralName subtypes. python-cryptography does not yet provide types for ediPartyName or x400Address, so we drop these name types. otherNames are NOT instantiated to more specific types where the type is known. Use ``process_othernames`` to do that. When python-cryptography can handle certs with unrecognised critical extensions and implements ediPartyName and x400Address, this function (and helpers) will be redundant and should go away. """ gns = self.__pyasn1_get_san_general_names() GENERAL_NAME_CONSTRUCTORS = { 'rfc822Name': lambda x: crypto_x509.RFC822Name(unicode(x)), 'dNSName': lambda x: crypto_x509.DNSName(unicode(x)), 'directoryName': _pyasn1_to_cryptography_directoryname, 'registeredID': _pyasn1_to_cryptography_registeredid, 'iPAddress': _pyasn1_to_cryptography_ipaddress, 'uniformResourceIdentifier': lambda x: crypto_x509.UniformResourceIdentifier(unicode(x)), 'otherName': _pyasn1_to_cryptography_othername, } result = [] for gn in gns: gn_type = gn.getName() if gn_type in GENERAL_NAME_CONSTRUCTORS: result.append(GENERAL_NAME_CONSTRUCTORS[gn_type]( gn.getComponent())) return result
def _create_client_certificate(self, cert: CertificateType, private_key: Key, issuer_key: Key) -> x509.Certificate: Certificate._check_issuer_provided(cert) Certificate._check_policies(cert) self._builder = x509.CertificateBuilder() self._set_basic(cert, private_key, issuer_key) self._builder = self._builder.add_extension( 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, ), critical=True, ) self._builder = self._builder.add_extension( x509.ExtendedKeyUsage([ ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.EMAIL_PROTECTION ]), critical=False, ) if cert.dn.subjectAltNames: alts = [] for altname in cert.dn.subjectAltNames: try: alt = x509.RFC822Name(altname) alts.append(alt) continue except ValueError: pass self._builder = self._builder.add_extension( x509.SubjectAlternativeName(alts), critical=False, ) return self._sign_certificate(issuer_key)
def test_subject_alt_name_unsupported_general_name(self, backend): private_key = RSA_KEY_2048.private_key(backend) builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([ x509.NameAttribute(x509.OID_COMMON_NAME, u"SAN"), ])).add_extension( x509.SubjectAlternativeName([ x509.RFC822Name(u"*****@*****.**"), ]), critical=False, ) with pytest.raises(NotImplementedError): builder.sign(private_key, hashes.SHA256(), backend)
def create_csr(key, **kwargs): csr = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([ # Provide various details about who we are. x509.NameAttribute(NameOID.COUNTRY_NAME, kwargs["name"].get("country")), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, kwargs["name"].get("state_provice")), x509.NameAttribute(NameOID.LOCALITY_NAME, kwargs["name"].get("locality")), x509.NameAttribute(NameOID.ORGANIZATION_NAME, kwargs["name"].get("organization")), x509.NameAttribute(NameOID.COMMON_NAME, kwargs["name"].get("common")), ])) # build list of sans # valid types: # x509.DNSName # x509.IPAddress # x509.RFC822Name - email san = [] for entry in kwargs.get("san"): if re.match(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$", entry): # ipv4 address san.append(x509.IPAddress(ipaddress.IPv4Address(entry))) elif re.match(r"^[A-Za-z0-9-\.]{1,63}", entry): # dns san.append(x509.DNSName(entry)) elif re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", entry): # email san.append(x509.RFC822Name(entry)) else: print(f"{entry} not valid for SAN") # only add extension if san is not empty if san: csr = csr.add_extension( x509.SubjectAlternativeName(san), critical=False, # Sign the CSR with our private key. ) csr = csr.sign(key, hashes.SHA256()) return csr
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)) 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 _identity_string_to_x509(identity): # Because we are a DWIM library for lazy slackers, we cheerfully pervert # the cryptography library's carefully type-safe API, and silently DTRT # for any of the following identity types: # # - "example.org" # - "example.org" # - "éxamplë.org" # - "xn--xampl-9rat.org" # - "xn--xampl-9rat.org" # - "127.0.0.1" # - "::1" # - "10.0.0.0/8" # - "2001::/16" # - "*****@*****.**" # # plus wildcard variants of the identities. if not isinstance(identity, unicode): raise TypeError("identities must be text (unicode on py2, str on py3)") if u"@" in identity: return x509.RFC822Name(identity) # Have to try ip_address first, because ip_network("127.0.0.1") is # interpreted as being the network 127.0.0.1/32. Which I guess would be # fine, actually, but why risk it. for ip_converter in [ipaddress.ip_address, ipaddress.ip_network]: try: ip_hostname = ip_converter(identity) except ValueError: continue else: return x509.IPAddress(ip_hostname) # Encode to an A-label, like cryptography wants if identity.startswith("*."): alabel_bytes = b"*." + idna.encode(identity[2:], uts46=True) else: alabel_bytes = idna.encode(identity, uts46=True) # Then back to text, which is mandatory on cryptography 2.0 and earlier, # and may or may not be deprecated in cryptography 2.1. alabel = alabel_bytes.decode("ascii") return x509.DNSName(alabel)
def cert_directory(): """ Provides a directory of certificates that are used to run the tests. By building these certificates each time we run the tests, we slow the test execution down quite substantially, but in return we get to ensure that the certificates are always in-date and valid. One thing this does *not* do is generate the keys each time. This is because keygen is the slowest and most CPU-intensive part of this process, and there is simply no need to forcibly repeat that process on a regular basis. """ # Begin by creating our temporary directory and copying all our keys into # it. tempdir = tempfile.mkdtemp() keys = glob.glob('keys/*.key') for key in keys: shutil.copy(key, tempdir) # Build our root and intermediate CA ca_data = build_ca_cert(tempdir) intermediate_data = build_intermediate_cert(tempdir, ca_data['cert'], ca_data['key']) # Ok, build our leaves. We need one client cert and one root cert, with the # root cert being valid for localhost. client_san = x509.SubjectAlternativeName( [x509.RFC822Name(u'*****@*****.**')]) server_san = x509.SubjectAlternativeName([x509.DNSName(u'localhost')]) build_leaf_certificate(tempdir, 'client.key', 'client.crt', u'PEP 543 Client Certificate', client_san, intermediate_data['cert'], intermediate_data['key']) build_leaf_certificate(tempdir, 'server.key', 'server.crt', u'localhost', server_san, intermediate_data['cert'], intermediate_data['key']) # Ok, we've set up. Let the tests run. global TEMPDIR TEMPDIR = tempdir yield # To cleanup, blow away the temporary directory. shutil.rmtree(tempdir)
def cryptography_get_name(name): ''' Given a name string, returns a cryptography x509.Name object. Raises an OpenSSLObjectError if the name is unknown or cannot be parsed. ''' try: if name.startswith('DNS:'): return x509.DNSName(to_text(name[4:])) if name.startswith('IP:'): return x509.IPAddress(ipaddress.ip_address(to_text(name[3:]))) if name.startswith('email:'): return x509.RFC822Name(to_text(name[6:])) if name.startswith('URI:'): return x509.UniformResourceIdentifier(to_text(name[4:])) except Exception as e: raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}": {1}'.format(name, e)) if ':' not in name: raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)'.format(name)) raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)'.format(name))
def cryptography_get_name(name): ''' Given a name string, returns a cryptography x509.Name object. Raises an OpenSSLObjectError if the name is unknown or cannot be parsed. ''' try: if name.startswith('DNS:'): return x509.DNSName(to_text(name[4:])) if name.startswith('IP:'): return x509.IPAddress(ipaddress.ip_address(to_text(name[3:]))) if name.startswith('email:'): return x509.RFC822Name(to_text(name[6:])) if name.startswith('URI:'): return x509.UniformResourceIdentifier(to_text(name[4:])) if name.startswith('RID:'): m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:])) if not m: raise OpenSSLObjectError( 'Cannot parse Subject Alternative Name "{0}"'.format(name)) return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1))) if name.startswith('otherName:'): m = re.match( r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:])) if not m: raise OpenSSLObjectError( 'Cannot parse Subject Alternative Name "{0}"'.format(name)) return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2))) if name.startswith('dirName:'): return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:])))) except Exception as e: raise OpenSSLObjectError( 'Cannot parse Subject Alternative Name "{0}": {1}'.format(name, e)) if ':' not in name: raise OpenSSLObjectError( 'Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)' .format(name)) raise OpenSSLObjectError( 'Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)' .format(name))
def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: str = None) -> Any: cert = csr.add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True, ).add_extension( x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( issuer.certificate.extensions.get_extension_for_class( x509.SubjectKeyIdentifier).value), critical=False) if rfc82name: cert.add_extension( x509.SubjectAlternativeName([x509.RFC822Name(rfc82name)]), critical=True, ) cert.add_extension(x509.ExtendedKeyUsage([ ExtendedKeyUsageOID.CLIENT_AUTH, ]), critical=True) return cert
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( "Cannot parse general name %s: Must be of type str (was: %s)." % (name, 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("Could not parse name: %s" % name) from e if typ == "uri": try: return x509.UniformResourceIdentifier(encode_url(name)) except idna.IDNAError as e: raise ValueError("Could not parse DNS name in URL: %s" % 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("Unsupported ASN type in otherName: %s" % asn_typ) return x509.OtherName(x509.ObjectIdentifier(oid), parsed_value) raise ValueError("Incorrect otherName format: %s" % 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("Could not parse DNS name: %s" % name) from e
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: 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 generate_csr(self, key): """Generates a CSR. Args: key - The users key Returns: csr - The CSR """ click.secho( "Generating CSR for {server}".format(server=self.server), fg="yellow", ) country = self.config.get(self.server, "country", fallback=None) state = self.config.get(self.server, "state", fallback=None) locality = self.config.get(self.server, "locality", fallback=None) organization_name = self.config.get(self.server, "organization_name") email = self.config.get(self.server, "email") csr_subject_arr = [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization_name), x509.NameAttribute(NameOID.COMMON_NAME, self.friendly_name), ] if self.config.getboolean( self.server, "sendEmailAsNameAttribute", fallback=False ): csr_subject_arr.append( x509.NameAttribute(NameOID.EMAIL_ADDRESS, email) ) if state: csr_subject_arr.append( x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state) ) if country: csr_subject_arr.append( x509.NameAttribute(NameOID.COUNTRY_NAME, country) ) if locality: csr_subject_arr.append( x509.NameAttribute(NameOID.LOCALITY_NAME, locality) ) builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name(x509.Name(csr_subject_arr)) builder = builder.add_extension( x509.SubjectAlternativeName([x509.RFC822Name(email)]), False ) csr = builder.sign(key, hashes.SHA256(), default_backend()) csr_fname = "{}.csr.asc".format(self.server) # If the user overrides the ini configuration programatically, don't # save the CSR as this could be a one off. if self.override: return csr with open( "{config}/{server}/{csr}".format( config=self.CONFIG_FOLDER_PATH, server=self.server, csr=csr_fname, ), "wb", ) as f: enc_csr = self.encrypt( csr.public_bytes(serialization.Encoding.PEM).decode("utf-8"), self.config.get(self.server, "fingerprint"), ) f.write(bytes(str(enc_csr), "utf-8")) return csr
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 generate(self, csr, issuer_crt, issuer_key, profile, ca=False, selfSigned=False, start=None, duration=None, digest=None, sans=[]): """Generate a certificate using: - Certificate request (csr) - Issuer certificate (issuer_crt) - Issuer key (issuer_key) - profile object (profile) Optional parameters set: - a CA certificate role (ca) - a self-signed certificate (selfSigned) - a specific start timestamp (start) """ # Retrieve subject from csr subject = csr.subject self.output('Subject found: {s}'.format(s=subject.rfc4514_string()), level="DEBUG") dn = self._get_dn(subject) self.output('DN found is {d}'.format(d=dn), level="DEBUG") try: alt_names = None alt_names = csr.extensions.get_extension_for_oid( ExtensionOID.SUBJECT_ALTERNATIVE_NAME) self.output('Subject alternate found: {s}'.format(s=alt_names), level="DEBUG") except x509.ExtensionNotFound as err: pass # Force default if necessary now = datetime.datetime.utcnow( ) if start is None else datetime.fromtimestamp(start) duration = profile['duration'] if duration is None else duration # Generate serial number try: serial_number = self._generate_serial() except Exception as err: raise Exception( 'Error during serial number generation: {e}'.format(e=err)) # For self-signed certificate issuer is certificate itself issuer_name = subject if selfSigned else issuer_crt.issuer issuer_serial = serial_number if selfSigned else issuer_crt.serial_number try: # Define basic constraints if ca: basic_contraints = x509.BasicConstraints(ca=True, path_length=0) else: basic_contraints = x509.BasicConstraints(ca=False, path_length=None) builder = (x509.CertificateBuilder().subject_name( subject).issuer_name(issuer_name).public_key( csr.public_key()).serial_number( serial_number).not_valid_before(now).not_valid_after( now + datetime.timedelta(days=duration)).add_extension( basic_contraints, critical=True)) except Exception as err: raise Exception('Unable to build structure: {e}'.format(e=err)) # We never trust CSR extensions # they may have been alterated by the user try: # Due to uPKI design (TLS for renew), digital_signature MUST be setup digital_signature = True # Initialize key usage 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 # Build Key Usages from profile for usage in profile['keyUsage']: if usage == 'digitalSignature': digital_signature = True elif usage == 'nonRepudiation': content_commitment = True elif usage == 'keyEncipherment': key_encipherment = True elif usage == 'dataEncipherment': data_encipherment = True elif usage == 'keyAgreement': key_agreement = True elif usage == 'keyCertSign': key_cert_sign = True elif usage == 'cRLSign': crl_sign = True elif usage == 'encipherOnly': encipher_only = True elif usage == 'decipherOnly': decipher_only = True # Setup X509 Key Usages key_usages = x509.KeyUsage(digital_signature=digital_signature, content_commitment=content_commitment, key_encipherment=key_encipherment, data_encipherment=data_encipherment, key_agreement=key_agreement, key_cert_sign=key_cert_sign, crl_sign=crl_sign, encipher_only=encipher_only, decipher_only=decipher_only) builder = builder.add_extension(key_usages, critical=True) except KeyError: # If no Key Usages are set, thats strange raise Exception('No Key Usages set.') except Exception as err: raise Exception('Unable to set Key Usages: {e}'.format(e=err)) try: # Build Key Usages extended based on profile key_usages_extended = list() for eusage in profile['extendedKeyUsage']: if eusage == 'serverAuth': key_usages_extended.append(ExtendedKeyUsageOID.SERVER_AUTH) elif eusage == 'clientAuth': key_usages_extended.append(ExtendedKeyUsageOID.CLIENT_AUTH) elif eusage == 'codeSigning': key_usages_extended.append( ExtendedKeyUsageOID.CODE_SIGNING) elif eusage == 'emailProtection': key_usages_extended.append( ExtendedKeyUsageOID.EMAIL_PROTECTION) elif eusage == 'timeStamping': key_usages_extended.append( ExtendedKeyUsageOID.TIME_STAMPING) elif eusage == 'OCSPSigning': key_usages_extended.append( ExtendedKeyUsageOID.OCSP_SIGNING) #### CHECK TROUBLES ASSOCIATED WITH THIS CHOICE ##### # Always add 'clientAuth' for automatic renewal if ExtendedKeyUsageOID.CLIENT_AUTH not in key_usages_extended: key_usages_extended.append(ExtendedKeyUsageOID.CLIENT_AUTH) ##################################################### # Add Deprecated nsCertType (still required by some software) # nsCertType_oid = x509.ObjectIdentifier('2.16.840.1.113730.1.1') # for c_type in profile['certType']: # if c_type.lower() in ['client', 'server', 'email', 'objsign']: # builder.add_extension(nsCertType_oid, c_type.lower()) # Set Key Usages if needed if len(key_usages_extended): builder = builder.add_extension( x509.ExtendedKeyUsage(key_usages_extended), critical=False) except KeyError: # If no extended key usages are set, do nothing pass except Exception as err: raise Exception( 'Unable to set Extended Key Usages: {e}'.format(e=err)) # Add alternate names if found in CSR if alt_names is not None: # Verify each time that SANS entry was registered # We can NOT trust CSR data (client manipulation) subject_alt = list([]) for entry in alt_names.value.get_values_for_type(x509.IPAddress): if entry not in sans: continue subject_alt.append(x509.IPAddress(ipaddress.ip_address(entry))) for entry in alt_names.value.get_values_for_type(x509.DNSName): if entry not in sans: continue subject_alt.append(x509.DNSName(entry)) for entry in alt_names.value.get_values_for_type(x509.RFC822Name): if entry not in sans: continue subject_alt.append(x509.RFC822Name(entry)) for entry in alt_names.value.get_values_for_type( x509.UniformResourceIdentifier): if entry not in sans: continue subject_alt.append(x509.UniformResourceIdentifier(entry)) try: # Add all alternates to certificate builder = builder.add_extension( x509.SubjectAlternativeName(subject_alt), critical=False) except Exception as err: raise Exception( 'Unable to set alternatives name: {e}'.format(e=err)) try: # Register signing authority issuer_key_id = x509.SubjectKeyIdentifier.from_public_key( issuer_key.public_key()) builder = builder.add_extension(x509.AuthorityKeyIdentifier( issuer_key_id.digest, [x509.DirectoryName(issuer_name)], issuer_serial), critical=False) except Exception as err: raise Exception( 'Unable to setup Authority Identifier: {e}'.format(e=err)) ca_endpoints = list() try: # Default value if not set in profile ca_url = profile['ca'] if profile[ 'ca'] else "https://certificates.{d}/certs/ca.crt".format( d=profile['domain']) except KeyError: ca_url = None try: # Default value if not set in profile ocsp_url = profile['ocsp'] if profile[ 'ocsp'] else "https://certificates.{d}/ocsp".format( d=profile['domain']) except KeyError: ocsp_url = None try: # Add CA certificate distribution point and OCSP validation url if ca_url: ca_endpoints.append( x509.AccessDescription( x509.oid.AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier(ca_url))) if ocsp_url: ca_endpoints.append( x509.AccessDescription( x509.oid.AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier(ocsp_url))) builder = builder.add_extension( x509.AuthorityInformationAccess(ca_endpoints), critical=False) except Exception as err: raise Exception( 'Unable to setup OCSP/CA endpoint: {e}'.format(e=err)) try: # Add CRL distribution point crl_endpoints = list() # Default value if not set in profile url = "https://certificates.{d}/certs/crl.pem".format( d=profile['domain']) try: if profile['csr']: url = profile['csr'] except KeyError: pass crl_endpoints.append( x509.DistributionPoint( [x509.UniformResourceIdentifier(url)], None, None, [x509.DNSName(issuer_name.rfc4514_string())])) builder = builder.add_extension( x509.CRLDistributionPoints(crl_endpoints), critical=False) except Exception as err: raise Exception('Unable to setup CRL endpoints: {e}'.format(e=err)) if digest is None: digest = profile['digest'] if digest == 'md5': digest = hashes.MD5() elif digest == 'sha1': digest = hashes.SHA1() elif digest == 'sha256': digest = hashes.SHA256() elif digest == 'sha512': digest = hashed.SHA512() else: raise NotImplementedError( 'Private key only support {s} digest signatures'.format( s=self._allowed.Digest)) try: pub_crt = builder.sign(private_key=issuer_key, algorithm=digest, backend=self.__backend) except Exception as err: raise Exception('Unable to sign certificate: {e}'.format(e=err)) return pub_crt
def generate(self, pkey, cn, profile, sans=None): """Generate a request based on: - privatekey (pkey) - commonName (cn) - profile object (profile) add Additional CommonName if needed sans argument """ subject = list([]) # Extract subject from profile try: for entry in profile['subject']: for subj, value in entry.items(): subj = subj.upper() if subj == 'C': subject.append( x509.NameAttribute(NameOID.COUNTRY_NAME, value)) elif subj == 'ST': subject.append( x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, value)) elif subj == 'L': subject.append( x509.NameAttribute(NameOID.LOCALITY_NAME, value)) elif subj == 'O': subject.append( x509.NameAttribute(NameOID.ORGANIZATION_NAME, value)) elif subj == 'OU': subject.append( x509.NameAttribute( NameOID.ORGANIZATIONAL_UNIT_NAME, value)) except Exception as err: raise Exception('Unable to extract subject: {e}'.format(e=err)) try: # Append cn at the end subject.append(x509.NameAttribute(NameOID.COMMON_NAME, cn)) except Exception as err: raise Exception('Unable to setup subject name: {e}'.format(e=err)) try: builder = x509.CertificateSigningRequestBuilder().subject_name( x509.Name(subject)) except Exception as err: raise Exception('Unable to create structure: {e}'.format(e=err)) subject_alt = list([]) # Best pratices wants to include FQDN in SANS for servers if profile['altnames']: # Add IPAddress for Goland compliance if validators.ipv4(cn): subject_alt.append(x509.DNSName(cn)) subject_alt.append(x509.IPAddress(ipaddress.ip_address(cn))) elif validators.domain(cn): subject_alt.append(x509.DNSName(cn)) elif validators.email(cn): subject_alt.append(x509.RFC822Name(cn)) elif validators.url(cn): subject_alt.append(x509.UniformResourceIdentifier(cn)) else: if 'server' in profile['certType']: self.output( 'ADD ALT NAMES {c}.{d} FOR SERVER USAGE'.format( c=cn, d=profile['domain'])) subject_alt.append( x509.DNSName("{c}.{d}".format(c=cn, d=profile['domain']))) if 'email' in profile['certType']: subject_alt.append( x509.RFC822Name("{c}@{d}".format(c=cn, d=profile['domain']))) # Add alternate names if needed if isinstance(sans, list) and len(sans): for entry in sans: # Add IPAddress for Goland compliance if validators.ipv4(entry): if x509.DNSName(entry) not in subject_alt: subject_alt.append(x509.DNSName(entry)) if x509.IPAddress( ipaddress.ip_address(entry)) not in subject_alt: subject_alt.append( x509.IPAddress(ipaddress.ip_address(entry))) elif validators.domain(entry) and (x509.DNSName(entry) not in subject_alt): subject_alt.append(x509.DNSName(entry)) elif validators.email(entry) and (x509.RFC822Name(entry) not in subject_alt): subject_alt.append(x509.RFC822Name(entry)) if len(subject_alt): try: builder = builder.add_extension( x509.SubjectAlternativeName(subject_alt), critical=False) except Exception as err: raise Exception( 'Unable to add alternate name: {e}'.format(e=err)) # Add Deprecated nsCertType (still required by some software) # nsCertType_oid = x509.ObjectIdentifier('2.16.840.1.113730.1.1') # for c_type in profile['certType']: # if c_type.lower() in ['client', 'server', 'email', 'objsign']: # builder.add_extension(nsCertType_oid, c_type.lower()) if profile['digest'] == 'md5': digest = hashes.MD5() elif profile['digest'] == 'sha1': digest = hashes.SHA1() elif profile['digest'] == 'sha256': digest = hashes.SHA256() elif profile['digest'] == 'sha512': digest = hashed.SHA512() else: raise NotImplementedError( 'Private key only support {s} digest signatures'.format( s=self._allowed.Digest)) try: csr = builder.sign(private_key=pkey, algorithm=digest, backend=self.__backend) except Exception as err: raise Exception( 'Unable to sign certificate request: {e}'.format(e=err)) return csr