Beispiel #1
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,
    })
Beispiel #2
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,
    })
Beispiel #3
0
def make_cms(
    cert: Certificate,
    hash_type: int,
    signed_attrs: CMSAttributes,
    sig: bytes,
    unsigned_attrs: Optional[CMSAttributes],
) -> ContentInfo:
    sid = SignerIdentifier(
        "issuer_and_serial_number",
        IssuerAndSerialNumber({
            "issuer":
            cert["tbs_certificate"]["issuer"],
            "serial_number":
            cert["tbs_certificate"]["serial_number"],
        }),
    )

    dg_algo = DigestAlgorithm({"algorithm": _get_digest_algo(hash_type)})

    sig_algo = SignedDigestAlgorithm(
        {"algorithm": SignedDigestAlgorithmId("rsassa_pkcs1v15")})

    sig_info = SignerInfo({
        "version": CMSVersion(1),
        "sid": sid,
        "digest_algorithm": dg_algo,
        "signed_attrs": signed_attrs,
        "signature_algorithm": sig_algo,
        "signature": OctetString(sig),
        "unsigned_attrs": unsigned_attrs,
    })

    certs = make_certificate_chain(cert)

    signed_data = SignedData({
        "version":
        CMSVersion(1),
        "digest_algorithms": [dg_algo],
        "encap_content_info":
        ContentInfo({"content_type": ContentType("data")}),
        "certificates":
        certs,
        "signer_infos": [sig_info],
    })

    return ContentInfo({
        "content_type": ContentType.unmap("signed_data"),
        "content": signed_data
    })
Beispiel #4
0
def hash_receipt_body(receipt):

    # receipt_hash = hashlib.sha256(receipt.encode('utf-8')).hexdigest()

    # Load the contents of the receipt file
    # receipt_file = open('./receipt_data.bin', 'rb').read()
    logging.debug('hashing receipt')
    # receipt_file = bytearray.fromhex(receipt);
    # receipt_file = bytes.fromhex(receipt)
    receipt_file = base64.decodebytes(receipt.encode('utf-8'))

    # Use asn1crypto's cms definitions to parse the PKCS#7 format
    pkcs_container = ContentInfo.load(receipt_file)

    # Extract the certificates, signature, and receipt_data
    certificates = pkcs_container['content']['certificates']
    signer_info = pkcs_container['content']['signer_infos'][0]
    receipt_data = pkcs_container['content']['encap_content_info']['content']
    logging.debug(
        f'extracted certificates {len(str(certificates))}B signer_info {len(str(signer_info))}B '
        f'receipt_data {len(str(receipt_data))}B')

    receipt_data_str = str(receipt_data).encode('utf-8')[
        50:]  # slice the string to remove random header
    logging.debug(f'receipt_data_str: \n{receipt_data_str}')

    receipt_hash = hashlib.sha256(receipt_data_str).hexdigest()
    logging.debug(f'receipt_hash: {receipt_hash}')

    return receipt_hash
Beispiel #5
0
 def _getCerts(self):
     for info in self._file.infolist():
         if info.filename.startswith(
                 'META-INF/') and info.filename.endswith('.RSA'):
             for cert in ContentInfo.load(
                     self._file.read(info))['content']['certificates']:
                 yield cert.dump()
Beispiel #6
0
    def deserialize(self, s: BinaryIO):
        super().deserialize(s)
        assert self.magic
        assert self.length
        to_read = self.length - 8
        cms_data = sread(s, to_read)

        self.cms = ContentInfo.load(cms_data)
Beispiel #7
0
 def verify(self, pubkey):
     self.check_valid()
     with open(self.filepath, 'rb') as zipfile:
         zipfile.seek(0, os.SEEK_SET)
         message = zipfile.read(self.signed_len)
         zipfile.seek(-self.signature_start, os.SEEK_END)
         signature_size = self.signature_start - FOOTER_SIZE
         signature_raw = zipfile.read(signature_size)
     sig = ContentInfo.load(signature_raw)['content']['signer_infos'][0]
     sig_contents = sig['signature'].contents
     sig_type = DigestAlgorithmId.map(
         sig['digest_algorithm']['algorithm'].dotted)
     with open(pubkey, 'rb') as keyfile:
         keydata = load_public_key(keyfile.read())
     return rsa_pkcs1v15_verify(keydata, sig_contents, message, sig_type)
