[英]How to validate / verify an X509 Certificate chain of trust in Python?
[英]Python pkcs#7 x509 chain of trust with cryptography
我正在開發一個 python 庫,以將 Apple Pay 有效負載處理為可用的卡詳細信息。 為此,我遵循此處的官方文檔。
除了 2 個驗證步驟外,一切都運行良好:
確保存在從簽名到根 CA 的有效 X.509 信任鏈。 具體來說,確保簽名是使用葉證書對應的私鑰創建的,葉證書由中間 CA 簽名,中間 CA 由 Apple Root CA - G3 簽名。
在這里,我最終使用密碼庫通過以下代碼進行了最后一次檢查:
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
def verify_root_ca_chain_of_trust(
trusted_root_ca: x509.Certificate,
intermediate_cert: x509.Certificate,
leaf_cert: x509.Certificate
) -> None:
try:
# verify that the intermediate CA is signed by the Apple Root CA - G3
trusted_pub = trusted_root_ca.public_key()
trusted_pub.verify(
intermediate_cert.signature,
intermediate_cert.tbs_certificate_bytes,
ECDSA(hashes.SHA256())
)
# verify that the leaf certificate is signed by the intermediate CA
trusted_intermediate_pub = intermediate_cert.public_key()
trusted_intermediate_pub.verify(
leaf_cert.signature,
leaf_cert.tbs_certificate_bytes,
ECDSA(hashes.SHA256())
)
except TypeError as err:
raise CustomError('error') from err
我的問題是我只實施了所需檢查的 2/3。 我不知道該怎么做是:
具體來說,確保簽名是使用葉證書對應的私鑰創建的
d。 對於 ECC (EC_v1),確保簽名是 ephemeralPublicKey、data、transactionId 和 applicationData 鍵的串聯值的有效 ECDSA 簽名 (ecdsa-with-SHA256 1.2.840.10045.4.3.2)。
e. 檢查簽名的 CMS 簽名時間,如 RFC 5652 的第 11.3 節所定義。如果時間簽名和事務時間相差超過幾分鍾,則令牌可能是重放攻擊。
到目前為止我嘗試了什么:
def validate_token_signature(
trusted_cert: x509.Certificate,
signature: str,
payment_data: str,
ephemeral_pub: str,
transaction_id: str,
application_data: str = None,
) -> None:
data_byte: bytes = base64.b64decode(ephemeral_pub)
payment_data_byte: bytes = base64.b64decode(payment_data)
transaction_id_byte: bytes = bytes.fromhex(transaction_id)
data: bytes = data_byte + payment_data_byte + transaction_id_byte
if application_data is not None:
application_data_byte: bytes = base64.b64decode(application_data)
data = data + application_data_byte
try:
trusted_leaf_pub = trusted_cert.public_key()
trusted_leaf_pub.verify(base64.b64decode(signature), data, ECDSA(hashes.SHA256()))
except InvalidSignature as err:
print(err)
# raise SignatureError('error') from err```
在這里,他們沒有指定要驗證的簽名,我認為它是 PKCS#7,但密碼學僅將 pkcs#7 處理為 x509 證書列表,如此處所述:
將 PEM 編碼的 PKCS7 blob 反序列化為證書列表。 PKCS7 可以包含許多其他類型的數據,包括 CRL,但是這個 function 將忽略除證書之外的所有內容。
有沒有辦法使用 python 加密進行這些檢查,還是我必須使用另一個庫,如 pyopenssl 或其他什么?
要讀取 cms 簽名時間並驗證 pkcs7 簽名,我需要訪問 cms 內容和簽名者信息。 使用 m2crypto 或密碼學中的 pkcs#7 模塊無法實現這一點。 我最終使用的是 asn1crypto。
步驟 1.c 和 1.d 是相同檢查的一部分,所以我確實保留了信任鏈的上述檢查:
def verify_root_ca_chain_of_trust(
trusted_root_ca: x509.Certificate,
intermediate_cert: x509.Certificate,
leaf_cert: x509.Certificate
) -> None:
try:
# verify that the intermediate CA is signed by the Apple Root CA - G3
trusted_pub = trusted_root_ca.public_key()
trusted_pub.verify(
intermediate_cert.signature,
intermediate_cert.tbs_certificate_bytes,
ECDSA(hashes.SHA256())
)
# verify that the leaf certificate is signed by the intermediate CA
trusted_intermediate_pub = intermediate_cert.public_key()
trusted_intermediate_pub.verify(
leaf_cert.signature,
leaf_cert.tbs_certificate_bytes,
ECDSA(hashes.SHA256())
)
except TypeError as err:
raise CustomError('error') from err
要執行最后一部分檢查'特別是,確保使用與葉證書對應的私鑰創建簽名',我確實使用了 asn1crypto:
from asn1crypto import cms
from asn1crypto import core
def validate_token_signature(
trusted_cert: x509.Certificate,
signature: bytes,
payment_data: bytes,
ephemeral_pub: bytes,
transaction_id: bytes,
application_data: bytes,
) -> None:
signed_data = cms.ContentInfo.load(signature)['content']
algo = signed_data['digest_algorithms'][0]['algorithm'].native
signers_info = signed_data['signer_infos']
attr_signature = signers_info[0].native['signature']
attrs = signers_info[0]['signed_attrs']
# Insure data signer and cert signer match
cert_issuer: str = trusted_cert.issuer.rdns[0].rfc4514_string().split("=")[1]
signed_data_issuer: str = dict(dict(dict(signers_info.native[0])['sid'])['issuer'])['common_name']
if not cert_issuer == signed_data_issuer:
raise CustomError('error')
# Verify that cert is still validate now
if not trusted_cert.not_valid_before < datetime.now() < trusted_cert.not_valid_after:
raise CustomError('error')
# user data
udata: bytes = ephemeral_pub + payment_data + transaction_id + application_data
mdData = getattr(hashlib, algo)(udata).digest()
if attrs is not None and not isinstance(attrs, core.Void):
# if attrs, mdSigned == message_digest attribute
mdSigned = None
for attr in attrs:
if attr['type'].native == 'message_digest':
mdSigned = attr['values'].native[0]
signedData = attrs.dump()
signedData = b'\x31' + signedData[1:]
else:
# if no attrs, mdSigned == hash of userdata
mdSigned = mdData
signedData = udata
# 2- verify() must succeed succeeded
try:
trusted_cert.public_key().verify(attr_signature, signedData, ECDSA(hashes.SHA256()))
except InvalidSignature:
raise CustomError('error')
# 3- hashok must be True
if not mdData == mdSigned:
raise CustomError('error')
對於 cms 簽名時間驗證(1.e),我確實也使用了通過 asn1 object 獲得的數據:
from asn1crypto import cms
def cms_compare(p7: bytes) -> None:
ci = cms.ContentInfo.load(p7)
try:
content = dict(ci.native['content'])
signed_time: str = dict(dict(content['signer_infos'][0])['signed_attrs'][1])['values'][0]
timed: datetime = datetime.strptime(str(signed_time), "%Y-%m-%d %H:%M:%S%z")
if int(time.time()) - int(timed.strftime('%s')) > 30:
raise CustomError('error')
except TypeError:
raise CustomError('error')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.