def create_client_certificate(client_csr: x509.CertificateSigningRequest, serial_number: int, ca_key: rsa.RSAPrivateKey, ca_cert: x509.Certificate) -> x509.Certificate: """ Creates client (slave) certificate signed by CA. Parameters ---------- client_csr Client Certificate Signing Request. serial_number Client certificate serial number. Number must be unique within CA and less than 20 bytes. ca_key Certificate Authority private key. ca_cert Certificate Authority certificate. Returns ------- Client certificate. """ one_day = datetime.timedelta(1, 0, 0) ten_days = datetime.timedelta(10, 0, 0) builder = x509.CertificateBuilder( issuer_name=ca_cert.subject, subject_name=client_csr.subject, public_key=client_csr.public_key(), serial_number=serial_number, not_valid_before=datetime.datetime.today() - one_day, not_valid_after=datetime.datetime.today() + ten_days, ) builder = builder.add_extension(x509.BasicConstraints(False, path_length=None), critical=False) builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key( client_csr.public_key()), critical=False) builder = builder.add_extension( x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ca_cert.extensions.get_extension_for_class( x509.SubjectKeyIdentifier)), critical=False) cert = builder.sign(ca_key, hashes.SHA256(), backend=default_backend()) return cert
def sign(self, request: x509.CertificateSigningRequest) -> x509.Certificate: """ Sign a Certificate Signing Request. The issued certificate is automatically persisted to the database. Args: request (x509.CertificateSigningRequest): The CSR object (cryptography) not the SQLAlchemy model. Returns: x509.Certificate: A signed certificate """ b = x509.CertificateBuilder() self.serial += 1 private_key_model = self.rsa_private_key private_key = private_key_model.to_crypto() # ca_certificate_model = self.certificate # ca_certificate = ca_certificate_model.to_crypto() name = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, self.common_name), x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'commandment') ]) cert = b.not_valid_before( datetime.datetime.utcnow() ).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=self.validity_period) ).serial_number( self.serial ).issuer_name( name ).subject_name( request.subject ).public_key( request.public_key() ).sign(private_key, hashes.SHA256(), default_backend()) # cert_model = DeviceIdentityCertificate().from_crypto(cert) # db.session.add(cert_model) # db.session.commit() return cert
def sign_csr( csr: CertificateSigningRequest, ca_cert: Certificate, key: EllipticCurvePrivateKey, expiration_date: date, custom_extensions: Iterable[Union[KeyUsage, UnrecognizedExtension, BasicConstraints]] ) -> Certificate: """ Sign a CSR with CA credentials. :param csr: the CSR :param ca_cert: the CA certificate :param key: the CA private key :param expiration_date: expiration date :param custom_extensions: custom extensions to be added to the certificate :return: a certificate object """ issuer = ca_cert.subject now = datetime.utcnow() cert_builder = CertificateBuilder().issuer_name(issuer).subject_name( csr.subject).public_key(csr.public_key()).serial_number( x509.random_serial_number()).not_valid_before(now).not_valid_after( datetime.combine(expiration_date, time(), None)).add_extension( extension=AuthorityKeyIdentifier.from_issuer_public_key( ca_cert.public_key()), critical=False) try: cert_builder = cert_builder.add_extension( csr.extensions.get_extension_for_class( SubjectAlternativeName).value, critical=False) except ExtensionNotFound: pass for extension in custom_extensions: if isinstance(extension, UnrecognizedExtension): critical = False else: critical = True # pyre-fixme[6]: Expected `ExtensionType` for 1st param but got # `Union[BasicConstraints, KeyUsage, UnrecognizedExtension]`. cert_builder = cert_builder.add_extension(extension, critical=critical) return cert_builder.sign(key, SHA256(), backends.default_backend())
def sign(self, csr: x509.CertificateSigningRequest) -> x509.Certificate: """Sign a certificate signing request. Args: csr (x509.CertificateSigningRequest): The certificate signing request Returns: Instance of x509.Certificate """ serial = self.serial + 1 builder = x509.CertificateBuilder() cert = builder.subject_name(csr.subject).issuer_name( self.certificate.subject).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta( days=365)).serial_number(serial).public_key( csr.public_key()).sign(self.private_key, hashes.SHA256(), default_backend()) self._storage.save_issued_certificate(cert) self.serial = serial return cert
def create_cert( self, ca: "CertificateAuthority", csr: x509.CertificateSigningRequest, subject: Optional[Subject] = None, expires: Expires = None, algorithm: typing.Optional[HashAlgorithm] = None, extensions: Optional[Union[Dict[str, Union[ "x509.Extension[x509.ExtensionType]", Extension[Any, Any, Any]]], Iterable[Union[Extension[Any, Any, Any]]], ]] = None, cn_in_san: Optional[bool] = None, add_crl_url: Optional[bool] = None, add_ocsp_url: Optional[bool] = None, add_issuer_url: Optional[bool] = None, add_issuer_alternative_name: Optional[bool] = None, password: Optional[Union[str, bytes]] = None, ) -> x509.Certificate: """Create a x509 certificate based on this profile, the passed CA and input parameters. .. deprecated:: 1.18.0 Since ``django-ca==1.18.0``, passing unparsed values is deprecated and will be removed in ``django-ca==1.20.0``. This affects the following parameters: * Passing a ``str`` as `algorithm`. This function is the core function used to create x509 certificates. In it's simplest form, you only need to pass a ca, a CSR and a subject to get a valid certificate:: >>> profile = get_profile('webserver') >>> profile.create_cert(ca, csr, subject='/CN=example.com') # doctest: +ELLIPSIS <Certificate(subject=<Name(...,CN=example.com)>, ...)> The function will add CRL, OCSP, Issuer and IssuerAlternativeName URLs based on the CA if the profile has the *add_crl_url*, *add_ocsp_url* and *add_issuer_url* and *add_issuer_alternative_name* values set. Parameters to this function with the same name allow you override this behavior. The function allows you to override profile values using the *expires* and *algorithm* values. You can pass additional *extensions* as a list, which will override any extensions from the profile, but the CA passed will append to these extensions unless the *add_...* values are ``False``. Parameters ---------- ca : :py:class:`~django_ca.models.CertificateAuthority` The CA to sign the certificate with. csr : :py:class:`~cg:cryptography.x509.CertificateSigningRequest` The CSR for the certificate. subject : dict or str or :py:class:`~django_ca.subject.Subject` Update the subject string, e.g. ``"/CN=example.com"`` or ``Subject("/CN=example.com")``. The values from the passed subject will update the profiles subject. expires : int or datetime or timedelta, optional Override when this certificate will expire. algorithm : :py:class:`~cg:cryptography.hazmat.primitives.hashes.HashAlgorithm`, optional Override the hash algorithm used when signing the certificate. extensions : list or dict of :py:class:`~django_ca.extensions.base.Extension` List or dict of additional extensions to set for the certificate. Note that values from the CA might update the passed extensions: For example, if you pass an :py:class:`~django_ca.extensions.IssuerAlternativeName` extension, *add_issuer_alternative_name* is ``True`` and the passed CA has an IssuerAlternativeName set, that value will be appended to the extension you pass here. If you pass a dict with a ``None`` value, that extension will be removed from the profile. cn_in_san : bool, optional Override if the CommonName should be added as an SubjectAlternativeName. If not passed, the value set in the profile is used. add_crl_url : bool, optional Override if any CRL URLs from the CA should be added to the CA. If not passed, the value set in the profile is used. add_ocsp_url : bool, optional Override if any OCSP URLs from the CA should be added to the CA. If not passed, the value set in the profile is used. add_issuer_url : bool, optional Override if any Issuer URLs from the CA should be added to the CA. If not passed, the value set in the profile is used. add_issuer_alternative_name : bool, optional Override if any IssuerAlternativeNames from the CA should be added to the CA. If not passed, the value set in the profile is used. password: bytes or str, optional The password to the private key of the CA. Returns ------- cryptography.x509.Certificate The signed certificate. """ # pylint: disable=too-many-locals,too-many-arguments # Compute default values if extensions is None: extensions_update: Dict[str, Extension[Any, Any, Any]] = {} elif isinstance(extensions, dict): extensions_update = { k: self._parse_extension_value(k, v) for k, v in extensions.items() if v is not None } else: extensions_update = {e.key: e for e in extensions} # Get overrides values from profile if not passed as parameter if cn_in_san is None: cn_in_san = self.cn_in_san if add_crl_url is None: add_crl_url = self.add_crl_url if add_ocsp_url is None: add_ocsp_url = self.add_ocsp_url if add_issuer_url is None: add_issuer_url = self.add_issuer_url if add_issuer_alternative_name is None: add_issuer_alternative_name = self.add_issuer_alternative_name cert_extensions = deepcopy(self.extensions) cert_extensions.update(extensions_update) cert_extensions = { k: v for k, v in cert_extensions.items() if v is not None } cert_subject = deepcopy(self.subject) # If extensions is a dict, filter any extenions where the value is None if isinstance(extensions, dict): for key in { k for k, v in extensions.items() if v is None and k in cert_extensions }: del cert_extensions[key] issuer_name = self._update_from_ca( ca, cert_extensions, add_crl_url=add_crl_url, add_ocsp_url=add_ocsp_url, add_issuer_url=add_issuer_url, add_issuer_alternative_name=add_issuer_alternative_name, ) if not isinstance(subject, Subject): subject = Subject(subject) # NOTE: also accepts None cert_subject.update(subject) if expires is None: expires = self.expires if algorithm is None: algorithm = self.algorithm elif isinstance(algorithm, str): warnings.warn( "Passing a str as algorithm is deprecated and will be removed in django-ca==1.20.0.", category=RemovedInDjangoCA120Warning, stacklevel=2, ) algorithm = parse_hash_algorithm(algorithm) # Make sure that expires is a fixed timestamp expires = parse_expires(expires) # Finally, update SAN with the current CN, if set and requested self._update_san_from_cn(cn_in_san, subject=cert_subject, extensions=cert_extensions) if not subject.get("CN") and ( SubjectAlternativeName.key not in extensions_update or not cast(SubjectAlternativeName, extensions_update[SubjectAlternativeName.key]).value): raise ValueError( "Must name at least a CN or a subjectAlternativeName.") pre_issue_cert.send( sender=self.__class__, ca=ca, csr=csr, expires=expires, algorithm=algorithm, subject=cert_subject, extensions=cert_extensions, password=password, ) public_key = csr.public_key() builder = get_cert_builder(expires) builder = builder.public_key(public_key) builder = builder.issuer_name(issuer_name) builder = builder.subject_name(cert_subject.name) for _key, extension in cert_extensions.items(): builder = builder.add_extension(*extension.for_builder()) # Add the SubjectKeyIdentifier if SubjectKeyIdentifier.key not in cert_extensions: builder = builder.add_extension( x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False) return builder.sign(private_key=ca.key(password), algorithm=algorithm, backend=default_backend())
def sign(self, csr: x509.CertificateSigningRequest, algorithm: str = 'sha256') -> x509.Certificate: """Sign a certificate signing request. Args: csr (x509.CertificateSigningRequest): The certificate signing request Returns: Instance of x509.Certificate """ serial = self.serial + 1 csr_subject = csr.subject builder = x509.CertificateBuilder() hash_functions = { 'sha1': hashes.SHA1, 'sha256': hashes.SHA256, 'sha512': hashes.SHA512, } builder = builder.subject_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "iOS Client") ])).issuer_name(self.certificate.subject).not_valid_before( datetime.datetime.utcnow()).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta( days=365)).serial_number(serial).public_key( csr.public_key()) builder = builder.add_extension( # Absolutely critical for SCEP 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), True) # builder = builder.add_extension( # x509.ExtendedKeyUsage([ObjectIdentifier('1.3.6.1.5.5.8.2.2')]), False # ) # # builder = builder.add_extension( # x509.SubjectKeyIdentifier.from_public_key(csr.public_key()), False # ) # # for requested_extension in csr.extensions: # builder = builder.add_extension(requested_extension.value, critical=requested_extension.critical) cert = builder.sign(self.private_key, hash_functions.get(algorithm, hashes.SHA256)(), default_backend()) self._storage.save_issued_certificate(cert) self.serial = serial return cert
def check_csr(csr: x509.CertificateSigningRequest, template: Template) -> str: """ find all differences (errors) between info in csr and template, return empty string if no errors""" extensions = { OID_NAMES.get(extension.oid, extension.oid): extension.value for extension in csr.extensions } # noinspection PyProtectedMember return '\n'.join(error for error in ( # verify csr signature '' if csr. is_signature_valid else 'hmmm... csr signature is not valid!!!', # verify signature hash algorithm '' if csr.signature_hash_algorithm.name == template.hash_algorithm.lower() else 'hmmm wrong hash algorithm!!!', # verify subject matches '' if template.subject == tuple( (OID_NAMES.get(attrib.oid), attrib.value) for attrib in csr.subject) else 'subject mismatch:\n{}\n{}\n'. format(csr.subject, template.subject), # verify subjectAltName '' if template.subject_alt_names is None and 'subject_alt_names' not in extensions else '' if tuple( x.value for x in extensions['subjectAltName']) == template. subject_alt_names else 'subject_alt_names mismatch:\n{}\n{}\n'. format(extensions['subjectAltName'], template.subject_alt_names), # verify basicConstraints ca '' if extensions['basicConstraints'].ca == template.basic_constraints. ca else 'basicConstraints ca mismatch:\n{}\n{}\n'.format( extensions['basicConstraints'].ca, template.basic_constraints.ca), # verify basicConstraints path_length '' if extensions['basicConstraints'].path_length == template.basic_constraints. path_length else 'basicConstraints path_length mismatch:\n{}\n{}\n'. format(extensions['basicConstraints'].path_length, template. basic_constraints.path_length), # verify keyUsage '' if template.key_usage is None and 'keyUsage' not in extensions else '' if all(( extensions['keyUsage'].digital_signature == template.key_usage.digital_signature, extensions['keyUsage'].content_commitment == template.key_usage.content_commitment, extensions['keyUsage'].key_encipherment == template.key_usage.key_encipherment, extensions['keyUsage'].data_encipherment == template.key_usage.data_encipherment, extensions['keyUsage'].key_agreement == template.key_usage.key_agreement, extensions['keyUsage'].key_cert_sign == template.key_usage.key_cert_sign, extensions['keyUsage'].crl_sign == template.key_usage.crl_sign, extensions['keyUsage']._encipher_only == template.key_usage.encipher_only, extensions['keyUsage']._decipher_only == template.key_usage.decipher_only, )) else 'keyUsage mismatch:\n{}\n{}\n'. format(extensions['keyUsage'], template.key_usage), # verify KeySize '' if template.key_size == csr.public_key().key_size else 'KeySize mismatch:\n{}\n{}\n'. format(csr.public_key().key_size, template.key_size), # verify KeySize >= 2048 '' if csr.public_key().key_size >= 2048 else 'weak key size {}'. format(csr.public_key().key_size)) if error != '')