Beispiel #8
0
    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
Beispiel #9
0
def decrypt_smime_content(payload: bytes, key: rsa.RSAPrivateKey) -> bytes:
    content_info = ContentInfo.load(payload)

    assert content_info['content_type'].native == 'enveloped_data'
    content: EnvelopedData = content_info['content']

    matching_recipient = content['recipient_infos'][0]

    # Need to see if we hold the key for any valid recipient.
    # for recipient_info in content['recipient_infos']:
    #     assert recipient_info.name == 'ktri'  # Only support KeyTransRecipientInfo
    #     ktri: KeyTransRecipientInfo = recipient_info.chosen
    #     recipient_id: RecipientIdentifier = ktri['rid']
    #     assert recipient_id.name == 'issuer_and_serial_number'  # Only support IssuerAndSerialNumber
    #     matching_recipient = recipient_info

    encryption_algo = matching_recipient.chosen['key_encryption_algorithm'].native
    encrypted_key = matching_recipient.chosen['encrypted_key'].native

    assert encryption_algo['algorithm'] == 'rsa'

    # Get the content key
    plain_key = key.decrypt(
        encrypted_key,
        padding=padding.PKCS1v15(),
    )

    # Now we have the plain key, we can decrypt the encrypted data
    encrypted_contentinfo = content['encrypted_content_info']

    algorithm: EncryptionAlgorithm = encrypted_contentinfo['content_encryption_algorithm']  #: EncryptionAlgorithm
    encrypted_content_bytes = encrypted_contentinfo['encrypted_content'].native

    symkey = None

    if algorithm.encryption_cipher == 'aes':
        symkey = AES(plain_key)
    elif algorithm.encryption_cipher == 'tripledes':
        symkey = TripleDES(plain_key)
    else:
        print('Dont understand encryption cipher: ', algorithm.encryption_cipher)

    cipher = Cipher(symkey, modes.CBC(algorithm.encryption_iv), backend=default_backend())
    decryptor = cipher.decryptor()

    decrypted_data = decryptor.update(encrypted_content_bytes) + decryptor.finalize()
    return decrypted_data
Beispiel #10
0
def getcacert(url: str) -> List[x509.Certificate]:
    """Query the SCEP Service for the CA Certificate."""
    res = requests.get(url, {'operation': 'GetCACert'})
    assert res.status_code == 200
    if res.headers[
            'content-type'] == 'application/x-x509-ca-cert':  # we dont support RA cert yet
        return [x509.load_der_x509_certificate(res.content, default_backend())]
    elif res.headers[
            'content-type'] == 'application/x-x509-ca-ra-cert':  # intermediate via chain
        ci = ContentInfo.load(res.content)
        #  print(ci['content_type'].native)
        assert ci['content_type'].native == 'signed_data'
        signed_data = ci['content']
        # convert certificates using cryptography lib since it is easier to deal with the decryption
        assert len(signed_data['certificates']) > 0
        certs = certificates_from_asn1(signed_data['certificates'])
        return certs
