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 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 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 })
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
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()
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)
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)
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
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
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
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)
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
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)
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)
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)
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)
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
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
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)
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()
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 encap_content_info(self) -> ContentInfo: return ContentInfo.load(self._signed_data.native)
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()