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