Beispiel #11
0
    def _pki_operation(self, identity, identity_private_key, envelope, message_type, cacaps, ca_certs, transaction_id=None):
        """Perform a PKIOperation using the PKI Envelope given."""
        envelope = envelope.add_recipient(ca_certs.recipient)

        envelope, key, iv = envelope.finalize()

        signer = Signer(identity, identity_private_key, cacaps.strongest_signature_algorithm())

        pki_msg_builder = PKIMessageBuilder().message_type(
            message_type
        ).pki_envelope(
            envelope
        ).add_signer(
            signer
        ).transaction_id(
            transaction_id
        ).sender_nonce()

        pki_msg = pki_msg_builder.finalize(digest_algorithm=cacaps.strongest_message_digest())

        res = self.__pki_operation(data=pki_msg.dump(), cacaps=cacaps)

        cert_rep = SCEPMessage.parse(raw=res.content, signer_cert=ca_certs.signer)
        cert_rep.debug()
        if cert_rep.pki_status == PKIStatus.FAILURE:
            return EnrollmentStatus(fail_info=cert_rep.fail_info)
        elif cert_rep.pki_status == PKIStatus.PENDING:
            return EnrollmentStatus(transaction_id=cert_rep.transaction_id)
        else:
            decrypted_bytes = cert_rep.get_decrypted_envelope_data(identity, identity_private_key)
            degenerate_info = ContentInfo.load(decrypted_bytes)
            assert degenerate_info['content_type'].native == 'signed_data'
            signed_response = degenerate_info['content']

            certificates = None
            revocation_list = None

            if (message_type is MessageType.PKCSReq) or (message_type is MessageType.GetCert) or (message_type is MessageType.CertPoll):
                certs = signed_response['certificates']
                certificates = [Certificate(der_string=cert.chosen.dump()) for cert in certs]
            elif message_type is MessageType.GetCRL:
                crls = signed_response['crls']
                received_crl = crls[0].chosen
                revocation_list = RevocationList(revocation_list=received_crl)

            return EnrollmentStatus(certificates=certificates, crl=revocation_list)
Beispiel #12
0
def find_recipient(cms_data: bytes) -> Optional[Certificate]:
    """Find the Certificate + Private Key of a recipient indicated by encoded CMS/PKCS#7 data from the database and
    return the database model that matches (if any).

    Requires that the indicated recipient is present in the `certificates` table, and has a matching private key in the
    `rsa_private_keys` table.
    """
    content_info = ContentInfo.load(cms_data)

    assert content_info['content_type'].native == 'enveloped_data'
    content: EnvelopedData = content_info['content']

    for recipient_info in content['recipient_infos']:
        if recipient_info.name == 'ktri':  # KeyTransRecipientInfo
            recipient: KeyTransRecipientInfo = recipient_info.chosen
            recipient_id: RecipientIdentifier = recipient['rid']
            assert recipient_id.name == 'issuer_and_serial_number'

        else:
            pass  # Unsupported recipient type

    return None
Beispiel #13
0
    def __init__(self, data: Union[str, bytes, BinaryIO]):
        """
        If data is a string, it is taken as a file name.

        If data is bytes, it is taken as p7m data.

        Otherwise, data is taken as a file-like object that reads bytes data.
        """
        if isinstance(data, str):
            with open(data, "rb") as fd:
                self.data = fd.read()
        elif isinstance(data, bytes):
            self.data = data
        else:
            self.data = data.read()

        # Data might potentially be base64 encoded

        try:
            self.data = base64.b64decode(self.data, validate=True)
        except binascii.Error:
            pass

        self.content_info = ContentInfo.load(self.data)
