def create_degenerate_pkcs7(*certificates: List[x509.Certificate]) -> ContentInfo: """Produce a PKCS#7 Degenerate case. The degenerate case is a SignedData content type in which there are no signers. Certificates are disseminated via the ``certificates`` attribute. Args: *certificates (List[x509.Certificate]): The certificates to attach to the degenerate pkcs#7 payload. The first must always be the issued certificate. Returns: ContentInfo: The ContentInfo containing a SignedData structure. """ certificates_der = [c.public_bytes(serialization.Encoding.DER) for c in certificates] certificates_asn1 = [parse_certificate(der_bytes) for der_bytes in certificates_der] # draft-gutmann-scep 3.4. content type must be omitted empty = ContentInfo({ 'content_type': ContentType('data') }) sd_certificates = CertificateSet([CertificateChoices('certificate', asn1) for asn1 in certificates_asn1]) sd = SignedData({ 'version': CMSVersion(1), 'encap_content_info': empty, 'digest_algorithms': DigestAlgorithms([]), 'certificates': sd_certificates, 'signer_infos': SignerInfos([]), 'crls': RevocationInfoChoices([]), }) return ContentInfo({ 'content_type': ContentType('signed_data'), 'content': sd, })
def create_degenerate_certificate(certificate: x509.Certificate) -> ContentInfo: """Produce a PKCS#7 Degenerate case with a single certificate. Args: certificate (x509.Certificate): The certificate to attach to the degenerate pkcs#7 payload. Returns: ContentInfo: The ContentInfo containing a SignedData structure. """ der_bytes = certificate.public_bytes( serialization.Encoding.DER ) asn1cert = parse_certificate(der_bytes) empty = ContentInfo({ 'content_type': ContentType('data') }) sd = SignedData({ 'version': CMSVersion(1), 'encap_content_info': empty, 'digest_algorithms': DigestAlgorithms([]), 'certificates': CertificateSet([CertificateChoices('certificate', asn1cert)]), 'signer_infos': SignerInfos([]), 'crls': RevocationInfoChoices([]), }) return ContentInfo({ 'content_type': ContentType('signed_data'), 'content': sd, })
def __init__(self): self._signers = [] self._cms_attributes = [] self._certificates = None self._pki_envelope = None self._certificates = CertificateSet()
class PKIMessageBuilder(object): """The PKIMessageBuilder builds pkiMessages as defined in the SCEP RFC. Attributes: _signers: List of signers to create signatures and populate signerinfos. _cms_attributes: List of CMSAttribute _certificates: List of Certificates _pki_envelope: The enveloped data being signed See Also: - `<https://tools.ietf.org/html/draft-nourse-scep-23#section-3.1>`_. """ def __init__(self): self._signers = [] self._cms_attributes = [] self._certificates = None self._pki_envelope = None self._certificates = CertificateSet() def certificates(self, *certificates: List[x509.Certificate]): """Add x.509 certificates to be attached to the certificates field. Args: certificates: variadic argument of x509.Certificate Returns: PKIMessageBuilder: This instance See Also: - `pkcs#7 RFC 2315 Section 9.1 <https://tools.ietf.org/html/rfc2315#section-9.1>`_. """ for cert in certificates: # Serialize and load to avoid constructing asn1crypto.Certificate ourselves (yuck) derp = cert.public_bytes(serialization.Encoding.DER) asn1cert = parse_certificate(derp) choice = CertificateChoices('certificate', asn1cert) self._certificates.append(choice) return self def add_signer(self, signer: Signer): """Add a signer to SignerInfos. Args: signer (Signer): Signer instance Returns: PKIMessageBuilder: This instance See Also: - `pkcs#7 RFC2315 Section 9.2 <https://tools.ietf.org/html/rfc2315#section-9.2>`_. """ self._signers.append(signer) der_certificate = signer.certificate.public_bytes(serialization.Encoding.DER) asn1_certificate = parse_certificate(der_certificate) self._certificates.append(CertificateChoices('certificate', asn1_certificate)) return self def message_type(self, message_type: MessageType): """Set the SCEP Message Type Attribute. Args: message_type (MessageType): A valid PKIMessage messageType Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.2. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ attr = CMSAttribute({ 'type': 'message_type', 'values': [PrintableString(message_type.value)], }) self._cms_attributes.append(attr) return self def pki_envelope(self, envelope: EnvelopedData): """Set content for encryption inside the pkcsPKIEnvelope Args: envelope (EnvelopedData): The pkcsPKIEnvelope Returns: PKIMessageBuilder: This instance """ self._pki_envelope = envelope return self def pki_status(self, status: PKIStatus, failure_info: FailInfo = None): """Set the PKI status of the operation. Args: status (PKIStatus): A valid pkiStatus value failure_info (FailInfo): A failure info type, which must be present if PKIStatus is failure. Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.3. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ attr = CMSAttribute({ 'type': 'pki_status', 'values': [PrintableString(status.value)], }) self._cms_attributes.append(attr) if status == PKIStatus.FAILURE: if failure_info is None: raise ValueError('You cannot specify failure without failure info') fail_attr = CMSAttribute({ 'type': 'fail_info', 'values': [PrintableString(failure_info.value)], }) self._cms_attributes.append(fail_attr) return self def sender_nonce(self, nonce: Union[bytes, OctetString] = None): """Add a sender nonce. Args: nonce (bytes or OctetString): Sender nonce Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.5. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(nonce, bytes): nonce = OctetString(nonce) elif nonce is None: nonce = OctetString(os.urandom(16)) attr = CMSAttribute({ 'type': 'sender_nonce', 'values': [nonce], }) self._cms_attributes.append(attr) return self def recipient_nonce(self, nonce: Union[bytes, OctetString]): """Add a recipient nonce. Args: nonce (bytes or OctetString): Recipient nonce Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.5. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(nonce, bytes): nonce = OctetString(nonce) attr = CMSAttribute({ 'type': 'recipient_nonce', 'values': [nonce], }) self._cms_attributes.append(attr) return self def transaction_id(self, trans_id: Union[str, PrintableString] = None): """Add a transaction ID. Args: trans_id (str or PrintableString): Transaction ID. If omitted, one is generated Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.1. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(trans_id, str): trans_id = PrintableString(trans_id) elif trans_id is None: trans_id = PrintableString(str(uuid4())) attr = CMSAttribute({ 'type': 'transaction_id', 'values': [trans_id] }) self._cms_attributes.append(attr) return self def _build_cmsattributes(self) -> CMSAttributes: """Finalize the set of CMS Attributes and return the collection. Returns: CMSAttributes: All of the added CMS attributes """ return CMSAttributes(value=self._cms_attributes) def _build_signerinfos(self, content: bytes, content_digest: bytes, cms_attributes: List[CMSAttribute]) -> SignerInfos: """Build all signer infos and return a collection. Returns: SignerInfos: all signers """ return SignerInfos(signer.sign(content, ContentType('data'), content_digest, cms_attributes) for signer in self._signers) def finalize(self) -> ContentInfo: """Build all data structures from the given parameters and return the top level contentInfo. Returns: ContentInfo: The PKIMessage """ pkcs_pki_envelope = self._pki_envelope pkienvelope_content_info = ContentInfo({ 'content_type': ContentType('enveloped_data'), 'content': pkcs_pki_envelope, }) # NOTE: This might not be needed for the degenerate CertRep encap_info = ContentInfo({ 'content_type': ContentType('data'), 'content': pkienvelope_content_info.dump() }) # encap_info_degen = ContentInfo({ # 'content_type': ContentType('data'), # 'content': pkcs_pki_envelope.dump() # }) # Calculate digest on encrypted content + signed_attrs #digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) digest = hashes.Hash(hashes.SHA512(), backend=default_backend()) #digest = hashes.Hash(hashes.SHA1(), backend=default_backend()) # digest.update(pkcs_pki_envelope.dump()) digest.update(pkienvelope_content_info.dump()) d = digest.finalize() # Now start building SignedData # signer_infos = self._build_signerinfos(pkcs_pki_envelope.dump(), d, self._cms_attributes) signer_infos = self._build_signerinfos(pkienvelope_content_info.dump(), d, self._cms_attributes) certificates = self._certificates #da_id = DigestAlgorithmId('sha256') # SHA-1 works for macOS # da_id = DigestAlgorithmId('sha1') da_id = DigestAlgorithmId('sha512') da = DigestAlgorithm({'algorithm': da_id}) das = DigestAlgorithms([da]) sd = SignedData({ 'version': 1, 'certificates': certificates, 'signer_infos': signer_infos, 'digest_algorithms': das, 'encap_content_info': encap_info, # should point to type data + content contentinfo }) ci = ContentInfo({ 'content_type': ContentType('signed_data'), 'content': sd, }) return ci
def sign(self): h = hashes.Hash(hashes.SHA256(), backend=default_backend()) h.update(self._content_mime.as_bytes()) message_digest = h.finalize() cs = CertificateSet() cs.append(load(self._certificate.public_bytes(Encoding.DER))) for ca_cert in self._ca: cs.append(load(ca_cert.public_bytes(Encoding.DER))) ec = ContentInfo({ 'content_type': ContentType('data'), }) sident = SignerIdentifier({ 'issuer_and_serial_number': IssuerAndSerialNumber({ 'issuer': load(self._issuer_name.public_bytes(default_backend())), 'serial_number': self._cert_serial, }) }) certv2 = ESSCertIDv2({ 'hash_algorithm': DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), 'cert_hash': OctetString(self._certificate.fingerprint(hashes.SHA256())), 'issuer_serial': IssuerSerial({ 'issuer': load(self._issuer_name.public_bytes(default_backend())), 'serial_number': self._cert_serial, }), }) now = datetime.now().replace(microsecond=0, tzinfo=pytz.utc) # .isoformat() sattrs = CMSAttributes({ CMSAttribute({ 'type': CMSAttributeType('content_type'), 'values': ["data"] }), CMSAttribute({ 'type': CMSAttributeType('message_digest'), 'values': [message_digest] }), CMSAttribute({ 'type': CMSAttributeType('signing_time'), 'values': (Time({'utc_time': UTCTime(now)}), ) }), CMSAttribute({ 'type': CMSAttributeType('signing_certificate_v2'), 'values': [SigningCertificateV2({'certs': (certv2, )})] }) }) signature = self._private_key.sign(sattrs.dump(), padding.PKCS1v15(), hashes.SHA256()) # si = SignerInfo({ 'version': 'v1', 'sid': sident, 'digest_algorithm': DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), 'signed_attrs': sattrs, 'signature_algorithm': SignedDigestAlgorithm( {'algorithm': SignedDigestAlgorithmId('rsassa_pkcs1v15')}), 'signature': signature, }) da = DigestAlgorithms( (DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), )) signed_data = SignedData({ 'version': 'v1', 'encap_content_info': ec, 'certificates': cs, 'digest_algorithms': da, 'signer_infos': SignerInfos((si, )) }) ci = ContentInfo({ 'content_type': ContentType('signed_data'), 'content': signed_data }) self._signature_mime = MIMEApplication(ci.dump(), _subtype="pkcs7-signature", name="smime.p7s", policy=email.policy.SMTPUTF8) self._signature_mime.add_header('Content-Disposition', 'attachment; filename=smime.p7s') super(CADESMIMESignature, self).attach(self._content_mime) super(CADESMIMESignature, self).attach(self._signature_mime)
def sign(self): h = hashes.Hash(hashes.SHA256(), backend=default_backend()) h.update(self._content_mime.as_bytes()) message_digest = h.finalize() cs = CertificateSet() cs.append(load(self._certificate.public_bytes(Encoding.DER))) for ca_cert in self._ca: cs.append(load(ca_cert.public_bytes(Encoding.DER))) ec = EncapsulatedContentInfo({ 'content_type': ContentType('data'), 'content': ParsableOctetString(self._content_mime.as_bytes()) }) sident = SignerIdentifier({ 'issuer_and_serial_number': IssuerAndSerialNumber({ 'issuer': load(self._issuer_name.public_bytes(default_backend())), 'serial_number': self._cert_serial, }) }) certv2 = ESSCertIDv2({ 'hash_algorithm': DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), 'cert_hash': OctetString(self._certificate.fingerprint(hashes.SHA256())), 'issuer_serial': IssuerSerial({ 'issuer': load( self._issuer_name.public_bytes(default_backend()) ), #[GeneralName({'directory_name': self._issuer_name.public_bytes(default_backend())})], 'serial_number': self._cert_serial, }), }) now = datetime.now().replace(microsecond=0, tzinfo=pytz.utc) sattrs = CMSAttributes({ CMSAttribute({ 'type': CMSAttributeType('content_type'), 'values': ["data"] }), CMSAttribute({ 'type': CMSAttributeType('message_digest'), 'values': [message_digest] }), CMSAttribute({ 'type': CMSAttributeType('signing_time'), 'values': (Time({'utc_time': UTCTime(now)}), ) }), # isti k v CMSAttribute({ 'type': CMSAttributeType('signing_certificate_v2'), 'values': [SigningCertificateV2({'certs': (certv2, )})] }) }) signature = self._private_key.sign(sattrs.dump(), padding.PKCS1v15(), hashes.SHA256()) si = SignerInfo({ 'version': 'v1', 'sid': sident, 'digest_algorithm': DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), 'signed_attrs': sattrs, 'signature_algorithm': SignedDigestAlgorithm( {'algorithm': SignedDigestAlgorithmId('rsassa_pkcs1v15')}), 'signature': signature, }) da = DigestAlgorithms( (DigestAlgorithm({'algorithm': DigestAlgorithmId('sha256')}), )) signed_data = SignedData({ 'version': 'v3', 'encap_content_info': ec, 'certificates': cs, 'digest_algorithms': da, 'signer_infos': SignerInfos((si, )) }) ci = ContentInfo({ 'content_type': ContentType('signed_data'), 'content': signed_data }) self.set_payload(ci.dump()) encode_base64(self)
class PKIMessageBuilder(object): """The PKIMessageBuilder builds pkiMessages as defined in the SCEP RFC. Attributes: _signers: List of signers to create signatures and populate signerinfos. _cms_attributes: List of CMSAttribute _certificates: List of Certificates _pki_envelope: The enveloped data being signed See Also: - `<https://tools.ietf.org/html/draft-nourse-scep-23#section-3.1>`_. """ def __init__(self): self._signers = [] self._cms_attributes = [] self._certificates = None self._pki_envelope = None self._certificates = CertificateSet() def certificates(self, *certificates): """Add x.509 certificates to be attached to the certificates field. Args: certificates: variadic argument of x509.Certificate Returns: PKIMessageBuilder: This instance See Also: - `pkcs#7 RFC 2315 Section 9.1 <https://tools.ietf.org/html/rfc2315#section-9.1>`_. """ for cert in certificates: choice = CertificateChoices('certificate', cert.to_asn1_certificate()) self._certificates.append(choice) return self def add_signer(self, signer): """Add a signer to SignerInfos. Args: signer (Signer): Signer instance Returns: PKIMessageBuilder: This instance See Also: - `pkcs#7 RFC2315 Section 9.2 <https://tools.ietf.org/html/rfc2315#section-9.2>`_. """ self._signers.append(signer) self._certificates.append( CertificateChoices('certificate', signer.certificate.to_asn1_certificate())) return self def message_type(self, message_type): """Set the SCEP Message Type Attribute. Args: message_type (MessageType): A valid PKIMessage messageType Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.2. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ attr = CMSAttribute({ 'type': u'message_type', 'values': [PrintableString(six.text_type(message_type.value))], }) logger.debug("{:<20}: {}".format('Message Type', message_type.value)) self._cms_attributes.append(attr) return self def pki_envelope(self, envelope): """Set content for encryption inside the pkcsPKIEnvelope Args: envelope (EnvelopedData): The pkcsPKIEnvelope Returns: PKIMessageBuilder: This instance """ self._pki_envelope = envelope return self def pki_status(self, status, failure_info=None): """Set the PKI status of the operation. Args: status (PKIStatus): A valid pkiStatus value failure_info (FailInfo): A failure info type, which must be present if PKIStatus is failure. Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.3. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ attr = CMSAttribute({ 'type': 'pki_status', 'values': [PrintableString(status.value)], }) self._cms_attributes.append(attr) logger.debug("{:<20}: {}".format('PKI Status', status.value)) if status == PKIStatus.FAILURE: if failure_info is None: raise ValueError( 'You cannot specify failure without failure info') fail_attr = CMSAttribute({ 'type': 'fail_info', 'values': [PrintableString(failure_info.value)], }) logger.debug("{:<20}: {}".format('Failure Info', failure_info.value)) self._cms_attributes.append(fail_attr) return self def sender_nonce(self, nonce=None): """Add a sender nonce. Args: nonce (bytes or OctetString): Sender nonce Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.5. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(nonce, bytes): nonce = OctetString(nonce) elif nonce is None: nonce = OctetString(os.urandom(16)) attr = CMSAttribute({ 'type': u'sender_nonce', 'values': [nonce], }) logger.debug("{:<20}: {}".format('Sender Nonce', b64encode(nonce.native))) self._cms_attributes.append(attr) return self def recipient_nonce(self, nonce): """Add a recipient nonce. Args: nonce (bytes or OctetString): Recipient nonce Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.5. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(nonce, bytes): nonce = OctetString(nonce) attr = CMSAttribute({ 'type': u'recipient_nonce', 'values': [nonce], }) logger.debug("{:<20}: {}".format('Recipient Nonce', b64encode(nonce.native))) self._cms_attributes.append(attr) return self def transaction_id(self, trans_id=None): """Add a transaction ID. Args: trans_id (str or PrintableString): Transaction ID. If omitted, one is generated Returns: PKIMessageBuilder: This instance See Also: - `draft-gutmann-scep Section 3.2.1.1. <https://datatracker.ietf.org/doc/draft-gutmann-scep/?include_text=1>`_. """ if isinstance(trans_id, str): trans_id = PrintableString(six.text_type(trans_id)) elif trans_id is None: trans_id = PrintableString(six.text_type(str(uuid4()))) attr = CMSAttribute({'type': u'transaction_id', 'values': [trans_id]}) logger.debug("{:<20}: {}".format('Transaction ID', trans_id)) self._cms_attributes.append(attr) return self def _build_cmsattributes(self): """Finalize the set of CMS Attributes and return the collection. Returns: CMSAttributes: All of the added CMS attributes """ return CMSAttributes(value=self._cms_attributes) def _build_signerinfos(self, content, content_digest, cms_attributes): """Build all signer infos and return a collection. Returns: SignerInfos: all signers """ return SignerInfos( signer.sign(content, ContentType(u'data'), content_digest, cms_attributes) for signer in self._signers) def finalize(self, digest_algorithm): """Build all data structures from the given parameters and return the top level contentInfo. Returns: ContentInfo: The PKIMessage """ pkcs_pki_envelope = self._pki_envelope pkienvelope_content_info = ContentInfo({ 'content_type': ContentType(u'enveloped_data'), 'content': pkcs_pki_envelope, }) # NOTE: This might not be needed for the degenerate CertRep encap_info = ContentInfo({ 'content_type': ContentType(u'data'), 'content': pkienvelope_content_info.dump() }) # Calculate digest on encrypted content + signed_attrs d = digest_for_data(algorithm=digest_algorithm, data=pkienvelope_content_info.dump()) # Now start building SignedData signer_infos = self._build_signerinfos(pkienvelope_content_info.dump(), d, self._cms_attributes) certificates = self._certificates da_id = DigestAlgorithmId(six.text_type(digest_algorithm)) da = DigestAlgorithm({u'algorithm': da_id}) das = DigestAlgorithms([da]) sd = SignedData({ 'version': 1, 'certificates': certificates, 'signer_infos': signer_infos, 'digest_algorithms': das, 'encap_content_info': encap_info, # should point to type data + content contentinfo }) ci = ContentInfo({ 'content_type': ContentType(u'signed_data'), 'content': sd, }) return ci