osmo-smdpp: Implement eUICC + EUM certificate signature chain validation
Change-Id: I961827c50ed5e34c6507bfdf853952ece5b0d121
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