File 2532-public_key-Add-support-for-EDDSA.patch of Package erlang
From a720a4eb918ab6b97daae02c0c6c9cd39e3a8819 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Tue, 6 Apr 2021 15:30:32 +0200
Subject: [PATCH 2/8] public_key: Add support for EDDSA
---
lib/public_key/asn1/ECPrivateKey.asn1 | 4 +-
lib/public_key/src/pubkey_cert.erl | 33 +++++--
lib/public_key/src/pubkey_cert_records.erl | 10 +-
lib/public_key/src/public_key.erl | 99 ++++++++++++++++---
lib/public_key/test/public_key_SUITE.erl | 17 ++++
.../public_key_SUITE_data/eddsa_key_pkcs8.pem | 3 +
6 files changed, 145 insertions(+), 21 deletions(-)
create mode 100644 lib/public_key/test/public_key_SUITE_data/eddsa_key_pkcs8.pem
diff --git a/lib/public_key/asn1/ECPrivateKey.asn1 b/lib/public_key/asn1/ECPrivateKey.asn1
index a20fa4009c..5f7267506f 100644
--- a/lib/public_key/asn1/ECPrivateKey.asn1
+++ b/lib/public_key/asn1/ECPrivateKey.asn1
@@ -16,9 +16,11 @@ EcpkParameters FROM PKIX1Algorithms88;
ECPrivateKey ::= SEQUENCE {
version INTEGER,
- privateKey OCTET STRING,
+ privateKey CurvePrivateKey,
parameters [0] EcpkParameters OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
+CurvePrivateKey ::= OCTET STRING
+
END
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index e84edffd53..66d37ebf96 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -74,7 +74,7 @@
%%--------------------------------------------------------------------
-spec verify_data(DER::binary()) ->
{DigestType, PlainText, Signature}
- when DigestType :: md5 | crypto:sha1() | crypto:sha2(),
+ when DigestType :: md5 | crypto:sha1() | crypto:sha2() | none,
PlainText :: binary(),
Signature :: binary().
%%
@@ -639,9 +639,12 @@ public_key_info(PublicKeyInfo,
case PublicKeyParams of
{null, 'NULL'} when WorkingAlgorithm == Algorithm ->
WorkingParams;
- {params, Params} ->
+ asn1_NOVALUE when Algorithm == ?'id-Ed25519';
+ Algorithm == ?'id-Ed448' ->
+ {namedCurve, Algorithm};
+ {params, Params} ->
Params;
- Params ->
+ Params ->
Params
end,
{Algorithm, PublicKey, NewPublicKeyParams}.
@@ -1256,6 +1259,10 @@ sign_algorithm({#'RSAPrivateKey'{} = Key,#'RSASSA-PSS-params'{} = Params}, _Opts
sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
#'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1',
parameters = {params,#'Dss-Parms'{p=P, q=Q, g=G}}};
+sign_algorithm(#'ECPrivateKey'{parameters = {namedCurve, EDCurve}}, _Opts) when EDCurve == ?'id-Ed25519';
+ EDCurve == ?'id-Ed448' ->
+ #'SignatureAlgorithm'{algorithm = EDCurve,
+ parameters = asn1_NOVALUE};
sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) ->
Type = ecdsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
#'SignatureAlgorithm'{algorithm = Type,
@@ -1363,9 +1370,23 @@ public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}, _) ->
parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y};
public_key(#'ECPrivateKey'{version = _Version,
- privateKey = _PrivKey,
- parameters = Params,
- publicKey = PubKey}, _) ->
+ privateKey = _PrivKey,
+ parameters = {namedCurve, ?'id-Ed25519' = ID},
+ publicKey = PubKey}, _) ->
+ Algo = #'PublicKeyAlgorithm'{algorithm= ID, parameters=asn1_NOVALUE},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = #'ECPoint'{point = PubKey}};
+public_key(#'ECPrivateKey'{version = _Version,
+ privateKey = _PrivKey,
+ parameters = {namedCurve, ?'id-Ed448' = ID},
+ publicKey = PubKey}, _) ->
+ Algo = #'PublicKeyAlgorithm'{algorithm= ID, parameters=asn1_NOVALUE},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = #'ECPoint'{point = PubKey}};
+public_key(#'ECPrivateKey'{version = _Version,
+ privateKey = _PrivKey,
+ parameters = Params,
+ publicKey = PubKey}, _) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = #'ECPoint'{point = PubKey}}.
diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index b556314ad1..1e45afd8bf 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -111,7 +111,11 @@ supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey';
supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey';
supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey';
supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint';
-supportedPublicKeyAlgorithms(?'id-RSASSA-PSS') -> 'RSAPublicKey'.
+supportedPublicKeyAlgorithms(?'id-RSASSA-PSS') -> 'RSAPublicKey';
+supportedPublicKeyAlgorithms(?'id-Ed25519') -> 'ECPoint';
+supportedPublicKeyAlgorithms(?'id-Ed448') -> 'ECPoint';
+supportedPublicKeyAlgorithms(?'id-X25519') -> 'ECPoint';
+supportedPublicKeyAlgorithms(?'id-X448') -> 'ECPoint'.
supportedCurvesTypes(?'characteristic-two-field') -> characteristic_two_field;
supportedCurvesTypes(?'prime-field') -> prime_field;
@@ -152,6 +156,8 @@ namedCurves(?'secp256r1') -> secp256r1;
namedCurves(?'secp192r1') -> secp192r1;
namedCurves(?'id-X25519') -> x25519;
namedCurves(?'id-X448') -> x448;
+namedCurves(?'id-Ed25519') -> ed25519;
+namedCurves(?'id-Ed448') -> ed448;
namedCurves(?'brainpoolP160r1') -> brainpoolP160r1;
namedCurves(?'brainpoolP160t1') -> brainpoolP160t1;
namedCurves(?'brainpoolP192r1') -> brainpoolP192r1;
@@ -201,6 +207,8 @@ namedCurves(secp256r1) -> ?'secp256r1';
namedCurves(secp192r1) -> ?'secp192r1';
namedCurves(x25519) -> ?'id-X25519';
namedCurves(x448) -> ?'id-X448';
+namedCurves(ed25519) -> ?'id-Ed25519';
+namedCurves(ed448) -> ?'id-Ed448';
namedCurves(brainpoolP160r1) -> ?'brainpoolP160r1';
namedCurves(brainpoolP160t1) -> ?'brainpoolP160t1';
namedCurves(brainpoolP192r1) -> ?'brainpoolP192r1';
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 8c8b5585a0..4f097a1e2d 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -226,6 +226,18 @@ pem_entry_encode('SubjectPublicKeyInfo',
Spki = {'SubjectPublicKeyInfo',
{'AlgorithmIdentifier', ?'id-dsa', ParamDer}, KeyDer},
pem_entry_encode('SubjectPublicKeyInfo', Spki);
+pem_entry_encode('SubjectPublicKeyInfo',
+ {#'ECPoint'{point = Key}, {namedCurve, ?'id-Ed25519' = ID}}) when is_binary(Key)->
+ Spki = {'SubjectPublicKeyInfo',
+ {'AlgorithmIdentifier', ID, asn1_NOVALUE},
+ Key},
+ pem_entry_encode('SubjectPublicKeyInfo', Spki);
+pem_entry_encode('SubjectPublicKeyInfo',
+ {#'ECPoint'{point = Key}, {namedCurve, ?'id-Ed448' = ID}}) when is_binary(Key)->
+ Spki = {'SubjectPublicKeyInfo',
+ {'AlgorithmIdentifier', ID , asn1_NOVALUE},
+ Key},
+ pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode('SubjectPublicKeyInfo',
{#'ECPoint'{point = Key}, ECParam}) when is_binary(Key)->
Params = der_encode('EcpkParameters',ECParam),
@@ -270,9 +282,10 @@ pem_entry_encode(Asn1Type, Entity, {{Cipher, Salt} = CipherInfo,
%%
%% Description: Decodes a public key asn1 der encoded entity.
%%--------------------------------------------------------------------
-der_decode(Asn1Type, Der) when (Asn1Type == 'PrivateKeyInfo') or
- (Asn1Type == 'EncryptedPrivateKeyInfo')
- andalso is_binary(Der) ->
+der_decode(Asn1Type, Der) when (((Asn1Type == 'PrivateKeyInfo')
+ orelse
+ (Asn1Type == 'EncryptedPrivateKeyInfo'))
+ andalso is_binary(Der)) ->
try
{ok, Decoded} = 'PKCS-FRAME':decode(Asn1Type, Der),
der_priv_key_decode(Decoded)
@@ -294,6 +307,11 @@ der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-ecPublicKey', {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
EcPrivKey = der_decode('ECPrivateKey', PrivKey),
EcPrivKey#'ECPrivateKey'{parameters = der_decode('EcpkParameters', Parameters)};
+der_priv_key_decode({'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', CurveOId, _}, PrivKey, _}) when
+ CurveOId == ?'id-Ed25519'orelse
+ CurveOId == ?'id-Ed448' ->
+ #'ECPrivateKey'{version = 1, parameters = {namedCurve, CurveOId}, privateKey = PrivKey};
der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', _}, PrivKey, _}) ->
der_decode('RSAPrivateKey', PrivKey);
@@ -336,6 +354,14 @@ der_encode('PrivateKeyInfo', {#'RSAPrivateKey'{} = PrivKey, Parameters}) ->
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS',
{asn1_OPENTYPE, der_encode('RSASSA-PSS-params', Parameters)}},
der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
+der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = {namedCurve, CurveOId} = Parameters,
+ privateKey = PrivKey}) when
+ CurveOId == ?'id-Ed25519' orelse
+ CurveOId == ?'id-Ed448' ->
+ der_encode('PrivateKeyInfo',
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', CurveOId, asn1_NOVALUE},
+ PrivKey, asn1_NOVALUE});
der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = Parameters} = PrivKey) ->
der_encode('PrivateKeyInfo',
{'PrivateKeyInfo', v1,
@@ -587,6 +613,12 @@ generate_key({rsa, ModulusSize, PublicExponent}) ->
when OthersECDHkey :: #'ECPoint'{},
MyECDHkey :: #'ECPrivateKey'{},
SharedSecret :: binary().
+compute_key(#'ECPoint'{point = Point}, #'ECPrivateKey'{privateKey = PrivKey,
+ parameters = {namedCurve, Curve} = Param})
+ when (Curve == ?'id-X25519') orelse
+ (Curve == ?'id-X448') ->
+ ECCurve = ec_curve_spec(Param),
+ crypto:compute_key(eddh, Point, PrivKey, ECCurve);
compute_key(#'ECPoint'{point = Point}, #'ECPrivateKey'{privateKey = PrivKey,
parameters = Param}) ->
ECCurve = ec_curve_spec(Param),
@@ -606,7 +638,7 @@ compute_key(PubKey, PrivKey, #'DHParameter'{prime = P, base = G}) ->
{DigestType, SignatureType}
when AlgorithmId :: oid(),
%% Relevant dsa digest type is a subset of rsa_digest_type()
- DigestType :: crypto:rsa_digest_type(),
+ DigestType :: crypto:rsa_digest_type() | none,
SignatureType :: rsa | dsa | ecdsa .
%% Description:
%%--------------------------------------------------------------------
@@ -639,7 +671,11 @@ pkix_sign_types(?'ecdsa-with-SHA256') ->
pkix_sign_types(?'ecdsa-with-SHA384') ->
{sha384, ecdsa};
pkix_sign_types(?'ecdsa-with-SHA512') ->
- {sha512, ecdsa}.
+ {sha512, ecdsa};
+pkix_sign_types(?'id-Ed25519') ->
+ {none, eddsa};
+pkix_sign_types(?'id-Ed448') ->
+ {none, eddsa}.
%%--------------------------------------------------------------------
-spec pkix_hash_type(HashOid::oid()) -> DigestType:: md5 | crypto:sha1() | crypto:sha2().
@@ -833,10 +869,21 @@ pkix_verify(DerCert, {#'RSAPublicKey'{} = RSAKey, #'RSASSA-PSS-params'{} = Para
{DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
verify(PlainText, DigestType, Signature, RSAKey, rsa_opts(Params));
-pkix_verify(DerCert, Key = {#'ECPoint'{}, _})
- when is_binary(DerCert) ->
- {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
- verify(PlainText, DigestType, Signature, Key).
+pkix_verify(DerCert, Key = {#'ECPoint'{}, {namedCurve, Curve}}) when (Curve == ?'id-Ed25519'orelse
+ Curve == ?'id-Ed448') andalso is_binary(DerCert) ->
+ case pubkey_cert:verify_data(DerCert) of
+ {none = DigestType, PlainText, Signature} ->
+ verify(PlainText, DigestType, Signature, Key);
+ _ ->
+ false
+ end;
+pkix_verify(DerCert, Key = {#'ECPoint'{}, _}) when is_binary(DerCert) ->
+ case pubkey_cert:verify_data(DerCert) of
+ {none, _, _} ->
+ false;
+ {DigestType, PlainText, Signature} ->
+ verify(PlainText, DigestType, Signature, Key)
+ end.
%%--------------------------------------------------------------------
-spec pkix_crl_verify(CRL, Cert) -> boolean()
@@ -1366,8 +1413,13 @@ format_sign_key(Key = #'RSAPrivateKey'{}) ->
{rsa, format_rsa_private_key(Key)};
format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
{dss, [P, Q, G, X]};
+format_sign_key(#'ECPrivateKey'{privateKey = PrivKey, parameters = {namedCurve, Curve} = Param})
+ when (Curve == ?'id-Ed25519') orelse (Curve == ?'id-Ed448')->
+ ECCurve = ec_curve_spec(Param),
+ {eddsa, [PrivKey, ECCurve]};
format_sign_key(#'ECPrivateKey'{privateKey = PrivKey, parameters = Param}) ->
- {ecdsa, [PrivKey, ec_curve_spec(Param)]};
+ ECCurve = ec_curve_spec(Param),
+ {ecdsa, [PrivKey, ECCurve]};
format_sign_key({ed_pri, Curve, _Pub, Priv}) ->
{eddsa, [Priv,Curve]};
format_sign_key(_) ->
@@ -1375,8 +1427,13 @@ format_sign_key(_) ->
format_verify_key(#'RSAPublicKey'{modulus = Mod, publicExponent = Exp}) ->
{rsa, [Exp, Mod]};
+format_verify_key({#'ECPoint'{point = Point}, {namedCurve, Curve} = Param}) when (Curve == ?'id-Ed25519') orelse
+ (Curve == ?'id-Ed448') ->
+ ECCurve = ec_curve_spec(Param),
+ {eddsa, [Point, ECCurve]};
format_verify_key({#'ECPoint'{point = Point}, Param}) ->
- {ecdsa, [Point, ec_curve_spec(Param)]};
+ ECCurve = ec_curve_spec(Param),
+ {ecdsa, [Point, ECCurve]};
format_verify_key({Key, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
{dss, [P, Q, G, Key]};
format_verify_key({ed_pub, Curve, Key}) ->
@@ -1628,7 +1685,8 @@ format_rsa_private_key(#'RSAPrivateKey'{modulus = N, publicExponent = E,
-spec ec_generate_key(ecpk_parameters_api()) -> #'ECPrivateKey'{}.
ec_generate_key(Params) ->
Curve = ec_curve_spec(Params),
- Term = crypto:generate_key(ecdh, Curve),
+ CurveType = ec_curve_type(Curve),
+ Term = crypto:generate_key(CurveType, Curve),
NormParams = ec_normalize_params(Params),
ec_key(Term, NormParams).
@@ -1646,16 +1704,31 @@ ec_curve_spec( #'ECParameters'{fieldID = #'FieldID'{fieldType = Type,
Curve = {PCurve#'Curve'.a, PCurve#'Curve'.b, none},
{Field, Curve, Base, Order, CoFactor};
ec_curve_spec({ecParameters, ECParams}) ->
- ec_curve_spec(ECParams);
+ ec_curve_spec(ECParams);
ec_curve_spec({namedCurve, OID}) when is_tuple(OID), is_integer(element(1,OID)) ->
ec_curve_spec({namedCurve, pubkey_cert_records:namedCurves(OID)});
ec_curve_spec({namedCurve, x25519 = Name}) ->
Name;
ec_curve_spec({namedCurve, x448 = Name}) ->
Name;
+ec_curve_spec({namedCurve, ed25519 = Name}) ->
+ Name;
+ec_curve_spec({namedCurve, ed448 = Name}) ->
+ Name;
ec_curve_spec({namedCurve, Name}) when is_atom(Name) ->
(Name).
+ec_curve_type(ed25519) ->
+ eddsa;
+ec_curve_type(ed448) ->
+ eddsa;
+ec_curve_type(x25519) ->
+ eddh;
+ec_curve_type(x448) ->
+ eddh;
+ec_curve_type(_) ->
+ ecdh.
+
format_field(characteristic_two_field = Type, Params0) ->
#'Characteristic-two'{
m = M,
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index cf3b5ea0c8..ae16eca83e 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -55,6 +55,8 @@
ec_pem2/1,
ec_priv_pkcs8/0,
ec_priv_pkcs8/1,
+ eddsa_priv_pkcs8/0,
+ eddsa_priv_pkcs8/1,
eddsa_pub/0,
eddsa_pub/1,
init_ec_pem_encode_generated/1,
@@ -157,6 +159,7 @@ groups() ->
[{pem_decode_encode, [], [dsa_pem, rsa_pem, rsa_pss_pss_pem, ec_pem, encrypted_pem,
dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2,
rsa_priv_pkcs8, dsa_priv_pkcs8, ec_priv_pkcs8,
+ eddsa_priv_pkcs8,
ec_pem_encode_generated,
gen_ec_param_prime_field, gen_ec_param_char_2_field
]},
@@ -399,6 +402,20 @@ ec_priv_pkcs8(Config) when is_list(Config) ->
ECPemNoEndNewLines = strip_superfluous_newlines(ECPrivPem),
ECPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PrivEntry0])).
+eddsa_priv_pkcs8() ->
+ [{doc, "EDDSA PKCS8 private key decode/encode"}].
+eddsa_priv_pkcs8(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(data_dir, Config),
+ {ok, ECPrivPem} = file:read_file(filename:join(Datadir, "eddsa_key_pkcs8.pem")),
+ [{'PrivateKeyInfo', _, not_encrypted} = PKCS8Key] = public_key:pem_decode(ECPrivPem),
+ ECPrivKey = public_key:pem_entry_decode(PKCS8Key),
+ true = check_entry_type(ECPrivKey, 'ECPrivateKey'),
+ true = ECPrivKey#'ECPrivateKey'.parameters == {namedCurve, ?'id-Ed25519'},
+ PrivEntry0 = public_key:pem_entry_encode('PrivateKeyInfo', ECPrivKey),
+ ECPemNoEndNewLines = strip_superfluous_newlines(ECPrivPem),
+ ECPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PrivEntry0])).
+
+
eddsa_pub() ->
[{doc, "EDDSA PKCS8 public key decode/encode"}].
eddsa_pub(Config) when is_list(Config) ->
diff --git a/lib/public_key/test/public_key_SUITE_data/eddsa_key_pkcs8.pem b/lib/public_key/test/public_key_SUITE_data/eddsa_key_pkcs8.pem
new file mode 100644
index 0000000000..e447080ae2
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/eddsa_key_pkcs8.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+-----END PRIVATE KEY-----
--
2.26.2