Example #1
0
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,
    })
Example #2
0
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,
    })
Example #3
0
 def __init__(self):
     self._signers = []
     self._cms_attributes = []
     self._certificates = None
     self._pki_envelope = None
     self._certificates = CertificateSet()
Example #4
0
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
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
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