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 test_eq(self): name = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1')]) name2 = x509.Name( [x509.NameAttribute(x509.ObjectIdentifier('oid'), 'value1')]) gn = x509.DirectoryName(x509.Name([name])) gn2 = x509.DirectoryName(x509.Name([name2])) assert gn == gn2
def test_dirname(self): """Test parsing a dirname.""" self.assertEqual(parse_general_name('/CN=example.com'), x509.DirectoryName(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'), ]))) self.assertEqual(parse_general_name('dirname:/CN=example.com'), x509.DirectoryName(x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'), ]))) self.assertEqual(parse_general_name('dirname:/C=AT/CN=example.com'), x509.DirectoryName(x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, 'AT'), x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'), ])))
def test_repr(self): name = x509.Name([x509.NameAttribute(x509.OID_COMMON_NAME, 'value1')]) gn = x509.DirectoryName(x509.Name([name])) assert repr(gn) == ( "<DirectoryName(value=<Name([<Name([<NameAttribute(oid=<ObjectIden" "tifier(oid=2.5.4.3, name=commonName)>, value='value1')>])>])>)>" )
def test_dirname(self): """Test formatting a dirname.""" name = x509.DirectoryName(x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, 'AT'), x509.NameAttribute(NameOID.COMMON_NAME, 'example.com'), ])) self.assertEqual(format_general_name(name), 'dirname:/C=AT/CN=example.com')
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 get_extension_params( extension: list, cert: typing.Union[x509.CertificateSigningRequestBuilder, x509.CertificateBuilder, None] = None, issuer: typing.Optional[x509.Certificate] = None ) -> list: params = [] if extension[0] == 'BasicConstraints': params = [extension[1].get('ca'), extension[1].get('path_length')] elif extension[0] == 'ExtendedKeyUsage': usages = [] for ext_usage in extension[1].get('usages', []): usages.append(getattr(x509.oid.ExtendedKeyUsageOID, ext_usage)) params = [usages] elif extension[0] == 'KeyUsage': params = [extension[1].get(k, False) for k in extensions()['KeyUsage']] elif extension[0] == 'AuthorityKeyIdentifier': params = [ x509.SubjectKeyIdentifier.from_public_key( issuer.public_key() if issuer else cert._public_key ).digest if cert or issuer else None, None, None ] if extension[1]['authority_cert_issuer'] and cert: params[1:] = [ [x509.DirectoryName(cert._issuer_name)], issuer.serial_number if issuer else cert._serial_number ] return params
def generate_crl(self, ca, certs, next_update=1): # There is a tricky case here - what happens if the root CA is compromised ? # In normal world scenarios, that CA is removed from app's trust store and any # subsequent certs it had issues wouldn't be validated by the app then. Making a CRL # for a revoked root CA in normal cases doesn't make sense as the thief can sign a # counter CRL saying that everything is fine. As our environment is controlled, # i think we are safe to create a crl for root CA as well which we can publish for # services which make use of it i.e openvpn and they'll know that the certs/ca's have been # compromised. # # `ca` is root ca from where the chain `certs` starts. # `certs` is a list of all certs ca inclusive which are to be # included in the CRL ( if root ca is compromised, it will be in `certs` as well ). private_key = load_private_key(ca['privatekey']) ca_cert = x509.load_pem_x509_certificate(ca['certificate'].encode(), default_backend()) if not private_key: return None ca_data = self.middleware.call_sync('cryptokey.load_certificate', ca['certificate']) issuer = {k: ca_data.get(v) for k, v in CERT_BACKEND_MAPPINGS.items()} crl_builder = x509.CertificateRevocationListBuilder().issuer_name( x509.Name([ x509.NameAttribute(getattr(NameOID, k.upper()), v) for k, v in issuer.items() if v ])).last_update(datetime.datetime.utcnow()).next_update( datetime.datetime.utcnow() + datetime.timedelta(next_update, 300, 0)) for cert in certs: crl_builder = crl_builder.add_revoked_certificate( x509.RevokedCertificateBuilder().serial_number( self.middleware.call_sync( 'cryptokey.load_certificate', cert['certificate'])['serial']).revocation_date( cert['revoked_date']).build(default_backend())) # https://www.ietf.org/rfc/rfc5280.txt # We should add AuthorityKeyIdentifier and CRLNumber at the very least crl = crl_builder.add_extension( x509.AuthorityKeyIdentifier( x509.SubjectKeyIdentifier.from_public_key( ca_cert.public_key()).digest, [ x509.DirectoryName( x509.Name([ x509.NameAttribute(getattr(NameOID, k.upper()), v) for k, v in issuer.items() if v ])) ], ca_cert.serial_number), False).add_extension(x509.CRLNumber(1), False).sign( private_key=private_key, algorithm=retrieve_signing_algorithm({}, private_key), backend=default_backend()) return crl.public_bytes(serialization.Encoding.PEM).decode()
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 _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 _pyasn1_to_cryptography_directoryname(dn): attrs = [] # Name is CHOICE { RDNSequence } (only one possibility) for rdn in dn.getComponent(): for ava in rdn: attr = crypto_x509.NameAttribute( _pyasn1_to_cryptography_oid(ava['type']), unicode(decoder.decode(ava['value'])[0])) attrs.append(attr) return crypto_x509.DirectoryName(crypto_x509.Name(attrs))
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 make_saml_cert(key, country='', state='', locality='', org='', cn='', email='', alt_names=None, days=365): if isinstance(key, basestring): key = private_key_from_cleaned_text(key) if alt_names and isinstance(alt_names, basestring): alt_names = alt_names.decode('utf-8').split() subject = x509.Name([ # Provide various details about who we are. x509.NameAttribute(NameOID.COUNTRY_NAME, country.decode('utf-8')), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state.decode('utf-8')), x509.NameAttribute(NameOID.LOCALITY_NAME, locality.decode('utf-8')), x509.NameAttribute(NameOID.ORGANIZATION_NAME, org.decode('utf-8')), x509.NameAttribute(NameOID.COMMON_NAME, cn.decode('utf-8')), x509.NameAttribute(NameOID.EMAIL_ADDRESS, email.decode('utf-8')), ]) pkey = key.public_key() skid = x509.SubjectKeyIdentifier.from_public_key(pkey) # Create self-signed serial_number = random_serial_number() crt = x509.CertificateBuilder().subject_name( subject ).issuer_name( subject ).public_key( pkey ).serial_number( serial_number ).not_valid_before( datetime.datetime.utcnow() ).not_valid_after( # Our certificate will be valid for 10 days datetime.datetime.utcnow() + datetime.timedelta(days=days) ).add_extension( x509.BasicConstraints(True, 0), False ).add_extension( skid, False ).add_extension( x509.AuthorityKeyIdentifier( skid.digest, [x509.DirectoryName(subject)], serial_number), False ) if alt_names: # Describe what sites we want this certificate for. crt = crt.add_extension( x509.SubjectAlternativeName([ x509.DNSName(n) for n in altnames]), critical=False) crt = crt.sign(key, hashes.SHA256(), default_backend()) crt_text = crt.public_bytes(serialization.Encoding.PEM) return (cleanup_x509_text(crt_text), crt)
def _decode_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: data = _asn1_string_to_bytes(backend, gn.d.dNSName).decode("utf8") return x509.DNSName._init_without_validation(data) elif gn.type == backend._lib.GEN_URI: data = _asn1_string_to_bytes( backend, gn.d.uniformResourceIdentifier ).decode("utf8") return x509.UniformResourceIdentifier._init_without_validation(data) elif gn.type == backend._lib.GEN_RID: oid = _asn1_string_to_bytes(backend, gn.d.iPAddress) data_len = len(data) if data_len == 8 or data_len == 32: 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 prefix == -1: prefix = len(bits) if "1" in bits[prefix]: raise ValueError("Invalid netmask") ip = piaddress.ip_network(base.exploded + u"/{}".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).decode("utf8") return x509.RFC822Name._init_without_validation(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: raise x509.UnsupportedGeneralnameType( "{} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type) ), gn.type )
def gen_self_signed(key: rsa.RSAPrivateKey, cert_opts: Dict, crypto_opts: Dict) -> None: # noqa # subject / issuer subject = issuer = _subject(cert_opts) # init builder builder = x509.CertificateBuilder() # set subject and issuer builder = builder.subject_name(subject) builder = builder.issuer_name(issuer) # set public key builder = builder.public_key(key.public_key()) # set serial number serial = x509.random_serial_number() builder = builder.serial_number(serial) # set expiration now = datetime.datetime.utcnow() days = cert_opts['days'] builder = builder.not_valid_before(now) builder = builder.not_valid_after(now + datetime.timedelta(days=days)) # add base extensions for is_critical, ext in _extensions(key, cert_opts): builder = builder.add_extension(ext, critical=is_critical) # add AuthorityKeyIdentifier extension (experimental feature) pkey = key.public_key() key_identifier = x509.extensions._key_identifier_from_public_key(pkey) authority_cert_issuer = [x509.DirectoryName(issuer)] builder = builder.add_extension(x509.AuthorityKeyIdentifier( key_identifier, authority_cert_issuer, serial), critical=False) # sign the certificate md_alg = MD_ALGS[crypto_opts['md_alg']] crt = builder.sign(key, md_alg) # write to file crt_out = crypto_opts['crt_out'] with open(str(crt_out), "wb") as fp: fp.write(crt.public_bytes(serialization.Encoding.PEM)) fp.close()
def selfsign_req(csr, issuer_pkey, serial=None, days=3650): if not serial: # FIXME: 排重 serial = random.getrandbits(64) cert = x509.CertificateBuilder()\ .subject_name(csr.subject)\ .issuer_name(csr.subject)\ .public_key(csr.public_key())\ .serial_number(serial)\ .not_valid_before(datetime.utcnow())\ .not_valid_after(datetime.utcnow()+timedelta(days=10)) cert._extensions = csr.extensions._extensions subkeyid = x509.SubjectKeyIdentifier.from_public_key(csr.public_key()) cert = cert.add_extension(subkeyid, critical=False) authkeyid = _key_identifier_from_public_key(issuer_pkey.public_key()) authkeyid = x509.AuthorityKeyIdentifier( authkeyid, [x509.DirectoryName(csr.subject), ], serial) cert = cert.add_extension(authkeyid, critical=False) return cert.sign(issuer_pkey, hashes.SHA256(), default_backend())
def create_ca_certificate(ca_name, key_size=4096, certificate_validity_days=365): key = rsa.generate_private_key(public_exponent=65537, key_size=key_size, backend=default_backend()) key_id = x509.SubjectKeyIdentifier.from_public_key(key.public_key()) subject = issuer = x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, u"%s" % ca_name)]) now = datetime.datetime.utcnow() serial = x509.random_serial_number() cert = x509.CertificateBuilder() \ .subject_name(subject) \ .issuer_name(issuer) \ .public_key(key.public_key()) \ .serial_number(serial) \ .not_valid_before(now) \ .not_valid_after(now + datetime.timedelta(days=certificate_validity_days)) \ .add_extension(key_id, critical=False) \ .add_extension(x509.AuthorityKeyIdentifier(key_id.digest, [x509.DirectoryName(issuer)], serial), critical=False) \ .add_extension(x509.BasicConstraints(ca=True, path_length=3), critical=True) \ .add_extension(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), critical=True) \ .sign(key, hashes.SHA256(), default_backend()) cert = cert.public_bytes(serialization.Encoding.PEM) key = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()) return key, cert
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 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 _new_cert(issuer=None, is_issuer=False, serial_number=None, **subject): backend = backends.default_backend() private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=backend) public_key = private_key.public_key() subject = x509.Name([ x509.NameAttribute(getattr(oid.NameOID, key.upper()), value) for key, value in subject.items() ]) builder = (x509.CertificateBuilder().subject_name(subject).public_key( public_key).serial_number(serial_number or int.from_bytes(os.urandom(8), "big"))) if issuer: issuer_cert, signing_key = issuer builder = (builder.issuer_name(issuer_cert.subject).not_valid_before( issuer_cert.not_valid_before).not_valid_after( issuer_cert.not_valid_after)) aki_ext = x509.AuthorityKeyIdentifier( key_identifier=issuer_cert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier).value.digest, authority_cert_issuer=[x509.DirectoryName(issuer_cert.subject)], authority_cert_serial_number=issuer_cert.serial_number, ) else: signing_key = private_key builder = (builder.issuer_name(subject).not_valid_before( datetime.datetime.today() - datetime.timedelta( days=1)).not_valid_after(datetime.datetime.today() + datetime.timedelta(weeks=1000))) aki_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key( public_key) if is_issuer: builder = (builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=False, ).add_extension( x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False, ).add_extension( aki_ext, critical=False, )) else: builder = (builder.add_extension( x509.KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False, ), critical=False, ).add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=False, ).add_extension( x509.ExtendedKeyUsage([oid.ExtendedKeyUsageOID.SERVER_AUTH]), critical=False, ).add_extension( x509.SubjectAlternativeName([x509.DNSName("localhost")]), critical=False, ).add_extension( x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False, ).add_extension( aki_ext, critical=False, )) certificate = builder.sign( private_key=signing_key, algorithm=hashes.SHA256(), backend=backend, ) return certificate, private_key
def _decode_general_name(backend, gn): if gn.type == backend._lib.GEN_DNS: # Convert to bytes and then decode to utf8. We don't use # asn1_string_to_utf8 here because it doesn't properly convert # utf8 from ia5strings. data = _asn1_string_to_bytes(backend, gn.d.dNSName).decode("utf8") # We don't use the constructor for DNSName so we can bypass validation # This allows us to create DNSName objects that have unicode chars # when a certificate (against the RFC) contains them. return x509.DNSName._init_without_validation(data) elif gn.type == backend._lib.GEN_URI: # Convert to bytes and then decode to utf8. We don't use # asn1_string_to_utf8 here because it doesn't properly convert # utf8 from ia5strings. data = _asn1_string_to_bytes( backend, gn.d.uniformResourceIdentifier).decode("utf8") # We don't use the constructor for URI so we can bypass validation # This allows us to create URI objects that have unicode chars # when a certificate (against the RFC) contains them. return x509.UniformResourceIdentifier._init_without_validation(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 + "/{}".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: # Convert to bytes and then decode to utf8. We don't use # asn1_string_to_utf8 here because it doesn't properly convert # utf8 from ia5strings. data = _asn1_string_to_bytes(backend, gn.d.rfc822Name).decode("utf8") # We don't use the constructor for RFC822Name so we can bypass # validation. This allows us to create RFC822Name objects that have # unicode chars when a certificate (against the RFC) contains them. return x509.RFC822Name._init_without_validation(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( "{} is not a supported type".format( x509._GENERAL_NAMES.get(gn.type, gn.type)), gn.type, )
def issue_certificate(csr, options, private_key=None): csr = x509.load_pem_x509_csr(csr.encode('utf-8'), default_backend()) if options.get("parent"): # creating intermediate authorities will have options['parent'] to specify the issuer # creating certificates will have options['authority'] to specify the issuer # This works around that by making sure options['authority'] can be referenced for either options['authority'] = options['parent'] if options.get("authority"): # Issue certificate signed by an existing lemur_certificates authority issuer_subject = options['authority'].authority_certificate.subject issuer_private_key = options['authority'].authority_certificate.private_key chain_cert_pem = options['authority'].authority_certificate.body authority_key_identifier_public = options['authority'].authority_certificate.public_key authority_key_identifier_subject = x509.SubjectKeyIdentifier.from_public_key(authority_key_identifier_public) authority_key_identifier_issuer = issuer_subject authority_key_identifier_serial = int(options['authority'].authority_certificate.serial) # TODO figure out a better way to increment serial # New authorities have a value at options['serial_number'] that is being ignored here. serial = int(uuid.uuid4()) else: # Issue certificate that is self-signed (new lemur_certificates root authority) issuer_subject = csr.subject issuer_private_key = private_key chain_cert_pem = "" authority_key_identifier_public = csr.public_key() authority_key_identifier_subject = None authority_key_identifier_issuer = csr.subject authority_key_identifier_serial = options['serial_number'] # TODO figure out a better way to increment serial serial = int(uuid.uuid4()) extensions = normalize_extensions(csr) builder = x509.CertificateBuilder( issuer_name=issuer_subject, subject_name=csr.subject, public_key=csr.public_key(), not_valid_before=options['validity_start'], not_valid_after=options['validity_end'], serial_number=serial, extensions=extensions) for k, v in options.get('extensions', {}).items(): if k == 'authority_key_identifier': # One or both of these options may be present inside the aki extension (authority_key_identifier, authority_identifier) = (False, False) for k2, v2 in v.items(): if k2 == 'use_key_identifier' and v2: authority_key_identifier = True if k2 == 'use_authority_cert' and v2: authority_identifier = True if authority_key_identifier: if authority_key_identifier_subject: # FIXME in python-cryptography. # from_issuer_subject_key_identifier(cls, ski) is looking for ski.value.digest # but the digest of the ski is at just ski.digest. Until that library is fixed, # this function won't work. The second line has the same result. # aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(authority_key_identifier_subject) aki = x509.AuthorityKeyIdentifier(authority_key_identifier_subject.digest, None, None) else: aki = x509.AuthorityKeyIdentifier.from_issuer_public_key(authority_key_identifier_public) elif authority_identifier: aki = x509.AuthorityKeyIdentifier(None, [x509.DirectoryName(authority_key_identifier_issuer)], authority_key_identifier_serial) builder = builder.add_extension(aki, critical=False) if k == 'certificate_info_access': # FIXME: Implement the AuthorityInformationAccess extension # descriptions = [ # x509.AccessDescription(x509.oid.AuthorityInformationAccessOID.OCSP, x509.UniformResourceIdentifier(u"http://FIXME")), # x509.AccessDescription(x509.oid.AuthorityInformationAccessOID.CA_ISSUERS, x509.UniformResourceIdentifier(u"http://FIXME")) # ] # for k2, v2 in v.items(): # if k2 == 'include_aia' and v2 == True: # builder = builder.add_extension( # x509.AuthorityInformationAccess(descriptions), # critical=False # ) pass if k == 'crl_distribution_points': # FIXME: Implement the CRLDistributionPoints extension # FIXME: Not implemented in lemur/schemas.py yet https://github.com/Netflix/lemur/issues/662 pass private_key = serialization.load_pem_private_key( bytes(str(issuer_private_key).encode('utf-8')), password=None, backend=default_backend() ) cert = builder.sign(private_key, hashes.SHA256(), default_backend()) cert_pem = cert.public_bytes( encoding=serialization.Encoding.PEM ).decode('utf-8') return cert_pem, chain_cert_pem
def get_ca_cert(self, key=None): """Get the CA Certificate. Args: key (cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): A key in cryptography RSAPrivateKey format. Returns: cryptography.x509.Certificate: The CA Certificate. Returns: cryptography.x509.Certificate: CA Certificate """ ca_cert_path = self.config.get("ca", "cert") if not os.path.isabs(ca_cert_path): ca_cert_path = os.path.abspath( os.path.join(os.path.dirname(__file__), ca_cert_path)) # Grab the CA Certificate from filesystem if it exists and return if os.path.isfile(ca_cert_path): with open(ca_cert_path, "rb") as cert_file: ca_cert = x509.load_pem_x509_certificate( cert_file.read(), default_backend()) return ca_cert if key is None: raise CertProcessorKeyNotFoundError() key_id = x509.SubjectKeyIdentifier.from_public_key(key.public_key()) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, self.config.get("ca", "issuer")) ]) now = datetime.datetime.utcnow() serial = x509.random_serial_number() ca_cert = (x509.CertificateBuilder().subject_name(subject).issuer_name( issuer).public_key( key.public_key()).serial_number(serial).not_valid_before(now). not_valid_after(now + datetime.timedelta(days=365)).add_extension( key_id, critical=False).add_extension( x509.AuthorityKeyIdentifier( key_id.digest, [x509.DirectoryName(issuer)], serial), critical=False, ).add_extension( x509.BasicConstraints( ca=True, path_length=0), critical=True).add_extension( 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, ), critical=True, ).sign(key, hashes.SHA256(), default_backend())) with open(ca_cert_path, "wb") as f: f.write(ca_cert.public_bytes(serialization.Encoding.PEM)) return ca_cert
def make_saml_cert(key, country=None, state=None, locality=None, org=None, domain=None, email=None, alt_names=None, days=365, self_sign=True): if isinstance(key, basestring): key = private_key_from_cleaned_text(key) if alt_names and isinstance(alt_names, basestring): alt_names = alt_names.split() name_components = [] if country: name_components.append( x509.NameAttribute(NameOID.COUNTRY_NAME, country)) if state: name_components.append( x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state)) if locality: name_components.append( x509.NameAttribute(NameOID.LOCALITY_NAME, locality)) if org: name_components.append( x509.NameAttribute(NameOID.ORGANIZATION_NAME, org)) if domain: name_components.append(x509.NameAttribute(NameOID.COMMON_NAME, domain)) if email: name_components.append(x509.NameAttribute(NameOID.EMAIL_ADDRESS, email)) subject = x509.Name(name_components) pkey = key.public_key() skid = x509.SubjectKeyIdentifier.from_public_key(pkey) if self_sign: # Create self-signed serial_number = random_serial_number() builder = x509.CertificateBuilder() else: builder = x509.CertificateSigningRequestBuilder() builder = builder.subject_name(subject).add_extension( x509.KeyUsage(True, False, True, True, False, False, False, False, False), False) if alt_names: # Describe what sites we want this certificate for. builder = builder.add_extension(x509.SubjectAlternativeName( [x509.DNSName(n) for n in alt_names]), critical=False) if self_sign: builder = builder.public_key(pkey).issuer_name(subject).serial_number( serial_number).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=days)).add_extension( skid, False).add_extension( x509.AuthorityKeyIdentifier( skid.digest, [x509.DirectoryName(subject)], serial_number), False) else: builder = builder.add_extension(x509.BasicConstraints(True, 0), False) builder = builder.sign(key, hashes.SHA256(), default_backend()) builder_text = builder.public_bytes(serialization.Encoding.PEM) return (builder_text, 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'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 test_not_name(self): with pytest.raises(TypeError): x509.DirectoryName(b"notaname") with pytest.raises(TypeError): x509.DirectoryName(1.3)
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 _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 setup_crts(ca_common_name, cert_common_name, san_list, ca_cert_file="./ca.crt", ca_key_file="./ca.key", cert_file="./chain.pem", key_file="./pkey.key"): """ Generate certificate authority cert, key and chain cert and key signed by CA generated. :param ca_common_name: :param cert_common_name: :param san_list: :param ca_cert_file: :param ca_key_file: :param cert_file: :param key_file: """ logger.info("Generating CA private key") root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) subject = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, ca_common_name), ]) issuer = [ x509.DirectoryName( x509.Name([ x509.NameAttribute(x509.OID_COMMON_NAME, ca_common_name), ])) ] skid = x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()) root_serial_number = x509.random_serial_number() logger.info("Building CA certificate") root_cert = x509.CertificateBuilder().subject_name(subject).issuer_name( subject).public_key(root_key.public_key( )).serial_number(root_serial_number).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=3650)).add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=False, ).add_extension( x509.KeyUsage(digital_signature=False, key_encipherment=False, content_commitment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False), critical=False).add_extension( skid, critical=False).add_extension( x509.AuthorityKeyIdentifier( key_identifier=skid.digest, authority_cert_issuer=issuer, authority_cert_serial_number=root_serial_number ), critical=False).sign(root_key, hashes.SHA256(), default_backend()) logger.info( "Building {} certificate signed by CA".format(cert_common_name)) # Generate cert for CA cert_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) new_subject = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, cert_common_name), ]) x509_sans = [] for san in san_list: x509_sans.append(x509.DNSName(san)) cert = x509.CertificateBuilder().subject_name(new_subject).issuer_name( root_cert.subject).public_key(cert_key.public_key()).serial_number( x509.random_serial_number()).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=365)).add_extension( x509.SubjectAlternativeName(x509_sans), critical=False, ).add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=False, ).add_extension( x509.ExtendedKeyUsage([ ExtendedKeyUsageOID.SERVER_AUTH, ]), critical=False, ).add_extension( x509.KeyUsage(digital_signature=True, key_encipherment=True, content_commitment=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.SubjectKeyIdentifier.from_public_key( cert_key.public_key()), critical=False ).add_extension(x509.AuthorityKeyIdentifier( key_identifier=skid.digest, authority_cert_issuer=issuer, authority_cert_serial_number=root_serial_number), critical=False).sign( root_key, hashes.SHA256(), default_backend()) # Dump to scratch ca_cert = root_cert.public_bytes(encoding=serialization.Encoding.PEM) ca_key = root_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) # Return PEM cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM) cert_key_pem = cert_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) crt = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem) crt_header = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, crt) logger.info("Dumping {}".format(ca_cert_file)) with open(ca_cert_file, "wb") as f: f.write(ca_cert) logger.info("Dumping {}".format(ca_key_file)) with open(ca_key_file, "wb") as f: f.write(ca_key) logger.info("Dumping {}".format(cert_file)) with open(cert_file, "wb") as f: f.write(crt_header + cert_pem) logger.info("Dumping {}".format(key_file)) with open(key_file, "wb") as f: f.write(cert_key_pem)