Skip to content

Instantly share code, notes, and snippets.

@pudquick
Last active November 29, 2022 21:06
Show Gist options
  • Save pudquick/5de9a727769b227b3212534defafbe9d to your computer and use it in GitHub Desktop.
Save pudquick/5de9a727769b227b3212534defafbe9d to your computer and use it in GitHub Desktop.
Verifying a CMS detached signature in pyobjc on macOS
import objc
from ctypes import create_string_buffer, c_void_p, cast
from Foundation import NSBundle
Security = NSBundle.bundleWithIdentifier_('com.apple.security')
# CMSDecoder.h
kCMSSignerUnsigned = 0
kCMSSignerValid = 1
kCMSSignerNeedsDetachedContent = 2
kCMSSignerInvalidSignature = 3
kCMSSignerInvalidCert = 4
kCMSSignerInvalidIndex = 5
# cssmerr.h - you don't need all of these, only the ones you want to check for
CSSMERR_TP_AUTHENTICATION_FAILED = -2147409657
CSSMERR_TP_CERTGROUP_INCOMPLETE = -2147409656
CSSMERR_TP_CERTIFICATE_CANT_OPERATE = -2147409655
CSSMERR_TP_CERT_EXPIRED = -2147409654
CSSMERR_TP_CERT_NOT_VALID_YET = -2147409653
CSSMERR_TP_CERT_REVOKED = -2147409652
CSSMERR_TP_CERT_SUSPENDED = -2147409651
CSSMERR_TP_CRL_ALREADY_SIGNED = -2147409849
CSSMERR_TP_DEVICE_FAILED = -2147409691
CSSMERR_TP_DEVICE_RESET = -2147409692
CSSMERR_TP_FUNCTION_FAILED = -2147409910
CSSMERR_TP_FUNCTION_NOT_IMPLEMENTED = -2147409913
CSSMERR_TP_INSUFFICIENT_CLIENT_IDENTIFICATION = -2147409693
CSSMERR_TP_INSUFFICIENT_CREDENTIALS = -2147409650
CSSMERR_TP_INTERNAL_ERROR = -2147409919
CSSMERR_TP_INVALID_ACTION = -2147409649
CSSMERR_TP_INVALID_ACTION_DATA = -2147409648
CSSMERR_TP_INVALID_ANCHOR_CERT = -2147409646
CSSMERR_TP_INVALID_AUTHORITY = -2147409645
CSSMERR_TP_INVALID_CALLBACK = -2147409625
CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER = -2147409663
CSSMERR_TP_INVALID_CERTGROUP = -2147409660
CSSMERR_TP_INVALID_CERTGROUP_POINTER = -2147409854
CSSMERR_TP_INVALID_CERTIFICATE = -2147409643
CSSMERR_TP_INVALID_CERT_AUTHORITY = -2147409642
CSSMERR_TP_INVALID_CERT_POINTER = -2147409853
CSSMERR_TP_INVALID_CL_HANDLE = -2147409838
CSSMERR_TP_INVALID_CONTEXT_HANDLE = -2147409856
CSSMERR_TP_INVALID_CRL = -2147409638
CSSMERR_TP_INVALID_CRLGROUP = -2147409659
CSSMERR_TP_INVALID_CRLGROUP_POINTER = -2147409658
CSSMERR_TP_INVALID_CRL_AUTHORITY = -2147409641
CSSMERR_TP_INVALID_CRL_ENCODING = -2147409640
CSSMERR_TP_INVALID_CRL_POINTER = -2147409852
CSSMERR_TP_INVALID_CRL_TYPE = -2147409639
CSSMERR_TP_INVALID_CSP_HANDLE = -2147409840
CSSMERR_TP_INVALID_DATA = -2147409850
CSSMERR_TP_INVALID_DB_HANDLE = -2147409846
CSSMERR_TP_INVALID_DB_LIST = -2147409844
CSSMERR_TP_INVALID_DB_LIST_POINTER = -2147409843
CSSMERR_TP_INVALID_DL_HANDLE = -2147409839
CSSMERR_TP_INVALID_FIELD_POINTER = -2147409851
CSSMERR_TP_INVALID_FORM_TYPE = -2147409637
CSSMERR_TP_INVALID_ID = -2147409636
CSSMERR_TP_INVALID_IDENTIFIER = -2147409635
CSSMERR_TP_INVALID_IDENTIFIER_POINTER = -2147409662
CSSMERR_TP_INVALID_INDEX = -2147409634
CSSMERR_TP_INVALID_INPUT_POINTER = -2147409915
CSSMERR_TP_INVALID_KEYCACHE_HANDLE = -2147409661
CSSMERR_TP_INVALID_NAME = -2147409633
CSSMERR_TP_INVALID_NETWORK_ADDR = -2147409833
CSSMERR_TP_INVALID_NUMBER_OF_FIELDS = -2147409848
CSSMERR_TP_INVALID_OUTPUT_POINTER = -2147409914
CSSMERR_TP_INVALID_PASSTHROUGH_ID = -2147409834
CSSMERR_TP_INVALID_POINTER = -2147409916
CSSMERR_TP_INVALID_POLICY_IDENTIFIERS = -2147409632
CSSMERR_TP_INVALID_REASON = -2147409630
CSSMERR_TP_INVALID_REQUEST_INPUTS = -2147409629
CSSMERR_TP_INVALID_RESPONSE_VECTOR = -2147409628
CSSMERR_TP_INVALID_SIGNATURE = -2147409627
CSSMERR_TP_INVALID_STOP_ON_POLICY = -2147409626
CSSMERR_TP_INVALID_TIMESTRING = -2147409631
CSSMERR_TP_INVALID_TUPLE = -2147409624
CSSMERR_TP_INVALID_TUPLEGROUP = -2147409614
CSSMERR_TP_INVALID_TUPLEGROUP_POINTER = -2147409615
CSSMERR_TP_IN_DARK_WAKE = -2147409690
CSSMERR_TP_MDS_ERROR = -2147409917
CSSMERR_TP_MEMORY_ERROR = -2147409918
CSSMERR_TP_NOT_SIGNER = -2147409623
CSSMERR_TP_NOT_TRUSTED = -2147409622
CSSMERR_TP_NO_DEFAULT_AUTHORITY = -2147409621
CSSMERR_TP_NO_USER_INTERACTION = -2147409696
CSSMERR_TP_OS_ACCESS_DENIED = -2147409911
CSSMERR_TP_REJECTED_FORM = -2147409620
CSSMERR_TP_REQUEST_LOST = -2147409619
CSSMERR_TP_REQUEST_REJECTED = -2147409618
CSSMERR_TP_SELF_CHECK_FAILED = -2147409912
CSSMERR_TP_SERVICE_NOT_AVAILABLE = -2147409694
CSSMERR_TP_UNKNOWN_FORMAT = -2147409842
CSSMERR_TP_UNKNOWN_TAG = -2147409841
CSSMERR_TP_UNSUPPORTED_ADDR_TYPE = -2147409617
CSSMERR_TP_UNSUPPORTED_SERVICE = -2147409616
CSSMERR_TP_USER_CANCELED = -2147409695
CSSMERR_TP_VERIFICATION_FAILURE = -2147409847
CSSMERR_TP_VERIFY_ACTION_FAILED = -2147409644
_ = [
('CMSDecoderGetTypeID', 'Q'),
('SecPolicyGetTypeID', 'Q'),
('SecTrustGetTypeID', 'Q'),
]
objc.loadBundleFunctions(Security, globals(), _)
CMSDecoderRef = objc.registerCFSignature('CMSDecoderRef', '^{_CMSDecoder=}', CMSDecoderGetTypeID())
SecPolicyRef = objc.registerCFSignature('SecPolicyRef', '^{OpaqueSecPolicyRef=}', SecPolicyGetTypeID())
SecTrustRef = objc.registerCFSignature('SecTrustRef', '^{__SecTrust=}', SecTrustGetTypeID())
_ = [
('CMSDecoderCreate', 'io^^{_CMSDecoder}'),
('CMSDecoderSetDetachedContent', 'i^{_CMSDecoder=}^{__CFData=}'),
('CMSDecoderUpdateMessage', 'i^{_CMSDecoder=}^vQ'),
('CMSDecoderFinalizeMessage', 'i^{_CMSDecoder=}'),
('CMSDecoderCopySignerStatus', 'i^{_CMSDecoder=}Q@Bo^Io^^{__SecTrust}o^i'),
('SecPolicyCreateBasicX509', '^{OpaqueSecPolicyRef=}'),
]
objc.loadBundleFunctions(Security, globals(), _)
def check_detached_sig(message_path, signature_path, signer_index=0, trust_policy=None, evaluate=True):
# Read in the message
f = open(message_path, 'rb')
message = f.read()
f.close()
message_bytes = buffer(message)
# Read in the signature
f = open(signature_path, 'rb')
signature = f.read()
f.close()
signature_bytes = cast(create_string_buffer(signature), c_void_p)
result, decoder = CMSDecoderCreate(None)
result = CMSDecoderSetDetachedContent(decoder, message_bytes)
# For some reason CMSDecoderUpdateMessage takes a freaking raw buffer
result = CMSDecoderUpdateMessage(decoder, signature_bytes.value, len(signature))
result = CMSDecoderFinalizeMessage(decoder)
if trust_policy is None:
trust_policy = SecPolicyCreateBasicX509()
# returns: result, signer_status, sec_trust, cert_verify_result
return CMSDecoderCopySignerStatus(decoder, signer_index, trust_policy, evaluate, None, None, None)
# Example usage:
result, signer_status, sec_trust, cert_verify_result = check_detached_sig('test.txt', 'test.txt.sig')
# In testing, I was able to generate a self-signed cert and create a detached signature:
# openssl smime -sign -signer signer_cert.pem -inkey signer_key.pem -binary -in test.txt -outform der -out test.txt.sig
#
# When tested against check_detached_sig, I received kCMSSignerInvalidCert (4) for signer_status
# which indicates that the message was properly signed, but the signer's cert is unknown regarding trust.
#
# Modifying the test.txt (but re-using the same signature), I received kCMSSignerInvalidSignature (3) as expected.
#
# By importing the signer_cert.pem into the Keychain and then marking it as trusted (since it's self-signed), I was
# able to achieve kCMSSignerValid (1).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment