Skip to content

Commit bb462d3

Browse files
committed
Merge branch 'dgud/public_key/ext_coding/GH-10404/OTP-19869' into maint
* dgud/public_key/ext_coding/GH-10404/OTP-19869: public_key: Fix encoding and decoding extensions
2 parents 76b366c + 0de5a61 commit bb462d3

File tree

3 files changed

+157
-81
lines changed

3 files changed

+157
-81
lines changed

lib/public_key/src/pubkey_cert_records.erl

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -352,34 +352,58 @@ encode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{
352352

353353
%%% Extensions
354354

355-
extension_id(?'id-ce-authorityKeyIdentifier') -> 'AuthorityKeyIdentifier';
356-
extension_id(?'id-ce-subjectKeyIdentifier') -> 'SubjectKeyIdentifier';
357-
extension_id(?'id-ce-keyUsage') -> 'KeyUsage';
358-
extension_id(?'id-ce-privateKeyUsagePeriod') -> 'PrivateKeyUsagePeriod';
359-
extension_id(?'id-ce-certificatePolicies') -> 'CertificatePolicies';
360-
extension_id(?'id-ce-policyMappings') -> 'PolicyMappings';
361-
extension_id(?'id-ce-subjectAltName') -> 'SubjectAltName';
362-
extension_id(?'id-ce-issuerAltName') -> 'IssuerAltName';
363-
extension_id(?'id-ce-subjectDirectoryAttributes') -> 'SubjectDirectoryAttributes';
364-
extension_id(?'id-ce-basicConstraints' ) -> 'BasicConstraints';
365-
extension_id(?'id-ce-nameConstraints') -> 'NameConstraints';
366-
extension_id(?'id-ce-policyConstraints') -> 'PolicyConstraints';
367-
extension_id(?'id-ce-extKeyUsage') -> 'ExtKeyUsageSyntax';
368-
extension_id(?'id-ce-inhibitAnyPolicy') -> 'InhibitAnyPolicy';
369-
extension_id(?'id-ce-freshestCRL') -> 'FreshestCRL';
370-
extension_id(?'id-ce-issuingDistributionPoint') -> 'IssuingDistributionPoint';
371-
%% Missing in public_key doc
372-
extension_id(?'id-pe-authorityInfoAccess') -> 'AuthorityInfoAccessSyntax';
373-
extension_id(?'id-pe-subjectInfoAccess') -> 'SubjectInfoAccessSyntax';
374-
extension_id(?'id-ce-cRLNumber') -> 'CRLNumber';
375-
extension_id(?'id-ce-deltaCRLIndicator') -> 'BaseCRLNumber';
376-
extension_id(?'id-ce-cRLReasons') -> 'CRLReason';
377-
extension_id(?'id-ce-certificateIssuer') -> 'CertificateIssuer';
378-
extension_id(?'id-ce-holdInstructionCode') -> 'HoldInstructionCode';
379-
extension_id(?'id-ce-invalidityDate') -> 'InvalidityDate';
380-
extension_id(?'id-ce-cRLDistributionPoints') -> 'CRLDistributionPoints';
355+
extension_id(?'id-ce-authorityKeyIdentifier') ->
356+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'AuthorityKeyIdentifier'};
357+
extension_id(?'id-ce-subjectKeyIdentifier') ->
358+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectKeyIdentifier'};
359+
extension_id(?'id-ce-keyUsage') ->
360+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'KeyUsage'};
361+
extension_id(?'id-ce-privateKeyUsagePeriod') ->
362+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PrivateKeyUsagePeriod'};
363+
extension_id(?'id-ce-certificatePolicies') ->
364+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'CertificatePolicies'};
365+
extension_id(?'id-ce-policyMappings') ->
366+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PolicyMappings'};
367+
extension_id(?'id-ce-subjectAltName') ->
368+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectAltName'};
369+
extension_id(?'id-ce-issuerAltName') ->
370+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'IssuerAltName'};
371+
extension_id(?'id-ce-subjectDirectoryAttributes') ->
372+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectDirectoryAttributes'};
373+
extension_id(?'id-ce-basicConstraints' ) ->
374+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'BasicConstraints'};
375+
extension_id(?'id-ce-nameConstraints') ->
376+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'NameConstraints'};
377+
extension_id(?'id-ce-policyConstraints') ->
378+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'PolicyConstraints'};
379+
extension_id(?'id-ce-extKeyUsage') ->
380+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'ExtKeyUsageSyntax'};
381+
extension_id(?'id-ce-inhibitAnyPolicy') ->
382+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'InhibitAnyPolicy'};
383+
extension_id(?'id-ce-freshestCRL') ->
384+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'FreshestCRL'};
385+
extension_id(?'id-ce-issuingDistributionPoint') ->
386+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'IssuingDistributionPoint'};
387+
extension_id(?'id-pe-authorityInfoAccess') ->
388+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'AuthorityInfoAccessSyntax'};
389+
extension_id(?'id-pe-subjectInfoAccess') ->
390+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'SubjectInfoAccessSyntax'};
391+
extension_id(?'id-ce-cRLNumber') ->
392+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'CRLNumber'};
393+
extension_id(?'id-ce-deltaCRLIndicator') ->
394+
{'PKIX1Implicit-2009', getdec_CrlExtensions, getenc_CrlExtensions, 'BaseCRLNumber'};
395+
extension_id(?'id-ce-cRLReasons') ->
396+
{'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'CRLReason'};
397+
extension_id(?'id-ce-certificateIssuer') ->
398+
{'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'CertificateIssuer'};
399+
extension_id(?'id-ce-holdInstructionCode') ->
400+
{'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'HoldInstructionCode'};
401+
extension_id(?'id-ce-invalidityDate') ->
402+
{'PKIX1Implicit-2009', getdec_CrlEntryExtensions, getenc_CrlEntryExtensions, 'InvalidityDate'};
403+
extension_id(?'id-ce-cRLDistributionPoints') ->
404+
{'PKIX1Implicit-2009', getdec_CertExtensions, getenc_CertExtensions, 'CRLDistributionPoints'};
381405
extension_id(_) ->
382-
undefined.
406+
{undefined, undefined, undefined, undefined}.
383407

384408
ext_oid('AuthorityKeyIdentifier') -> ?'id-ce-authorityKeyIdentifier';
385409
ext_oid('SubjectKeyIdentifier') -> ?'id-ce-subjectKeyIdentifier';
@@ -419,10 +443,8 @@ decode_extensions(Exts, WhenCRL) ->
419443
lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) ->
420444
%% Some Extensions only has special decoding functions
421445
%% with other naming-convention
422-
ExtId = extension_id(Id),
423-
case ExtId =/= undefined andalso
424-
'PKIX1Implicit-2009':getdec_CertExtensions(Id)
425-
of
446+
{Mod, DecLookup, _Enc, ExtId} = extension_id(Id),
447+
case ExtId =/= undefined andalso Mod:DecLookup(Id) of
426448
false ->
427449
Ext;
428450
DecodeExt when ExtId =:= 'CertificatePolicies',
@@ -473,10 +495,8 @@ encode_extensions(Exts) ->
473495
%% Some Extensions only has special decoding functions
474496
%% with other naming-convention
475497
lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) ->
476-
ExtId = extension_id(Id),
477-
case ExtId =/= undefined andalso
478-
'PKIX1Implicit-2009':getenc_CertExtensions(Id)
479-
of
498+
{Mod, _Dec, EncLookup, ExtId} = extension_id(Id),
499+
case ExtId =/= undefined andalso Mod:EncLookup(Id) of
480500
false ->
481501
Ext;
482502
EncodeExt when is_function(EncodeExt, 3) ->

lib/public_key/src/public_key.erl

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -608,28 +608,25 @@ der_decode('ExtensionRequest', Value) ->
608608
der_decode('AttributeTypeAndValue', Der) ->
609609
{ok, Decoded} = 'PKIX1Explicit-2009':decode('SingleAttribute', Der),
610610
pubkey_cert_records:transform(Decoded, decode);
611-
der_decode(Asn1ExtType, Der) when Asn1ExtType == 'SubjectAltName';
612-
Asn1ExtType == 'IssuerAltName';
613-
Asn1ExtType == 'ExtKeyUsage';
614-
Asn1ExtType == 'InhibitAnyPolicy';
615-
Asn1ExtType == 'FreshestCRL';
616-
Asn1ExtType == 'AuthorityInfoAccess';
617-
Asn1ExtType == 'DeltaCRLIndicator';
618-
Asn1ExtType == 'CertificateIssuer';
619-
Asn1ExtType == 'HoldInstructionCode';
620-
Asn1ExtType == 'InvalidityDate' ->
621-
Oid = pubkey_cert_records:ext_oid(Asn1ExtType),
622-
[#'Extension'{extnValue = Value}]
623-
= pubkey_cert_records:decode_extensions([#'Extension'{extnID = Oid, extnValue = Der}]),
624-
Value;
625611
der_decode(Asn1Type, Der) when is_atom(Asn1Type), is_binary(Der) ->
626-
Asn1Module = get_asn1_module(Asn1Type),
627-
try
628-
{ok, Decoded} = Asn1Module:decode(Asn1Type, Der),
629-
pubkey_translation:decode(Decoded)
630-
catch
631-
error:{badmatch, {error, _}} = Error ->
632-
erlang:error(Error)
612+
case get_asn1_module(Asn1Type) of
613+
undefined ->
614+
case pubkey_cert_records:ext_oid(Asn1Type) of
615+
undefined ->
616+
error({badarg, {unknown_type, Asn1Type}});
617+
ExtOid ->
618+
Ext = #'Extension'{extnID = ExtOid, extnValue = Der},
619+
[Res] = pubkey_cert_records:decode_extensions([Ext]),
620+
Res#'Extension'.extnValue
621+
end;
622+
Asn1Module when is_atom(Asn1Module) ->
623+
try
624+
{ok, Decoded} = Asn1Module:decode(Asn1Type, Der),
625+
pubkey_translation:decode(Decoded)
626+
catch
627+
error:{badmatch, {error, _}} = Error ->
628+
erlang:error(Error)
629+
end
633630
end.
634631

635632
%% X509 RFC 5280
@@ -640,11 +637,9 @@ get_asn1_module('ExtKeyUsageSyntax') -> 'PKIX1Implicit-2009';
640637
get_asn1_module('KeyUsage') -> 'PKIX1Implicit-2009';
641638
get_asn1_module('Certificate') -> 'PKIX1Explicit-2009';
642639
get_asn1_module('TBSCertificate') -> 'PKIX1Explicit-2009';
643-
get_asn1_module('SubjectAltName') -> 'PKIX1Implicit-2009';
644640
get_asn1_module('CRLDistributionPoints') -> 'PKIX1Implicit-2009';
645641
get_asn1_module('CRLReason') -> 'PKIX1Implicit-2009';
646642
get_asn1_module('CRLNumber') -> 'PKIX1Implicit-2009';
647-
get_asn1_module('FreshestCRL') -> 'PKIX1Implicit-2009';
648643
get_asn1_module('IssuingDistributionPoint') -> 'PKIX1Implicit-2009';
649644
get_asn1_module('GeneralNames') -> 'PKIX1Implicit-2009';
650645
get_asn1_module('SubjectPublicKeyInfo') -> 'PKIX1Explicit-2009';
@@ -710,7 +705,8 @@ get_asn1_module('PBMParameter') -> 'PKIXCMP-2023';
710705
get_asn1_module('ProtectedPart') -> 'PKIXCMP-2023';
711706
%% PKIXCRMF
712707
get_asn1_module('OldCertId') -> 'PKIXCRMF-2009';
713-
get_asn1_module('CertRequest') -> 'PKIXCRMF-2009'.
708+
get_asn1_module('CertRequest') -> 'PKIXCRMF-2009';
709+
get_asn1_module(_) -> undefined.
714710

715711

716712
handle_pkcs_frame_error('PrivateKeyInfo', Der, _) ->
@@ -963,29 +959,26 @@ der_encode('AttributeTypeAndValue', Value) ->
963959
Term = pubkey_cert_records:transform(Value, encode),
964960
{ok, Encoded} = 'PKIX1Explicit-2009':encode('SingleAttribute', Term),
965961
Encoded;
966-
der_encode(Asn1ExtType, Value) when Asn1ExtType == 'SubjectAltName';
967-
Asn1ExtType == 'IssuerAltName';
968-
Asn1ExtType == 'ExtKeyUsage';
969-
Asn1ExtType == 'InhibitAnyPolicy';
970-
Asn1ExtType == 'FreshestCRL';
971-
Asn1ExtType == 'AuthorityInfoAccess';
972-
Asn1ExtType == 'DeltaCRLIndicator';
973-
Asn1ExtType == 'CertificateIssuer';
974-
Asn1ExtType == 'HoldInstructionCode';
975-
Asn1ExtType == 'InvalidityDate' ->
976-
Oid = pubkey_cert_records:ext_oid(Asn1ExtType),
977-
[#'Extension'{extnValue = Encoded}] =
978-
pubkey_cert_records:encode_extensions([#'Extension'{extnID = Oid, extnValue = Value}]),
979-
Encoded;
980962
der_encode(Asn1Type, Entity0) when is_atom(Asn1Type) ->
981-
Asn1Module = get_asn1_module(Asn1Type),
982-
try
983-
Entity = pubkey_translation:encode(Entity0),
984-
{ok, Encoded} = Asn1Module:encode(Asn1Type, Entity),
985-
Encoded
986-
catch
987-
error:{badmatch, {error, _}} = Error ->
988-
erlang:error(Error)
963+
case get_asn1_module(Asn1Type) of
964+
undefined ->
965+
case pubkey_cert_records:ext_oid(Asn1Type) of
966+
undefined ->
967+
error({badarg, {unknown_type, Asn1Type}});
968+
ExtOid ->
969+
Ext = #'Extension'{extnID = ExtOid, extnValue = Entity0},
970+
[Res] = pubkey_cert_records:encode_extensions([Ext]),
971+
Res#'Extension'.extnValue
972+
end;
973+
Asn1Module when is_atom(Asn1Module) ->
974+
try
975+
Entity = pubkey_translation:encode(Entity0),
976+
{ok, Encoded} = Asn1Module:encode(Asn1Type, Entity),
977+
Encoded
978+
catch
979+
error:{badmatch, {error, _}} = Error ->
980+
erlang:error(Error)
981+
end
989982
end.
990983

991984
%%--------------------------------------------------------------------

lib/public_key/test/public_key_SUITE.erl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
encrypted_pem_pwdstring/1,
7878
encrypted_pem_pwdfun/0,
7979
encrypted_pem_pwdfun/1,
80+
ext_encoding/1,
8081
dh_pem/0,
8182
dh_pem/1,
8283
pkcs10_pem/0,
@@ -191,6 +192,7 @@ all() ->
191192
{group, pem_decode_encode},
192193
encrypt_decrypt,
193194
encrypt_decrypt_sign_fun,
195+
ext_encoding,
194196
{group, sign_verify},
195197
pkix,
196198
pkix_cmp,
@@ -777,6 +779,67 @@ cert_pem(Config) when is_list(Config) ->
777779
asn1_encode_decode(Entry1),
778780
asn1_encode_decode(Entry2).
779781

782+
%%--------------------------------------------------------------------
783+
ext_encoding(_Config) ->
784+
Exts = [
785+
{'AuthorityKeyIdentifier',
786+
#'AuthorityKeyIdentifier'{authorityCertIssuer = [{rfc822Name, "a name"}]}},
787+
{'SubjectKeyIdentifier', "Octet String"},
788+
{'KeyUsage', [keyEncipherment, encipherOnly]},
789+
{'PrivateKeyUsagePeriod', #'PrivateKeyUsagePeriod'{notAfter = "20220127000000Z"}},
790+
{'CertificatePolicies', [#'PolicyInformation'{policyIdentifier = ?'anyPolicy'}]},
791+
{'PolicyMappings', [#'PolicyMappings_SEQOF'{issuerDomainPolicy = ?'anyPolicy',
792+
subjectDomainPolicy = ?'anyPolicy'}]},
793+
{'SubjectAltName', [{rfc822Name, "a name"}]},
794+
{'IssuerAltName', [{rfc822Name, "a name"}]},
795+
{'SubjectDirectoryAttributes', [#'AttributeSet'{type = ?'id-at-name',
796+
values = [{utf8String, ~"a string"}]}]},
797+
{'BasicConstraints', #'BasicConstraints'{cA = true}},
798+
{'NameConstraints', #'NameConstraints'{}},
799+
{'PolicyConstraints', #'PolicyConstraints'{requireExplicitPolicy = 5}},
800+
{'ExtKeyUsageSyntax', [?anyPolicy]},
801+
{'InhibitAnyPolicy', 42},
802+
{'FreshestCRL', [#'DistributionPoint'{}]},
803+
{'IssuingDistributionPoint',
804+
#'IssuingDistributionPoint'{indirectCRL = true,
805+
onlyContainsUserCerts = false,
806+
onlyContainsCACerts = false,
807+
onlyContainsAttributeCerts = false
808+
}},
809+
{'AuthorityInfoAccessSyntax',
810+
[#'AccessDescription'{accessMethod = ?'id-at-name',
811+
accessLocation = {rfc822Name, "a name"}}]},
812+
{'SubjectInfoAccessSyntax',
813+
[#'AccessDescription'{accessMethod = ?'id-at-name',
814+
accessLocation = {rfc822Name, "a name"}}]},
815+
{'CRLNumber', 4711},
816+
{'BaseCRLNumber', 4710},
817+
{'CRLReason', 'cessationOfOperation'},
818+
{'CertificateIssuer', [{rfc822Name, "a name"}]},
819+
{'HoldInstructionCode', ?'id-holdinstruction-callissuer'},
820+
{'InvalidityDate', "20220127000000Z"},
821+
{'CRLDistributionPoints', [#'DistributionPoint'{}]}
822+
],
823+
Check = fun({Ext, Value}) ->
824+
try
825+
Der = public_key:der_encode(Ext, Value),
826+
true = is_binary(Der),
827+
case public_key:der_decode(Ext, Der) of
828+
Value -> false;
829+
Bin when is_binary(Bin) ->
830+
Value =/= binary_to_list(Bin)
831+
end
832+
catch Err:Reason:St ->
833+
{true, {fail, Ext, Value, Err, Reason, St}}
834+
end
835+
end,
836+
[] = lists:filtermap(Check, Exts),
837+
838+
Badarg = {badarg, {unknown_type, type_do_not_exist}},
839+
{'EXIT', {Badarg,_}} = (catch public_key:der_encode(type_do_not_exist, 4711)),
840+
{'EXIT', {Badarg,_}} = (catch public_key:der_decode(type_do_not_exist, <<47,11>>)),
841+
ok.
842+
780843
%%--------------------------------------------------------------------
781844
encrypt_decrypt() ->
782845
[{doc, "Test public_key:encrypt_private and public_key:decrypt_public"}].

0 commit comments

Comments
 (0)