%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% -module(pubkey_cert_records). -include("public_key.hrl"). -export([decode_cert/1, transform/2, supportedPublicKeyAlgorithms/1, supportedCurvesTypes/1, namedCurves/1]). %%==================================================================== %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- -spec decode_cert(DerCert::binary()) -> {ok, #'OTPCertificate'{}}. %% %% Description: Recursively decodes a Certificate. %%-------------------------------------------------------------------- decode_cert(DerCert) -> {ok, Cert} = 'OTP-PUB-KEY':decode('OTPCertificate', DerCert), #'OTPCertificate'{tbsCertificate = TBS} = Cert, {ok, Cert#'OTPCertificate'{tbsCertificate = decode_tbs(TBS)}}. %%-------------------------------------------------------------------- -spec transform(term(), encode | decode) ->term(). %% %% Description: Transforms between encoded and decode otp formated %% certificate parts. %%-------------------------------------------------------------------- transform(#'OTPCertificate'{tbsCertificate = TBS} = Cert, encode) -> Cert#'OTPCertificate'{tbsCertificate=encode_tbs(TBS)}; transform(#'OTPCertificate'{tbsCertificate = TBS} = Cert, decode) -> Cert#'OTPCertificate'{tbsCertificate=decode_tbs(TBS)}; transform(#'OTPTBSCertificate'{}= TBS, encode) -> encode_tbs(TBS); transform(#'OTPTBSCertificate'{}= TBS, decode) -> decode_tbs(TBS); transform(#'AttributeTypeAndValue'{type=Id,value=Value0} = ATAV, Func) -> {ok, Value} = case attribute_type(Id) of 'X520countryName'when Func == decode -> %% Workaround that some certificates break the ASN-1 spec %% and encode countryname as utf8 case 'OTP-PUB-KEY':Func('OTP-X520countryname', Value0) of {ok, {utf8String, Utf8Value}} -> {ok, unicode:characters_to_list(Utf8Value)}; {ok, {printableString, ASCCI}} -> {ok, ASCCI} end; 'EmailAddress' when Func == decode -> %% Workaround that some certificates break the ASN-1 spec %% and encode emailAddress as utf8 case 'OTP-PUB-KEY':Func('OTP-emailAddress', Value0) of {ok, {utf8String, Utf8Value}} -> {ok, unicode:characters_to_list(Utf8Value)}; {ok, {ia5String, Ia5Value}} -> {ok, Ia5Value} end; Type when is_atom(Type) -> 'OTP-PUB-KEY':Func(Type, Value0); _UnknownType -> {ok, Value0} end, ATAV#'AttributeTypeAndValue'{value=Value}; transform(AKI = #'AuthorityKeyIdentifier'{authorityCertIssuer=ACI},Func) -> AKI#'AuthorityKeyIdentifier'{authorityCertIssuer=transform(ACI,Func)}; transform(List = [{directoryName, _}],Func) -> [{directoryName, transform(Value,Func)} || {directoryName, Value} <- List]; transform({directoryName, Value},Func) -> {directoryName, transform(Value,Func)}; transform({rdnSequence, SeqList},Func) when is_list(SeqList) -> {rdnSequence, lists:map(fun(Seq) -> lists:map(fun(Element) -> transform(Element,Func) end, Seq) end, SeqList)}; transform(#'NameConstraints'{permittedSubtrees=Permitted, excludedSubtrees=Excluded}, Func) -> #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func), excludedSubtrees=transform_sub_tree(Excluded,Func)}; transform(Other,_) -> Other. %%-------------------------------------------------------------------- -spec supportedPublicKeyAlgorithms(Oid::tuple()) -> public_key:asn1_type(). %% %% Description: Returns the public key type for an algorithm %% identifier tuple as found in SubjectPublicKeyInfo. %% %%-------------------------------------------------------------------- supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey'; supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey'; supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey'; supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey'; supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'. supportedCurvesTypes(?'characteristic-two-field') -> characteristic_two_field; supportedCurvesTypes(?'prime-field') -> prime_field. namedCurves(?'sect571r1') -> sect571r1; namedCurves(?'sect571k1') -> sect571k1; namedCurves(?'sect409r1') -> sect409r1; namedCurves(?'sect409k1') -> sect409k1; namedCurves(?'secp521r1') -> secp521r1; namedCurves(?'secp384r1') -> secp384r1; namedCurves(?'secp224r1') -> secp224r1; namedCurves(?'secp224k1') -> secp224k1; namedCurves(?'secp192k1') -> secp192k1; namedCurves(?'secp160r2') -> secp160r2; namedCurves(?'secp128r2') -> secp128r2; namedCurves(?'secp128r1') -> secp128r1; namedCurves(?'sect233r1') -> sect233r1; namedCurves(?'sect233k1') -> sect233k1; namedCurves(?'sect193r2') -> sect193r2; namedCurves(?'sect193r1') -> sect193r1; namedCurves(?'sect131r2') -> sect131r2; namedCurves(?'sect131r1') -> sect131r1; namedCurves(?'sect283r1') -> sect283r1; namedCurves(?'sect283k1') -> sect283k1; namedCurves(?'sect163r2') -> sect163r2; namedCurves(?'secp256k1') -> secp256k1; namedCurves(?'secp160k1') -> secp160k1; namedCurves(?'secp160r1') -> secp160r1; namedCurves(?'secp112r2') -> secp112r2; namedCurves(?'secp112r1') -> secp112r1; namedCurves(?'sect113r2') -> sect113r2; namedCurves(?'sect113r1') -> sect113r1; namedCurves(?'sect239k1') -> sect239k1; namedCurves(?'sect163r1') -> sect163r1; namedCurves(?'sect163k1') -> sect163k1; namedCurves(?'secp256r1') -> secp256r1; namedCurves(?'secp192r1') -> secp192r1; namedCurves(?'brainpoolP160r1') -> brainpoolP160r1; namedCurves(?'brainpoolP160t1') -> brainpoolP160t1; namedCurves(?'brainpoolP192r1') -> brainpoolP192r1; namedCurves(?'brainpoolP192t1') -> brainpoolP192t1; namedCurves(?'brainpoolP224r1') -> brainpoolP224r1; namedCurves(?'brainpoolP224t1') -> brainpoolP224t1; namedCurves(?'brainpoolP256r1') -> brainpoolP256r1; namedCurves(?'brainpoolP256t1') -> brainpoolP256t1; namedCurves(?'brainpoolP320r1') -> brainpoolP320r1; namedCurves(?'brainpoolP320t1') -> brainpoolP320t1; namedCurves(?'brainpoolP384r1') -> brainpoolP384r1; namedCurves(?'brainpoolP384t1') -> brainpoolP384t1; namedCurves(?'brainpoolP512r1') -> brainpoolP512r1; namedCurves(?'brainpoolP512t1') -> brainpoolP512t1; namedCurves(sect571r1) -> ?'sect571r1'; namedCurves(sect571k1) -> ?'sect571k1'; namedCurves(sect409r1) -> ?'sect409r1'; namedCurves(sect409k1) -> ?'sect409k1'; namedCurves(secp521r1) -> ?'secp521r1'; namedCurves(secp384r1) -> ?'secp384r1'; namedCurves(secp224r1) -> ?'secp224r1'; namedCurves(secp224k1) -> ?'secp224k1'; namedCurves(secp192k1) -> ?'secp192k1'; namedCurves(secp160r2) -> ?'secp160r2'; namedCurves(secp128r2) -> ?'secp128r2'; namedCurves(secp128r1) -> ?'secp128r1'; namedCurves(sect233r1) -> ?'sect233r1'; namedCurves(sect233k1) -> ?'sect233k1'; namedCurves(sect193r2) -> ?'sect193r2'; namedCurves(sect193r1) -> ?'sect193r1'; namedCurves(sect131r2) -> ?'sect131r2'; namedCurves(sect131r1) -> ?'sect131r1'; namedCurves(sect283r1) -> ?'sect283r1'; namedCurves(sect283k1) -> ?'sect283k1'; namedCurves(sect163r2) -> ?'sect163r2'; namedCurves(secp256k1) -> ?'secp256k1'; namedCurves(secp160k1) -> ?'secp160k1'; namedCurves(secp160r1) -> ?'secp160r1'; namedCurves(secp112r2) -> ?'secp112r2'; namedCurves(secp112r1) -> ?'secp112r1'; namedCurves(sect113r2) -> ?'sect113r2'; namedCurves(sect113r1) -> ?'sect113r1'; namedCurves(sect239k1) -> ?'sect239k1'; namedCurves(sect163r1) -> ?'sect163r1'; namedCurves(sect163k1) -> ?'sect163k1'; namedCurves(secp256r1) -> ?'secp256r1'; namedCurves(secp192r1) -> ?'secp192r1'; namedCurves(brainpoolP160r1) -> ?'brainpoolP160r1'; namedCurves(brainpoolP160t1) -> ?'brainpoolP160t1'; namedCurves(brainpoolP192r1) -> ?'brainpoolP192r1'; namedCurves(brainpoolP192t1) -> ?'brainpoolP192t1'; namedCurves(brainpoolP224r1) -> ?'brainpoolP224r1'; namedCurves(brainpoolP224t1) -> ?'brainpoolP224t1'; namedCurves(brainpoolP256r1) -> ?'brainpoolP256r1'; namedCurves(brainpoolP256t1) -> ?'brainpoolP256t1'; namedCurves(brainpoolP320r1) -> ?'brainpoolP320r1'; namedCurves(brainpoolP320t1) -> ?'brainpoolP320t1'; namedCurves(brainpoolP384r1) -> ?'brainpoolP384r1'; namedCurves(brainpoolP384t1) -> ?'brainpoolP384t1'; namedCurves(brainpoolP512r1) -> ?'brainpoolP512r1'; namedCurves(brainpoolP512t1) -> ?'brainpoolP512t1'. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %%% SubjectPublicKey decode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = #'PublicKeyAlgorithm'{algorithm=Algo}, subjectPublicKey = SPK0}) -> Type = supportedPublicKeyAlgorithms(Algo), SPK = case Type of 'ECPoint' -> #'ECPoint'{point = SPK0}; _ -> {ok, SPK1} = 'OTP-PUB-KEY':decode(Type, SPK0), SPK1 end, #'OTPSubjectPublicKeyInfo'{subjectPublicKey = SPK, algorithm=PA}. encode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = #'PublicKeyAlgorithm'{algorithm=Algo}, subjectPublicKey = SPK0}) -> Type = supportedPublicKeyAlgorithms(Algo), SPK = case Type of 'ECPoint' -> SPK0#'ECPoint'.point; _ -> {ok, SPK1} = 'OTP-PUB-KEY':encode(Type, SPK0), SPK1 end, #'OTPSubjectPublicKeyInfo'{subjectPublicKey = SPK, algorithm=PA}. %%% 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-cRLDistributionPoints') -> 'CRLDistributionPoints'; extension_id(?'id-ce-extKeyUsage') -> 'ExtKeyUsageSyntax'; extension_id(?'id-ce-inhibitAnyPolicy') -> 'InhibitAnyPolicy'; extension_id(?'id-ce-freshestCRL') -> 'FreshestCRL'; %% 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-issuingDistributionPoint') -> 'IssuingDistributionPoint'; 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(_) -> undefined. decode_extensions(asn1_NOVALUE) -> asn1_NOVALUE; decode_extensions(Exts) -> lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) -> case extension_id(Id) of undefined -> Ext; Type -> {ok, Value} = 'OTP-PUB-KEY':decode(Type, iolist_to_binary(Value0)), Ext#'Extension'{extnValue=transform(Value,decode)} end end, Exts). encode_extensions(asn1_NOVALUE) -> asn1_NOVALUE; encode_extensions(Exts) -> lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) -> case extension_id(Id) of undefined -> Ext; Type -> Value1 = transform(Value0,encode), {ok, Value} = 'OTP-PUB-KEY':encode(Type, Value1), Ext#'Extension'{extnValue=Value} end end, Exts). encode_tbs(TBS=#'OTPTBSCertificate'{issuer=Issuer0, subject=Subject0, subjectPublicKeyInfo=Spki0, extensions=Exts0}) -> Issuer = transform(Issuer0,encode), Subject = transform(Subject0,encode), Spki = encode_supportedPublicKey(Spki0), Exts = encode_extensions(Exts0), TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject, subjectPublicKeyInfo=Spki,extensions=Exts}. decode_tbs(TBS = #'OTPTBSCertificate'{issuer=Issuer0, subject=Subject0, subjectPublicKeyInfo=Spki0, extensions=Exts0}) -> Issuer = transform(Issuer0,decode), Subject = transform(Subject0,decode), Spki = decode_supportedPublicKey(Spki0), Exts = decode_extensions(Exts0), TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject, subjectPublicKeyInfo=Spki,extensions=Exts}. transform_sub_tree(asn1_NOVALUE,_) -> asn1_NOVALUE; transform_sub_tree(TreeList,Func) -> [Tree#'GeneralSubtree'{base=transform(Name,Func)} || Tree = #'GeneralSubtree'{base=Name} <- TreeList]. attribute_type(?'id-at-name') -> 'X520name'; attribute_type(?'id-at-surname') -> 'X520name'; attribute_type(?'id-at-givenName') -> 'X520name'; attribute_type(?'id-at-initials') -> 'X520name'; attribute_type(?'id-at-generationQualifier') -> 'X520name'; attribute_type(?'id-at-commonName') -> 'X520CommonName'; attribute_type(?'id-at-localityName') -> 'X520LocalityName'; attribute_type(?'id-at-stateOrProvinceName') -> 'X520StateOrProvinceName'; attribute_type(?'id-at-organizationName') -> 'X520OrganizationName'; attribute_type(?'id-at-organizationalUnitName') -> 'X520OrganizationalUnitName'; attribute_type(?'id-at-title') -> 'X520Title'; attribute_type(?'id-at-dnQualifier') -> 'X520dnQualifier'; attribute_type(?'id-at-countryName') -> 'X520countryName'; attribute_type(?'id-at-serialNumber') -> 'X520SerialNumber'; attribute_type(?'id-at-pseudonym') -> 'X520Pseudonym'; attribute_type(?'id-domainComponent') -> 'DomainComponent'; attribute_type(?'id-emailAddress') -> 'EmailAddress'; attribute_type(Type) -> Type.