Beispiel #14
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)
Beispiel #15
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)
Beispiel #16
0
def pkcsreq(url: str, private_key_path: str = None):
    """Perform a PKCSReq operation by submitting a CSR to the SCEP service."""

    logger.info('Request: GetCACaps')
    cacaps = getcacaps(url)
    logger.debug(cacaps)
    logger.info('Request: GetCACert')
    ca_certificates = getcacert(url)
    logger.debug('CA Certificate Subject(s) Follows')
    for c in ca_certificates:
        logger.debug(c.subject)

    if private_key_path:
        with open(private_key_path, 'rb') as fd:
            data = fd.read()
            private_key = serialization.load_pem_private_key(
                data, backend=default_backend(), password=None)

        logger.debug('Successfully read private key from filesystem')
        private_key, csr = generate_csr(private_key)
    else:
        private_key, csr = generate_csr()

        logger.debug('Writing RSA private key to ./scep.key')
        pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )
        with open('scep.key', 'wb') as fd:
            fd.write(pem)

    ssc = generate_self_signed(
        private_key,
        x509.Name([
            x509.NameAttribute(NameOID.COMMON_NAME, 'SCEPy SCEP SIGNER'),
            x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'),
        ]))

    envelope = PKCSPKIEnvelopeBuilder().encrypt(
        csr.public_bytes(serialization.Encoding.DER), '3des')
    # Temporary for MSCEP/NDES
    #envelope.add_recipient(ca_certificates[1])

    for recipient in ca_certificates:
        envelope = envelope.add_recipient(recipient)

    envelope, key, iv = envelope.finalize()

    with open('scepyclient.csr', 'wb') as fd:
        fd.write(csr.public_bytes(serialization.Encoding.DER))

    signer = Signer(ssc, private_key, 'sha512')

    pki_msg_builder = PKIMessageBuilder().message_type(
        MessageType.PKCSReq).pki_envelope(envelope).add_signer(
            signer).transaction_id().sender_nonce()

    pki_msg = pki_msg_builder.finalize()

    # if args.dump_request:
    #     with open(args.dump_pkcsreq, 'wb') as fd:
    #         fd.write(pki_msg.dump())
    #     logger.debug('Dumped PKCSReq data to {}'.format(args.dump_pkcsreq))

    with open('scepyclient-request.bin', 'wb') as fd:
        fd.write(pki_msg.dump())

    res = pkioperation(url, data=pki_msg.dump())

    logger.debug('Response: Status {}'.format(res.status_code))
    if res.status_code != 200:
        return -1
    #
    # with open('ndes-response.bin', 'wb') as fd:

    cert_rep = SCEPMessage.parse(res.content)
    # if args.dump_response:
    #     with open(args.dump_response, 'wb') as fd:
    #         fd.write(res.content)
    #     logger.debug('Dumped CertRep data to {}'.format(args.dump_response))

    logger.debug('pkiMessage response follows')
    logger.debug('Transaction ID: %s', cert_rep.transaction_id)
    logger.debug('PKI Status: %s', PKIStatus(cert_rep.pki_status))

    if PKIStatus(cert_rep.pki_status) == PKIStatus.FAILURE:
        logger.error('SCEP Request Failed: {}'.format(
            FailInfo(cert_rep.fail_info)))

    elif PKIStatus(cert_rep.pki_status) == PKIStatus.SUCCESS:
        # This should be the PKCS#7 Degenerate
        decrypted_bytes = cert_rep.get_decrypted_envelope_data(
            ssc, private_key)
        degenerate_info = ContentInfo.load(decrypted_bytes)
        degenerate_info.debug()

        assert degenerate_info['content_type'].native == 'signed_data'
        signed_response = degenerate_info['content']
        certs = signed_response['certificates']

        my_cert = certs[0].chosen

        result = x509.load_der_x509_certificate(my_cert.dump(),
                                                default_backend())
        subject = result.subject

        logger.info(
            'SCEP CA issued a certificate with serial #{}, subject: {}'.format(
                result.serial_number, subject))

        pem_data = result.public_bytes(serialization.Encoding.PEM)
        with open('scep.cer', 'wb') as fd:
            fd.write(pem_data)
