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

openSUSE Build Service is sponsored by