dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/onomondo-eim/+/43938?usp=email )
Change subject: onomondo_eim_app: check eIM certificate on startup ......................................................................
onomondo_eim_app: check eIM certificate on startup
The public key from the eIM certificate is used to generate the eIM configuration, which is quieried by REST-API users to provision the eIM configuration into new and existing eUICCs. The related private key is then used to sign eCOs and PSMOs which are sent to remote eUICCs. Since the provisioning of invalid key material may lead to catastrophic failures, let's implement a check ensures that the configured key material is correctly configured and valid.
Related: SYS#8287 Change-Id: Ib4463ef29bc39c4c11a7c78c2135ec28ba6a2e1f --- M src/crypto_utils.erl M src/onomondo_eim_app.erl 2 files changed, 59 insertions(+), 26 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/onomondo-eim refs/changes/38/43938/1
diff --git a/src/crypto_utils.erl b/src/crypto_utils.erl index d07b931..ee7e6d3 100644 --- a/src/crypto_utils.erl +++ b/src/crypto_utils.erl @@ -11,7 +11,8 @@ verify_euiccPackageResultSigned/2, verify_euiccPackageErrorSigned/2, store_euicc_pubkey_from_authenticateResponseOk/2, - store_euicc_pubkey_from_ipaEuiccDataResponse/2 + store_euicc_pubkey_from_ipaEuiccDataResponse/2, + test_eim_certificate/0 ]).
%Convert from DER encoded signature format to plain format (see also BSI TR03111 5.2.1) @@ -36,6 +37,18 @@ AssociationTokenLength = utils:integer_to_bytes(byte_size(AssociationTokenBinary)), utils:join_binary_list([<<132>>, AssociationTokenLength, AssociationTokenBinary]).
+sign_with_private_eim_key(MsgToBeSigned) -> + % Load private key from eIM certificate + {ok, EimKeyPath} = utils:get_env_priv_file_path(onomondo_eim, eim_key), + {ok, EimKeyPem} = file:read_file(EimKeyPath), + [EimKeyPemEntry] = public_key:pem_decode(EimKeyPem), + EimKeyECPrivateKey = public_key:pem_entry_decode(EimKeyPemEntry), + + % Sign message + % We use SHA-256 as signature hash/digest, see also GSMA SGP.22, section 2.6.5 and + % https://www.erlang.org/doc/apps/public_key/public_key#sign/4 + der_to_plain(public_key:sign(MsgToBeSigned, sha256, EimKeyECPrivateKey)). + sign_euiccPackageSigned(EuiccPackageSigned, EidValue) -> % Read the AssociationToken {ok, AssociationToken} = mnesia_db_euicc:state_get(EidValue, associationToken), @@ -48,16 +61,8 @@ EuiccPackageSignedEnc, enc_association_token(AssociationToken) ]),
- % Load private key from eIM certificate - {ok, EimKeyPath} = utils:get_env_priv_file_path(onomondo_eim, eim_key), - {ok, EimKeyPem} = file:read_file(EimKeyPath), - [EimKeyPemEntry] = public_key:pem_decode(EimKeyPem), - EimKeyECPrivateKey = public_key:pem_entry_decode(EimKeyPemEntry), - % Sign message - % We use SHA-256 as signature hash/digest, see also GSMA SGP.22, section 2.6.5 and - % https://www.erlang.org/doc/apps/public_key/public_key#sign/4 - der_to_plain(public_key:sign(MsgToBeSigned, sha256, EimKeyECPrivateKey)). + sign_with_private_eim_key(MsgToBeSigned).
verify_signature(Message, Signature, EidValue) -> DERSignature = plain_to_der(Signature), @@ -164,11 +169,7 @@ ok end.
-pubkey_from_cert(Cert) -> - TbsCertificate = maps:get(tbsCertificate, Cert), - SubjectPublicKeyInfo = maps:get(subjectPublicKeyInfo, TbsCertificate), - Algorithm = maps:get(algorithm, SubjectPublicKeyInfo), - SubjectPublicKey = maps:get(subjectPublicKey, SubjectPublicKeyInfo), +namedCurve_from_algorithm(Algorithm) -> BrainpoolP256r1 = #{ algorithm => {1, 2, 840, 10045, 2, 1}, parameters => <<6, 9, 43, 36, 3, 3, 2, 8, 1, 1, 7>> @@ -177,17 +178,23 @@ algorithm => {1, 2, 840, 10045, 2, 1}, parameters => <<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>> }, - NamedCurve = - case Algorithm of - Prime256v1 -> - {1, 2, 840, 10045, 3, 1, 7}; - BrainpoolP256r1 -> - {1, 3, 36, 3, 3, 2, 8, 1, 1, 7}; - _ -> - throw( - "Incorrect root CI certificate, only BrainpoolP256r1 or Prime256v1 may be used!" - ) - end, + case Algorithm of + Prime256v1 -> + {1, 2, 840, 10045, 3, 1, 7}; + BrainpoolP256r1 -> + {1, 3, 36, 3, 3, 2, 8, 1, 1, 7}; + _ -> + throw( + "Incorrect root CI certificate, only BrainpoolP256r1 or Prime256v1 may be used!" + ) + end. + +pubkey_from_cert(Cert) -> + TbsCertificate = maps:get(tbsCertificate, Cert), + SubjectPublicKeyInfo = maps:get(subjectPublicKeyInfo, TbsCertificate), + Algorithm = maps:get(algorithm, SubjectPublicKeyInfo), + SubjectPublicKey = maps:get(subjectPublicKey, SubjectPublicKeyInfo), + NamedCurve = namedCurve_from_algorithm(Algorithm), {{'ECPoint', SubjectPublicKey}, {namedCurve, NamedCurve}}.
verify_cert(TrustedCert, VerifyCert) -> @@ -325,3 +332,26 @@ % There is already a public key stored for this eUICC ok end. + +% Make sure that the key material (eIM certificate) for signing eUICC packages is correctly configured and valid. +test_eim_certificate() -> + % Sign an arbitrary message using the eIM private key + Message = list_to_binary("The quick brown fox jumps over the lazy dog."), + Signature = sign_with_private_eim_key(Message), + DERSignature = plain_to_der(Signature), + + % Retrieve the eIM public key from the eIM certificate and verify the signature of the message we have signed above. + EimCertSubjectPublicKeyInfo = eim_cfg:get_eimPublicKey_from_eim_cert(), + Algorithm = maps:get(algorithm, EimCertSubjectPublicKeyInfo), + SubjectPublicKey = maps:get(subjectPublicKey, EimCertSubjectPublicKeyInfo), + NamedCurve = namedCurve_from_algorithm(Algorithm), + ECPublicKey = {{'ECPoint', SubjectPublicKey}, {namedCurve, NamedCurve}}, + Result = public_key:verify(Message, sha256, DERSignature, ECPublicKey), + case Result of + false -> + throw( + "eIM certificate (eUICC package signing) unuseable, check key material and algorithm (sys.config)!~n" + ); + true -> + ok + end. diff --git a/src/onomondo_eim_app.erl b/src/onomondo_eim_app.erl index 688f83a..227c938 100644 --- a/src/onomondo_eim_app.erl +++ b/src/onomondo_eim_app.erl @@ -104,6 +104,9 @@ % effective than fumbling with string format arguments. logger:notice("eIM! version: ~s~n", [Vsn]),
+ % Ensure that the key material that is used for signing PSMOs and eCOs is correctly configured and valid. + crypto_utils:test_eim_certificate(), + % Startup database ok = mnesia_db:init(),