laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/35635?usp=email )
Change subject: osmo-smdpp: Implement eUICC + EUM certificate signature chain validation ......................................................................
osmo-smdpp: Implement eUICC + EUM certificate signature chain validation
Change-Id: I961827c50ed5e34c6507bfdf853952ece5b0d121 --- M osmo-smdpp.py M pySim/esim/rsp.py 2 files changed, 43 insertions(+), 20 deletions(-)
Approvals: laforge: Looks good to me, approved Jenkins Builder: Verified
diff --git a/osmo-smdpp.py b/osmo-smdpp.py index 7e0db27..58b83ff 100755 --- a/osmo-smdpp.py +++ b/osmo-smdpp.py @@ -36,7 +36,8 @@
import pySim.esim.rsp as rsp from pySim.esim.es8p import * -from pySim.esim.x509_cert import oid, cert_policy_has_oid, CertAndPrivkey +from pySim.esim.x509_cert import oid, cert_policy_has_oid, cert_get_auth_key_id +from pySim.esim.x509_cert import CertAndPrivkey, CertificateSet, cert_get_subject_key_id, VerifyError
# HACK: make this configurable DATA_DIR = './smdpp-data' @@ -214,7 +215,12 @@ if 'euiccCiPKIdListForSigningV3' in euiccInfo1: pkid_list = pkid_list + euiccInfo1['euiccCiPKIdListForSigningV3'] # verify it supports one of the keys indicated by euiccCiPKIdListForSigning - if not any(self.ci_get_cert_for_pkid(x) for x in pkid_list): + ci_cert = None + for x in pkid_list: + ci_cert = self.ci_get_cert_for_pkid(x) + if ci_cert: + break + if not ci_cert: raise ApiError('8.8.2', '3.1', 'None of the proposed Public Key Identifiers is supported by the SM-DP+')
# TODO: Determine the set of CERT.DPauth.SIG that satisfy the following criteria: @@ -257,7 +263,8 @@ #output['otherCertsInChain'] = b64encode2str()
# create SessionState and store it in rss - self.rss[transactionId] = rsp.RspSessionState(transactionId, serverChallenge) + self.rss[transactionId] = rsp.RspSessionState(transactionId, serverChallenge, + cert_get_subject_key_id(ci_cert))
return output
@@ -292,29 +299,35 @@ euicc_cert = x509.load_der_x509_certificate(euiccCertificate_bin) eum_cert = x509.load_der_x509_certificate(eumCertificate_bin)
- # TODO: Verify the validity of the eUICC certificate chain - # raise ApiError('8.1.3', '6.1', 'Verification failed') - # raise ApiError('8.1.3', '6.3', 'Expired') - - # TODO: Verify that the Root Certificate of the eUICC certificate chain corresponds to the - # euiccCiPKIdToBeUsed or euiccCiPKIdToBeUsedV3 - # raise ApiError('8.11.1', '3.9', 'Unknown') - - # Verify euiccSignature1 over euiccSigned1 using pubkey from euiccCertificate. - # Otherwise, the SM-DP+ SHALL return a status code "eUICC - Verification failed" - if not self._ecdsa_verify(euicc_cert, euiccSignature1_bin, euiccSigned1_bin): - raise ApiError('8.1', '6.1', 'Verification failed') - # Verify that the transactionId is known and relates to an ongoing RSP session. Otherwise, the SM-DP+ # SHALL return a status code "TransactionId - Unknown" ss = self.rss.get(transactionId, None) if ss is None: raise ApiError('8.10.1', '3.9', 'Unknown') ss.euicc_cert = euicc_cert - ss.eum_cert = eum_cert # do we need this in the state? + ss.eum_cert = eum_cert # TODO: do we need this in the state?
- # TODO: verify eUICC cert is signed by EUM cert - # TODO: verify EUM cert is signed by CI cert + # Verify that the Root Certificate of the eUICC certificate chain corresponds to the + # euiccCiPKIdToBeUsed or TODO: euiccCiPKIdToBeUsedV3 + if cert_get_auth_key_id(eum_cert) != ss.ci_cert_id: + raise ApiError('8.11.1', '3.9', 'Unknown') + + # Verify the validity of the eUICC certificate chain + cs = CertificateSet(self.ci_get_cert_for_pkid(ss.ci_cert_id)) + cs.add_intermediate_cert(eum_cert) + # TODO v3: otherCertsInChain + try: + cs.verify_cert_chain(euicc_cert) + except VerifyError: + raise ApiError('8.1.3', '6.1', 'Verification failed') + # raise ApiError('8.1.3', '6.3', 'Expired') + + + # Verify euiccSignature1 over euiccSigned1 using pubkey from euiccCertificate. + # Otherwise, the SM-DP+ SHALL return a status code "eUICC - Verification failed" + if not self._ecdsa_verify(euicc_cert, euiccSignature1_bin, euiccSigned1_bin): + raise ApiError('8.1', '6.1', 'Verification failed') + # TODO: verify EID of eUICC cert is within permitted range of EUM cert
ss.eid = ss.euicc_cert.subject.get_attributes_for_oid(x509.oid.NameOID.SERIAL_NUMBER)[0].value diff --git a/pySim/esim/rsp.py b/pySim/esim/rsp.py index b5289be..1f8e989 100644 --- a/pySim/esim/rsp.py +++ b/pySim/esim/rsp.py @@ -35,10 +35,11 @@ and subsequently used by further API calls using the same transactionId. The session state is removed either after cancelSession or after notification. TODO: add some kind of time based expiration / garbage collection.""" - def __init__(self, transactionId: str, serverChallenge: bytes): + def __init__(self, transactionId: str, serverChallenge: bytes, ci_cert_id: bytes): self.transactionId = transactionId self.serverChallenge = serverChallenge # used at a later point between API calsl + self.ci_cert_id = ci_cert_id self.euicc_cert: Optional[x509.Certificate] = None self.eum_cert: Optional[x509.Certificate] = None self.eid: Optional[bytes] = None