Beispiel #17
0
def embed_payload_with_cms(pdf_writer: BasePdfFileWriter,
                           file_spec_string: str,
                           payload: embed.EmbeddedFileObject,
                           cms_obj: cms.ContentInfo,
                           extension='.sig',
                           file_name: Optional[str] = None,
                           file_spec_kwargs=None,
                           cms_file_spec_kwargs=None):
    """
    Embed some data as an embedded file stream into a PDF, and associate it
    with a CMS object.

    The resulting CMS object will also be turned into an embedded file, and
    associated with the original payload through a related file relationship.

    This can be used to bundle (non-PDF) detached signatures with PDF
    attachments, for example.

    .. versionadded:: 0.7.0

    :param pdf_writer:
        The PDF writer to use.
    :param file_spec_string:
        See :attr:`~pyhanko.pdf_utils.embed.FileSpec.file_spec_string` in
        :class:`~pyhanko.pdf_utils.embed.FileSpec`.
    :param payload:
        Payload object.
    :param cms_obj:
        CMS object pertaining to the payload.
    :param extension:
        File extension to use for the CMS attachment.
    :param file_name:
        See :attr:`~pyhanko.pdf_utils.embed.FileSpec.file_name` in
        :class:`~pyhanko.pdf_utils.embed.FileSpec`.
    :param file_spec_kwargs:
        Extra arguments to pass to the
        :class:`~pyhanko.pdf_utils.embed.FileSpec` constructor
        for the main attachment specification.
    :param cms_file_spec_kwargs:
        Extra arguments to pass to the
        :class:`~pyhanko.pdf_utils.embed.FileSpec` constructor
        for the CMS attachment specification.
    """

    # prepare an embedded file object for the signature
    now = datetime.now(tz=tzlocal.get_localzone())
    cms_ef_obj = embed.EmbeddedFileObject.from_file_data(
        pdf_writer=pdf_writer,
        data=cms_obj.dump(),
        compress=False,
        mime_type='application/pkcs7-mime',
        params=embed.EmbeddedFileParams(creation_date=now,
                                        modification_date=now))

    # replace extension
    cms_data_f = file_spec_string.rsplit('.', 1)[0] + extension

    # deal with new-style Unicode file names
    cms_data_uf = uf_related_files = None
    if file_name is not None:
        cms_data_uf = file_name.rsplit('.', 1)[0] + extension
        uf_related_files = [
            embed.RelatedFileSpec(cms_data_uf, embedded_data=cms_ef_obj)
        ]

    spec = embed.FileSpec(
        file_spec_string=file_spec_string,
        file_name=file_name,
        embedded_data=payload,
        f_related_files=[
            embed.RelatedFileSpec(cms_data_f, embedded_data=cms_ef_obj)
        ],
        uf_related_files=uf_related_files,
        **(file_spec_kwargs or {}),
    )

    embed.embed_file(pdf_writer, spec)

    # also embed the CMS data as a standalone attachment
    cms_spec = embed.FileSpec(file_spec_string=cms_data_f,
                              file_name=cms_data_uf,
                              embedded_data=cms_ef_obj,
                              **(cms_file_spec_kwargs or {}))
    embed.embed_file(pdf_writer, cms_spec)
