File 0196-public_key-Fix-encoding-and-decoding-extensions.patch of Package erlang

From 0de5a615cd6912b3d8f2ca4e03409d70e655a785 Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Tue, 25 Nov 2025 17:13:04 +0100
Subject: [PATCH] public_key: Fix encoding and decoding extensions

Extensions was not looked up correctly they are split depending
on extension structure, currently three different functions.

Clean up code in public_key to lookup the extensions before
enc/decoding them so that all supported extensions will be available.

Also remove the type that where extensions and couldn't be called
directly, SubjectAltName and FreshestCRL.

Test encode/decode all them.
---
 lib/public_key/src/pubkey_cert_records.erl | 90 +++++++++++++---------
 lib/public_key/src/public_key.erl          | 85 ++++++++++----------
 lib/public_key/test/public_key_SUITE.erl   | 63 +++++++++++++++
 3 files changed, 157 insertions(+), 81 deletions(-)

diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index 4bc174e39d..b82d47da74 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -352,34 +352,58 @@ encode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{
 
 %%% Extensions
 
-extension_id(?'id-ce-authorityKeyIdentifier') ->  'AuthorityKeyIdentifier';
-extension_id(?'id-ce-subjectKeyIdentifier') ->    'SubjectKeyIdentifier';
-extension_id(?'id-ce-keyUsage') -> 	          'KeyUsage';
-extension_id(?'id-ce-privateKeyUsagePeriod') ->   'PrivateKeyUsagePeriod';
-extension_id(?'id-ce-certificatePolicies') -> 	  'CertificatePolicies';
-extension_id(?'id-ce-policyMappings') -> 	  'PolicyMappings';
-extension_id(?'id-ce-subjectAltName') -> 	  'SubjectAltName';
-extension_id(?'id-ce-issuerAltName') -> 	  'IssuerAltName';
-extension_id(?'id-ce-subjectDirectoryAttributes') -> 	  'SubjectDirectoryAttributes';
-extension_id(?'id-ce-basicConstraints' ) -> 	  'BasicConstraints';
-extension_id(?'id-ce-nameConstraints') -> 	  'NameConstraints';
-extension_id(?'id-ce-policyConstraints') -> 	  'PolicyConstraints';
-extension_id(?'id-ce-extKeyUsage') -> 	          'ExtKeyUsageSyntax';
-extension_id(?'id-ce-inhibitAnyPolicy') -> 	  'InhibitAnyPolicy';
-extension_id(?'id-ce-freshestCRL') -> 	          'FreshestCRL';
-extension_id(?'id-ce-issuingDistributionPoint') -> 'IssuingDistributionPoint';
-%% Missing in public_key doc
-extension_id(?'id-pe-authorityInfoAccess') -> 	  'AuthorityInfoAccessSyntax';
-extension_id(?'id-pe-subjectInfoAccess') -> 	  'SubjectInfoAccessSyntax';
-extension_id(?'id-ce-cRLNumber') -> 	          'CRLNumber';
-extension_id(?'id-ce-deltaCRLIndicator') -> 	   'BaseCRLNumber';
-extension_id(?'id-ce-cRLReasons') -> 	          'CRLReason';
-extension_id(?'id-ce-certificateIssuer') -> 	  'CertificateIssuer';
-extension_id(?'id-ce-holdInstructionCode') -> 	  'HoldInstructionCode';
-extension_id(?'id-ce-invalidityDate') -> 	  'InvalidityDate';
-extension_id(?'id-ce-cRLDistributionPoints') ->   'CRLDistributionPoints';
+extension_id(?'id-ce-authorityKeyIdentifier') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'AuthorityKeyIdentifier'};
+extension_id(?'id-ce-subjectKeyIdentifier') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectKeyIdentifier'};
+extension_id(?'id-ce-keyUsage') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'KeyUsage'};
+extension_id(?'id-ce-privateKeyUsagePeriod') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PrivateKeyUsagePeriod'};
+extension_id(?'id-ce-certificatePolicies') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'CertificatePolicies'};
+extension_id(?'id-ce-policyMappings') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PolicyMappings'};
+extension_id(?'id-ce-subjectAltName') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectAltName'};
+extension_id(?'id-ce-issuerAltName') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'IssuerAltName'};
+extension_id(?'id-ce-subjectDirectoryAttributes') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectDirectoryAttributes'};
+extension_id(?'id-ce-basicConstraints' ) ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'BasicConstraints'};
+extension_id(?'id-ce-nameConstraints') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'NameConstraints'};
+extension_id(?'id-ce-policyConstraints') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PolicyConstraints'};
+extension_id(?'id-ce-extKeyUsage') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'ExtKeyUsageSyntax'};
+extension_id(?'id-ce-inhibitAnyPolicy') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'InhibitAnyPolicy'};
+extension_id(?'id-ce-freshestCRL') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'FreshestCRL'};
+extension_id(?'id-ce-issuingDistributionPoint') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'IssuingDistributionPoint'};
+extension_id(?'id-pe-authorityInfoAccess') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'AuthorityInfoAccessSyntax'};
+extension_id(?'id-pe-subjectInfoAccess') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectInfoAccessSyntax'};
+extension_id(?'id-ce-cRLNumber') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'CRLNumber'};
+extension_id(?'id-ce-deltaCRLIndicator') ->
+    {'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'BaseCRLNumber'};
+extension_id(?'id-ce-cRLReasons') ->
+    {'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'CRLReason'};
+extension_id(?'id-ce-certificateIssuer') ->
+    {'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'CertificateIssuer'};
+extension_id(?'id-ce-holdInstructionCode') ->
+    {'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'HoldInstructionCode'};
+extension_id(?'id-ce-invalidityDate') ->
+    {'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'InvalidityDate'};
+extension_id(?'id-ce-cRLDistributionPoints') ->
+    {'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'CRLDistributionPoints'};
 extension_id(_) ->
-    undefined.
+    {undefined, undefined, undefined, undefined}.
 
 ext_oid('AuthorityKeyIdentifier') ->     ?'id-ce-authorityKeyIdentifier';
 ext_oid('SubjectKeyIdentifier') ->       ?'id-ce-subjectKeyIdentifier';
@@ -419,10 +443,8 @@ decode_extensions(Exts, WhenCRL) ->
     lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) ->
                       %% Some Extensions only has special decoding functions
                       %% with other naming-convention
-                      ExtId = extension_id(Id),
-		      case ExtId =/= undefined andalso
-                          'PKIX1Implicit-2009':getdec_CertExtensions(Id)
-                      of
+                      {Mod, DecLookup, _Enc, ExtId} = extension_id(Id),
+		      case ExtId =/= undefined andalso Mod:DecLookup(Id) of
 			  false ->
                               Ext;
                           DecodeExt when ExtId =:= 'CertificatePolicies',
@@ -473,10 +495,8 @@ encode_extensions(Exts) ->
     %% Some Extensions only has special decoding functions
     %% with other naming-convention
     lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) ->
-                      ExtId = extension_id(Id),
-		      case ExtId =/= undefined andalso
-                          'PKIX1Implicit-2009':getenc_CertExtensions(Id)
-                      of
+                      {Mod, _Dec, EncLookup, ExtId} = extension_id(Id),
+		      case ExtId =/= undefined andalso Mod:EncLookup(Id) of
 			  false ->
                               Ext;
 			  EncodeExt when is_function(EncodeExt, 3) ->
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index e7ddaa284f..fa34c0b473 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -608,28 +608,25 @@ der_decode('ExtensionRequest', Value) ->
 der_decode('AttributeTypeAndValue', Der) ->
     {ok, Decoded} = 'PKIX1Explicit-2009':decode('SingleAttribute', Der),
     pubkey_cert_records:transform(Decoded, decode);
-der_decode(Asn1ExtType, Der) when Asn1ExtType == 'SubjectAltName';
-                                  Asn1ExtType == 'IssuerAltName';
-                                  Asn1ExtType == 'ExtKeyUsage';
-                                  Asn1ExtType == 'InhibitAnyPolicy';
-                                  Asn1ExtType == 'FreshestCRL';
-                                  Asn1ExtType == 'AuthorityInfoAccess';
-                                  Asn1ExtType == 'DeltaCRLIndicator';
-                                  Asn1ExtType == 'CertificateIssuer';
-                                  Asn1ExtType == 'HoldInstructionCode';
-                                  Asn1ExtType == 'InvalidityDate' ->
-    Oid = pubkey_cert_records:ext_oid(Asn1ExtType),
-    [#'Extension'{extnValue = Value}]
-        = pubkey_cert_records:decode_extensions([#'Extension'{extnID = Oid, extnValue = Der}]),
-    Value;
 der_decode(Asn1Type, Der) when is_atom(Asn1Type), is_binary(Der) ->
-    Asn1Module = get_asn1_module(Asn1Type),
-    try
-	{ok, Decoded} = Asn1Module:decode(Asn1Type, Der),
-        pubkey_translation:decode(Decoded)
-    catch
-	error:{badmatch, {error, _}} = Error ->
-	    erlang:error(Error)
+    case get_asn1_module(Asn1Type) of
+        undefined ->
+            case pubkey_cert_records:ext_oid(Asn1Type) of
+                undefined ->
+                    error({badarg, {unknown_type, Asn1Type}});
+                ExtOid ->
+                    Ext = #'Extension'{extnID = ExtOid, extnValue = Der},
+                    [Res] = pubkey_cert_records:decode_extensions([Ext]),
+                    Res#'Extension'.extnValue
+            end;
+        Asn1Module when is_atom(Asn1Module) ->
+            try
+                {ok, Decoded} = Asn1Module:decode(Asn1Type, Der),
+                pubkey_translation:decode(Decoded)
+            catch
+                error:{badmatch, {error, _}} = Error ->
+                    erlang:error(Error)
+            end
     end.
 
 %% X509 RFC 5280
@@ -640,11 +637,9 @@ get_asn1_module('ExtKeyUsageSyntax') -> 'PKIX1Implicit-2009';
 get_asn1_module('KeyUsage') -> 'PKIX1Implicit-2009';
 get_asn1_module('Certificate') -> 'PKIX1Explicit-2009';
 get_asn1_module('TBSCertificate') -> 'PKIX1Explicit-2009';
-get_asn1_module('SubjectAltName') -> 'PKIX1Implicit-2009';
 get_asn1_module('CRLDistributionPoints') -> 'PKIX1Implicit-2009';
 get_asn1_module('CRLReason') ->  'PKIX1Implicit-2009';
 get_asn1_module('CRLNumber') ->  'PKIX1Implicit-2009';
-get_asn1_module('FreshestCRL') ->  'PKIX1Implicit-2009';
 get_asn1_module('IssuingDistributionPoint') ->  'PKIX1Implicit-2009';
 get_asn1_module('GeneralNames') -> 'PKIX1Implicit-2009';
 get_asn1_module('SubjectPublicKeyInfo') -> 'PKIX1Explicit-2009';
@@ -710,7 +705,8 @@ get_asn1_module('PBMParameter') -> 'PKIXCMP-2023';
 get_asn1_module('ProtectedPart') -> 'PKIXCMP-2023';
 %% PKIXCRMF 
 get_asn1_module('OldCertId') -> 'PKIXCRMF-2009';
-get_asn1_module('CertRequest') -> 'PKIXCRMF-2009'.
+get_asn1_module('CertRequest') -> 'PKIXCRMF-2009';
+get_asn1_module(_) -> undefined.
 
 
 handle_pkcs_frame_error('PrivateKeyInfo', Der, _) ->
@@ -963,29 +959,26 @@ der_encode('AttributeTypeAndValue', Value) ->
     Term = pubkey_cert_records:transform(Value, encode),
     {ok, Encoded} = 'PKIX1Explicit-2009':encode('SingleAttribute', Term),
     Encoded;
-der_encode(Asn1ExtType, Value) when Asn1ExtType == 'SubjectAltName';
-                                    Asn1ExtType == 'IssuerAltName';
-                                    Asn1ExtType == 'ExtKeyUsage';
-                                    Asn1ExtType == 'InhibitAnyPolicy';
-                                    Asn1ExtType == 'FreshestCRL';
-                                    Asn1ExtType == 'AuthorityInfoAccess';
-                                    Asn1ExtType == 'DeltaCRLIndicator';
-                                    Asn1ExtType == 'CertificateIssuer';
-                                    Asn1ExtType == 'HoldInstructionCode';
-                                    Asn1ExtType == 'InvalidityDate' ->
-    Oid = pubkey_cert_records:ext_oid(Asn1ExtType),
-    [#'Extension'{extnValue = Encoded}] =
-         pubkey_cert_records:encode_extensions([#'Extension'{extnID = Oid, extnValue = Value}]),
-     Encoded;
 der_encode(Asn1Type, Entity0) when is_atom(Asn1Type) ->
-    Asn1Module = get_asn1_module(Asn1Type),
-    try
-        Entity = pubkey_translation:encode(Entity0),
-	{ok, Encoded} = Asn1Module:encode(Asn1Type, Entity),
-	Encoded
-    catch
-	error:{badmatch, {error, _}} = Error ->
-	    erlang:error(Error)
+    case get_asn1_module(Asn1Type) of
+        undefined ->
+            case pubkey_cert_records:ext_oid(Asn1Type) of
+                undefined ->
+                    error({badarg, {unknown_type, Asn1Type}});
+                ExtOid ->
+                    Ext = #'Extension'{extnID = ExtOid, extnValue = Entity0},
+                    [Res] = pubkey_cert_records:encode_extensions([Ext]),
+                    Res#'Extension'.extnValue
+            end;
+        Asn1Module when is_atom(Asn1Module) ->
+            try
+                Entity = pubkey_translation:encode(Entity0),
+                {ok, Encoded} = Asn1Module:encode(Asn1Type, Entity),
+                Encoded
+            catch
+                error:{badmatch, {error, _}} = Error ->
+                    erlang:error(Error)
+            end
     end.
 
 %%--------------------------------------------------------------------
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 387efd76e2..fd15609a42 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -77,6 +77,7 @@
          encrypted_pem_pwdstring/1,
          encrypted_pem_pwdfun/0,
          encrypted_pem_pwdfun/1,
+         ext_encoding/1,
          dh_pem/0,
          dh_pem/1,
          pkcs10_pem/0,
@@ -191,6 +192,7 @@ all() ->
      {group, pem_decode_encode},
      encrypt_decrypt,
      encrypt_decrypt_sign_fun,
+     ext_encoding,
      {group, sign_verify},
      pkix,
      pkix_cmp,
@@ -777,6 +779,67 @@ cert_pem(Config) when is_list(Config) ->
     asn1_encode_decode(Entry1),
     asn1_encode_decode(Entry2).
 
+%%--------------------------------------------------------------------
+ext_encoding(_Config) ->
+    Exts = [
+            {'AuthorityKeyIdentifier',
+             #'AuthorityKeyIdentifier'{authorityCertIssuer = [{rfc822Name, "a name"}]}},
+            {'SubjectKeyIdentifier', "Octet String"},
+            {'KeyUsage', [keyEncipherment, encipherOnly]},
+            {'PrivateKeyUsagePeriod', #'PrivateKeyUsagePeriod'{notAfter = "20220127000000Z"}},
+            {'CertificatePolicies', [#'PolicyInformation'{policyIdentifier = ?'anyPolicy'}]},
+            {'PolicyMappings', [#'PolicyMappings_SEQOF'{issuerDomainPolicy = ?'anyPolicy',
+                                                        subjectDomainPolicy = ?'anyPolicy'}]},
+            {'SubjectAltName', [{rfc822Name, "a name"}]},
+            {'IssuerAltName', [{rfc822Name, "a name"}]},
+            {'SubjectDirectoryAttributes', [#'AttributeSet'{type = ?'id-at-name',
+                                                            values = [{utf8String, ~"a string"}]}]},
+            {'BasicConstraints', #'BasicConstraints'{cA = true}},
+            {'NameConstraints', #'NameConstraints'{}},
+            {'PolicyConstraints', #'PolicyConstraints'{requireExplicitPolicy = 5}},
+            {'ExtKeyUsageSyntax', [?anyPolicy]},
+            {'InhibitAnyPolicy', 42},
+            {'FreshestCRL', [#'DistributionPoint'{}]},
+            {'IssuingDistributionPoint',
+             #'IssuingDistributionPoint'{indirectCRL = true,
+                                         onlyContainsUserCerts = false,
+                                         onlyContainsCACerts = false,
+                                         onlyContainsAttributeCerts = false
+                                        }},
+            {'AuthorityInfoAccessSyntax',
+             [#'AccessDescription'{accessMethod = ?'id-at-name',
+                                   accessLocation = {rfc822Name, "a name"}}]},
+            {'SubjectInfoAccessSyntax',
+             [#'AccessDescription'{accessMethod = ?'id-at-name',
+                                   accessLocation = {rfc822Name, "a name"}}]},
+            {'CRLNumber', 4711},
+            {'BaseCRLNumber', 4710},
+            {'CRLReason', 'cessationOfOperation'},
+            {'CertificateIssuer', [{rfc822Name, "a name"}]},
+            {'HoldInstructionCode', ?'id-holdinstruction-callissuer'},
+            {'InvalidityDate', "20220127000000Z"},
+            {'CRLDistributionPoints', [#'DistributionPoint'{}]}
+           ],
+    Check = fun({Ext, Value}) ->
+                    try
+                        Der = public_key:der_encode(Ext, Value),
+                        true = is_binary(Der),
+                        case public_key:der_decode(Ext, Der) of
+                            Value -> false;
+                            Bin when is_binary(Bin) ->
+                                Value =/= binary_to_list(Bin)
+                        end
+                    catch Err:Reason:St ->
+                            {true, {fail, Ext, Value, Err, Reason, St}}
+                    end
+            end,
+    [] = lists:filtermap(Check, Exts),
+
+    Badarg = {badarg, {unknown_type, type_do_not_exist}},
+    {'EXIT', {Badarg,_}} = (catch public_key:der_encode(type_do_not_exist, 4711)),
+    {'EXIT', {Badarg,_}} = (catch public_key:der_decode(type_do_not_exist, <<47,11>>)),
+    ok.
+
 %%--------------------------------------------------------------------
 encrypt_decrypt() ->
     [{doc, "Test public_key:encrypt_private and public_key:decrypt_public"}].
-- 
2.51.0

openSUSE Build Service is sponsored by