dexter has uploaded this change for review.
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(),
To view, visit change 43938. To unsubscribe, or for help writing mail filters, visit settings.