Beispiel #18
0
    def parse(cls, raw: bytes):
        msg = cls()

        cinfo = ContentInfo.load(raw)
        assert cinfo['content_type'].native == 'signed_data'
        signed_data = cinfo['content']

        # convert certificates from ASN.1 using cryptography lib since it is easier to deal with the decryption
        if len(signed_data['certificates']) > 0:
            certs = certificates_from_asn1(signed_data['certificates'])
            print('{} certificate(s) attached to signedData'.format(
                len(certs)))
            msg._certificates = certs
        else:
            certs = None
            print('No certificates attached to SignedData')

        # Iterate through signers and verify the signature for each.
        # Set convenience attributes at the same time
        for signer_info in cinfo['content']['signer_infos']:
            # version can be 1 (issuerandserial) or 3 (subjectkeyidentifier)
            # assert signer_info['version'] == 1
            identifier = signer_info['sid'].chosen
            assert isinstance(
                identifier,
                IssuerAndSerialNumber)  # TODO: also support other signer ids

            signer_cert = None
            if certs is not None:
                for c in certs:  # find signer cert
                    if c.serial_number == identifier[
                            'serial_number'].native:  # TODO: also convert issuer
                        signer_cert = c
                        break

            # assert signer_cert is not None

            sig_algo = signer_info['signature_algorithm'].signature_algo
            print('Using signature algorithm: {}'.format(sig_algo))
            hash_algo = signer_info['digest_algorithm']['algorithm'].native
            print('Using digest algorithm: {}'.format(hash_algo))

            if hash_algo == 'sha1':
                hasher = hashes.SHA1()
            elif hash_algo == 'sha256':
                hasher = hashes.SHA256()
            elif hash_algo == 'sha512':
                hasher = hashes.SHA512()
            else:
                raise ValueError(
                    'Unsupported hash algorithm: {}'.format(hash_algo))

            assert sig_algo == 'rsassa_pkcs1v15'  # We only support PKCS1v1.5
            if certs is not None and len(certs) > 0:  # verify content
                verifier = signer_cert.public_key().verifier(
                    signer_info['signature'].native, asympad.PKCS1v15(),
                    hasher)

                assert signed_data['encap_content_info'][
                    'content_type'].native == 'data'
                #verifier.update(signed_data['encap_content_info']['content'].native)
                if 'signed_attrs' in signer_info:
                    print('signed attrs added to signature')
                    verifier.update(signer_info['signed_attrs'].dump())

                # Calculate Digest
                content_digest = hashes.Hash(
                    hashes.SHA512(), backend=default_backend())  # Was: SHA-256
                content_digest.update(
                    signed_data['encap_content_info']['content'].native)
                content_digest_r = content_digest.finalize()
                # print('expecting SHA-256 digest: {}'.format(b64encode(content_digest_r)))
                for attr in signer_info['signed_attrs']:
                    if attr['type'].native == 'message_digest':
                        pass
                        # print('signer says digest is: {}'.format(b64encode(attr['values'][0].native)))

                # Calculate Digest on content + signed attrs
                cdsa = hashes.Hash(hashes.SHA512(),
                                   backend=default_backend())  # Was: SHA-256
                #cdsa.update(signed_data['encap_content_info']['content'].native)
                cdsa.update(signer_info['signed_attrs'].dump())
                cdsa_r = cdsa.finalize()
                # print('signature digest: {}'.format(b64encode(cdsa_r)))
                # print('expecting signature: {}'.format(b64encode(signer_info['signature'].native)))
                # verifier.verify()

            # Set the signer for convenience on the instance
            msg._signer_info = signer_info

            if 'signed_attrs' in signer_info:
                for signed_attr in signer_info['signed_attrs']:
                    name = asn1.SCEPCMSAttributeType.map(
                        signed_attr['type'].native)

                    if name == 'transaction_id':
                        msg._transaction_id = signed_attr['values'][0].native
                    elif name == 'message_type':
                        msg._message_type = MessageType(
                            signed_attr['values'][0].native)
                    elif name == 'sender_nonce':
                        msg._sender_nonce = signed_attr['values'][0].native
                    elif name == 'recipient_nonce':
                        msg._recipient_nonce = signed_attr['values'][0].native
                    elif name == 'pki_status':
                        msg._pki_status = signed_attr['values'][0].native
                    elif name == 'fail_info':
                        msg._fail_info = signed_attr['values'][0].native

        msg._signed_data = cinfo['content']['encap_content_info']['content']

        return msg
Beispiel #19
0
def extract_certificates(binary, data):
    """
    Extract and parse certificates from the file
    Largely inspired from macholibre
    """
    header = b"\xfa\xde\x0c\x05"
    cdata = None
    res = {}
    for c in binary.commands:
        if c.command.name == "CODE_SIGNATURE":
            cdata = data[c.data_offset:c.data_offset + c.data_size]
    if not cdata:
        return {}
    if not cdata.startswith(b'\xfa\xde\x0c\xc0'):
        print("Invalid Code signature header, weird")
        return {}
    b = BytesIO(cdata)
    _ = b.read(4)  # Magic number
    size = read_int(b)
    count = read_int(b)

    for _ in range(count):
        index_type = read_int(b)
        if index_type not in CODE_INDEX:
            print("Unknown code signature index")
            b.read(4)
            continue
        index_offset = read_int(b)
        if index_type == 0x10000:
            # Certificates
            bb = BytesIO(cdata[index_offset:])
            if bb.read(4) != b"\xfa\xde\x0b\x01":
                print("Unknown magic type for certificates")
                continue
            size = read_int(bb)
            signed_data = ContentInfo.load(bb.read(size))['content']
            res['certs'] = []
            for cert in signed_data['certificates']:
                cert = cert.chosen
                subject = {}
                for rdn in cert.subject.chosen:
                    name = rdn[0]['type'].human_friendly
                    value = rdn[0]['value']

                    if name == 'Country':
                        subject['country'] = str(value.chosen)
                    elif name == 'Organization':
                        subject['org'] = str(value.chosen)
                    elif name == 'Organizational Unit':
                        subject['org_unit'] = str(value.chosen)
                    elif name == 'Common Name':
                        subject['common_name'] = str(value.chosen)
                    else:
                        if isinstance(value, DirectoryString):
                            subject[name] = str(value.chosen)
                        else:
                            subject[name] = str(value.parsed)

                issuer = {}

                for rdn in cert.issuer.chosen:
                    name = rdn[0]['type'].human_friendly
                    value = rdn[0]['value']

                    if name == 'Country':
                        issuer['country'] = str(value.chosen)
                    elif name == 'Organization':
                        issuer['org'] = str(value.chosen)
                    elif name == 'Organizational Unit':
                        issuer['org_unit'] = str(value.chosen)
                    elif name == 'Common Name':
                        issuer['common_name'] = str(value.chosen)
                    else:
                        if isinstance(value, DirectoryString):
                            issuer[name] = str(value.chosen)
                        else:
                            issuer[name] = str(value.parsed)

                res['certs'].append({
                    'subject': subject,
                    'issuer': issuer,
                    'serial': cert.serial_number,
                    'is_ca': cert.ca
                })

    return res
