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 make_signed_attrs(digest: bytes, hash_type: int) -> CMSAttributes: content_type = CMSAttribute({ "type": CMSAttributeType.unmap("content_type"), "values": [ContentType.unmap("data")], }) time_now = UTCTime() time_now.set(datetime.now(timezone.utc)) signing_time = CMSAttribute({ "type": CMSAttributeType.unmap("signing_time"), "values": [time_now] }) message_digest = CMSAttribute({ "type": CMSAttributeType.unmap("message_digest"), "values": [OctetString(digest)], }) ha_v1 = make_hash_agility_v1(digest) ha_v2 = make_hash_agility_v2(digest, hash_type) return CMSAttributes( [content_type, signing_time, message_digest, ha_v1, ha_v2])
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 make_hash_agility_v2(digest: bytes, hash_type: int) -> CMSAttribute: """ CMSAttribute: type: HASH_AGILITY_V2_OID values: Set of HashAgility type: DigestAlgorithmId data: digest """ dg_algo = _get_digest_algo(hash_type) ha = HashAgility({"type": dg_algo, "data": OctetString(digest)}) return CMSAttribute({"type": HASH_AGILITY_V2_OID, "values": [ha]})
def make_signature(self): assert self.sig.code_dir_blob # Redo the code hashes self._set_code_hashes() # Make the signature signed_attrs: CMSAttributes = make_signed_attrs( self.sig.code_dir_blob.get_hash(self.hash_type), self.hash_type) actual_privkey = load_private_key(self.privkey) signature = rsa_pkcs1v15_sign(actual_privkey, signed_attrs.dump(), self.hash_type_str) # Get the timestamp from Apple digest = get_hash(signature, self.hash_type) tst = CMSAttribute({ "type": CMSAttributeType("signature_time_stamp_token"), "values": [get_timestamp_token(digest, self.hash_type)], }) # Make the CMS self.sig.sig_blob = SignatureBlob() self.sig.sig_blob.cms = make_cms(self.cert, self.hash_type, signed_attrs, signature, CMSAttributes([tst])) # Get the CodeSignature section. It should be the last in the binary cs_sec = self.macho.sect[-1] assert cs_sec == self.get_linkedit_segment().sect[-1] assert isinstance(cs_sec, CodeSignature) sig_cmd = self.get_sig_command() # Serialize the signature f = BytesIO() self.sig.serialize(f) f.write((sig_cmd.datasize - f.tell()) * b"\x00") if self.detach_target: target_dir = os.path.join(self.detach_target, "Contents", "MacOS") os.makedirs(target_dir, exist_ok=True) target_file = os.path.join( target_dir, os.path.basename(self.filename) + f".{CPU_NAMES[self.macho.Mhdr.cputype]}sign", ) with open(target_file, "wb") as tf: tf.write(f.getvalue()) self.files_modified.append(target_file) else: # Set the section's content to be the signature cs_sec.content = StrPatchwork(f.getvalue())
def make_hash_agility_v1(digest: bytes) -> CMSAttribute: """ CMSAttribue: type: HASH_AGILITY_V1_OID values: Set of 1 XML Plist dict: { "cdhashes": [digset truncated to 20 bytes] } """ plist_dict = {"cdhashes": [digest[:20]]} plist_bytes = plistlib.dumps(plist_dict, fmt=plistlib.FMT_XML) return CMSAttribute({ "type": HASH_AGILITY_V1_OID, "values": [OctetString(plist_bytes)] })
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 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 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 sign(self, data: bytes, content_type: ContentType, content_digest: bytes, cms_attributes: List[CMSAttribute]) -> SignerInfo: """Generate a signature encrypted with the signer's private key and return the SignerInfo.""" # The CMS standard requires that the content-type authenticatedAttribute and the message-digest # attribute must be present if any authenticatedAttribute exists at all. self.signed_attributes = cms_attributes self.signed_attributes.insert(0, CMSAttribute({ 'type': 'signing_time', 'values': [GeneralizedTime(datetime.datetime.utcnow())] })) self.signed_attributes.insert(0, CMSAttribute({ 'type': 'message_digest', 'values': [OctetString(content_digest)], })) # This refers to whatever the content of EncapsulatedContentInfo is self.signed_attributes.insert(0, CMSAttribute({ 'type': 'content_type', 'values': [content_type], })) cms_attributes = CMSAttributes(self.signed_attributes) # NOTE: no need to calculate this digest as .signer() does the hashing # RFC5652 # The message digest is # computed on either the content being signed or the content # together with the signed attributes using the process described in # Section 5.4. # digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) # the initial input is the encapContentInfo eContent OCTET STRING # RFC5652 Section 5.4 - When the field (signed_attrs) is present, however, the result is the message # digest of the complete DER encoding of the SignedAttrs value # contained in the signedAttrs field. # NOTE: it is not clear whether data is included #digest.update(data) # digest.update(cms_attributes.dump()) # d = digest.finalize() # Make DigestInfo from result # NOTE: It is not clear whether this applies: RFC5652 - Section 5.5. # digest_info = DigestInfo({ # 'digest_algorithm': self.digest_algorithm, # 'digest': d, # }) # Get the RSA key to sign the digestinfo digest_function = { 'sha1': hashes.SHA1, # macOS 'sha256': hashes.SHA256, 'sha512': hashes.SHA512 }[self.digest_algorithm_id.native] signer = self.private_key.signer( asympad.PKCS1v15(), digest_function(), ) # NOTE: this is not the digest `d` above because crypto.io already hashes stuff for us!! signer.update(cms_attributes.dump()) signature = signer.finalize() signer_info = SignerInfo({ # Version must be 1 if signer uses IssuerAndSerialNumber as sid 'version': CMSVersion(1), 'sid': self.sid, 'digest_algorithm': self.digest_algorithm, 'signed_attrs': cms_attributes, # Referred to as ``digestEncryptionAlgorithm`` in the RFC 'signature_algorithm': self.signed_digest_algorithm, # Referred to as ``encryptedDigest`` in the RFC 'signature': OctetString(signature), }) return signer_info
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 sign(self, data, content_type, content_digest, cms_attributes): """Generate a signature encrypted with the signer's private key and return the SignerInfo.""" # The CMS standard requires that the content-type authenticatedAttribute and the message-digest # attribute must be present if any authenticatedAttribute exists at all. self.signed_attributes = cms_attributes # NDES does not even include this # self.signed_attributes.insert(0, CMSAttribute({ # 'type': 'signing_time', # 'values': [GeneralizedTime(datetime.datetime.utcnow())] # })) self.signed_attributes.insert( 0, CMSAttribute({ 'type': u'message_digest', 'values': [OctetString(content_digest)], })) # This refers to whatever the content of EncapsulatedContentInfo is self.signed_attributes.insert( 0, CMSAttribute({ 'type': u'content_type', 'values': [content_type], })) cms_attributes = CMSAttributes(self.signed_attributes) # NOTE: no need to calculate this digest as .signer() does the hashing # RFC5652 # The message digest is # computed on either the content being signed or the content # together with the signed attributes using the process described in # Section 5.4. # digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) # the initial input is the encapContentInfo eContent OCTET STRING # RFC5652 Section 5.4 - When the field (signed_attrs) is present, however, the result is the message # digest of the complete DER encoding of the SignedAttrs value # contained in the signedAttrs field. # NOTE: it is not clear whether data is included #digest.update(data) # digest.update(cms_attributes.dump()) # d = digest.finalize() # Make DigestInfo from result # NOTE: It is not clear whether this applies: RFC5652 - Section 5.5. # digest_info = DigestInfo({ # 'digest_algorithm': self.digest_algorithm, # 'digest': d, # }) signature = self.private_key.sign( data=cms_attributes.dump(), padding_type='pkcs', algorithm=self.digest_algorithm_id.native) signer_info = SignerInfo({ # Version must be 1 if signer uses IssuerAndSerialNumber as sid 'version': CMSVersion(1), 'sid': self.sid, 'digest_algorithm': self.digest_algorithm, 'signed_attrs': cms_attributes, # Referred to as ``digestEncryptionAlgorithm`` in the RFC 'signature_algorithm': self.signed_digest_algorithm, # Referred to as ``encryptedDigest`` in the RFC 'signature': OctetString(signature), }) return signer_info