Beispiel #20
0
    def parse_certs(self, signature, offset):
        prev = self.f.tell()
        true_offset = signature.offset + offset
        self.f.seek(true_offset)
        magic = get_int(self.f)
        if magic != dictionary.signatures['BLOBWRAPPER']:
            data = {
                'offset': true_offset,
                'magic': hex(magic),
                'expected': hex(dictionary.signatures['BLOBWRAPPER'])
            }
            a = Abnormality(title='BAD MAGIC - BLOBWRAPPER', data=data)
            self.add_abnormality(a)
            self.f.seek(prev)
            return

        size = get_int(self.f) - 8

        if size > 0:
            signed_data = ContentInfo.load(self.f.read(size))['content']

            for cert in signed_data['certificates']:
                cert = cert.chosen

                serial = cert.serial_number

                subject = {}

                for rdn in cert.subject.chosen:
                    name = rdn[0]['type'].human_friendly
                    value = unicode(rdn[0]['value'].chosen)

                    if name == 'Country':
                        subject['country'] = value
                    elif name == 'Organization':
                        subject['org'] = value
                    elif name == 'Organizational Unit':
                        subject['org_unit'] = value
                    elif name == 'Common Name':
                        subject['common_name'] = value

                issuer = {}

                for rdn in cert.issuer.chosen:
                    name = rdn[0]['type'].human_friendly
                    value = unicode(rdn[0]['value'].chosen)

                    if name == 'Country':
                        issuer['country'] = value
                    elif name == 'Organization':
                        issuer['org'] = value
                    elif name == 'Organizational Unit':
                        issuer['org_unit'] = value
                    elif name == 'Common Name':
                        issuer['common_name'] = value

                is_ca = cert.ca

                cert = Certificate(serial=serial,
                                   subject=subject,
                                   issuer=issuer,
                                   ca=is_ca)

                signature.add_cert(cert)
        else:
            data = {'offset': true_offset, 'size': size}
            a = Abnormality(title='NON-POSITIVE CMS SIZE', data=data)
            self.add_abnormality(a)

        self.f.seek(prev)
Beispiel #21
0
 def _getCerts(self):
  for info in self._file.infolist():
   if info.filename.startswith('META-INF/') and info.filename.endswith('.RSA'):
    for cert in ContentInfo.load(self._file.read(info))['content']['certificates']:
     yield cert.dump()
Beispiel #22
0
    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
Beispiel #23
0
 def encap_content_info(self) -> ContentInfo:
     return ContentInfo.load(self._signed_data.native)
Beispiel #24
0
import sys
import os.path
from asn1crypto.cms import SignedData, ContentInfo
from scepy import asn1
from asn1crypto.cms import CMSAttribute

CMSAttribute._fields = [
    ('type', asn1.SCEPCMSAttributeType),
    ('values', None),
]

if __name__ == '__main__':
    if len(sys.argv) != 2:
        sys.exit(1)

    with open(sys.argv[1], 'rb') as fd:
        content = ContentInfo.load(fd.read())
        content.debug()