diff options
Diffstat (limited to 'lib/public_key')
44 files changed, 3304 insertions, 1654 deletions
diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1 index 2bcacc0990..ad704191a9 100644 --- a/lib/public_key/asn1/OTP-PKIX.asn1 +++ b/lib/public_key/asn1/OTP-PKIX.asn1 @@ -302,18 +302,25 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { -- DSA Keys and Signatures + + DSAParams ::= CHOICE + { + params Dss-Parms, + null NULL + } + -- SubjectPublicKeyInfo: dsa PUBLIC-KEY-ALGORITHM-CLASS ::= { ID id-dsa - TYPE Dss-Parms -- XXX Must be OPTIONAL + TYPE DSAParams -- XXX Must be OPTIONAL PUBLIC-KEY-TYPE DSAPublicKey } -- Certificate.signatureAlgorithm dsa-with-sha1 SIGNATURE-ALGORITHM-CLASS ::= { - ID id-dsa-with-sha1 - TYPE NULL } -- XXX Must be empty and not NULL + ID id-dsa-with-sha1 + TYPE DSAParams } -- -- RSA Keys and Signatures diff --git a/lib/public_key/doc/src/book.xml b/lib/public_key/doc/src/book.xml index d3b8c7a2c7..f8d1205e57 100644 --- a/lib/public_key/doc/src/book.xml +++ b/lib/public_key/doc/src/book.xml @@ -5,7 +5,7 @@ <header titlestyle="normal"> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/doc/src/cert_records.xml b/lib/public_key/doc/src/cert_records.xml index 8cfe57f670..ad4f5812cb 100644 --- a/lib/public_key/doc/src/cert_records.xml +++ b/lib/public_key/doc/src/cert_records.xml @@ -1,11 +1,11 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="iso-8859-1" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -37,7 +37,7 @@ <p>This chapter briefly describes erlang records derived from asn1 specifications used to handle X509 certificates. The intent is to describe the data types and not to specify the meaning of each - component for this we refer you to RFC 3280. + component for this we refer you to RFC 5280. </p> <p>Use the following include directive to get access to the @@ -45,11 +45,7 @@ <code> -include_lib("public_key/include/public_key.hrl"). </code> - <p>The used specification is available in <c>OTP-PKIX.asn1</c>, - which is an amelioration of - the <c>PKIX1Explicit88.asn1</c>, <c>PKIX1Implicit88.asn1</c> - and <c>PKIX1Algorithms88.asn1</c> modules. - You find all these modules in the <c>asn1</c> subdirectory + <p>The used asn1 specifications are available <c>asn1</c> subdirectory of the application <c>public_key</c>. </p> @@ -62,6 +58,9 @@ marker="public_key">public key reference manual </seealso> or follows here.</p> + <p><c>oid() - a tuple of integers + as generated by the asn1 compiler.</c></p> + <p><c>time() = uct_time() | general_time()</c></p> <p><c>uct_time() = {utcTime, "YYMMDDHHMMSSZ"} </c></p> @@ -119,9 +118,31 @@ algorithm, % oid() parameters % asn1_der_encoded() }. +</code> + +<code> +#'OTPCertificate'{ + tbsCertificate, % #'OTPTBSCertificate'{} + signatureAlgorithm, % #'SignatureAlgorithm' + signature % {0, binary()} - asn1 compact bitstring + }. + +#'OTPTBSCertificate'{ + version, % v1 | v2 | v3 + serialNumber, % integer() + signature, % #'SignatureAlgorithm' + issuer, % {rdnSequence, [#AttributeTypeAndValue'{}]} + validity, % #'Validity'{} + subject, % {rdnSequence, [#AttributeTypeAndValue'{}]} + subjectPublicKeyInfo, % #'SubjectPublicKeyInfo'{} + issuerUniqueID, % binary() | asn1_novalue + subjectUniqueID, % binary() | asn1_novalue + extensions % [#'Extension'{}] + }. + #'SignatureAlgorithm'{ algorithm, % id_signature_algorithm() - parameters % public_key_params() + parameters % asn1_novalue | #'Dss-Parms'{} }. </code> diff --git a/lib/public_key/doc/src/introduction.xml b/lib/public_key/doc/src/introduction.xml index 71488e435a..8cf11ee10e 100644 --- a/lib/public_key/doc/src/introduction.xml +++ b/lib/public_key/doc/src/introduction.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index 33a424f432..14b43041ce 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -1,11 +1,11 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="iso-8859-1" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2010</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -33,6 +33,159 @@ <rev>A</rev> <file>notes.xml</file> </header> + +<section><title>Public_Key 0.11</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Allows the public_key module to decode and encode RSA and + DSA keys encoded using the SubjectPublicKeyInfo format. + When pem_entry_encode is called on an RSA or DSA public + key type, the key is wrapped in the SubjectPublicKeyInfo + format.</p> + <p> + Own Id: OTP-9061</p> + </item> + </list> + </section> + +</section> + +<section><title>Public_Key 0.10</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Improved dialyzer specs.</p> + <p> + Own Id: OTP-8964</p> + </item> + </list> + </section> + +</section> + +<section><title>Public_Key 0.9</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Updated ssl to ignore CA certs that violate the asn1-spec + for a certificate, and updated public key asn1 spec to + handle inherited DSS-params.</p> + <p> + Own Id: OTP-7884</p> + </item> + <item> + <p> + Changed ssl implementation to retain backwards + compatibility for old option {verify, 0} that shall be + equivalent to {verify, verify_none}, also separate the + cases unknown ca and selfsigned peer cert, and restored + return value of deprecated function + public_key:pem_to_der/1.</p> + <p> + Own Id: OTP-8858</p> + </item> + <item> + <p> + Better handling of v1 and v2 certificates. V1 and v2 + certificates does not have any extensions so then + validate_extensions should just accept that there are + none and not end up in missing_basic_constraints clause.</p> + <p> + Own Id: OTP-8867</p> + </item> + <item> + <p> + Changed the verify fun so that it differentiate between + the peer certificate and CA certificates by using + valid_peer or valid as the second argument to the verify + fun. It may not always be trivial or even possible to + know when the peer certificate is reached otherwise.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-8873</p> + </item> + </list> + </section> + +</section> + +<section><title>Public_Key 0.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handling of unknown CA certificates was changed in ssl + and public_key to work as intended.</p> + <p> + Own Id: OTP-8788</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Revise the public_key API - Cleaned up and documented the + public_key API to make it useful for general use, also + changed ssl to use the new API.</p> + <p> + Own Id: OTP-8722</p> + </item> + <item> + <p> + Added the functionality so that the verification fun will + be called when a certificate is considered valid by the + path validation to allow access to each certificate in + the path to the user application. Also try to verify + subject-AltName, if unable to verify it let the + application verify it.</p> + <p> + Own Id: OTP-8825</p> + </item> + </list> + </section> + +</section> + +<section><title>Public_Key 0.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Certificates without any extensions could not be handled + by public_key.</p> + <p> + Own Id: OTP-8626</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Code cleanup and minor bugfixes.</p> + <p> + Own Id: OTP-8649</p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 0.6</title> <section><title>Improvements and New Features</title> diff --git a/lib/public_key/doc/src/part.xml b/lib/public_key/doc/src/part.xml index b85fa063ce..c338a71613 100644 --- a/lib/public_key/doc/src/part.xml +++ b/lib/public_key/doc/src/part.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/doc/src/part_notes.xml b/lib/public_key/doc/src/part_notes.xml index 37ca516bc8..f855e76a6d 100644 --- a/lib/public_key/doc/src/part_notes.xml +++ b/lib/public_key/doc/src/part_notes.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index dc9a96906f..c5f57214b1 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -1,11 +1,11 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="iso-8859-1" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd"> <erlref> <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -34,11 +34,7 @@ <modulesummary> API module for public key infrastructure.</modulesummary> <description> <p>This module provides functions to handle public key infrastructure - from RFC 3280 - X.509 certificates (will later be upgraded to RFC 5280) - and some parts of the PKCS-standard. - Currently this application is mainly used by the new - ssl implementation. The API is yet under construction - and only a few of the functions are currently documented and thereby supported. + from RFC 5280 - X.509 certificates and some parts of the PKCS-standard. </p> </description> @@ -60,44 +56,43 @@ <p><em>Data Types </em></p> - <p><c>boolean() = true | false</c></p> - - <p><c>string = [bytes()]</c></p> - - <p><c>asn1_der_encoded() = binary() | [bytes()]</c></p> + <p><code>boolean() = true | false</code></p> - <p><c>der_bin() = binary() </c></p> + <p><code>string = [bytes()]</code></p> + + <p><code>pki_asn1_type() = 'Certificate' | 'RSAPrivateKey'| 'RSAPublicKey' + 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' | 'SubjectPublicKeyInfo'</code></p> - <p><c>oid() - a tuple of integers - as generated by the asn1 compiler.</c></p> - - <p><c>public_key() = rsa_public_key() | dsa_public_key()</c></p> - - <p><c>rsa_public_key() = #'RSAPublicKey'{}</c></p> + <p><code>pem_entry () = {pki_asn1_type(), binary() %% DER or encrypted DER + not_encrypted | {"DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)}}.</code></p> - <p><c>rsa_private_key() = #'RSAPrivateKey'{} </c></p> + <p><code>rsa_public_key() = #'RSAPublicKey'{}</code></p> + + <p><code>rsa_private_key() = #'RSAPrivateKey'{} </code></p> - <p><c>dsa_public_key() = integer() </c></p> + <p><code>dsa_public_key() = {integer(), #'Dss-Parms'{}} </code></p> - <p><c>public_key_params() = dsa_key_params() </c></p> + <p><code>rsa_private_key() = #'RSAPrivateKey'{} </code></p> - <p><c>dsa_key_params() = #'Dss-Parms'{} </c></p> + <p><code>dsa_private_key() = #'DSAPrivateKey'{}</code></p> - <p><c>private_key() = rsa_private_key() | dsa_private_key()</c></p> + <p><code> public_crypt_options() = [{rsa_pad, rsa_padding()}]. </code></p> - <p><c>rsa_private_key() = #'RSAPrivateKey'{} </c></p> + <p><code> rsa_padding() = 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding' + | 'rsa_no_padding'</code></p> - <p><c>dsa_private_key() = #'DSAPrivateKey'{}</c></p> + <p><code> rsa_digest_type() = 'md5' | 'sha' </code></p> - <p><c>x509_certificate() = "#Certificate{}"</c></p> + <p><code> dss_digest_type() = 'none' | 'sha' </code></p> - <p><c>x509_tbs_certificate() = #'TBSCertificate'{} </c></p> - -<!-- <p><c>policy_tree() = [Root, Children]</c></p> --> + <p><code> ssh_file() = openssh_public_key | rfc4716_public_key | + known_hosts | auth_keys </code></p> + +<!-- <p><code>policy_tree() = [Root, Children]</code></p> --> -<!-- <p><c>Root = #policy_tree_node{}</c></p> --> +<!-- <p><code>Root = #policy_tree_node{}</code></p> --> -<!-- <p><c>Children = [] | policy_tree()</c></p> --> +<!-- <p><code>Children = [] | policy_tree()</code></p> --> <!-- <p> The policy_tree_node record has the following fields:</p> --> @@ -121,197 +116,357 @@ <!-- that would satisfy this policy in the certificate x+1. </item> --> <!-- </taglist> --> </section> - -<funcs> - <func> - <name>decode_private_key(KeyInfo) -> </name> - <name>decode_private_key(KeyInfo, Password) -> {ok, PrivateKey} | {error, Reason}</name> - <fsummary> Decodes an asn1 der encoded private key.</fsummary> - <type> - <v> KeyInfo = {KeyType, der_bin(), ChipherInfo} </v> - <d> As returned from pem_to_der/1 for private keys</d> - <v> KeyType = rsa_private_key | dsa_private_key </v> - <v> ChipherInfo = opaque() | no_encryption </v> - <d> ChipherInfo may contain encryption parameters if the private key is password - protected, these are opaque to the user just pass the value returned by pem_to_der/1 - to this function.</d> - <v> Password = string() </v> - <d>Must be specified if CipherInfo =/= no_encryption</d> - <v> PrivateKey = private_key() </v> - <v> Reason = term() </v> - </type> - <desc> - <p>Decodes an asn1 der encoded private key.</p> - </desc> + +<funcs> + + <func> + <name>decrypt_private(CipherText, Key [, Options]) -> binary()</name> + <fsummary>Public key decryption.</fsummary> + <type> + <v>CipherText = binary()</v> + <v>Key = rsa_private_key()</v> + <v>Options = public_crypt_options()</v> + </type> + <desc> + <p>Public key decryption using the private key.</p> + </desc> + </func> + + <func> + <name>decrypt_public(CipherText, Key [, Options]) - > binary()</name> + <fsummary></fsummary> + <type> + <v>CipherText = binary()</v> + <v>Key = rsa_public_key()</v> + <v>Options = public_crypt_options()</v> + </type> + <desc> + <p> Public key decryption using the public key.</p> + </desc> </func> - + <func> - <name>pem_to_der(File) -> {ok, [Entry]}</name> - <fsummary>Reads a PEM file and translates it into its asn1 der - encoded parts.</fsummary> + <name>der_decode(Asn1type, Der) -> term()</name> + <fsummary> Decodes a public key asn1 der encoded entity.</fsummary> <type> - <v>File = path()</v> - <v>Password = string()</v> - <v>Entry = {entry_type(), der_bin(), CipherInfo}</v> - <v> ChipherInfo = opaque() | no_encryption </v> - <d> ChipherInfo may contain encryption parameters if the private key is password - protected, these will be handled by the function decode_private_key/2. </d> - <v>entry_type() = cert | cert_req | rsa_private_key | dsa_private_key | - dh_params </v> + <v>Asn1Type = atom() -</v> + <d> Asn1 type present in the public_key applications + asn1 specifications.</d> + <v>Der = der_encoded()</v> + </type> + <desc> + <p> Decodes a public key asn1 der encoded entity.</p> + </desc> + </func> + + <func> + <name>der_encode(Asn1Type, Entity) -> der_encoded()</name> + <fsummary> Encodes a public key entity with asn1 DER encoding.</fsummary> + <type> + <v>Asn1Type = atom()</v> + <d> Asn1 type present in the public_key applications + asn1 specifications.</d> + <v>Entity = term() - The erlang representation of <c> Asn1Type</c></v> </type> <desc> - <p>Reads a PEM file and translates it into its asn1 der - encoded parts.</p> + <p> Encodes a public key entity with asn1 DER encoding.</p> + </desc> + </func> + + <func> + <name>pem_decode(PemBin) -> [pem_entry()]</name> + <fsummary>Decode PEM binary data and return + entries as asn1 der encoded entities. </fsummary> + <type> + <v>PemBin = binary()</v> + <d>Example {ok, PemBin} = file:read_file("cert.pem").</d> + </type> + <desc> + <p>Decode PEM binary data and return + entries as asn1 der encoded entities.</p> </desc> </func> - <func> - <name>pkix_decode_cert(Cert, Type) -> {ok, DecodedCert} | {error, Reason}</name> - <fsummary> Decodes an asn1 der encoded pkix certificate. </fsummary> - <type> - <v>Cert = asn1_der_encoded() </v> - <v>Type = plain | otp</v> - <v>DecodeCert = x509_certificate() </v> - <d>When type is specified as otp the asn1 spec OTP-PKIX.asn1 is used to decode known - extensions and enhance the signature field in - #'Certificate'{} and '#TBSCertificate'{}. This is currently used by the new ssl - implementation but not documented and supported for the public_key application.</d> - <v>Reason = term() </v> + <func> + <name>pem_encode(PemEntries) -> binary()</name> + <fsummary>Creates a PEM binary</fsummary> + <type> + <v> PemEntries = [pem_entry()] </v> + </type> + <desc> + <p>Creates a PEM binary</p> + </desc> + </func> + + <func> + <name>pem_entry_decode(PemEntry [, Password]) -> term()</name> + <fsummary>Decodes a pem entry.</fsummary> + <type> + <v> PemEntry = pem_entry() </v> + <v> Password = string() </v> + </type> + <desc> + <p>Decodes a pem entry. pem_decode/1 returns a list of pem + entries. Note that if the pem entry is of type + 'SubjectPublickeyInfo' it will be further decoded to an + rsa_public_key() or dsa_public_key().</p> + </desc> + </func> + + <func> + <name>pem_entry_encode(Asn1Type, Entity [,{CipherInfo, Password}]) -> pem_entry()</name> + <fsummary> Creates a pem entry that can be fed to pem_encode/1.</fsummary> + <type> + <v>Asn1Type = pki_asn1_type()</v> + <v>Entity = term() - The Erlang representation of + <c>Asn1Type</c>. If <c>Asn1Type</c> is 'SubjectPublicKeyInfo' + then <c>Entity</c> must be either an rsa_public_key() or a + dsa_public_key() and this function will create the appropriate + 'SubjectPublicKeyInfo' entry. + </v> + <v>CipherInfo = {"DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)}</v> + <v>Password = string()</v> + </type> + <desc> + <p> Creates a pem entry that can be feed to pem_encode/1.</p> + </desc> + </func> + + <func> + <name>encrypt_private(PlainText, Key) -> binary()</name> + <fsummary> Public key encryption using the private key.</fsummary> + <type> + <v>PlainText = binary()</v> + <v>Key = rsa_private_key()</v> + </type> + <desc> + <p> Public key encryption using the private key.</p> + </desc> + </func> + + <func> + <name>encrypt_public(PlainText, Key) -> binary()</name> + <fsummary> Public key encryption using the public key.</fsummary> + <type> + <v>PlainText = binary()</v> + <v>Key = rsa_public_key()</v> + </type> + <desc> + <p> Public key encryption using the public key.</p> + </desc> + </func> + + <func> + <name>pkix_decode_cert(Cert, otp|plain) -> #'Certificate'{} | #'OTPCertificate'{}</name> + <fsummary> Decodes an asn1 der encoded pkix x509 certificate.</fsummary> + <type> + <v>Cert = der_encoded()</v> + </type> + <desc> + <p>Decodes an asn1 der encoded pkix certificate. The otp option + will use the customized asn1 specification OTP-PKIX.asn1 for + decoding and also recursively decode most of the standard + parts.</p> + </desc> + </func> + + <func> + <name>pkix_encode(Asn1Type, Entity, otp | plain) -> der_encoded()</name> + <fsummary>Der encodes a pkix x509 certificate or part of such a + certificate.</fsummary> + <type> + <v>Asn1Type = atom()</v> + <d>The asn1 type can be 'Certificate', 'OTPCertificate' or a subtype of either .</d> + </type> + <desc> + <p>Der encodes a pkix x509 certificate or part of such a + certificate. This function must be used for encoding certificates or parts of certificates + that are decoded/created on the otp format, whereas for the plain format this + function will directly call der_encode/2. </p> + </desc> + </func> + + <func> + <name>pkix_is_issuer(Cert, IssuerCert) -> boolean()</name> + <fsummary> Checks if <c>IssuerCert</c> issued <c>Cert</c> </fsummary> + <type> + <v>Cert = der_encode() | #'OTPCertificate'{}</v> + <v>IssuerCert = der_encode() | #'OTPCertificate'{}</v> + </type> + <desc> + <p> Checks if <c>IssuerCert</c> issued <c>Cert</c> </p> + </desc> + </func> + + <func> + <name>pkix_is_fixed_dh_cert(Cert) -> boolean()</name> + <fsummary> Checks if a Certificate is a fixed Diffie-Hellman Cert.</fsummary> + <type> + <v>Cert = der_encode() | #'OTPCertificate'{}</v> + </type> + <desc> + <p> Checks if a Certificate is a fixed Diffie-Hellman Cert.</p> + </desc> + </func> + + <func> + <name>pkix_is_self_signed(Cert) -> boolean()</name> + <fsummary> Checks if a Certificate is self signed.</fsummary> + <type> + <v>Cert = der_encode() | #'OTPCertificate'{}</v> + </type> + <desc> + <p> Checks if a Certificate is self signed.</p> + </desc> + </func> + + <func> + <name>pkix_issuer_id(Cert, IssuedBy) -> {ok, IssuerID} | {error, Reason}</name> + <fsummary> Returns the issuer id.</fsummary> + <type> + <v>Cert = der_encode() | #'OTPCertificate'{}</v> + <v>IssuedBy = self | other</v> + <v>IssuerID = {integer(), {rdnSequence, [#'AttributeTypeAndValue'{}]}}</v> + <d>The issuer id consists of the serial number and the issuers name.</d> + <v>Reason = term()</v> + </type> + <desc> + <p> Returns the issuer id.</p> + </desc> + </func> + + <func> + <name>pkix_normalize_name(Issuer) -> Normalized</name> + <fsummary>Normalizes a issuer name so that it can be easily + compared to another issuer name. </fsummary> + <type> + <v>Issuer = {rdnSequence,[#'AttributeTypeAndValue'{}]}</v> + <v>Normalized = {rdnSequence, [#'AttributeTypeAndValue'{}]}</v> + </type> + <desc> + <p>Normalizes a issuer name so that it can be easily + compared to another issuer name.</p> + </desc> + </func> + + <!-- <func> --> + <!-- <name>pkix_path_validation()</name> --> + <!-- <fsummary> Performs a basic path validation according to RFC 5280.</fsummary> --> + <!-- <type> --> + <!-- <v></v> --> + <!-- </type> --> + <!-- <desc> --> + <!-- <p> Performs a basic path validation according to RFC 5280.</p> --> + <!-- </desc> --> + <!-- </func> --> + + + <func> + <name>pkix_sign(#'OTPTBSCertificate'{}, Key) -> der_encode()</name> + <fsummary>Signs certificate.</fsummary> + <type> + <v>Key = rsa_public_key() | dsa_public_key()</v> </type> - <desc> - <p> Decodes an asn1 encoded pkix certificate.</p> + <desc> + <p>Signs a 'OTPTBSCertificate'. Returns the corresponding + der encoded certificate.</p> </desc> + </func> + + <func> + <name>pkix_verify(Cert, Key) -> boolean()</name> + <fsummary> Verify pkix x.509 certificate signature.</fsummary> + <type> + <v>Cert = der_encode()</v> + <v>Key = rsa_public_key() | dsa_public_key()</v> + </type> + <desc> + <p> Verify pkix x.509 certificate signature.</p> + </desc> + </func> + + <func> + <name>sign(Msg, DigestType, Key) -> binary()</name> + <fsummary> Create digital signature.</fsummary> + <type> + <v>Msg = binary()</v> + <d>The msg is either the binary "plain text" data to be + signed or in the case that digest type is <c>none</c> + it is the hashed value of "plain text" i.e. the digest.</d> + <v>DigestType = rsa_digest_type() | dsa_digest_type()</v> + <v>Key = rsa_public_key() | dsa_public_key()</v> + </type> + <desc> + <p> Creates a digital signature.</p> + </desc> + </func> + + <func> + <name>ssh_decode(SshBin, Type) -> [{public_key(), Attributes::list()}]</name> + <fsummary>Decodes a ssh file-binary. </fsummary> + <type> + <v>SshBin = binary()</v> + <d>Example {ok, SshBin} = file:read_file("known_hosts").</d> + <v> Type = public_key | ssh_file()</v> + <d>If <c>Type</c> is <c>public_key</c> the binary may be either + a rfc4716 public key or a openssh public key.</d> + </type> + <desc> + <p> Decodes a ssh file-binary. In the case of know_hosts or + auth_keys the binary may include one or more lines of the + file. Returns a list of public keys and their attributes, possible + attribute values depends on the file type represented by the + binary. + </p> + + <taglist> + <tag>rfc4716 attributes - see RFC 4716</tag> + <item>{headers, [{string(), utf8_string()}]}</item> + <tag>auth_key attributes - see man sshd </tag> + <item>{comment, string()}</item> + <item>{options, [string()]}</item> + <item>{bits, integer()} - In ssh version 1 files</item> + <tag>known_host attributes - see man sshd</tag> + <item>{hostnames, [string()]}</item> + <item>{comment, string()}</item> + <item>{bits, integer()} - In ssh version 1 files</item> + </taglist> + + </desc> + </func> + + <func> + <name>ssh_encode([{Key, Attributes}], Type) -> binary()</name> + <fsummary> Encodes a list of ssh file entries to a binary.</fsummary> + <type> + <v>Key = public_key()</v> + <v>Attributes = list()</v> + <v>Type = ssh_file()</v> + </type> + <desc> + <p>Encodes a list of ssh file entries (public keys and attributes) to a binary. Possible + attributes depends on the file type, see <seealso + marker="ssh_decode"> ssh_decode/2 </seealso></p> + </desc> + </func> + + <func> + <name>verify(Msg, DigestType, Signature, Key) -> boolean()</name> + <fsummary>Verifies a digital signature.</fsummary> + <type> + <v>Msg = binary()</v> + <d>The msg is either the binary "plain text" data + or in the case that digest type is <c>none</c> + it is the hashed value of "plain text" i.e. the digest.</d> + <v>DigestType = rsa_digest_type() | dsa_digest_type()</v> + <v>Signature = binary()</v> + <v>Key = rsa_public_key() | dsa_public_key()</v> + </type> + <desc> + <p>Verifies a digital signature</p> + </desc> </func> -<!-- <func> --> -<!-- <name> pkix_encode_cert(Cert) -> {ok, EncodedCert} | {error, Reason}</name> --> -<!-- <fsummary>Encodes a certificate record using asn1. </fsummary> --> -<!-- <type> --> -<!-- <v>Cert = x509_certificate() </v> --> -<!-- <v>EncodedCert = asn1_der_encoded() </v> --> -<!-- <v>Reason = term() </v> --> -<!-- </type> --> -<!-- <desc> --> -<!-- <p> Encodes a certificate record using asn1.</p> --> -<!-- </desc> --> -<!-- </func> --> - -<!-- <func> --> -<!-- <name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, Result} | {error, Reason}</name> --> - -<!-- <fsummary>Performs a basic path validation according to RFC 3280</fsummary> --> -<!-- <type> --> -<!-- <v>TrustedCert = asn1_der_encoded()</v> --> -<!-- <v>CertChain = [asn1_der_encoded()]</v> --> -<!-- <v>Options = [{Option, Value}]</v> --> -<!-- <v>Result = {{algorithm(), public_key(), --> -<!-- public_key_params()}, policy_tree()}</v> --> -<!-- </type> --> - -<!-- <desc> --> -<!-- <p>Available options are: </p> --> -<!-- <taglist> --> -<!-- <tag>{validate_extension_fun, fun()}</tag> --> -<!-- <item> A fun behaving according to the following outline: --> -<!-- <code> --> -<!-- [...] --> -<!-- ValidateExtensionFun = fun(Extensions, UserState) -> --> -<!-- validate_extensions(Extensions, UserState, []) --> -<!-- end, --> -<!-- [...] --> - -<!-- validate_extensions([], UserState, UnknowExtension) -> --> -<!-- {UserState, UnknowExtension}; --> -<!-- validate_extensions([#'Extension'{} = Ext | Rest], UserState, UnknowExtension) -> --> -<!-- case valid_extension(Ext) of --> -<!-- {true, NewUserState} -> --> -<!-- validate_extensions(Rest, NewUserState, UnknowExtension); --> -<!-- unknown -> --> -<!-- validate_extensions(Rest, UserState, [Ext | UnknowExtension]); --> -<!-- {false, Reason} -> --> -<!-- throw(bad_cert, Reason) --> -<!-- end. --> -<!-- </code> --> - -<!-- </item> --> - -<!-- <tag>{policy_set, [oid()]}</tag> --> -<!-- <item>A set of certificate policy --> -<!-- identifiers naming the policies that are acceptable to the --> -<!-- certificate user. If the user is not concerned about --> -<!-- certificate policy there is no need --> -<!-- to set this option. Defaults to the --> -<!-- special value [?anyPolicy]. --> -<!-- </item> --> - -<!-- <tag>{policy_mapping, boolean()}</tag> --> -<!-- <item>Indicates if policy --> -<!-- mapping, initially, is allowed in the certification path. --> -<!-- Defaults to false. --> -<!-- </item> --> - -<!-- <tag> {explicit_policy, boolean()}</tag> --> -<!-- <item>Indicates if the path, initially, must be --> -<!-- valid for at least one of the certificate policies in the user --> -<!-- specified policy set. --> -<!-- Defaults to false. --> -<!-- </item> --> - -<!-- <tag>{inhibit_any_policy, boolean()}</tag> --> -<!-- <item>Indicates whether the anyPolicy OID, initially, should --> -<!-- be processed if it is included in a certificate. --> -<!-- Defaults to false. --> -<!-- </item> --> - -<!-- </taglist> --> - -<!-- <p>Performs a basic path validation according to RFC 3280, --> -<!-- e.i. signature validation, time validation, issuer validation, --> -<!-- alternative subject name validation, CRL validation, policy --> -<!-- validation and checks that no unknown extensions --> -<!-- are marked as critical. The option <c>validate_extension_fun</c> --> -<!-- may be used to validate application specific extensions. If --> -<!-- a validation criteria is found to be invalid the validation process --> -<!-- will immediately be stopped and this functions will return --> -<!-- {error, Reason}. --> -<!-- </p> --> -<!-- </desc> --> -<!-- </func> --> - -<!-- <func> --> -<!-- <name>sign(DigestOrTBSCert, Key) -> </name> --> -<!-- <name>sign(DigestOrTBSCert, Key, KeyParams) -> {ok, SignatureOrDerCert} | {error, Reason}</name> --> -<!-- <fsummary>Signs Digest/Certificate using Key.</fsummary> --> -<!-- <type> --> -<!-- <v>DigestOrTBSCert = binary() | x509_tbs_certificate()</v> --> -<!-- <v>Key = private_key()</v> --> -<!-- <v>SignatureORDerCert = binary() | der_bin() </v> --> -<!-- <v>Reason = term() </v> --> -<!-- </type> --> -<!-- <desc> --> -<!-- <p> Signs Digest/Certificate using Key, in the later --> -<!-- case a der encoded x509_certificate() will be returned. </p> --> -<!-- </desc> --> -<!-- </func> --> - -<!-- <func> --> -<!-- <name>verify_signature(Digest, Signature, Key) -> </name> --> -<!-- <name>verify_signature(DerCert, Key, KeyParams) -> </name> --> -<!-- <name>verify_signature(Digest, Signature, Key, Params) -> Verified </name> --> -<!-- <fsummary> Verifies the signature. </fsummary> --> -<!-- <type> --> -<!-- <v>Digest = binary() </v> --> -<!-- <v>DerCert = der_bin() </v> --> -<!-- <v>Signature = binary() </v> --> -<!-- <v>Key = public_key() </v> --> -<!-- <v>Params = key_params()</v> --> -<!-- <v>Verified = boolean()</v> --> -<!-- </type> --> -<!-- <desc> --> -<!-- <p> Verifies the signature Signature. If the key is an rsa-key no --> -<!-- paramters are neeed.</p> --> -<!-- </desc> --> -<!-- </func> --> </funcs> </erlref> diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml index 45b7106859..bb90290266 100644 --- a/lib/public_key/doc/src/public_key_records.xml +++ b/lib/public_key/doc/src/public_key_records.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/doc/src/ref_man.xml b/lib/public_key/doc/src/ref_man.xml index 0f11281d05..285cc36c6f 100644 --- a/lib/public_key/doc/src/ref_man.xml +++ b/lib/public_key/doc/src/ref_man.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2008</year> + <year>2011</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl index fbce10f0eb..5f97d80f7e 100644 --- a/lib/public_key/include/public_key.hrl +++ b/lib/public_key/include/public_key.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -28,6 +28,17 @@ algorithm, parameters = asn1_NOVALUE}). +-define(DEFAULT_VERIFYFUN, + {fun(_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}). + -record(path_validation_state, { valid_policy_tree, explicit_policy, @@ -42,7 +53,7 @@ working_public_key_parameters, working_issuer_name, max_path_length, - acc_errors, %% If verify_none option is set + verify_fun, user_state }). @@ -59,4 +70,18 @@ interim_reasons_mask }). +-type public_key() :: rsa_public_key() | dsa_public_key(). +-type rsa_public_key() :: #'RSAPublicKey'{}. +-type rsa_private_key() :: #'RSAPrivateKey'{}. +-type dsa_private_key() :: #'DSAPrivateKey'{}. +-type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. +-type pki_asn1_type() :: 'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey' + | 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' + | 'SubjectPublicKeyInfo'. +-type pem_entry() :: {pki_asn1_type(), binary(), %% DER or Encrypted DER + not_encrypted | {Cipher :: string(), Salt :: binary()}}. +-type asn1_type() :: atom(). %% see "OTP-PUB-KEY.hrl +-type ssh_file() :: openssh_public_key | rfc4716_public_key | known_hosts | + auth_keys. + -endif. % -ifdef(public_key). diff --git a/lib/public_key/src/Makefile b/lib/public_key/src/Makefile index c30399f33a..5a24b02d2a 100644 --- a/lib/public_key/src/Makefile +++ b/lib/public_key/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2008-2009. All Rights Reserved. +# Copyright Ericsson AB 2008-2011. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -41,9 +41,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/public_key-$(VSN) MODULES = \ public_key \ pubkey_pem \ + pubkey_ssh \ pubkey_cert \ - pubkey_cert_records \ - pubkey_crypto + pubkey_cert_records HRL_FILES = $(INCLUDE)/public_key.hrl diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index 799e3820d1..5ab9642279 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,14 +23,13 @@ -include("public_key.hrl"). --export([verify_signature/3, - init_validation_state/3, prepare_for_next_cert/2, +-export([init_validation_state/3, prepare_for_next_cert/2, validate_time/3, validate_signature/6, validate_issuer/4, validate_names/6, validate_revoked_status/3, validate_extensions/4, - validate_unknown_extensions/3, - normalize_general_name/1, digest_type/1, digest/2, is_self_signed/1, - is_issuer/2, issuer_id/2, is_fixed_dh_cert/1]). + normalize_general_name/1, digest_type/1, is_self_signed/1, + is_issuer/2, issuer_id/2, is_fixed_dh_cert/1, + verify_data/1, verify_fun/4]). -define(NULL, 0). @@ -38,10 +37,22 @@ %% Internal application API %%==================================================================== -verify_signature(DerCert, Key, KeyParams) -> - {ok, OtpCert} = pubkey_cert_records:decode_cert(DerCert, otp), - verify_signature(OtpCert, DerCert, Key, KeyParams). +%%-------------------------------------------------------------------- +-spec verify_data(DER::binary()) -> {md5 | sha, binary(), binary()}. +%% +%% Description: Extracts data from DerCert needed to call public_key:verify/4. +%%-------------------------------------------------------------------- +verify_data(DerCert) -> + {ok, OtpCert} = pubkey_cert_records:decode_cert(DerCert), + extract_verify_data(OtpCert, DerCert). +%%-------------------------------------------------------------------- +-spec init_validation_state(#'OTPCertificate'{}, integer(), list()) -> + #path_validation_state{}. +%% +%% Description: Creates inital version of path_validation_state for +%% basic path validation of x509 certificates. +%%-------------------------------------------------------------------- init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen, Options) -> PolicyTree = #policy_tree_node{valid_policy = ?anyPolicy, @@ -56,16 +67,23 @@ init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen, Options, false)), PolicyMapping = policy_indicator(MaxLen, proplists:get_value(policy_mapping, Options, false)), - AccErrors = proplists:get_value(acc_errors, Options, []), - State = #path_validation_state{max_path_length = MaxLen, - valid_policy_tree = PolicyTree, - explicit_policy = ExplicitPolicy, - inhibit_any_policy = InhibitAnyPolicy, - policy_mapping = PolicyMapping, - acc_errors = AccErrors, + {VerifyFun, UserState} = proplists:get_value(verify_fun, Options, ?DEFAULT_VERIFYFUN), + State = #path_validation_state{max_path_length = MaxLen, + valid_policy_tree = PolicyTree, + explicit_policy = ExplicitPolicy, + inhibit_any_policy = InhibitAnyPolicy, + policy_mapping = PolicyMapping, + verify_fun = VerifyFun, + user_state = UserState, cert_num = 0}, prepare_for_next_cert(OtpCert, State). +%%-------------------------------------------------------------------- +-spec prepare_for_next_cert(#'OTPCertificate'{}, #path_validation_state{}) -> + #path_validation_state{}. +%% +%% Description: Update path_validation_state for next iteration. +%%-------------------------------------------------------------------- prepare_for_next_cert(OtpCert, ValidationState = #path_validation_state{ working_public_key_algorithm = PrevAlgo, working_public_key_parameters = @@ -92,8 +110,14 @@ prepare_for_next_cert(OtpCert, ValidationState = #path_validation_state{ working_issuer_name = Issuer, cert_num = ValidationState#path_validation_state.cert_num + 1 }. - -validate_time(OtpCert, AccErr, Verify) -> + + %%-------------------------------------------------------------------- +-spec validate_time(#'OTPCertificate'{}, term(), fun()) -> term(). +%% +%% Description: Check that the certificate validity period includes the +%% current time. +%%-------------------------------------------------------------------- +validate_time(OtpCert, UserState, VerifyFun) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, {'Validity', NotBeforeStr, NotAfterStr} = TBSCert#'OTPTBSCertificate'.validity, @@ -103,34 +127,52 @@ validate_time(OtpCert, AccErr, Verify) -> case ((NotBefore =< Now) and (Now =< NotAfter)) of true -> - AccErr; + UserState; false -> - not_valid({bad_cert, cert_expired}, Verify, AccErr) + verify_fun(OtpCert, {bad_cert, cert_expired}, UserState, VerifyFun) end. - -validate_issuer(OtpCert, Issuer, AccErr, Verify) -> +%%-------------------------------------------------------------------- +-spec validate_issuer(#'OTPCertificate'{}, term(), term(), fun()) -> term(). +%% +%% Description: Check that the certificate issuer name is the working_issuer_name +%% in path_validation_state. +%%-------------------------------------------------------------------- +validate_issuer(OtpCert, Issuer, UserState, VerifyFun) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, case is_issuer(Issuer, TBSCert#'OTPTBSCertificate'.issuer) of true -> - AccErr; + UserState; _ -> - not_valid({bad_cert, invalid_issuer}, Verify, AccErr) + verify_fun(OtpCert, {bad_cert, invalid_issuer}, UserState, VerifyFun) end. - +%%-------------------------------------------------------------------- +-spec validate_signature(#'OTPCertificate'{}, DER::binary(), + term(),term(), term(), fun()) -> term(). + +%% +%% Description: Check that the signature on the certificate can be verified using +%% working_public_key_algorithm, the working_public_key, and +%% the working_public_key_parameters in path_validation_state. +%%-------------------------------------------------------------------- validate_signature(OtpCert, DerCert, Key, KeyParams, - AccErr, Verify) -> + UserState, VerifyFun) -> case verify_signature(OtpCert, DerCert, Key, KeyParams) of true -> - AccErr; + UserState; false -> - not_valid({bad_cert, invalid_signature}, Verify, AccErr) + verify_fun(OtpCert, {bad_cert, invalid_signature}, UserState, VerifyFun) end. - -validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) -> +%%-------------------------------------------------------------------- +-spec validate_names(#'OTPCertificate'{}, no_constraints | list(), list(), + term(), term(), fun())-> term(). +%% +%% Description: Validate Subject Alternative Name. +%%-------------------------------------------------------------------- +validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) -> case is_self_signed(OtpCert) andalso (not Last) of true -> - ok; + UserState; false -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, Subject = TBSCert#'OTPTBSCertificate'.subject, @@ -143,8 +185,10 @@ validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) -> Name = [{directoryName, Subject}|EmailAddress], AltNames = case AltSubject of - undefined -> []; - _ -> AltSubject#'Extension'.extnValue + undefined -> + []; + _ -> + AltSubject#'Extension'.extnValue end, case (is_permitted(Name, Permit) andalso @@ -152,65 +196,74 @@ validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) -> (not is_excluded(Name, Exclude)) andalso (not is_excluded(AltNames, Exclude))) of true -> - AccErr; + UserState; false -> - not_valid({bad_cert, name_not_permitted}, - Verify, AccErr) + verify_fun(OtpCert, {bad_cert, name_not_permitted}, + UserState, VerifyFun) end end. - -%% See rfc3280 4.1.2.6 Subject: regarding emails. -extract_email({rdnSequence, List}) -> - extract_email2(List). -extract_email2([[#'AttributeTypeAndValue'{type=?'id-emailAddress', - value=Mail}]|_]) -> - [{rfc822Name, Mail}]; -extract_email2([_|Rest]) -> - extract_email2(Rest); -extract_email2([]) -> []. - -validate_revoked_status(_OtpCert, _Verify, AccErr) -> - %% true | +%%-------------------------------------------------------------------- +-spec validate_revoked_status(#'OTPCertificate'{}, term(), fun()) -> + term(). +%% +%% Description: Check if certificate has been revoked. +%%-------------------------------------------------------------------- +validate_revoked_status(_OtpCert, UserState, _VerifyFun) -> + %% TODO: Implement or leave for application?! + %% valid | %% throw({bad_cert, cert_revoked}) - AccErr. - -validate_extensions(OtpCert, ValidationState, Verify, AccErr) -> + UserState. +%%-------------------------------------------------------------------- +-spec validate_extensions(#'OTPCertificate'{}, #path_validation_state{}, + term(), fun())-> + {#path_validation_state{}, UserState :: term()}. +%% +%% Description: Check extensions included in basic path validation. +%%-------------------------------------------------------------------- +validate_extensions(OtpCert, ValidationState, UserState, VerifyFun) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, - Extensions = TBSCert#'OTPTBSCertificate'.extensions, - validate_extensions(Extensions, ValidationState, no_basic_constraint, - is_self_signed(OtpCert), [], Verify, AccErr). - -validate_unknown_extensions([], AccErr, _Verify) -> - AccErr; -validate_unknown_extensions([#'Extension'{critical = true} | _], - AccErr, Verify) -> - not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr); -validate_unknown_extensions([#'Extension'{critical = false} | Rest], - AccErr, Verify) -> - validate_unknown_extensions(Rest, AccErr, Verify). - + case TBSCert#'OTPTBSCertificate'.version of + N when N >= 3 -> + Extensions = TBSCert#'OTPTBSCertificate'.extensions, + validate_extensions(OtpCert, Extensions, + ValidationState, no_basic_constraint, + is_self_signed(OtpCert), UserState, VerifyFun); + _ -> %% Extensions not present in versions 1 & 2 + {ValidationState, UserState} + end. +%%-------------------------------------------------------------------- +-spec normalize_general_name({rdnSequence, term()}) -> {rdnSequence, term()}. +%% +%% Description: Normalizes a general name so that it can be easily +%% compared to another genral name. +%%-------------------------------------------------------------------- normalize_general_name({rdnSequence, Issuer}) -> - NormIssuer = normalize_general_name(Issuer), - {rdnSequence, NormIssuer}; - -normalize_general_name(Issuer) -> - Normalize = fun([{Description, Type, {printableString, Value}}]) -> - NewValue = string:to_lower(strip_spaces(Value)), - {Description, Type, {printableString, NewValue}}; - (Atter) -> - Atter - end, - lists:sort(lists:map(Normalize, Issuer)). + NormIssuer = do_normalize_general_name(Issuer), + {rdnSequence, NormIssuer}. +%%-------------------------------------------------------------------- +-spec is_self_signed(#'OTPCertificate'{}) -> boolean(). +%% +%% Description: Checks if the certificate is self signed. +%%-------------------------------------------------------------------- is_self_signed(#'OTPCertificate'{tbsCertificate= #'OTPTBSCertificate'{issuer = Issuer, subject = Subject}}) -> is_issuer(Issuer, Subject). - +%%-------------------------------------------------------------------- +-spec is_issuer({rdnSequence, term()}, {rdnSequence, term()}) -> boolean(). +%% +%% Description: Checks if <Issuer> issued <Candidate>. +%%-------------------------------------------------------------------- is_issuer({rdnSequence, Issuer}, {rdnSequence, Candidate}) -> is_dir_name(Issuer, Candidate, true). - +%%-------------------------------------------------------------------- +-spec issuer_id(#'OTPCertificate'{}, self | other) -> + {ok, {integer(), term()}} | {error, issuer_not_found}. +%% +%% Description: Extracts the issuer id from a certificate if possible. +%%-------------------------------------------------------------------- issuer_id(Otpcert, other) -> TBSCert = Otpcert#'OTPCertificate'.tbsCertificate, Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), @@ -227,7 +280,12 @@ issuer_id(Otpcert, self) -> SerialNr = TBSCert#'OTPTBSCertificate'.serialNumber, {ok, {SerialNr, normalize_general_name(Issuer)}}. - +%%-------------------------------------------------------------------- +-spec is_fixed_dh_cert(#'OTPCertificate'{}) -> boolean(). +%% +%% Description: Checks if the certificate can be be used +%% for DH key agreement. +%%-------------------------------------------------------------------- is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subjectPublicKeyInfo = SubjectPublicKeyInfo, @@ -235,31 +293,79 @@ is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate = Extensions}}) -> is_fixed_dh_cert(SubjectPublicKeyInfo, extensions_list(Extensions)). + +%%-------------------------------------------------------------------- +-spec verify_fun(#'OTPCertificate'{}, {bad_cert, atom()} | {extension, #'Extension'{}}| + valid | valid_peer, term(), fun()) -> term(). +%% +%% Description: Gives the user application the opportunity handle path +%% validation errors and unknown extensions and optional do other +%% things with a validated certificate. +%% -------------------------------------------------------------------- +verify_fun(Otpcert, Result, UserState0, VerifyFun) -> + case VerifyFun(Otpcert, Result, UserState0) of + {valid,UserState} -> + UserState; + {fail, Reason} -> + case Result of + {bad_cert, _} -> + throw(Result); + _ -> + throw({bad_cert, Reason}) + end; + {unknown, UserState} -> + case Result of + {extension, #'Extension'{critical = true}} -> + throw({bad_cert, unknown_critical_extension}); + _ -> + UserState + end + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +do_normalize_general_name(Issuer) -> + Normalize = fun([{Description, Type, {printableString, Value}}]) -> + NewValue = string:to_lower(strip_spaces(Value)), + [{Description, Type, {printableString, NewValue}}]; + (Atter) -> + Atter + end, + lists:sort(lists:map(Normalize, Issuer)). + +%% See rfc3280 4.1.2.6 Subject: regarding emails. +extract_email({rdnSequence, List}) -> + extract_email2(List). +extract_email2([[#'AttributeTypeAndValue'{type=?'id-emailAddress', + value=Mail}]|_]) -> + [{rfc822Name, Mail}]; +extract_email2([_|Rest]) -> + extract_email2(Rest); +extract_email2([]) -> []. extensions_list(asn1_NOVALUE) -> []; extensions_list(Extensions) -> Extensions. -not_valid(Error, true, _) -> - throw(Error); -not_valid(Error, false, AccErrors) -> - [Error | AccErrors]. -verify_signature(OtpCert, DerCert, Key, KeyParams) -> - %% Signature is an ASN1 compact bit string +extract_verify_data(OtpCert, DerCert) -> {0, Signature} = OtpCert#'OTPCertificate'.signature, SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm, SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm, - EncTBSCert = encoded_tbs_cert(DerCert), - verify(SigAlg, EncTBSCert, Signature, Key, KeyParams). + PlainText = encoded_tbs_cert(DerCert), + DigestType = digest_type(SigAlg), + {DigestType, PlainText, Signature}. -verify(Alg, PlainText, Signature, Key, KeyParams) -> - public_key:verify_signature(PlainText, digest_type(Alg), - Signature, Key, KeyParams). +verify_signature(OtpCert, DerCert, Key, KeyParams) -> + {DigestType, PlainText, Signature} = extract_verify_data(OtpCert, DerCert), + case Key of + #'RSAPublicKey'{} -> + public_key:verify(PlainText, DigestType, Signature, Key); + _ -> + public_key:verify(PlainText, DigestType, Signature, {Key, KeyParams}) + end. encoded_tbs_cert(Cert) -> {ok, PKIXCert} = @@ -275,13 +381,6 @@ digest_type(?md5WithRSAEncryption) -> digest_type(?'id-dsa-with-sha1') -> sha. -digest(?sha1WithRSAEncryption, Msg) -> - crypto:sha(Msg); -digest(?md5WithRSAEncryption, Msg) -> - crypto:md5(Msg); -digest(?'id-dsa-with-sha1', Msg) -> - crypto:sha(Msg). - public_key_info(PublicKeyInfo, #path_validation_state{working_public_key_algorithm = WorkingAlgorithm, @@ -295,10 +394,12 @@ public_key_info(PublicKeyInfo, NewPublicKeyParams = case PublicKeyParams of - 'NULL' when WorkingAlgorithm == Algorithm -> + {null, 'NULL'} when WorkingAlgorithm == Algorithm -> WorkingParams; - _ -> - PublicKeyParams + {params, Params} -> + Params; + Params -> + Params end, {Algorithm, PublicKey, NewPublicKeyParams}. @@ -332,12 +433,6 @@ is_dir_name([[{'AttributeTypeAndValue', Type, What1}]|Rest1], true -> is_dir_name(Rest1,Rest2,Exact); false -> false end; -is_dir_name([{'AttributeTypeAndValue', Type, What1}|Rest1], - [{'AttributeTypeAndValue', Type, What2}|Rest2], Exact) -> - case is_dir_name2(What1,What2) of - true -> is_dir_name(Rest1,Rest2,Exact); - false -> false - end; is_dir_name(_,[],false) -> true; is_dir_name(_,_,_) -> @@ -382,210 +477,179 @@ select_extension(Id, [_ | Extensions]) -> select_extension(Id, Extensions). %% No extensions present -validate_extensions(asn1_NOVALUE, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr) -> - validate_extensions([], ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - -validate_extensions([], ValidationState, basic_constraint, _SelfSigned, - UnknownExtensions, _Verify, AccErr) -> - {ValidationState, UnknownExtensions, AccErr}; -validate_extensions([], ValidationState = - #path_validation_state{max_path_length = Len, - last_cert = Last}, - no_basic_constraint, SelfSigned, UnknownExtensions, - Verify, AccErr0) -> +validate_extensions(OtpCert, asn1_NOVALUE, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun) -> + validate_extensions(OtpCert, [], ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); + +validate_extensions(_,[], ValidationState, basic_constraint, _SelfSigned, + UserState, _) -> + {ValidationState, UserState}; +validate_extensions(OtpCert, [], ValidationState = + #path_validation_state{max_path_length = Len, + last_cert = Last}, + no_basic_constraint, SelfSigned, UserState0, VerifyFun) -> case Last of true when SelfSigned -> - {ValidationState, UnknownExtensions, AccErr0}; + {ValidationState, UserState0}; true -> {ValidationState#path_validation_state{max_path_length = Len - 1}, - UnknownExtensions, AccErr0}; + UserState0}; %% basic_constraint must appear in certs used for digital sign %% see 4.2.1.10 in rfc 3280 false -> - AccErr = not_valid({bad_cert, missing_basic_constraint}, - Verify, AccErr0), + UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint}, + UserState0, VerifyFun), case SelfSigned of true -> - {ValidationState, UnknownExtensions, AccErr}; + {ValidationState, UserState}; false -> {ValidationState#path_validation_state{max_path_length = - Len - 1}, - UnknownExtensions, AccErr} + Len - 1}, + UserState} end end; -validate_extensions([#'Extension'{extnID = ?'id-ce-basicConstraints', +validate_extensions(OtpCert, + [#'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = - #'BasicConstraints'{cA = true, - pathLenConstraint = N}} | + #'BasicConstraints'{cA = true, + pathLenConstraint = N}} | Rest], - ValidationState = - #path_validation_state{max_path_length = Len}, _, - SelfSigned, UnknownExtensions, Verify, AccErr) -> - Length = if SelfSigned -> min(N, Len); - true -> min(N, Len-1) + ValidationState = + #path_validation_state{max_path_length = Len}, _, + SelfSigned, UserState, VerifyFun) -> + Length = if SelfSigned -> erlang:min(N, Len); + true -> erlang:min(N, Len-1) end, - validate_extensions(Rest, + validate_extensions(OtpCert, Rest, ValidationState#path_validation_state{max_path_length = - Length}, - basic_constraint, SelfSigned, UnknownExtensions, - Verify, AccErr); + Length}, + basic_constraint, SelfSigned, + UserState, VerifyFun); %% The pathLenConstraint field is meaningful only if cA is set to %% TRUE. -validate_extensions([#'Extension'{extnID = ?'id-ce-basicConstraints', - extnValue = - #'BasicConstraints'{cA = false}} | - Rest], ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr) -> - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - -%% -validate_extensions([#'Extension'{extnID = ?'id-ce-keyUsage', - extnValue = KeyUse - } | Rest], - #path_validation_state{last_cert=Last} = ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr0) -> +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = + #'BasicConstraints'{cA = false}} | + Rest], ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun) -> + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); + +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = KeyUse + } | Rest], + #path_validation_state{last_cert=Last} = ValidationState, + ExistBasicCon, SelfSigned, + UserState0, VerifyFun) -> case Last orelse is_valid_key_usage(KeyUse, keyCertSign) of true -> - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, - AccErr0); + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState0, VerifyFun); false -> - AccErr = not_valid({bad_cert, invalid_key_usage}, Verify, AccErr0), - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, - AccErr) + UserState = verify_fun(OtpCert, {bad_cert, invalid_key_usage}, + UserState0, VerifyFun), + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun) end; -validate_extensions([#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = Names} | Rest], - ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr0) -> +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = Names, + critical = true} = Ext | Rest], + ValidationState, ExistBasicCon, + SelfSigned, UserState0, VerifyFun) -> case validate_subject_alt_names(Names) of - true when Names =/= [] -> - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, - AccErr0); - _ -> - AccErr = - not_valid({bad_cert, invalid_subject_altname}, - Verify, AccErr0), - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, - AccErr) + true -> + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState0, VerifyFun); + false -> + UserState = verify_fun(OtpCert, {extension, Ext}, + UserState0, VerifyFun), + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun) end; -%% This extension SHOULD NOT be marked critical. Its value -%% does not have to be further validated at this point. -validate_extensions([#'Extension'{extnID = ?'id-ce-issuerAltName', - extnValue = _} | Rest], - ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr) -> - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - -%% This extension MUST NOT be marked critical.Its value -%% does not have to be further validated at this point. -validate_extensions([#'Extension'{extnID = Id, - extnValue = _, - critical = false} | Rest], - ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr) - when Id == ?'id-ce-subjectKeyIdentifier'; - Id == ?'id-ce-authorityKeyIdentifier'-> - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - -validate_extensions([#'Extension'{extnID = ?'id-ce-nameConstraints', +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-nameConstraints', extnValue = NameConst} | Rest], ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr) -> + ExistBasicCon, SelfSigned, UserState, VerifyFun) -> Permitted = NameConst#'NameConstraints'.permittedSubtrees, Excluded = NameConst#'NameConstraints'.excludedSubtrees, NewValidationState = add_name_constraints(Permitted, Excluded, ValidationState), - validate_extensions(Rest, NewValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - + validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); -validate_extensions([#'Extension'{extnID = ?'id-ce-certificatePolicies', - critical = true} | Rest], ValidationState, - ExistBasicCon, SelfSigned, - UnknownExtensions, Verify, AccErr0) -> +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-certificatePolicies', + critical = true} = Ext| Rest], ValidationState, + ExistBasicCon, SelfSigned, UserState0, VerifyFun) -> %% TODO: Remove this clause when policy handling is %% fully implemented - AccErr = - not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr0), - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); - -validate_extensions([#'Extension'{extnID = ?'id-ce-certificatePolicies', - extnValue = #'PolicyInformation'{ - policyIdentifier = Id, - policyQualifiers = Qualifier}} - | Rest], #path_validation_state{valid_policy_tree = Tree} + UserState = verify_fun(OtpCert, {extension, Ext}, + UserState0, VerifyFun), + validate_extensions(OtpCert,Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); + +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-certificatePolicies', + extnValue = #'PolicyInformation'{ + policyIdentifier = Id, + policyQualifiers = Qualifier}} + | Rest], #path_validation_state{valid_policy_tree = Tree} = ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr) -> + ExistBasicCon, SelfSigned, UserState, VerifyFun) -> %% TODO: Policy imp incomplete NewTree = process_policy_tree(Id, Qualifier, Tree), - validate_extensions(Rest, + validate_extensions(OtpCert, Rest, ValidationState#path_validation_state{ valid_policy_tree = NewTree}, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr); + ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions([#'Extension'{extnID = ?'id-ce-policyConstraints', - critical = true} | Rest], ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, Verify, - AccErr0) -> +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints', + critical = true} = Ext | Rest], ValidationState, + ExistBasicCon, SelfSigned, UserState0, VerifyFun) -> %% TODO: Remove this clause when policy handling is %% fully implemented - AccErr = - not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr0), - validate_extensions(Rest, ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); -validate_extensions([#'Extension'{extnID = ?'id-ce-policyConstraints', - extnValue = #'PolicyConstraints'{ - requireExplicitPolicy = ExpPolicy, - inhibitPolicyMapping = MapPolicy}} - | Rest], ValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr) -> + UserState = verify_fun(OtpCert, {extension, Ext}, + UserState0, VerifyFun), + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); +validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints', + extnValue = #'PolicyConstraints'{ + requireExplicitPolicy = ExpPolicy, + inhibitPolicyMapping = MapPolicy}} + | Rest], ValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun) -> %% TODO: Policy imp incomplete - NewValidationState = add_policy_constraints(ExpPolicy, MapPolicy, + NewValidationState = add_policy_constraints(ExpPolicy, MapPolicy, ValidationState), - validate_extensions(Rest, NewValidationState, ExistBasicCon, - SelfSigned, UnknownExtensions, Verify, AccErr); + validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + SelfSigned, UserState, VerifyFun); -validate_extensions([Extension | Rest], ValidationState, - ExistBasicCon, SelfSigned, UnknownExtensions, - Verify, AccErr) -> - validate_extensions(Rest, ValidationState, ExistBasicCon, SelfSigned, - [Extension | UnknownExtensions], Verify, AccErr). +validate_extensions(OtpCert, [#'Extension'{} = Extension | Rest], + ValidationState, ExistBasicCon, + SelfSigned, UserState0, VerifyFun) -> + UserState = verify_fun(OtpCert, {extension, Extension}, UserState0, VerifyFun), + validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned, + UserState, VerifyFun). is_valid_key_usage(KeyUse, Use) -> lists:member(Use, KeyUse). validate_subject_alt_names([]) -> - true; + false; validate_subject_alt_names([AltName | Rest]) -> case is_valid_subject_alt_name(AltName) of true -> - validate_subject_alt_names(Rest); + true; false -> - false + validate_subject_alt_names(Rest) end. is_valid_subject_alt_name({Name, Value}) when Name == rfc822Name; @@ -613,14 +677,11 @@ is_valid_subject_alt_name({directoryName, _}) -> true; is_valid_subject_alt_name({_, [_|_]}) -> true; +is_valid_subject_alt_name({otherName, #'AnotherName'{}}) -> + false; is_valid_subject_alt_name({_, _}) -> false. -min(N, M) when N =< M -> - N; -min(_, M) -> - M. - is_ip_address(Address) -> case inet_parse:address(Address) of {ok, _} -> @@ -683,10 +744,11 @@ split_auth_path(URIPart) -> end. split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) -> - case regexp:first_match(UriPart, SplitChar) of - {match, Match, _} -> - {string:substr(UriPart, 1, Match - SkipLeft), - string:substr(UriPart, Match + SkipRight, length(UriPart))}; + case re:run(UriPart, SplitChar) of + {match,[{Start, _}]} -> + StrPos = Start + 1, + {string:substr(UriPart, 1, StrPos - SkipLeft), + string:substr(UriPart, StrPos + SkipRight, length(UriPart))}; nomatch -> NoMatchResult end. @@ -939,7 +1001,7 @@ add_policy_constraints(ExpPolicy, MapPolicy, policy_constraint(Current, asn1_NOVALUE, _) -> Current; policy_constraint(Current, New, CertNum) -> - min(Current, New + CertNum). + erlang:min(Current, New + CertNum). process_policy_tree(_,_, ?NULL) -> ?NULL; diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl index c7d4080adb..b86d7a1f0c 100644 --- a/lib/public_key/src/pubkey_cert_records.erl +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,96 +23,81 @@ -include("public_key.hrl"). --export([decode_cert/2, encode_cert/1, encode_tbs_cert/1, transform/2]). - --export([old_decode_cert/2, old_encode_cert/1]). %% Debugging and testing new code. +-export([decode_cert/1, transform/2, supportedPublicKeyAlgorithms/1]). %%==================================================================== %% Internal application API %%==================================================================== -decode_cert(DerCert, plain) -> - 'OTP-PUB-KEY':decode('Certificate', DerCert); -decode_cert(DerCert, otp) -> +%%-------------------------------------------------------------------- +-spec decode_cert(DerCert::binary()) -> {ok, #'OTPCertificate'{}}. +%% +%% Description: Recursively decodes a Certificate. +%%-------------------------------------------------------------------- +decode_cert(DerCert) -> {ok, Cert} = 'OTP-PUB-KEY':decode('OTPCertificate', DerCert), - {ok, decode_all_otp(Cert)}. - -old_decode_cert(DerCert, otp) -> - {ok, Cert} = 'OTP-PUB-KEY':decode('Certificate', DerCert), - {ok, plain_to_otp(Cert)}. - -old_encode_cert(Cert) -> - PlainCert = otp_to_plain(Cert), - {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', PlainCert), - list_to_binary(EncCert). - - -encode_cert(Cert = #'Certificate'{}) -> - {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', Cert), - list_to_binary(EncCert); -encode_cert(C = #'OTPCertificate'{tbsCertificate = 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), - %% io:format("Extensions ~p~n",[Exts]), - Cert = C#'OTPCertificate'{tbsCertificate= - TBS#'OTPTBSCertificate'{ - issuer=Issuer, subject=Subject, - subjectPublicKeyInfo=Spki, - extensions=Exts}}, - {ok, EncCert} = 'OTP-PUB-KEY':encode('OTPCertificate', Cert), - list_to_binary(EncCert). + #'OTPCertificate'{tbsCertificate = TBS} = Cert, + {ok, Cert#'OTPCertificate'{tbsCertificate = decode_tbs(TBS)}}. -encode_tbs_cert(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), - TBSCert = TBS#'OTPTBSCertificate'{issuer=Issuer,subject=Subject, - subjectPublicKeyInfo=Spki,extensions=Exts}, - {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', TBSCert), - list_to_binary(EncTBSCert). +%%-------------------------------------------------------------------- +-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 + 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. %%-------------------------------------------------------------------- -%%% Internal functions +-spec supportedPublicKeyAlgorithms(Oid::tuple()) -> asn1_type(). +%% +%% Description: Returns the public key type for an algorithm +%% identifier tuple as found in SubjectPublicKeyInfo. +%% %%-------------------------------------------------------------------- - -decode_all_otp(C = #'OTPCertificate'{tbsCertificate = 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), - %% io:format("Extensions ~p~n",[Exts]), - C#'OTPCertificate'{tbsCertificate= - TBS#'OTPTBSCertificate'{ - issuer=Issuer, subject=Subject, - subjectPublicKeyInfo=Spki,extensions=Exts}}. - - -%%% SubjectPublicKey supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey'; supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey'; supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey'; supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey'; supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +%%% SubjectPublicKey + decode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = #'PublicKeyAlgorithm'{algorithm=Algo}, subjectPublicKey = {0,SPK0}}) -> @@ -186,33 +171,28 @@ encode_extensions(Exts) -> end end, Exts). -transform(#'AttributeTypeAndValue'{type=Id,value=Value0} = ATAV, Func) -> - {ok, Value} = - case attribute_type(Id) of - 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(List = [{rdnSequence, _}|_],Func) -> -%% lists:map(fun(Element) -> transform(Element,Func) end, List); -transform(#'NameConstraints'{permittedSubtrees=Permitted, excludedSubtrees=Excluded}, Func) -> - Res = #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func), - excludedSubtrees=transform_sub_tree(Excluded,Func)}, -%% io:format("~p~n",[Res]), - Res; -transform(Other,_) -> - Other. +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)} || @@ -236,303 +216,3 @@ attribute_type(?'id-at-pseudonym') -> 'X520Pseudonym'; attribute_type(?'id-domainComponent') -> 'DomainComponent'; attribute_type(?'id-emailAddress') -> 'EmailAddress'; attribute_type(Type) -> Type. - -%%% Old code transforms - -plain_to_otp(#'Certificate'{tbsCertificate = TBSCert, - signatureAlgorithm = SigAlg, - signature = Signature} = Cert) -> - Cert#'Certificate'{tbsCertificate = plain_to_otp(TBSCert), - signatureAlgorithm = plain_to_otp(SigAlg), - signature = plain_to_otp(Signature)}; - -plain_to_otp(#'TBSCertificate'{signature = Signature, - issuer = Issuer, - subject = Subject, - subjectPublicKeyInfo = SPubKeyInfo, - extensions = Extensions} = TBSCert) -> - - TBSCert#'TBSCertificate'{signature = plain_to_otp(Signature), - issuer = plain_to_otp(Issuer), - subject = - plain_to_otp(Subject), - subjectPublicKeyInfo = - plain_to_otp(SPubKeyInfo), - extensions = - plain_to_otp_extensions(Extensions) - }; - -plain_to_otp(#'AlgorithmIdentifier'{algorithm = Algorithm, - parameters = Params}) -> - SignAlgAny = - #'SignatureAlgorithm-Any'{algorithm = Algorithm, - parameters = Params}, - {ok, AnyEnc} = 'OTP-PUB-KEY':encode('SignatureAlgorithm-Any', - SignAlgAny), - {ok, SignAlg} = 'OTP-PUB-KEY':decode('SignatureAlgorithm', - list_to_binary(AnyEnc)), - SignAlg; - -plain_to_otp({rdnSequence, SeqList}) when is_list(SeqList) -> - {rdnSequence, - lists:map(fun(Seq) -> - lists:map(fun(Element) -> - plain_to_otp(Element) - end, - Seq) - end, SeqList)}; - -plain_to_otp(#'AttributeTypeAndValue'{} = ATAV) -> - {ok, ATAVEnc} = - 'OTP-PUB-KEY':encode('AttributeTypeAndValue', ATAV), - {ok, ATAVDec} = 'OTP-PUB-KEY':decode('OTPAttributeTypeAndValue', - list_to_binary(ATAVEnc)), - #'AttributeTypeAndValue'{type = ATAVDec#'OTPAttributeTypeAndValue'.type, - value = - ATAVDec#'OTPAttributeTypeAndValue'.value}; - -plain_to_otp(#'SubjectPublicKeyInfo'{algorithm = - #'AlgorithmIdentifier'{algorithm - = Algo, - parameters = - Params}, - subjectPublicKey = PublicKey}) -> - - AnyAlgo = #'PublicKeyAlgorithm'{algorithm = Algo, - parameters = Params}, - {0, AnyKey} = PublicKey, - AnyDec = #'OTPSubjectPublicKeyInfo-Any'{algorithm = AnyAlgo, - subjectPublicKey = AnyKey}, - {ok, AnyEnc} = - 'OTP-PUB-KEY':encode('OTPSubjectPublicKeyInfo-Any', AnyDec), - {ok, InfoDec} = 'OTP-PUB-KEY':decode('OTPOLDSubjectPublicKeyInfo', - list_to_binary(AnyEnc)), - - AlgorithmDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.algorithm, - AlgoDec = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.algo, - NewParams = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.parameters, - PublicKeyDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.subjectPublicKey, - NewAlgorithmDec = - #'SubjectPublicKeyInfoAlgorithm'{algorithm = AlgoDec, - parameters = NewParams}, - #'SubjectPublicKeyInfo'{algorithm = NewAlgorithmDec, - subjectPublicKey = PublicKeyDec - }; - -plain_to_otp(#'Extension'{extnID = ExtID, - critical = Critical, - extnValue = Value}) - when ExtID == ?'id-ce-authorityKeyIdentifier'; - ExtID == ?'id-ce-subjectKeyIdentifier'; - ExtID == ?'id-ce-keyUsage'; - ExtID == ?'id-ce-privateKeyUsagePeriod'; - ExtID == ?'id-ce-certificatePolicies'; - ExtID == ?'id-ce-policyMappings'; - ExtID == ?'id-ce-subjectAltName'; - ExtID == ?'id-ce-issuerAltName'; - ExtID == ?'id-ce-subjectDirectoryAttributes'; - ExtID == ?'id-ce-basicConstraints'; - ExtID == ?'id-ce-nameConstraints'; - ExtID == ?'id-ce-policyConstraints'; - ExtID == ?'id-ce-extKeyUsage'; - ExtID == ?'id-ce-cRLDistributionPoints'; - ExtID == ?'id-ce-inhibitAnyPolicy'; - ExtID == ?'id-ce-freshestCRL' -> - ExtAny = #'Extension-Any'{extnID = ExtID, - critical = Critical, - extnValue = Value}, - {ok, AnyEnc} = 'OTP-PUB-KEY':encode('Extension-Any', ExtAny), - {ok, ExtDec} = 'OTP-PUB-KEY':decode('OTPExtension', - list_to_binary(AnyEnc)), - - ExtValue = plain_to_otp_extension_value(ExtID, - ExtDec#'OTPExtension'.extnValue), - #'Extension'{extnID = ExtID, - critical = ExtDec#'OTPExtension'.critical, - extnValue = ExtValue}; - -plain_to_otp(#'Extension'{} = Ext) -> - Ext; - -plain_to_otp(#'AuthorityKeyIdentifier'{} = Ext) -> - CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, - Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = - plain_to_otp(CertIssuer)}; - - -plain_to_otp([{directoryName, Value}]) -> - [{directoryName, plain_to_otp(Value)}]; - -plain_to_otp(Value) -> - Value. - -otp_to_plain(#'Certificate'{tbsCertificate = TBSCert, - signatureAlgorithm = SigAlg, - signature = Signature} = Cert) -> - Cert#'Certificate'{tbsCertificate = otp_to_plain(TBSCert), - signatureAlgorithm = - otp_to_plain(SigAlg), - signature = otp_to_plain(Signature)}; - -otp_to_plain(#'TBSCertificate'{signature = Signature, - issuer = Issuer, - subject = Subject, - subjectPublicKeyInfo = SPubKeyInfo, - extensions = Extensions} = TBSCert) -> - - TBSCert#'TBSCertificate'{signature = otp_to_plain(Signature), - issuer = otp_to_plain(Issuer), - subject = - otp_to_plain(Subject), - subjectPublicKeyInfo = - otp_to_plain(SPubKeyInfo), - extensions = otp_to_plain_extensions(Extensions) - }; - -otp_to_plain(#'SignatureAlgorithm'{} = SignAlg) -> - {ok, EncSignAlg} = 'OTP-PUB-KEY':encode('SignatureAlgorithm', SignAlg), - {ok, #'SignatureAlgorithm-Any'{algorithm = Algorithm, - parameters = Params}} = - 'OTP-PUB-KEY':decode('SignatureAlgorithm-Any', - list_to_binary(EncSignAlg)), - #'AlgorithmIdentifier'{algorithm = Algorithm, - parameters = Params}; - -otp_to_plain({rdnSequence, SeqList}) when is_list(SeqList) -> - {rdnSequence, - lists:map(fun(Seq) -> - lists:map(fun(Element) -> - otp_to_plain(Element) - end, - Seq) - end, SeqList)}; - -otp_to_plain(#'AttributeTypeAndValue'{type = Type, value = Value}) -> - {ok, ATAVEnc} = - 'OTP-PUB-KEY':encode('OTPAttributeTypeAndValue', - #'OTPAttributeTypeAndValue'{type = Type, - value = Value}), - {ok, ATAVDec} = 'OTP-PUB-KEY':decode('AttributeTypeAndValue', - list_to_binary(ATAVEnc)), - ATAVDec; - -otp_to_plain(#'SubjectPublicKeyInfo'{algorithm = - #'SubjectPublicKeyInfoAlgorithm'{ - algorithm = Algo, - parameters = - Params}, - subjectPublicKey = PublicKey}) -> - - OtpAlgo = #'OTPOLDSubjectPublicKeyInfo_algorithm'{algo = Algo, - parameters = Params}, - OtpDec = #'OTPOLDSubjectPublicKeyInfo'{algorithm = OtpAlgo, - subjectPublicKey = PublicKey}, - {ok, OtpEnc} = - 'OTP-PUB-KEY':encode('OTPOLDSubjectPublicKeyInfo', OtpDec), - - {ok, AnyDec} = 'OTP-PUB-KEY':decode('OTPSubjectPublicKeyInfo-Any', - list_to_binary(OtpEnc)), - - #'OTPSubjectPublicKeyInfo-Any'{algorithm = #'PublicKeyAlgorithm'{ - algorithm = NewAlgo, - parameters = NewParams}, - subjectPublicKey = Bin} = AnyDec, - - #'SubjectPublicKeyInfo'{algorithm = - #'AlgorithmIdentifier'{ - algorithm = NewAlgo, - parameters = plain_key_params(NewParams)}, - subjectPublicKey = - {0, Bin} - }; - -otp_to_plain(#'Extension'{extnID = ExtID, - extnValue = Value} = Ext) -> - ExtValue = - otp_to_plain_extension_value(ExtID, Value), - - Ext#'Extension'{extnValue = ExtValue}; - -otp_to_plain(#'AuthorityKeyIdentifier'{} = Ext) -> - CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, - Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = - otp_to_plain(CertIssuer)}; - -otp_to_plain([{directoryName, Value}]) -> - [{directoryName, otp_to_plain(Value)}]; - -otp_to_plain(Value) -> - Value. - -plain_key_params('NULL') -> - <<5,0>>; -plain_key_params(Value) -> - Value. - -plain_to_otp_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> - plain_to_otp(Value); -plain_to_otp_extension_value(_, Value) -> - Value. - -plain_to_otp_extensions(Exts) when is_list(Exts) -> - lists:map(fun(Ext) -> plain_to_otp(Ext) end, Exts). - -otp_to_plain_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('AuthorityKeyIdentifier', - otp_to_plain(Value)), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectKeyIdentifier', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectKeyIdentifier', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-keyUsage', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('KeyUsage', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-privateKeyUsagePeriod', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PrivateKeyUsagePeriod', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-certificatePolicies', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('CertificatePolicies', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-policyMappings', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyMappings', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectAltName', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectAltName', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-issuerAltName', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('IssuerAltName', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-subjectDirectoryAttributes', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectDirectoryAttributes', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-basicConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('BasicConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-nameConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('NameConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-policyConstraints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyConstraints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-extKeyUsage', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('ExtKeyUsage', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-cRLDistributionPoints', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('CRLDistributionPoints', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-inhibitAnyPolicy', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('InhibitAnyPolicy', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(?'id-ce-freshestCRL', Value) -> - {ok, Enc} = 'OTP-PUB-KEY':encode('FreshestCRL', Value), - otp_to_plain_extension_value_format(Enc); -otp_to_plain_extension_value(_Id, Value) -> - Value. - -otp_to_plain_extension_value_format(Value) -> - list_to_binary(Value). - -otp_to_plain_extensions(Exts) when is_list(Exts) -> - lists:map(fun(Ext) -> - otp_to_plain(Ext) - end, Exts). diff --git a/lib/public_key/src/pubkey_crypto.erl b/lib/public_key/src/pubkey_crypto.erl deleted file mode 100644 index 4ab655e977..0000000000 --- a/lib/public_key/src/pubkey_crypto.erl +++ /dev/null @@ -1,160 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%% Description: Functions that call the crypto driver. - --module(pubkey_crypto). - --include("public_key.hrl"). - --export([encrypt_public/3, decrypt_private/3, - encrypt_private/3, decrypt_public/3, - sign/2, sign/3, verify/5, gen_key/2]). - --define(UINT32(X), X:32/unsigned-big-integer). - -%%==================================================================== -%% Internal application API -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: encrypt(PlainText, Key, Padding) -> Encrypted -%% -%% PlainText = binary() -%% Key = rsa_public_key() | rsa_private_key() -%% Padding = rsa_pkcs1_padding | rsa_pkcs1_oaep_padding -%% Encrypted = binary() -%% -%% Description: Public key encrypts PlainText. -%%-------------------------------------------------------------------- -encrypt_public(PlainText, #'RSAPublicKey'{modulus=N,publicExponent=E}, - Padding) -> - crypto:rsa_public_encrypt(PlainText, [crypto:mpint(E),crypto:mpint(N)], - Padding); -encrypt_public(PlainText, #'RSAPrivateKey'{modulus=N,publicExponent=E}, - Padding) -> - crypto:rsa_public_encrypt(PlainText, [crypto:mpint(E),crypto:mpint(N)], - Padding). - -encrypt_private(PlainText, #'RSAPrivateKey'{modulus = N, - publicExponent = E, - privateExponent = D}, Padding) -> - crypto:rsa_private_encrypt(PlainText, [crypto:mpint(E), - crypto:mpint(N), - crypto:mpint(D)], Padding). - -%%-------------------------------------------------------------------- -%% Function: decrypt(CipherText, Key) -> PlainText -%% -%% ChipherText = binary() -%% Key = rsa_private_key() -%% Padding = rsa_pkcs1_padding | rsa_pkcs1_oaep_padding -%% PlainText = binary() -%% -%% Description: Uses private key to decrypt public key encrypted data. -%%-------------------------------------------------------------------- -decrypt_private(CipherText, - #'RSAPrivateKey'{modulus = N,publicExponent = E, - privateExponent = D}, - Padding) -> - crypto:rsa_private_decrypt(CipherText, - [crypto:mpint(E), crypto:mpint(N), - crypto:mpint(D)], Padding). -decrypt_public(CipherText, #'RSAPublicKey'{modulus = N, publicExponent = E}, - Padding) -> - crypto:rsa_public_decrypt(CipherText,[crypto:mpint(E), crypto:mpint(N)], - Padding); -decrypt_public(CipherText, #'RSAPrivateKey'{modulus = N, publicExponent = E}, - Padding) -> - crypto:rsa_public_decrypt(CipherText,[crypto:mpint(E), crypto:mpint(N)], - Padding). - -%%-------------------------------------------------------------------- -%% Function: sign(PlainText, Key) -> -%% sign(DigestType, PlainText, Key) -> Signature -%% -%% DigestType = sha | md5 -%% PlainText = binary() -%% Key = rsa_private_key() | dsa_private_key() -%% Signature = binary() -%% -%% Description: Signs PlainText using Key. -%%-------------------------------------------------------------------- -sign(PlainText, Digest) -> - sign(sha, PlainText, Digest). - -sign(DigestType, PlainText, #'RSAPrivateKey'{modulus = N, publicExponent = E, - privateExponent = D}) -> - crypto:rsa_sign(DigestType, sized_binary(PlainText), [crypto:mpint(E), - crypto:mpint(N), - crypto:mpint(D)]); - -sign(sha, PlainText, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> - crypto:dss_sign(sized_binary(PlainText), - [crypto:mpint(P), crypto:mpint(Q), - crypto:mpint(G), crypto:mpint(X)]). - -%%-------------------------------------------------------------------- -%% Function: verify(DigestType, PlainText, Signature, Key) -> true | false -%% -%% DigestType = sha | md5 -%% PlainText = binary() -%% Signature = binary() -%% Key = rsa_public_key() | dsa_public_key() -%% -%% Description: Verifies the signature <Signature>. -%%-------------------------------------------------------------------- -verify(DigestType, PlainText, Signature, - #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}, _) -> - crypto:rsa_verify(DigestType, - sized_binary(PlainText), - sized_binary(Signature), - [crypto:mpint(Exp), crypto:mpint(Mod)]); - -verify(sha, PlainText, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) -> - crypto:dss_verify(sized_binary(PlainText), - sized_binary(Signature), - [crypto:mpint(P), crypto:mpint(Q), - crypto:mpint(G), crypto:mpint(Key)]). - - -%%-------------------------------------------------------------------- -%% Function: gen_key(Type, Params) -> -%% Type = diffie_hellman -%% Params = [P,G] | [Y, P, G] -%% Description: Generates keys. -%% ----------------------------------------------------------------- -gen_key(diffie_hellman, [Y, P, G]) -> - crypto:dh_generate_key(crypto:mpint(Y), [crypto:mpint(P), - crypto:mpint(G)]); -gen_key(diffie_hellman, [P, G]) -> - crypto:dh_generate_key([crypto:mpint(P), crypto:mpint(G)]). - -%%% TODO: Support rsa, dss key_gen - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -sized_binary(Binary) when is_binary(Binary) -> - Size = size(Binary), - <<?UINT32(Size), Binary/binary>>; -sized_binary(List) -> - sized_binary(list_to_binary(List)). - diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl index 9fc17b6f73..c26815bc04 100644 --- a/lib/public_key/src/pubkey_pem.erl +++ b/lib/public_key/src/pubkey_pem.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -40,7 +40,10 @@ -module(pubkey_pem). --export([read_file/1, read_file/2, write_file/2, decode/2]). +-include("public_key.hrl"). + +-export([encode/1, decode/1, decipher/2, cipher/3]). +%% Backwards compatibility -export([decode_key/2]). -define(ENCODED_LINE_LENGTH, 64). @@ -48,28 +51,84 @@ %%==================================================================== %% Internal application API %%==================================================================== -read_file(File) -> - read_file(File, no_passwd). -read_file(File, Passwd) -> - {ok, Bin} = file:read_file(File), - decode(Bin, Passwd). +%%-------------------------------------------------------------------- +-spec decode(binary()) -> [pem_entry()]. +%% +%% Description: Decodes a PEM binary. +%%-------------------------------------------------------------------- +decode(Bin) -> + decode_pem_entries(split_bin(Bin), []). -write_file(File, Ds) -> - file:write_file(File, encode_file(Ds)). +%%-------------------------------------------------------------------- +-spec encode([pem_entry()]) -> iolist(). +%% +%% Description: Encodes a list of PEM entries. +%%-------------------------------------------------------------------- +encode(PemEntries) -> + encode_pem_entries(PemEntries). -decode_key({_Type, Bin, not_encrypted}, _) -> - Bin; -decode_key({_Type, Bin, {Chipher,Salt}}, Password) -> - decode_key(Bin, Password, Chipher, Salt). +%%-------------------------------------------------------------------- +-spec decipher({pki_asn1_type(), DerEncrypted::binary(),{Cipher :: string(), + Salt :: binary()}}, + string()) -> Der::binary(). +%% +%% Description: Deciphers a decrypted pem entry. +%%-------------------------------------------------------------------- +decipher({_, DecryptDer, {Cipher,Salt}}, Password) -> + decode_key(DecryptDer, Password, Cipher, Salt). -decode(Bin, Passwd) -> - decode_file(split_bin(Bin), Passwd). +%%-------------------------------------------------------------------- +-spec cipher(Der::binary(),{Cipher :: string(), Salt :: binary()} , + string()) -> binary(). +%% +%% Description: Ciphers a PEM entry +%%-------------------------------------------------------------------- +cipher(Der, {Cipher,Salt}, Password)-> + encode_key(Der, Password, Cipher, Salt). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +encode_pem_entries(Entries) -> + [encode_pem_entry(Entry) || Entry <- Entries]. +encode_pem_entry({Type, Der, not_encrypted}) -> + StartStr = pem_start(Type), + [StartStr, "\n", b64encode_and_split(Der), "\n", pem_end(StartStr) ,"\n\n"]; +encode_pem_entry({Type, Der, {Cipher, Salt}}) -> + StartStr = pem_start(Type), + [StartStr,"\n", pem_decrypt(),"\n", pem_decrypt_info(Cipher, Salt),"\n", + b64encode_and_split(Der), "\n", pem_end(StartStr) ,"\n\n"]. + +decode_pem_entries([], Entries) -> + lists:reverse(Entries); +decode_pem_entries([<<>>], Entries) -> + lists:reverse(Entries); +decode_pem_entries([<<>> | Lines], Entries) -> + decode_pem_entries(Lines, Entries); +decode_pem_entries([Start| Lines], Entries) -> + case pem_end(Start) of + undefined -> + decode_pem_entries(Lines, Entries); + _End -> + {Entry, RestLines} = join_entry(Lines, []), + decode_pem_entries(RestLines, [decode_pem_entry(Start, Entry) | Entries]) + end. + +decode_pem_entry(Start, [<<"Proc-Type: 4,ENCRYPTED", _/binary>>, Line | Lines]) -> + Type = asn1_type(Start), + Cs = erlang:iolist_to_binary(Lines), + Decoded = base64:mime_decode(Cs), + [_, DekInfo0] = string:tokens(binary_to_list(Line), ": "), + [Cipher, Salt] = string:tokens(DekInfo0, ","), + {Type, Decoded, {Cipher, unhex(Salt)}}; +decode_pem_entry(Start, Lines) -> + Type = asn1_type(Start), + Cs = erlang:iolist_to_binary(Lines), + Decoded = base64:mime_decode(Cs), + {Type, Decoded, not_encrypted}. + split_bin(Bin) -> split_bin(0, Bin). @@ -85,82 +144,22 @@ split_bin(N, Bin) -> split_bin(N+1, Bin) end. -decode_file(Bin, Passwd) -> - decode_file(Bin, [], [Passwd]). - -decode_file([<<"-----BEGIN CERTIFICATE REQUEST-----", _/binary>>|Rest], Ens, Info) -> - decode_file2(Rest, [], Ens, cert_req, Info); -decode_file([<<"-----BEGIN CERTIFICATE-----", _/binary>>|Rest], Ens, Info) -> - decode_file2(Rest, [], Ens, cert, Info); -decode_file([<<"-----BEGIN RSA PRIVATE KEY-----", _/binary>>|Rest], Ens, Info) -> - decode_file2(Rest, [], Ens, rsa_private_key, Info); -decode_file([<<"-----BEGIN DSA PRIVATE KEY-----", _/binary>>|Rest], Ens, Info) -> - decode_file2(Rest, [], Ens, dsa_private_key, Info); -decode_file([<<"-----BEGIN DH PARAMETERS-----", _/binary>>|Rest], Ens, Info) -> - decode_file2(Rest, [], Ens, dh_params, Info); -decode_file([_|Rest], Ens, Info) -> - decode_file(Rest, Ens, Info); -decode_file([], Ens, _Info) -> - {ok, lists:reverse(Ens)}. - -decode_file2([<<"Proc-Type: 4,ENCRYPTED", _/binary>>| Rest0], RLs, Ens, Tag, Info0) -> - [InfoLine|Rest] = Rest0, - Info = dek_info(InfoLine, Info0), - decode_file2(Rest, RLs, Ens, Tag, Info); -decode_file2([<<"-----END", _/binary>>| Rest], RLs, Ens, Tag, Info0) -> - Cs = erlang:iolist_to_binary(lists:reverse(RLs)), - Bin = base64:mime_decode(Cs), - case Info0 of - [Password, Cipher, SaltHex | Info1] -> - Salt = unhex(SaltHex), - Enc = {Cipher, Salt}, - Decoded = decode_key(Bin, Password, Cipher, Salt), - decode_file(Rest, [{Tag, Decoded, Enc}| Ens], Info1); - _ -> - decode_file(Rest, [{Tag, Bin, not_encrypted}| Ens], Info0) - end; -decode_file2([L|Rest], RLs, Ens, Tag, Info0) -> - decode_file2(Rest, [L|RLs], Ens, Tag, Info0); -decode_file2([], _, Ens, _, _) -> - {ok, lists:reverse(Ens)}. - -%% TODO Support same as decode_file -encode_file(Ds) -> - lists:map( - fun({cert, Bin}) -> - %% PKIX (X.509) - ["-----BEGIN CERTIFICATE-----\n", - b64encode_and_split(Bin), - "-----END CERTIFICATE-----\n\n"]; - ({cert_req, Bin}) -> - %% PKCS#10 - ["-----BEGIN CERTIFICATE REQUEST-----\n", - b64encode_and_split(Bin), - "-----END CERTIFICATE REQUEST-----\n\n"]; - ({rsa_private_key, Bin}) -> - %% PKCS#? - ["XXX Following key assumed not encrypted\n", - "-----BEGIN RSA PRIVATE KEY-----\n", - b64encode_and_split(Bin), - "-----END RSA PRIVATE KEY-----\n\n"] - end, Ds). - -dek_info(Line0, Info) -> - Line = binary_to_list(Line0), - [_, DekInfo0] = string:tokens(Line, ": "), - DekInfo1 = string:tokens(DekInfo0, ",\n"), - Info ++ DekInfo1. +b64encode_and_split(Bin) -> + split_lines(base64:encode(Bin)). -unhex(S) -> - unhex(S, []). +split_lines(<<Text:?ENCODED_LINE_LENGTH/binary>>) -> + [Text]; +split_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) -> + [Text, $\n | split_lines(Rest)]; +split_lines(Bin) -> + [Bin]. -unhex("", Acc) -> - list_to_binary(lists:reverse(Acc)); -unhex([D1, D2 | Rest], Acc) -> - unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]). +%% Ignore white space at end of line +join_entry([<<"-----END ", _/binary>>| Lines], Entry) -> + {lists:reverse(Entry), Lines}; +join_entry([Line | Lines], Entry) -> + join_entry(Lines, [Line | Entry]). -decode_key(Data, no_passwd, _Alg, _Salt) -> - Data; decode_key(Data, Password, "DES-CBC", Salt) -> Key = password_to_key(Password, Salt, 8), IV = Salt, @@ -171,6 +170,16 @@ decode_key(Data, Password, "DES-EDE3-CBC", Salt) -> <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key, crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data). +encode_key(Data, Password, "DES-CBC", Salt) -> + Key = password_to_key(Password, Salt, 8), + IV = Salt, + crypto:des_cbc_encrypt(Key, IV, Data); +encode_key(Data, Password, "DES-EDE3-CBC", Salt) -> + Key = password_to_key(Password, Salt, 24), + IV = Salt, + <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key, + crypto:des_ede3_cbc_encrypt(Key1, Key2, Key3, IV, Data). + password_to_key(Data, Salt, KeyLen) -> <<Key:KeyLen/binary, _/binary>> = password_to_key(<<>>, Data, Salt, KeyLen, <<>>), @@ -182,11 +191,69 @@ password_to_key(Prev, Data, Salt, Len, Acc) -> M = crypto:md5([Prev, Data, Salt]), password_to_key(M, Data, Salt, Len - size(M), <<Acc/binary, M/binary>>). -b64encode_and_split(Bin) -> - split_lines(base64:encode(Bin)). +unhex(S) -> + unhex(S, []). -split_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) -> - [Text, $\n | split_lines(Rest)]; -split_lines(Bin) -> - [Bin, $\n]. +unhex("", Acc) -> + list_to_binary(lists:reverse(Acc)); +unhex([D1, D2 | Rest], Acc) -> + unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]). + +hexify(L) -> [[hex_byte(B)] || B <- binary_to_list(L)]. + +hex_byte(B) when B < 16#10 -> ["0", erlang:integer_to_list(B, 16)]; +hex_byte(B) -> erlang:integer_to_list(B, 16). + +pem_start('Certificate') -> + <<"-----BEGIN CERTIFICATE-----">>; +pem_start('RSAPrivateKey') -> + <<"-----BEGIN RSA PRIVATE KEY-----">>; +pem_start('RSAPublicKey') -> + <<"-----BEGIN RSA PUBLIC KEY-----">>; +pem_start('SubjectPublicKeyInfo') -> + <<"-----BEGIN PUBLIC KEY-----">>; +pem_start('DSAPrivateKey') -> + <<"-----BEGIN DSA PRIVATE KEY-----">>; +pem_start('DHParameter') -> + <<"-----BEGIN DH PARAMETERS-----">>. +pem_end(<<"-----BEGIN CERTIFICATE-----">>) -> + <<"-----END CERTIFICATE-----">>; +pem_end(<<"-----BEGIN RSA PRIVATE KEY-----">>) -> + <<"-----END RSA PRIVATE KEY-----">>; +pem_end(<<"-----BEGIN RSA PUBLIC KEY-----">>) -> + <<"-----END RSA PUBLIC KEY-----">>; +pem_end(<<"-----BEGIN PUBLIC KEY-----">>) -> + <<"-----END PUBLIC KEY-----">>; +pem_end(<<"-----BEGIN DSA PRIVATE KEY-----">>) -> + <<"-----END DSA PRIVATE KEY-----">>; +pem_end(<<"-----BEGIN DH PARAMETERS-----">>) -> + <<"-----END DH PARAMETERS-----">>; +pem_end(_) -> + undefined. + +asn1_type(<<"-----BEGIN CERTIFICATE-----">>) -> + 'Certificate'; +asn1_type(<<"-----BEGIN RSA PRIVATE KEY-----">>) -> + 'RSAPrivateKey'; +asn1_type(<<"-----BEGIN RSA PUBLIC KEY-----">>) -> + 'RSAPublicKey'; +asn1_type(<<"-----BEGIN PUBLIC KEY-----">>) -> + 'SubjectPublicKeyInfo'; +asn1_type(<<"-----BEGIN DSA PRIVATE KEY-----">>) -> + 'DSAPrivateKey'; +asn1_type(<<"-----BEGIN DH PARAMETERS-----">>) -> + 'DHParameter'. + +pem_decrypt() -> + <<"Proc-Type: 4,ENCRYPTED">>. +pem_decrypt_info(Cipher, Salt) -> + io_lib:format("DEK-Info: ~s,~s", [Cipher, lists:flatten(hexify(Salt))]). + +%%-------------------------------------------------------------------- +%%% Deprecated +%%-------------------------------------------------------------------- +decode_key({_Type, Bin, not_encrypted}, _) -> + Bin; +decode_key({_Type, Bin, {Chipher,Salt}}, Password) -> + decode_key(Bin, Password, Chipher, Salt). diff --git a/lib/public_key/src/pubkey_ssh.erl b/lib/public_key/src/pubkey_ssh.erl new file mode 100644 index 0000000000..f342eab159 --- /dev/null +++ b/lib/public_key/src/pubkey_ssh.erl @@ -0,0 +1,431 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(pubkey_ssh). + +-include("public_key.hrl"). + +-export([decode/2, encode/2]). + +-define(UINT32(X), X:32/unsigned-big-integer). +%% Max encoded line length is 72, but conformance examples use 68 +%% Comment from rfc 4716: "The following are some examples of public +%% key files that are compliant (note that the examples all wrap +%% before 72 bytes to meet IETF document requirements; however, they +%% are still compliant.)" So we choose to use 68 also. +-define(ENCODED_LINE_LENGTH, 68). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec decode(binary(), public_key | ssh_file()) -> [{public_key(), Attributes::list()}]. +%% +%% Description: Decodes a ssh file-binary. +%%-------------------------------------------------------------------- +decode(Bin, public_key)-> + case binary:match(Bin, begin_marker()) of + nomatch -> + openssh_decode(Bin, openssh_public_key); + _ -> + rfc4716_decode(Bin) + end; +decode(Bin, rfc4716_public_key) -> + rfc4716_decode(Bin); +decode(Bin, Type) -> + openssh_decode(Bin, Type). + +%%-------------------------------------------------------------------- +-spec encode([{public_key(), Attributes::list()}], ssh_file()) -> + binary(). +%% +%% Description: Encodes a list of ssh file entries. +%%-------------------------------------------------------------------- +encode(Entries, Type) -> + erlang:iolist_to_binary(lists:map(fun({Key, Attributes}) -> + do_encode(Type, Key, Attributes) + end, Entries)). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +begin_marker() -> + <<"---- BEGIN SSH2 PUBLIC KEY ----">>. +end_marker() -> + <<"---- END SSH2 PUBLIC KEY ----">>. + +rfc4716_decode(Bin) -> + Lines = binary:split(Bin, <<"\n">>, [global]), + do_rfc4716_decode(Lines, []). + +do_rfc4716_decode([<<"---- BEGIN SSH2 PUBLIC KEY ----", _/binary>> | Lines], Acc) -> + do_rfc4716_decode(Lines, Acc); +%% Ignore empty lines before or after begin/end - markers. +do_rfc4716_decode([<<>> | Lines], Acc) -> + do_rfc4716_decode(Lines, Acc); +do_rfc4716_decode([], Acc) -> + lists:reverse(Acc); +do_rfc4716_decode(Lines, Acc) -> + {Headers, PubKey, Rest} = rfc4716_decode_lines(Lines, []), + case Headers of + [_|_] -> + do_rfc4716_decode(Rest, [{PubKey, [{headers, Headers}]} | Acc]); + _ -> + do_rfc4716_decode(Rest, [{PubKey, []} | Acc]) + end. + +rfc4716_decode_lines([Line | Lines], Acc) -> + case binary:last(Line) of + $\\ -> + NewLine = binary:replace(Line,<<"\\">>, hd(Lines), []), + rfc4716_decode_lines([NewLine | tl(Lines)], Acc); + _ -> + rfc4716_decode_line(Line, Lines, Acc) + end. + +rfc4716_decode_line(Line, Lines, Acc) -> + case binary:split(Line, <<":">>) of + [Tag, Value] -> + rfc4716_decode_lines(Lines, [{string_decode(Tag), unicode_decode(Value)} | Acc]); + _ -> + {Body, Rest} = join_entry([Line | Lines], []), + {lists:reverse(Acc), rfc4716_pubkey_decode(base64:mime_decode(Body)), Rest} + end. + +join_entry([<<"---- END SSH2 PUBLIC KEY ----", _/binary>>| Lines], Entry) -> + {lists:reverse(Entry), Lines}; +join_entry([Line | Lines], Entry) -> + join_entry(Lines, [Line | Entry]). + + +rfc4716_pubkey_decode(<<?UINT32(Len), Type:Len/binary, + ?UINT32(SizeE), E:SizeE/binary, + ?UINT32(SizeN), N:SizeN/binary>>) when Type == <<"ssh-rsa">> -> + #'RSAPublicKey'{modulus = erlint(SizeN, N), + publicExponent = erlint(SizeE, E)}; + +rfc4716_pubkey_decode(<<?UINT32(Len), Type:Len/binary, + ?UINT32(SizeP), P:SizeP/binary, + ?UINT32(SizeQ), Q:SizeQ/binary, + ?UINT32(SizeG), G:SizeG/binary, + ?UINT32(SizeY), Y:SizeY/binary>>) when Type == <<"ssh-dss">> -> + {erlint(SizeY, Y), + #'Dss-Parms'{p = erlint(SizeP, P), + q = erlint(SizeQ, Q), + g = erlint(SizeG, G)}}. + +openssh_decode(Bin, FileType) -> + Lines = binary:split(Bin, <<"\n">>, [global]), + do_openssh_decode(FileType, Lines, []). + +do_openssh_decode(_, [], Acc) -> + lists:reverse(Acc); +%% Ignore empty lines +do_openssh_decode(FileType, [<<>> | Lines], Acc) -> + do_openssh_decode(FileType, Lines, Acc); +%% Ignore lines that start with # +do_openssh_decode(FileType,[<<"#", _/binary>> | Lines], Acc) -> + do_openssh_decode(FileType, Lines, Acc); +do_openssh_decode(auth_keys = FileType, [Line | Lines], Acc) -> + Split = binary:split(Line, <<" ">>, [global]), + case mend_split(Split, []) of + %% ssh2 + [Options, KeyType, Base64Enc, Comment] when KeyType == <<"ssh-rsa">>; + KeyType == <<"ssh-dss">> -> + do_openssh_decode(FileType, Lines, + [{openssh_pubkey_decode(KeyType, Base64Enc), + [{comment, string_decode(Comment)}, + {options, comma_list_decode(Options)}]} + | Acc]); + + [KeyType, Base64Enc, Comment] when KeyType == <<"ssh-rsa">>; + KeyType == <<"ssh-dss">> -> + do_openssh_decode(FileType, Lines, + [{openssh_pubkey_decode(KeyType, Base64Enc), + [{comment, string_decode(Comment)}]} | Acc]); + %% ssh1 + [Options, Bits, Exponent, Modulus, Comment] -> + do_openssh_decode(FileType, Lines, + [{ssh1_rsa_pubkey_decode(Modulus, Exponent), + [{comment, string_decode(Comment)}, + {options, comma_list_decode(Options)}, + {bits, integer_decode(Bits)}]} | Acc]); + [Bits, Exponent, Modulus, Comment] -> + do_openssh_decode(FileType, Lines, + [{ssh1_rsa_pubkey_decode(Modulus, Exponent), + [{comment, string_decode(Comment)}, + {bits, integer_decode(Bits)}]} | Acc]) + end; + +do_openssh_decode(known_hosts = FileType, [Line | Lines], Acc) -> + case binary:split(Line, <<" ">>, [global]) of + %% ssh 2 + [HostNames, KeyType, Base64Enc] when KeyType == <<"ssh-rsa">>; + KeyType == <<"ssh-dss">> -> + do_openssh_decode(FileType, Lines, + [{openssh_pubkey_decode(KeyType, Base64Enc), + [{hostnames, comma_list_decode(HostNames)}]}| Acc]); + [HostNames, KeyType, Base64Enc, Comment] when KeyType == <<"ssh-rsa">>; + KeyType == <<"ssh-dss">> -> + do_openssh_decode(FileType, Lines, + [{openssh_pubkey_decode(KeyType, Base64Enc), + [{comment, string_decode(Comment)}, + {hostnames, comma_list_decode(HostNames)}]} | Acc]); + %% ssh 1 + [HostNames, Bits, Exponent, Modulus, Comment] -> + do_openssh_decode(FileType, Lines, + [{ssh1_rsa_pubkey_decode(Modulus, Exponent), + [{comment, string_decode(Comment)}, + {hostnames, comma_list_decode(HostNames)}, + {bits, integer_decode(Bits)}]} | Acc]); + [HostNames, Bits, Exponent, Modulus] -> + do_openssh_decode(FileType, Lines, + [{ssh1_rsa_pubkey_decode(Modulus, Exponent), + [{comment, []}, + {hostnames, comma_list_decode(HostNames)}, + {bits, integer_decode(Bits)}]} | Acc]) + end; + +do_openssh_decode(openssh_public_key = FileType, [Line | Lines], Acc) -> + case binary:split(Line, <<" ">>, [global]) of + [KeyType, Base64Enc, Comment0] when KeyType == <<"ssh-rsa">>; + KeyType == <<"ssh-dss">> -> + Comment = string:strip(binary_to_list(Comment0), right, $\n), + do_openssh_decode(FileType, Lines, + [{openssh_pubkey_decode(KeyType, Base64Enc), + [{comment, Comment}]} | Acc]) + end. + + +openssh_pubkey_decode(<<"ssh-rsa">>, Base64Enc) -> + <<?UINT32(StrLen), _:StrLen/binary, + ?UINT32(SizeE), E:SizeE/binary, + ?UINT32(SizeN), N:SizeN/binary>> + = base64:mime_decode(Base64Enc), + #'RSAPublicKey'{modulus = erlint(SizeN, N), + publicExponent = erlint(SizeE, E)}; + +openssh_pubkey_decode(<<"ssh-dss">>, Base64Enc) -> + <<?UINT32(StrLen), _:StrLen/binary, + ?UINT32(SizeP), P:SizeP/binary, + ?UINT32(SizeQ), Q:SizeQ/binary, + ?UINT32(SizeG), G:SizeG/binary, + ?UINT32(SizeY), Y:SizeY/binary>> + = base64:mime_decode(Base64Enc), + {erlint(SizeY, Y), + #'Dss-Parms'{p = erlint(SizeP, P), + q = erlint(SizeQ, Q), + g = erlint(SizeG, G)}}. + +erlint(MPIntSize, MPIntValue) -> + Bits= MPIntSize * 8, + <<Integer:Bits/integer>> = MPIntValue, + Integer. + +ssh1_rsa_pubkey_decode(MBin, EBin) -> + #'RSAPublicKey'{modulus = integer_decode(MBin), + publicExponent = integer_decode(EBin)}. + +integer_decode(BinStr) -> + list_to_integer(binary_to_list(BinStr)). + +string_decode(BinStr) -> + binary_to_list(BinStr). + +unicode_decode(BinStr) -> + unicode:characters_to_list(BinStr). + +comma_list_decode(BinOpts) -> + CommaList = binary:split(BinOpts, <<",">>, [global]), + lists:map(fun(Item) -> + binary_to_list(Item) + end, CommaList). + +do_encode(rfc4716_public_key, Key, Attributes) -> + rfc4716_encode(Key, proplists:get_value(headers, Attributes, []), []); + +do_encode(Type, Key, Attributes) -> + openssh_encode(Type, Key, Attributes). + +rfc4716_encode(Key, [],[]) -> + erlang:iolist_to_binary([begin_marker(),"\n", + split_lines(base64:encode(ssh2_pubkey_encode(Key))), + "\n", end_marker(), "\n"]); +rfc4716_encode(Key, [], [_|_] = Acc) -> + erlang:iolist_to_binary([begin_marker(), "\n", + lists:reverse(Acc), + split_lines(base64:encode(ssh2_pubkey_encode(Key))), + "\n", end_marker(), "\n"]); +rfc4716_encode(Key, [ Header | Headers], Acc) -> + LinesStr = rfc4716_encode_header(Header), + rfc4716_encode(Key, Headers, [LinesStr | Acc]). + +rfc4716_encode_header({Tag, Value}) -> + TagLen = length(Tag), + ValueLen = length(Value), + case TagLen + 1 + ValueLen of + N when N > ?ENCODED_LINE_LENGTH -> + NumOfChars = ?ENCODED_LINE_LENGTH - (TagLen + 1), + {First, Rest} = lists:split(NumOfChars, Value), + [Tag,":" , First, [$\\], "\n", rfc4716_encode_value(Rest) , "\n"]; + _ -> + [Tag, ":", Value, "\n"] + end. + +rfc4716_encode_value(Value) -> + case length(Value) of + N when N > ?ENCODED_LINE_LENGTH -> + {First, Rest} = lists:split(?ENCODED_LINE_LENGTH, Value), + [First, [$\\], "\n", rfc4716_encode_value(Rest)]; + _ -> + Value + end. + +openssh_encode(openssh_public_key, Key, Attributes) -> + Comment = proplists:get_value(comment, Attributes), + Enc = base64:encode(ssh2_pubkey_encode(Key)), + erlang:iolist_to_binary([key_type(Key), " ", Enc, " ", Comment, "\n"]); + +openssh_encode(auth_keys, Key, Attributes) -> + Comment = proplists:get_value(comment, Attributes, ""), + Options = proplists:get_value(options, Attributes, undefined), + Bits = proplists:get_value(bits, Attributes, undefined), + case Bits of + undefined -> + openssh_ssh2_auth_keys_encode(Options, Key, Comment); + _ -> + openssh_ssh1_auth_keys_encode(Options, Bits, Key, Comment) + end; +openssh_encode(known_hosts, Key, Attributes) -> + Comment = proplists:get_value(comment, Attributes, ""), + Hostnames = proplists:get_value(hostnames, Attributes), + Bits = proplists:get_value(bits, Attributes, undefined), + case Bits of + undefined -> + openssh_ssh2_know_hosts_encode(Hostnames, Key, Comment); + _ -> + openssh_ssh1_known_hosts_encode(Hostnames, Bits, Key, Comment) + end. + +openssh_ssh2_auth_keys_encode(undefined, Key, Comment) -> + erlang:iolist_to_binary([key_type(Key)," ", base64:encode(ssh2_pubkey_encode(Key)), line_end(Comment)]); +openssh_ssh2_auth_keys_encode(Options, Key, Comment) -> + erlang:iolist_to_binary([comma_list_encode(Options, []), " ", + key_type(Key)," ", base64:encode(ssh2_pubkey_encode(Key)), line_end(Comment)]). + +openssh_ssh1_auth_keys_encode(undefined, Bits, + #'RSAPublicKey'{modulus = N, publicExponent = E}, + Comment) -> + erlang:iolist_to_binary([integer_to_list(Bits), " ", integer_to_list(E), " ", integer_to_list(N), + line_end(Comment)]); +openssh_ssh1_auth_keys_encode(Options, Bits, + #'RSAPublicKey'{modulus = N, publicExponent = E}, + Comment) -> + erlang:iolist_to_binary([comma_list_encode(Options, []), " ", integer_to_list(Bits), + " ", integer_to_list(E), " ", integer_to_list(N), line_end(Comment)]). + +openssh_ssh2_know_hosts_encode(Hostnames, Key, Comment) -> + erlang:iolist_to_binary([comma_list_encode(Hostnames, []), " ", + key_type(Key)," ", base64:encode(ssh2_pubkey_encode(Key)), line_end(Comment)]). + +openssh_ssh1_known_hosts_encode(Hostnames, Bits, + #'RSAPublicKey'{modulus = N, publicExponent = E}, + Comment) -> + erlang:iolist_to_binary([comma_list_encode(Hostnames, [])," ", integer_to_list(Bits)," ", + integer_to_list(E)," ", integer_to_list(N), line_end(Comment)]). + +line_end("") -> + "\n"; +line_end(Comment) -> + [" ", Comment, "\n"]. + +key_type(#'RSAPublicKey'{}) -> + <<"ssh-rsa">>; +key_type({_, #'Dss-Parms'{}}) -> + <<"ssh-dss">>. + +comma_list_encode([Option], []) -> + Option; +comma_list_encode([Option], Acc) -> + Acc ++ "," ++ Option; +comma_list_encode([Option | Rest], []) -> + comma_list_encode(Rest, Option); +comma_list_encode([Option | Rest], Acc) -> + comma_list_encode(Rest, Acc ++ "," ++ Option). + +ssh2_pubkey_encode(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> + TypeStr = <<"ssh-rsa">>, + StrLen = size(TypeStr), + EBin = crypto:mpint(E), + NBin = crypto:mpint(N), + <<?UINT32(StrLen), TypeStr:StrLen/binary, + EBin/binary, + NBin/binary>>; +ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) -> + TypeStr = <<"ssh-dss">>, + StrLen = size(TypeStr), + PBin = crypto:mpint(P), + QBin = crypto:mpint(Q), + GBin = crypto:mpint(G), + YBin = crypto:mpint(Y), + <<?UINT32(StrLen), TypeStr:StrLen/binary, + PBin/binary, + QBin/binary, + GBin/binary, + YBin/binary>>. + +mend_split([Part1, Part2 | Rest] = List, Acc) -> + case option_end(Part1, Part2) of + true -> + lists:reverse(Acc) ++ List; + false -> + case length(binary:matches(Part1, <<"\"">>)) of + N when N rem 2 == 0 -> + mend_split(Rest, [Part1 | Acc]); + _ -> + mend_split([<<Part1/binary, Part2/binary>> | Rest], Acc) + end + end. + +option_end(Part1, Part2) -> + (is_key_field(Part1) orelse is_bits_field(Part1)) + orelse + (is_key_field(Part2) orelse is_bits_field(Part2)). + +is_key_field(<<"ssh-dss">>) -> + true; +is_key_field(<<"ssh-rsa">>) -> + true; +is_key_field(_) -> + false. + +is_bits_field(Part) -> + try list_to_integer(binary_to_list(Part)) of + _ -> + true + catch _:_ -> + false + end. + +split_lines(<<Text:?ENCODED_LINE_LENGTH/binary>>) -> + [Text]; +split_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) -> + [Text, $\n | split_lines(Rest)]; +split_lines(Bin) -> + [Bin]. diff --git a/lib/public_key/src/public_key.app.src b/lib/public_key/src/public_key.app.src index edede7c874..1963bd05d4 100644 --- a/lib/public_key/src/public_key.app.src +++ b/lib/public_key/src/public_key.app.src @@ -1,10 +1,9 @@ {application, public_key, [{description, "Public key infrastructure"}, {vsn, "%VSN%"}, - {modules, [ - public_key, - pubkey_pem, - pubkey_crypto, + {modules, [ public_key, + pubkey_pem, + pubkey_ssh, pubkey_cert, pubkey_cert_records, 'OTP-PUB-KEY' @@ -13,4 +12,5 @@ {registered, []}, {env, []} ] -}.
\ No newline at end of file +}. + diff --git a/lib/public_key/src/public_key.appup.src b/lib/public_key/src/public_key.appup.src index 46e5ecca33..c65ac7bc99 100644 --- a/lib/public_key/src/public_key.appup.src +++ b/lib/public_key/src/public_key.appup.src @@ -1,39 +1,49 @@ %% -*- erlang -*- {"%VSN%", [ - {"0.5", + {"0.10", [ {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []} + ] + }, + {"0.9", + [ + {update, public_key, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] }, - {"0.4", + {"0.8", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} - ] + ] } ], [ - {"0.5", + {"0.10", [ {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []} + ] + }, + {"0.9", + [ + {update, public_key, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] }, - {"0.4", + {"0.8", [ + {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []}, {update, public_key, soft, soft_purge, soft_purge, []}, - {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, - {update, pubkey_crypto, soft, soft_purge, soft_purge, []}, {update, pubkey_pem, soft, soft_purge, soft_purge, []}, + {update, pubkey_cert_records, soft, soft_purge, soft_purge, []}, {update, pubkey_cert, soft, soft_purge, soft_purge, []} ] } diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 157e76bb21..2901020e83 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,240 +23,423 @@ -include("public_key.hrl"). --export([decode_private_key/1, decode_private_key/2, decode_dhparams/1, - decrypt_private/2, decrypt_private/3, encrypt_public/2, - encrypt_public/3, decrypt_public/2, decrypt_public/3, - encrypt_private/2, encrypt_private/3, gen_key/1, sign/2, sign/3, - verify_signature/3, verify_signature/4, verify_signature/5, - pem_to_der/1, pem_to_der/2, der_to_pem/2, - pkix_decode_cert/2, pkix_encode_cert/1, pkix_transform/2, - pkix_is_self_signed/1, pkix_is_fixed_dh_cert/1, +-export([pem_decode/1, pem_encode/1, + der_decode/2, der_encode/2, + pem_entry_decode/1, + pem_entry_decode/2, + pem_entry_encode/2, + pem_entry_encode/3, + pkix_decode_cert/2, pkix_encode/3, + encrypt_private/2, encrypt_private/3, + decrypt_private/2, decrypt_private/3, + encrypt_public/2, encrypt_public/3, + decrypt_public/2, decrypt_public/3, + sign/3, verify/4, + pkix_sign/2, pkix_verify/2, + pkix_is_self_signed/1, + pkix_is_fixed_dh_cert/1, + pkix_is_issuer/2, pkix_issuer_id/2, - pkix_is_issuer/2, pkix_normalize_general_name/1, - pkix_path_validation/3 + pkix_normalize_name/1, + pkix_path_validation/3, + ssh_decode/2, ssh_encode/2 ]). +%% Deprecated +-export([decode_private_key/1, decode_private_key/2, pem_to_der/1]). + +-deprecated({pem_to_der, 1, next_major_release}). +-deprecated({decode_private_key, 1, next_major_release}). +-deprecated({decode_private_key, 2, next_major_release}). + +-type rsa_padding() :: 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding' + | 'rsa_no_padding'. +-type public_crypt_options() :: [{rsa_pad, rsa_padding()}]. +-type rsa_digest_type() :: 'md5' | 'sha'. +-type dss_digest_type() :: 'none' | 'sha'. + +-define(UINT32(X), X:32/unsigned-big-integer). +-define(DER_NULL, <<5, 0>>). + %%==================================================================== %% API %%==================================================================== - %%-------------------------------------------------------------------- -%% Function: decode_private_key(KeyInfo [,Password]) -> -%% {ok, PrivateKey} | {error, Reason} +-spec pem_decode(binary()) -> [pem_entry()]. %% -%% KeyInfo = {Type, der_bin(), ChipherInfo} - as returned from -%% pem_to_der/[1,2] for private keys -%% Type = rsa_private_key | dsa_private_key -%% ChipherInfo = opaque() | no_encryption -%% -%% Description: Decodes an asn1 der encoded private key. +%% Description: Decode PEM binary data and return +%% entries as asn1 der encoded entities. %%-------------------------------------------------------------------- -decode_private_key(KeyInfo) -> - decode_private_key(KeyInfo, no_passwd). - -decode_private_key(KeyInfo = {rsa_private_key, _, _}, Password) -> - DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), - 'OTP-PUB-KEY':decode('RSAPrivateKey', DerEncoded); -decode_private_key(KeyInfo = {dsa_private_key, _, _}, Password) -> - DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), - 'OTP-PUB-KEY':decode('DSAPrivateKey', DerEncoded). - +pem_decode(PemBin) when is_binary(PemBin) -> + pubkey_pem:decode(PemBin). %%-------------------------------------------------------------------- -%% Function: decode_dhparams(DhParamInfo) -> -%% {ok, DhParams} | {error, Reason} -%% -%% DhParamsInfo = {Type, der_bin(), ChipherInfo} - as returned from -%% pem_to_der/[1,2] for DH parameters. -%% Type = dh_params -%% ChipherInfo = opaque() | no_encryption +-spec pem_encode([pem_entry()]) -> binary(). %% -%% Description: Decodes an asn1 der encoded DH parameters. +%% Description: Creates a PEM binary. %%-------------------------------------------------------------------- -decode_dhparams({dh_params, DerEncoded, not_encrypted}) -> - 'OTP-PUB-KEY':decode('DHParameter', DerEncoded). +pem_encode(PemEntries) when is_list(PemEntries) -> + iolist_to_binary(pubkey_pem:encode(PemEntries)). %%-------------------------------------------------------------------- -%% Function: decrypt_private(CipherText, Key) -> -%% decrypt_private(CipherText, Key, Options) -> PlainTex -%% decrypt_public(CipherText, Key) -> -%% decrypt_public(CipherText, Key, Options) -> PlainTex -%% -%% CipherText = binary() -%% Key = rsa_key() -%% PlainText = binary() -%% -%% Description: Decrypts <CipherText>. +-spec pem_entry_decode(pem_entry(), [string()]) -> term(). +% +%% Description: Decodes a pem entry. pem_decode/1 returns a list of +%% pem entries. +%%-------------------------------------------------------------------- +pem_entry_decode({'SubjectPublicKeyInfo', Der, _}) -> + {_, {'AlgorithmIdentifier', AlgId, Params}, {0, Key0}} + = der_decode('SubjectPublicKeyInfo', Der), + KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(AlgId), + case KeyType of + 'RSAPublicKey' -> + der_decode(KeyType, Key0); + 'DSAPublicKey' -> + {params, DssParams} = der_decode('DSAParams', Params), + {der_decode(KeyType, Key0), DssParams} + end; +pem_entry_decode({Asn1Type, Der, not_encrypted}) when is_atom(Asn1Type), + is_binary(Der) -> + der_decode(Asn1Type, Der). +pem_entry_decode({Asn1Type, Der, not_encrypted}, _) when is_atom(Asn1Type), + is_binary(Der) -> + der_decode(Asn1Type, Der); +pem_entry_decode({Asn1Type, CryptDer, {Cipher, Salt}} = PemEntry, + Password) when is_atom(Asn1Type), + is_binary(CryptDer), + is_list(Cipher), + is_binary(Salt), + erlang:byte_size(Salt) == 8 + -> + Der = pubkey_pem:decipher(PemEntry, Password), + der_decode(Asn1Type, Der). + +%%-------------------------------------------------------------------- +-spec pem_entry_encode(pki_asn1_type(), term()) -> pem_entry(). +-spec pem_entry_encode(pki_asn1_type(), term(), + {{Cipher :: string(), Salt :: binary()}, string()}) -> + pem_entry(). +% +%% Description: Creates a pem entry that can be feed to pem_encode/1. +%%-------------------------------------------------------------------- +pem_entry_encode('SubjectPublicKeyInfo', Entity=#'RSAPublicKey'{}) -> + Der = der_encode('RSAPublicKey', Entity), + Spki = {'SubjectPublicKeyInfo', + {'AlgorithmIdentifier', ?'rsaEncryption', ?DER_NULL}, {0, Der}}, + pem_entry_encode('SubjectPublicKeyInfo', Spki); +pem_entry_encode('SubjectPublicKeyInfo', + {DsaInt, Params=#'Dss-Parms'{}}) when is_integer(DsaInt) -> + KeyDer = der_encode('DSAPublicKey', DsaInt), + ParamDer = der_encode('DSAParams', {params, Params}), + Spki = {'SubjectPublicKeyInfo', + {'AlgorithmIdentifier', ?'id-dsa', ParamDer}, {0, KeyDer}}, + pem_entry_encode('SubjectPublicKeyInfo', Spki); +pem_entry_encode(Asn1Type, Entity) when is_atom(Asn1Type) -> + Der = der_encode(Asn1Type, Entity), + {Asn1Type, Der, not_encrypted}. +pem_entry_encode(Asn1Type, Entity, + {{Cipher, Salt}= CipherInfo, Password}) when is_atom(Asn1Type), + is_list(Cipher), + is_binary(Salt), + erlang:byte_size(Salt) == 8, + is_list(Password)-> + Der = der_encode(Asn1Type, Entity), + DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password), + {Asn1Type, DecryptDer, CipherInfo}. + +%%-------------------------------------------------------------------- +-spec der_decode(asn1_type(), Der::binary()) -> term(). +%% +%% Description: Decodes a public key asn1 der encoded entity. +%%-------------------------------------------------------------------- +der_decode(Asn1Type, Der) when is_atom(Asn1Type), is_binary(Der) -> + try + {ok, Decoded} = 'OTP-PUB-KEY':decode(Asn1Type, Der), + Decoded + catch + error:{badmatch, {error, _}} = Error -> + erlang:error(Error) + end. + +%%-------------------------------------------------------------------- +-spec der_encode(asn1_type(), term()) -> Der::binary(). +%% +%% Description: Encodes a public key entity with asn1 DER encoding. +%%-------------------------------------------------------------------- +der_encode(Asn1Type, Entity) when is_atom(Asn1Type) -> + try + {ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity), + iolist_to_binary(Encoded) + catch + error:{badmatch, {error, _}} = Error -> + erlang:error(Error) + end. + +%%-------------------------------------------------------------------- +-spec pkix_decode_cert(Cert::binary(), plain | otp) -> + #'Certificate'{} | #'OTPCertificate'{}. +%% +%% Description: Decodes an asn1 der encoded pkix certificate. The otp +%% option will use the customized asn1 specification OTP-PKIX.asn1 for +%% decoding and also recursively decode most of the standard +%% extensions. +%% -------------------------------------------------------------------- +pkix_decode_cert(DerCert, plain) when is_binary(DerCert) -> + der_decode('Certificate', DerCert); +pkix_decode_cert(DerCert, otp) when is_binary(DerCert) -> + try + {ok, #'OTPCertificate'{}= Cert} = + pubkey_cert_records:decode_cert(DerCert), + Cert + catch + error:{badmatch, {error, _}} = Error -> + erlang:error(Error) + end. + +%%-------------------------------------------------------------------- +-spec pkix_encode(asn1_type(), term(), otp | plain) -> Der::binary(). +%% +%% Description: Der encodes a certificate or part of a certificate. +%% This function must be used for encoding certificates or parts of certificates +%% that are decoded with the otp format, whereas for the plain format this +%% function will only call der_encode/2. +%%-------------------------------------------------------------------- +pkix_encode(Asn1Type, Term, plain) when is_atom(Asn1Type) -> + der_encode(Asn1Type, Term); + +pkix_encode(Asn1Type, Term0, otp) when is_atom(Asn1Type) -> + Term = pubkey_cert_records:transform(Term0, encode), + der_encode(Asn1Type, Term). + +%%-------------------------------------------------------------------- +-spec decrypt_private(CipherText :: binary(), rsa_private_key()) -> + PlainText :: binary(). +-spec decrypt_private(CipherText :: binary(), rsa_private_key(), + public_crypt_options()) -> PlainText :: binary(). +%% +%% Description: Public key decryption using the private key. %%-------------------------------------------------------------------- decrypt_private(CipherText, Key) -> decrypt_private(CipherText, Key, []). -decrypt_private(CipherText, Key, Options) -> - Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), - pubkey_crypto:decrypt_private(CipherText, Key, Padding). -decrypt_public(CipherText, Key) -> - decrypt_public(CipherText, Key, []). -decrypt_public(CipherText, Key, Options) -> +decrypt_private(CipherText, + #'RSAPrivateKey'{modulus = N,publicExponent = E, + privateExponent = D}, + Options) when is_binary(CipherText), + is_list(Options) -> Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), - pubkey_crypto:decrypt_public(CipherText, Key, Padding). + crypto:rsa_private_decrypt(CipherText, + [crypto:mpint(E), crypto:mpint(N), + crypto:mpint(D)], Padding). %%-------------------------------------------------------------------- -%% Function: encrypt_public(PlainText, Key, Options) -> CipherText -%% encrypt_private(PlainText, Key, Options) -> CipherText -%% -%% PlainText = iolist() -%% Key = rsa_private_key() -%% CipherText = binary() +-spec decrypt_public(CipherText :: binary(), rsa_public_key() | rsa_private_key()) -> + PlainText :: binary(). +-spec decrypt_public(CipherText :: binary(), rsa_public_key() | rsa_private_key(), + public_crypt_options()) -> PlainText :: binary(). +%% NOTE: The rsa_private_key() is not part of the documented API it is +%% here for testing purposes, in a real situation this is not a relevant +%% thing to do. %% -%% Description: Encrypts <Plain> +%% Description: Public key decryption using the public key. %%-------------------------------------------------------------------- -encrypt_public(PlainText, Key) -> - encrypt_public(PlainText, Key, []). -encrypt_public(PlainText, Key, Options) -> - Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), - pubkey_crypto:encrypt_public(PlainText, Key, Padding). +decrypt_public(CipherText, Key) -> + decrypt_public(CipherText, Key, []). -encrypt_private(PlainText, Key) -> - encrypt_private(PlainText, Key, []). -encrypt_private(PlainText, Key, Options) -> - Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), - pubkey_crypto:encrypt_private(PlainText, Key, Padding). +decrypt_public(CipherText, #'RSAPublicKey'{modulus = N, publicExponent = E}, + Options) when is_binary(CipherText), is_list(Options) -> + decrypt_public(CipherText, N,E, Options); -%%-------------------------------------------------------------------- -%% Function: gen_key(Params) -> Keys -%% -%% Params = #'DomainParameters'{} - Currently only supported option -%% Keys = {PublicDHKey = integer(), PrivateDHKey = integer()} -%% -%% Description: Generates keys. Currently supports Diffie-Hellman keys. -%%-------------------------------------------------------------------- -gen_key(#'DHParameter'{prime = P, base = G}) when is_integer(P), - is_integer(G) -> - pubkey_crypto:gen_key(diffie_hellman, [P, G]). +decrypt_public(CipherText,#'RSAPrivateKey'{modulus = N, publicExponent = E}, + Options) when is_binary(CipherText), is_list(Options) -> + decrypt_public(CipherText, N,E, Options). %%-------------------------------------------------------------------- -%% Function: pem_to_der(CertSource) -> -%% pem_to_der(CertSource, Password) -> {ok, [Entry]} | -%% {error, Reason} -%% -%% CertSource = File | CertData -%% CertData = binary() -%% File = path() -%% Entry = {entry_type(), der_bin(), ChipherInfo} -%% ChipherInfo = opague() | no_encryption -%% der_bin() = binary() -%% entry_type() = cert | cert_req | rsa_private_key | dsa_private_key -%% dh_params +-spec encrypt_public(PlainText :: binary(), rsa_public_key() | rsa_private_key()) -> + CipherText :: binary(). +-spec encrypt_public(PlainText :: binary(), rsa_public_key() | rsa_private_key(), + public_crypt_options()) -> CipherText :: binary(). + +%% NOTE: The rsa_private_key() is not part of the documented API it is +%% here for testing purposes, in a real situation this is not a relevant +%% thing to do. %% -%% Description: decode PEM binary data or a PEM file and return -%% entries as asn1 der encoded entities. Currently supported entry -%% types are certificates, certificate requests, rsa private keys and -%% dsa private keys. In the case of a key entry ChipherInfo will be -%% private keys and Diffie Hellam parameters .In the case of a key -%% entry ChipherInfo will be used by decode_private_key/2 if the key -%% is protected by a password. +%% Description: Public key encryption using the public key. %%-------------------------------------------------------------------- -pem_to_der(CertSource) -> - pem_to_der(CertSource, no_passwd). +encrypt_public(PlainText, Key) -> + encrypt_public(PlainText, Key, []). -pem_to_der(File, Password) when is_list(File) -> - pubkey_pem:read_file(File, Password); -pem_to_der(PemBin, Password) when is_binary(PemBin) -> - pubkey_pem:decode(PemBin, Password). +encrypt_public(PlainText, #'RSAPublicKey'{modulus=N,publicExponent=E}, + Options) when is_binary(PlainText), is_list(Options) -> + encrypt_public(PlainText, N,E, Options); -der_to_pem(File, TypeDerList) -> - pubkey_pem:write_file(File, TypeDerList). +encrypt_public(PlainText, #'RSAPrivateKey'{modulus=N,publicExponent=E}, + Options) when is_binary(PlainText), is_list(Options) -> + encrypt_public(PlainText, N,E, Options). %%-------------------------------------------------------------------- -%% Function: pkix_decode_cert(BerCert, Type) -> {ok, Cert} | {error, Reason} -%% -%% BerCert = binary() -%% Type = plain | otp -%% Cert = certificate() +-spec encrypt_private(PlainText :: binary(), rsa_private_key()) -> + CipherText :: binary(). +-spec encrypt_private(PlainText :: binary(), rsa_private_key(), + public_crypt_options()) -> CipherText :: binary(). %% -%% Description: Decodes an asn1 ber encoded pkix certificate. -%% otp - Uses OTP-PKIX.asn1 to decode known extensions and -%% enhance the signature field in #'Certificate'{} and '#TBSCertificate'{}. +%% Description: Public key encryption using the private key. %%-------------------------------------------------------------------- -pkix_decode_cert(BinCert, Type) -> - pubkey_cert_records:decode_cert(BinCert, Type). +encrypt_private(PlainText, Key) -> + encrypt_private(PlainText, Key, []). + +encrypt_private(PlainText, #'RSAPrivateKey'{modulus = N, + publicExponent = E, + privateExponent = D}, + Options) when is_binary(PlainText), is_list(Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), + crypto:rsa_private_encrypt(PlainText, [crypto:mpint(E), + crypto:mpint(N), + crypto:mpint(D)], Padding). %%-------------------------------------------------------------------- -%% Function: pkix_encode_cert(Cert) -> {ok, binary()} | {error, Reason} -%% -%% Cert = #'Certificate'{} +-spec sign(PlainTextOrDigest :: binary(), rsa_digest_type() | dss_digest_type(), + rsa_private_key() | + dsa_private_key()) -> Signature :: binary(). %% -%% Description: Encodes a certificate record using asn1. +%% Description: Create digital signature. %%-------------------------------------------------------------------- -pkix_encode_cert(Cert) -> - pubkey_cert_records:encode_cert(Cert). +sign(PlainText, DigestType, #'RSAPrivateKey'{modulus = N, publicExponent = E, + privateExponent = D}) + when is_binary(PlainText), + (DigestType == md5 orelse + DigestType == sha) -> + crypto:rsa_sign(DigestType, sized_binary(PlainText), [crypto:mpint(E), + crypto:mpint(N), + crypto:mpint(D)]); + +sign(Digest, none, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) + when is_binary(Digest)-> + crypto:dss_sign(none, Digest, + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(X)]); + +sign(PlainText, sha, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) + when is_binary(PlainText) -> + crypto:dss_sign(sized_binary(PlainText), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(X)]). + +%%-------------------------------------------------------------------- +-spec verify(PlainTextOrDigest :: binary(), rsa_digest_type() | dss_digest_type(), + Signature :: binary(), rsa_public_key() + | dsa_public_key()) -> boolean(). +%% +%% Description: Verifies a digital signature. +%%-------------------------------------------------------------------- +verify(PlainText, DigestType, Signature, + #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}) + when is_binary (PlainText), DigestType == sha; DigestType == md5 -> + crypto:rsa_verify(DigestType, + sized_binary(PlainText), + sized_binary(Signature), + [crypto:mpint(Exp), crypto:mpint(Mod)]); + +verify(Digest, none, Signature, {Key, #'Dss-Parms'{p = P, q = Q, g = G}}) + when is_integer(Key), is_binary(Digest), is_binary(Signature) -> + crypto:dss_verify(none, + Digest, + sized_binary(Signature), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(Key)]); + +verify(PlainText, sha, Signature, {Key, #'Dss-Parms'{p = P, q = Q, g = G}}) + when is_integer(Key), is_binary(PlainText), is_binary(Signature) -> + crypto:dss_verify(sized_binary(PlainText), + sized_binary(Signature), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(Key)]). %%-------------------------------------------------------------------- -%% Function: pkix_transform(CertPart, Op) -> TransformedCertPart -%% -%% CertPart = pkix part data -%% Op = encode | decode +-spec pkix_sign(#'OTPTBSCertificate'{}, + rsa_private_key() | dsa_private_key()) -> Der::binary(). %% -%% Description: Transform parts of a pkix certificate between 'plain' format -%% and the internal 'otp' format, see pkix_decode_cert/2. -%% Decode transforms from 'plain' to 'otp' and encode from 'otp' to 'plain' -%% format. +%% Description: Sign a pkix x.509 certificate. Returns the corresponding +%% der encoded 'Certificate'{} %%-------------------------------------------------------------------- -pkix_transform(CertPart, Op) -> - pubkey_cert_records:transform(CertPart, Op). +pkix_sign(#'OTPTBSCertificate'{signature = + #'SignatureAlgorithm'{algorithm = Alg} + = SigAlg} = TBSCert, Key) -> + + Msg = pkix_encode('OTPTBSCertificate', TBSCert, otp), + DigestType = pubkey_cert:digest_type(Alg), + Signature = sign(Msg, DigestType, Key), + Cert = #'OTPCertificate'{tbsCertificate= TBSCert, + signatureAlgorithm = SigAlg, + signature = {0, Signature} + }, + pkix_encode('OTPCertificate', Cert, otp). %%-------------------------------------------------------------------- -%% Function: pkix_path_validation(TrustedCert, CertChain, Options) -> -%% {ok, {{algorithm(), public_key(), public_key_params()} policy_tree()}} | -%% {error, Reason} +-spec pkix_verify(Cert::binary(), rsa_public_key()| + dsa_public_key()) -> boolean(). %% -%% Description: Performs a bacis path validation according to RFC 3280. +%% Description: Verify pkix x.509 certificate signature. %%-------------------------------------------------------------------- -pkix_path_validation(TrustedCert, CertChain, Options) - when is_binary(TrustedCert) -> - {ok, OtpCert} = pkix_decode_cert(TrustedCert, otp), - pkix_path_validation(OtpCert, CertChain, Options); +pkix_verify(DerCert, {Key, #'Dss-Parms'{}} = DSAKey) + when is_binary(DerCert), is_integer(Key) -> + {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert), + verify(PlainText, DigestType, Signature, DSAKey); -pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) - when is_list(CertChain), is_list(Options) -> - MaxPathDefault = length(CertChain), - ValidationState = pubkey_cert:init_validation_state(TrustedCert, - MaxPathDefault, - Options), - Fun = proplists:get_value(validate_extensions_fun, Options, - fun(Extensions, State, _, AccError) -> - {Extensions, State, AccError} - end), - Verify = proplists:get_value(verify, Options, true), - path_validation(CertChain, ValidationState, Fun, Verify). -%%-------------------------------------------------------------------- -%% Function: pkix_is_fixed_dh_cert(Cert) -> true | false +pkix_verify(DerCert, #'RSAPublicKey'{} = RSAKey) + when is_binary(DerCert) -> + {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert), + verify(PlainText, DigestType, Signature, RSAKey). + +%%-------------------------------------------------------------------- +-spec pkix_is_issuer(Cert::binary()| #'OTPCertificate'{}, + IssuerCert::binary()| + #'OTPCertificate'{}) -> boolean(). %% -%% Description: Checks if a Certificate is a fixed Diffie-Hellman Cert +%% Description: Checks if <IssuerCert> issued <Cert>. %%-------------------------------------------------------------------- -pkix_is_fixed_dh_cert(#'OTPCertificate'{} = OTPCert) -> - pubkey_cert:is_fixed_dh_cert(OTPCert); -pkix_is_fixed_dh_cert(Cert) when is_binary(Cert) -> - {ok, OtpCert} = pkix_decode_cert(Cert, otp), - pkix_is_fixed_dh_cert(OtpCert). +pkix_is_issuer(Cert, IssuerCert) when is_binary(Cert) -> + OtpCert = pkix_decode_cert(Cert, otp), + pkix_is_issuer(OtpCert, IssuerCert); +pkix_is_issuer(Cert, IssuerCert) when is_binary(IssuerCert) -> + OtpIssuerCert = pkix_decode_cert(IssuerCert, otp), + pkix_is_issuer(Cert, OtpIssuerCert); +pkix_is_issuer(#'OTPCertificate'{tbsCertificate = TBSCert}, + #'OTPCertificate'{tbsCertificate = Candidate}) -> + pubkey_cert:is_issuer(TBSCert#'OTPTBSCertificate'.issuer, + Candidate#'OTPTBSCertificate'.subject). %%-------------------------------------------------------------------- -%% Function: pkix_is_self_signed(Cert) -> true | false +-spec pkix_is_self_signed(Cert::binary()| #'OTPCertificate'{}) -> boolean(). %% %% Description: Checks if a Certificate is self signed. %%-------------------------------------------------------------------- pkix_is_self_signed(#'OTPCertificate'{} = OTPCert) -> pubkey_cert:is_self_signed(OTPCert); pkix_is_self_signed(Cert) when is_binary(Cert) -> - {ok, OtpCert} = pkix_decode_cert(Cert, otp), + OtpCert = pkix_decode_cert(Cert, otp), pkix_is_self_signed(OtpCert). - + %%-------------------------------------------------------------------- -%% Function: pkix_issuer_id(Cert) -> {ok, {SerialNr, Issuer}} | {error, Reason} -%% -%% Cert = asn1_der_encoded() | 'OTPCertificate'{} +-spec pkix_is_fixed_dh_cert(Cert::binary()| #'OTPCertificate'{}) -> boolean(). %% -%% Description: Returns the issuer id. +%% Description: Checks if a Certificate is a fixed Diffie-Hellman Cert. +%%-------------------------------------------------------------------- +pkix_is_fixed_dh_cert(#'OTPCertificate'{} = OTPCert) -> + pubkey_cert:is_fixed_dh_cert(OTPCert); +pkix_is_fixed_dh_cert(Cert) when is_binary(Cert) -> + OtpCert = pkix_decode_cert(Cert, otp), + pkix_is_fixed_dh_cert(OtpCert). + +%%-------------------------------------------------------------------- +-spec pkix_issuer_id(Cert::binary()| #'OTPCertificate'{}, + IssuedBy :: self | other) -> + {ok, {SerialNr :: integer(), + Issuer :: {rdnSequence, + [#'AttributeTypeAndValue'{}]}}} + | {error, Reason :: term()}. +% +%% Description: Returns the issuer id. %%-------------------------------------------------------------------- pkix_issuer_id(#'OTPCertificate'{} = OtpCert, self) -> pubkey_cert:issuer_id(OtpCert, self); @@ -265,151 +448,142 @@ pkix_issuer_id(#'OTPCertificate'{} = OtpCert, other) -> pubkey_cert:issuer_id(OtpCert, other); pkix_issuer_id(Cert, Signed) when is_binary(Cert) -> - {ok, OtpCert} = pkix_decode_cert(Cert, otp), + OtpCert = pkix_decode_cert(Cert, otp), pkix_issuer_id(OtpCert, Signed). %%-------------------------------------------------------------------- -%% Function: pkix_is_issuer(Cert, IssuerCert) -> true | false +-spec pkix_normalize_name({rdnSequence, + [#'AttributeTypeAndValue'{}]}) -> + {rdnSequence, + [#'AttributeTypeAndValue'{}]}. %% -%% Cert = asn1_der_encoded() | 'OTPCertificate'{} -%% IssuerCert = asn1_der_encoded() | 'OTPCertificate'{} -%% -%% Description: Checks if <IssuerCert> issued <Cert>. +%% Description: Normalizes a issuer name so that it can be easily +%% compared to another issuer name. %%-------------------------------------------------------------------- -pkix_is_issuer(Cert, IssuerCert) when is_binary(Cert) -> - {ok, OtpCert} = pkix_decode_cert(Cert, otp), - pkix_is_issuer(OtpCert, IssuerCert); +pkix_normalize_name(Issuer) -> + pubkey_cert:normalize_general_name(Issuer). -pkix_is_issuer(Cert, IssuerCert) when is_binary(IssuerCert) -> - {ok, OtpIssuerCert} = pkix_decode_cert(IssuerCert, otp), - pkix_is_issuer(Cert, OtpIssuerCert); +%%-------------------------------------------------------------------- +-spec pkix_path_validation(Cert::binary()| #'OTPCertificate'{} | atom(), + CertChain :: [binary()] , + Options :: list()) -> + {ok, {PublicKeyInfo :: term(), + PolicyTree :: term()}} | + {error, {bad_cert, Reason :: term()}}. +%% Description: Performs a basic path validation according to RFC 5280. +%%-------------------------------------------------------------------- +pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)-> + {VerifyFun, Userstat0} = + proplists:get_value(verify_fun, Options0, ?DEFAULT_VERIFYFUN), + Otpcert = pkix_decode_cert(Cert, otp), + Reason = {bad_cert, PathErr}, + try VerifyFun(Otpcert, Reason, Userstat0) of + {valid, Userstate} -> + Options = proplists:delete(verify_fun, Options0), + pkix_path_validation(Otpcert, Chain, [{verify_fun, + {VerifyFun, Userstate}}| Options]); + {fail, _} -> + {error, Reason} + catch + _:_ -> + {error, Reason} + end; +pkix_path_validation(TrustedCert, CertChain, Options) when + is_binary(TrustedCert) -> OtpCert = pkix_decode_cert(TrustedCert, + otp), pkix_path_validation(OtpCert, CertChain, Options); -pkix_is_issuer(#'OTPCertificate'{tbsCertificate = TBSCert}, - #'OTPCertificate'{tbsCertificate = Candidate}) -> - pubkey_cert:is_issuer(TBSCert#'OTPTBSCertificate'.issuer, - Candidate#'OTPTBSCertificate'.subject). - -%%-------------------------------------------------------------------- -%% Function: pkix_normalize_general_name(Issuer) -> -%% -%% Issuer = general_name() - see PKIX -%% -%% Description: Normalizes a general name so that it can be easily -%% compared to another genral name. -%%-------------------------------------------------------------------- -pkix_normalize_general_name(Issuer) -> - pubkey_cert:normalize_general_name(Issuer). +pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) + when is_list(CertChain), is_list(Options) -> + MaxPathDefault = length(CertChain), + ValidationState = pubkey_cert:init_validation_state(TrustedCert, + MaxPathDefault, + Options), + path_validation(CertChain, ValidationState). %%-------------------------------------------------------------------- -%% Function:sign(Msg, Key) -> {ok, Signature} -%% sign(Msg, Key, KeyParams) -> {ok, Signature} -%% -%% Msg = binary() | #'TBSCertificate'{} -%% Key = private_key() -%% KeyParams = key_params() -%% Signature = binary() +-spec ssh_decode(binary(), public_key | ssh_file()) -> [{public_key(), Attributes::list()}]. %% -%% Description: Signs plaintext Msg or #TBSCertificate{}, in the later -%% case a der encoded "#Certificate{}" will be returned. +%% Description: Decodes a ssh file-binary. In the case of know_hosts +%% or auth_keys the binary may include one or more lines of the +%% file. Returns a list of public keys and their attributes, possible +%% attribute values depends on the file type represented by the +%% binary. %%-------------------------------------------------------------------- -sign(Msg, #'RSAPrivateKey'{} = Key) when is_binary(Msg) -> - pubkey_crypto:sign(Msg, Key); - -sign(Msg, #'DSAPrivateKey'{} = Key) when is_binary(Msg) -> - pubkey_crypto:sign(Msg, Key); - -sign(#'OTPTBSCertificate'{signature = #'SignatureAlgorithm'{algorithm = Alg} - = SigAlg} = TBSCert, Key) -> - Msg = pubkey_cert_records:encode_tbs_cert(TBSCert), - DigestType = pubkey_cert:digest_type(Alg), - Signature = pubkey_crypto:sign(DigestType, Msg, Key), - Cert = #'OTPCertificate'{tbsCertificate= TBSCert, - signatureAlgorithm = SigAlg, - signature = {0, Signature} - }, - pkix_encode_cert(Cert). - -sign(DigestType, Msg, Key) -> - pubkey_crypto:sign(DigestType, Msg, Key). +ssh_decode(SshBin, Type) when is_binary(SshBin), + Type == public_key; + Type == rfc4716_public_key; + Type == openssh_public_key; + Type == auth_keys; + Type == known_hosts -> + pubkey_ssh:decode(SshBin, Type). %%-------------------------------------------------------------------- -%% Function: verify_signature(PlainText, DigestType, Signature, Key) -> -%% verify_signature(PlainText, DigestType, -%% Signature, Key, KeyParams) -> -%% verify_signature(DerCert, Key, KeyParams) -> -%% -%% PlainText = binary() -%% DigestType = md5 | sha -%% DerCert = asn1_der_encoded() -%% Signature = binary() -%% Key = public_key() -%% KeyParams = key_params() -%% Verified = boolean() -%% -%% Description: Verifies the signature <Signature>. -%%-------------------------------------------------------------------- -verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key) - when is_binary(PlainText), is_binary(Signature), DigestType == sha; - DigestType == md5 -> - pubkey_crypto:verify(DigestType, PlainText, Signature, Key, undefined). - -verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key, - KeyParams) - when is_binary(PlainText), is_binary(Signature), DigestType == sha; - DigestType == md5 -> - pubkey_crypto:verify(DigestType, PlainText, Signature, Key, KeyParams); -verify_signature(PlainText, sha, Signature, Key, #'Dss-Parms'{} = KeyParams) - when is_binary(PlainText), is_binary(Signature), is_integer(Key) -> - pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams). - -verify_signature(DerCert, Key, #'Dss-Parms'{} = KeyParams) - when is_binary(DerCert), is_integer(Key) -> - pubkey_cert:verify_signature(DerCert, Key, KeyParams); -verify_signature(DerCert, #'RSAPublicKey'{} = Key, KeyParams) - when is_binary(DerCert) -> - pubkey_cert:verify_signature(DerCert, Key, KeyParams). +-spec ssh_encode([{public_key(), Attributes::list()}], ssh_file()) -> + binary(). +%% Description: Encodes a list of ssh file entries (public keys and +%% attributes) to a binary. Possible attributes depends on the file +%% type. +%%-------------------------------------------------------------------- +ssh_encode(Entries, Type) when is_list(Entries), + Type == rfc4716_public_key; + Type == openssh_public_key; + Type == auth_keys; + Type == known_hosts -> + pubkey_ssh:encode(Entries, Type). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- + +encrypt_public(PlainText, N, E, Options)-> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), + crypto:rsa_public_encrypt(PlainText, [crypto:mpint(E),crypto:mpint(N)], + Padding). + +decrypt_public(CipherText, N,E, Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), + crypto:rsa_public_decrypt(CipherText,[crypto:mpint(E), crypto:mpint(N)], + Padding). + path_validation([], #path_validation_state{working_public_key_algorithm = Algorithm, working_public_key = PublicKey, working_public_key_parameters = PublicKeyParams, - valid_policy_tree = Tree, - acc_errors = AccErrors - }, _, _) -> - {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree, AccErrors}}; + valid_policy_tree = Tree + }) -> + {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree}}; path_validation([DerCert | Rest], ValidationState = #path_validation_state{ - max_path_length = Len}, - Fun, Verify) when Len >= 0 -> - try validate(DerCert, - ValidationState#path_validation_state{last_cert=Rest=:=[]}, - Fun, Verify) of + max_path_length = Len}) when Len >= 0 -> + try validate(DerCert, + ValidationState#path_validation_state{last_cert=Rest=:=[]}) of #path_validation_state{} = NewValidationState -> - path_validation(Rest, NewValidationState, Fun, Verify) + path_validation(Rest, NewValidationState) catch throw:Reason -> {error, Reason} end; -path_validation(_, _, _, true) -> - {error, {bad_cert, max_path_length_reached}}; - -path_validation(_, #path_validation_state{working_public_key_algorithm - = Algorithm, - working_public_key = - PublicKey, - working_public_key_parameters - = PublicKeyParams, - valid_policy_tree = Tree, - acc_errors = AccErrors - }, _, false) -> - {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree, - [{bad_cert, max_path_length_reached}|AccErrors]}}. +path_validation([DerCert | _] = Path, + #path_validation_state{user_state = UserState0, + verify_fun = VerifyFun} = + ValidationState) -> + Reason = {bad_cert, max_path_length_reached}, + OtpCert = pkix_decode_cert(DerCert, otp), + try VerifyFun(OtpCert, Reason, UserState0) of + {valid, UserState} -> + path_validation(Path, + ValidationState#path_validation_state{ + max_path_length = 0, + user_state = UserState}); + {fail, _} -> + {error, Reason} + catch + _:_ -> + {error, Reason} + end. validate(DerCert, #path_validation_state{working_issuer_name = Issuer, working_public_key = Key, @@ -419,38 +593,58 @@ validate(DerCert, #path_validation_state{working_issuer_name = Issuer, excluded_subtrees = Exclude, last_cert = Last, user_state = UserState0, - acc_errors = AccErr0} = - ValidationState0, ValidateExtensionFun, Verify) -> - {ok, OtpCert} = pkix_decode_cert(DerCert, otp), - %% All validate functions will throw {bad_cert, Reason} if they - %% fail and Verify = true if Verify = false errors - %% will be accumulated in the validationstate - AccErr1 = pubkey_cert:validate_time(OtpCert, AccErr0, Verify), - - AccErr2 = pubkey_cert:validate_issuer(OtpCert, Issuer, AccErr1, Verify), - - AccErr3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last, - AccErr2, Verify), - AccErr4 = - pubkey_cert:validate_revoked_status(OtpCert, Verify, AccErr3), + verify_fun = VerifyFun} = + ValidationState0) -> + + OtpCert = pkix_decode_cert(DerCert, otp), + + UserState1 = pubkey_cert:validate_time(OtpCert, UserState0, VerifyFun), + + UserState2 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState1, VerifyFun), + + UserState3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last, + UserState2,VerifyFun), + + UserState4 = pubkey_cert:validate_revoked_status(OtpCert, UserState3, VerifyFun), - {ValidationState1, UnknownExtensions0, AccErr5} = - pubkey_cert:validate_extensions(OtpCert, ValidationState0, Verify, - AccErr4), - %% We want the key_usage extension to be checked before we validate + {ValidationState1, UserState5} = + pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState4, + VerifyFun), + + %% We want the key_usage extension to be checked before we validate %% the signature. - AccErr6 = - pubkey_cert:validate_signature(OtpCert, DerCert, Key, KeyParams, - AccErr5, Verify), - - {UnknownExtensions, UserState, AccErr7} = - ValidateExtensionFun(UnknownExtensions0, UserState0, Verify, AccErr6), - - %% Check that all critical extensions have been handled - AccErr = - pubkey_cert:validate_unknown_extensions(UnknownExtensions, AccErr7, - Verify), + UserState6 = pubkey_cert:validate_signature(OtpCert, DerCert, + Key, KeyParams, UserState5, VerifyFun), + UserState = case Last of + false -> + pubkey_cert:verify_fun(OtpCert, valid, UserState6, VerifyFun); + true -> + pubkey_cert:verify_fun(OtpCert, valid_peer, + UserState6, VerifyFun) + end, + ValidationState = - ValidationState1#path_validation_state{user_state = UserState, - acc_errors = AccErr}, + ValidationState1#path_validation_state{user_state = UserState}, + pubkey_cert:prepare_for_next_cert(OtpCert, ValidationState). + +sized_binary(Binary) -> + Size = size(Binary), + <<?UINT32(Size), Binary/binary>>. + +%%-------------------------------------------------------------------- +%%% Deprecated functions +%%-------------------------------------------------------------------- +pem_to_der(CertSource) -> + {ok, Bin} = file:read_file(CertSource), + {ok, pubkey_pem:decode(Bin)}. + +decode_private_key(KeyInfo) -> + decode_private_key(KeyInfo, no_passwd). + +decode_private_key(KeyInfo = {'RSAPrivateKey', _, _}, Password) -> + DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), + 'OTP-PUB-KEY':decode('RSAPrivateKey', DerEncoded); +decode_private_key(KeyInfo = {'DSAPrivateKey', _, _}, Password) -> + DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), + 'OTP-PUB-KEY':decode('DSAPrivateKey', DerEncoded). diff --git a/lib/public_key/test/Makefile b/lib/public_key/test/Makefile index c7215020c7..6889ae9a8a 100644 --- a/lib/public_key/test/Makefile +++ b/lib/public_key/test/Makefile @@ -28,6 +28,7 @@ INCLUDES= -I. -I ../include # ---------------------------------------------------- MODULES= \ + erl_make_certs \ public_key_SUITE \ pkits_SUITE @@ -40,6 +41,9 @@ TARGET_FILES= \ SPEC_FILES = public_key.spec +COVER_FILE = public_key.cover + + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -74,9 +78,9 @@ release_spec: opt release_tests_spec: opt $(INSTALL_DIR) $(RELSYSDIR) - $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(HRL_FILES)$(RELSYSDIR) + $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(COVER_FILE) $(HRL_FILES) $(RELSYSDIR) $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR) - chmod -f -R u+w $(RELSYSDIR) + chmod -R u+w $(RELSYSDIR) @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) release_docs_spec: diff --git a/lib/public_key/test/erl_make_certs.erl b/lib/public_key/test/erl_make_certs.erl new file mode 100644 index 0000000000..8b01ca3ad4 --- /dev/null +++ b/lib/public_key/test/erl_make_certs.erl @@ -0,0 +1,421 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% Create test certificates + +-module(erl_make_certs). +-include_lib("public_key/include/public_key.hrl"). + +-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]). +-compile(export_all). + +%%-------------------------------------------------------------------- +%% @doc Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%% @end +%%-------------------------------------------------------------------- + +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:pkix_sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% @spec (::string(), ::string(), {Cert,Key}) -> ok +%% @end +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"), + [{'Certificate', Cert, not_encrypted}]), + ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% @doc Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Verifies cert signatures +%% @spec (::binary(), ::tuple()) -> ::boolean() +%% @end +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, _KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:pkix_verify(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(PemEntry = {_,_,_}, Pw) -> + public_key:pem_entry_decode(PemEntry, Pw); +decode_key(PemBin, Pw) -> + [KeyInfo] = public_key:pem_decode(PemBin), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {'RSAPrivateKey', list_to_binary(Der), not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {'DSAPrivateKey', list_to_binary(Der), not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + + IssuerProp = proplists:get_value(issuer, Opts, true), + {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + Subject = case IssuerProp of + true -> %% Is a Root Ca + Issuer; + _ -> + subject(proplists:get_value(subject, Opts),false) + end, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = Subject, + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(true, Opts, SubjectKey) -> + %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; +issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; +issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)}. + +issuer_der(Issuer) -> + Decoded = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsRootCA) -> + User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20). %% Bytes i.e. {1024, 160} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p–1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X} + end. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(crypto:mpint(P), 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + crypto:erlint(prime_odd(Rand, 0)). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + NotPrime = crypto:erlint(Rand), + prime_odd(crypto:mpint(NotPrime+2), N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate), + case crypto:mod_exp(CoPrime, Candidate, Candidate) of + CoPrime -> is_prime(Candidate, Test-1); + _ -> false + end. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(crypto:mpint(Min), crypto:mpint(Max)). + +odd_rand(Min,Max) -> + Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max), + BitSkip = (Sz+4)*8-1, + case Rand of + Odd = <<_:BitSkip, 1:1>> -> Odd; + Even = <<_:BitSkip, 0:1>> -> + crypto:mpint(crypto:erlint(Even)+1) + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. + +pem_to_der(File) -> + {ok, PemBin} = file:read_file(File), + public_key:pem_decode(PemBin). + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). diff --git a/lib/public_key/test/pkits_SUITE.erl b/lib/public_key/test/pkits_SUITE.erl index 5d58b39e26..660af4e8ab 100644 --- a/lib/public_key/test/pkits_SUITE.erl +++ b/lib/public_key/test/pkits_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,8 +25,8 @@ -compile(export_all). -%%-include_lib("public_key/include/public_key.hrl"). --include("public_key.hrl"). +-include_lib("public_key/include/public_key.hrl"). +%%-include("public_key.hrl"). -define(error(Format,Args), error(Format,Args,?FILE,?LINE)). -define(warning(Format,Args), warning(Format,Args,?FILE,?LINE)). @@ -43,25 +43,24 @@ -define(NIST6, "2.16.840.1.101.3.2.1.48.6"). %% -all(doc) -> - ["PKITS tests for RFC3280 compliance"]; -all(suite) -> - [signature_verification, - validity_periods, +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [signature_verification, validity_periods, verifying_name_chaining, - %% basic_certificate_revocation_tests, verifying_paths_with_self_issued_certificates, - verifying_basic_constraints, - key_usage, -%% certificate_policies, -%% require_explicit_policy, -%% policy_mappings, -%% inhibit_policy_mapping, -%% inhibit_any_policy, - name_constraints, -%% distribution_points, -%% delta_crls, - private_certificate_extensions]. + verifying_basic_constraints, key_usage, + name_constraints, private_certificate_extensions]. + +groups() -> + []. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + signature_verification(doc) -> [""]; signature_verification(suite) -> []; @@ -129,7 +128,6 @@ private_certificate_extensions(Config) when is_list(Config) -> run(private_certificate_extensions()). run() -> - catch crypto:start(), Tests = [signature_verification(), validity_periods(), @@ -187,9 +185,9 @@ run([],_) -> ok. read_certs(Test) -> File = test_file(Test), %% io:format("Read ~p ",[File]), - {ok, Ders} = public_key:pem_to_der(File), + Ders = erl_make_certs:pem_to_der(File), %% io:format("Ders ~p ~n",[length(Ders)]), - [Cert || {cert,Cert,not_encrypted} <- Ders]. + [Cert || {'Certificate', Cert, not_encrypted} <- Ders]. test_file(Test) -> file(?CONV, lists:append(string:tokens(Test, " -")) ++ ".pem"). @@ -581,17 +579,21 @@ init_per_testcase(_Func, Config) -> put(datadir, Datadir), Config. -fin_per_testcase(_Func, Config) -> +end_per_testcase(_Func, Config) -> %% Nodes = select_nodes(all, Config, ?FILE, ?LINE), %% rpc:multicall(Nodes, mnesia, lkill, []), Config. init_per_suite(Config) -> - crypto:start(), - Config. + try crypto:start() of + ok -> + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. end_per_suite(_Config) -> - crypto:stop(). + application:stop(crypto). error(Format, Args, File0, Line) -> File = filename:basename(File0), diff --git a/lib/public_key/test/public_key.cover b/lib/public_key/test/public_key.cover new file mode 100644 index 0000000000..ec00814578 --- /dev/null +++ b/lib/public_key/test/public_key.cover @@ -0,0 +1,4 @@ +{incl_app,public_key,details}. + + +{excl_mods, public_key, ['OTP-PUB-KEY']}. diff --git a/lib/public_key/test/public_key.spec b/lib/public_key/test/public_key.spec index dee9ad44ed..1749822c2d 100644 --- a/lib/public_key/test/public_key.spec +++ b/lib/public_key/test/public_key.spec @@ -1,2 +1 @@ -{topcase, {dir, "../public_key_test"}}. - +{suites,"../public_key_test",all}. diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 8cc36e490d..b11e4d092a 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,10 +23,10 @@ %% Note: This directive should only be used in test suites. -compile(export_all). --include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct.hrl"). -include_lib("test_server/include/test_server_line.hrl"). --include("public_key.hrl"). +-include_lib("public_key/include/public_key.hrl"). -define(TIMEOUT, 120000). % 2 min @@ -41,9 +41,12 @@ %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> - crypto:start(), - Config. - + try crypto:start() of + ok -> + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ %% Config - [tuple()] @@ -51,7 +54,7 @@ init_per_suite(Config) -> %% Description: Cleanup after the whole suite %%-------------------------------------------------------------------- end_per_suite(_Config) -> - crypto:stop(). + application:stop(crypto). %%-------------------------------------------------------------------- %% Function: init_per_testcase(TestCase, Config) -> Config @@ -96,21 +99,34 @@ end_per_testcase(_TestCase, Config) -> %% Name of a test case. %% Description: Returns a list of all test cases in this test suite %%-------------------------------------------------------------------- -all(doc) -> - ["Test the public_key rsa functionality"]; - -all(suite) -> - [app, - pem_to_der, - decode_private_key -%% encrypt_decrypt, -%% rsa_verify -%% dsa_verify_sign, -%% pkix_encode_decode, -%% pkix_verify_sign, -%% pkix_path_validation +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [app, + {group, pem_decode_encode}, + {group, ssh_public_key_decode_encode}, + encrypt_decrypt, + {group, sign_verify}, + pkix, pkix_path_validation, deprecated]. + +groups() -> + [{pem_decode_encode, [], [dsa_pem, rsa_pem, encrypted_pem, + dh_pem, cert_pem]}, + {ssh_public_key_decode_encode, [], + [ssh_rsa_public_key, ssh_dsa_public_key, ssh_rfc4716_rsa_comment, + ssh_rfc4716_dsa_comment, ssh_rfc4716_rsa_subject, ssh_known_hosts, + ssh_auth_keys, ssh1_known_hosts, ssh1_auth_keys, ssh_openssh_public_key_with_comment, + ssh_openssh_public_key_long_header]}, + {sign_verify, [], [rsa_sign_verify, dsa_sign_verify]} ]. +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + %% Test cases starts here. %%-------------------------------------------------------------------- @@ -118,144 +134,606 @@ app(doc) -> "Test that the public_key app file is ok"; app(suite) -> []; -app(Config) when list(Config) -> +app(Config) when is_list(Config) -> ok = test_server:app_test(public_key). -pem_to_der(doc) -> - ["Check that supported PEM files are decoded into the expected entry type"]; -pem_to_der(suite) -> +%%-------------------------------------------------------------------- + +dsa_pem(doc) -> + [""]; +dsa_pem(suite) -> []; -pem_to_der(Config) when is_list(Config) -> +dsa_pem(Config) when is_list(Config) -> Datadir = ?config(data_dir, Config), - {ok,[{dsa_private_key, _, not_encrypted}]} = - public_key:pem_to_der(filename:join(Datadir, "dsa.pem")), - {ok,[{rsa_private_key, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "client_key.pem")), - {ok,[{rsa_private_key, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "rsa.pem")), - {ok,[{rsa_private_key, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "rsa.pem"), "abcd1234"), - {ok, Bin0} = file:read_file(filename:join(Datadir, "rsa.pem")), - {ok, [{rsa_private_key, _, _}]} = public_key:pem_to_der(Bin0, "abcd1234"), - - {ok,[{dh_params, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "dh.pem")), - {ok,[{cert, _, not_encrypted}]} = - public_key:pem_to_der(filename:join(Datadir, "client_cert.pem")), - {ok,[{cert_req, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "req.pem")), - {ok,[{cert, _, _}, {cert, _, _}]} = - public_key:pem_to_der(filename:join(Datadir, "cacerts.pem")), - - {ok, Bin1} = file:read_file(filename:join(Datadir, "cacerts.pem")), - {ok, [{cert, _, _}, {cert, _, _}]} = public_key:pem_to_der(Bin1), + + [{'DSAPrivateKey', DerDSAKey, not_encrypted} = Entry0 ] = + erl_make_certs:pem_to_der(filename:join(Datadir, "dsa.pem")), + + DSAKey = public_key:der_decode('DSAPrivateKey', DerDSAKey), + + DSAKey = public_key:pem_entry_decode(Entry0), + + {ok, DSAPubPem} = file:read_file(filename:join(Datadir, "dsa_pub.pem")), + [{'SubjectPublicKeyInfo', _, _} = PubEntry0] = + public_key:pem_decode(DSAPubPem), + DSAPubKey = public_key:pem_entry_decode(PubEntry0), + true = check_entry_type(DSAPubKey, 'DSAPublicKey'), + PubEntry0 = public_key:pem_entry_encode('SubjectPublicKeyInfo', DSAPubKey), + DSAPubPemNoEndNewLines = strip_ending_newlines(DSAPubPem), + DSAPubPemNoEndNewLines = strip_ending_newlines(public_key:pem_encode([PubEntry0])). + +%%-------------------------------------------------------------------- + +rsa_pem(doc) -> + [""]; +rsa_pem(suite) -> + []; +rsa_pem(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + [{'RSAPrivateKey', DerRSAKey, not_encrypted} = Entry0 ] = + erl_make_certs:pem_to_der(filename:join(Datadir, "client_key.pem")), + + RSAKey0 = public_key:der_decode('RSAPrivateKey', DerRSAKey), + + RSAKey0 = public_key:pem_entry_decode(Entry0), - ok. + [{'RSAPrivateKey', _, {_,_}} = Entry1] = + erl_make_certs:pem_to_der(filename:join(Datadir, "rsa.pem")), + + true = check_entry_type(public_key:pem_entry_decode(Entry1, "abcd1234"), + 'RSAPrivateKey'), + + {ok, RSAPubPem} = file:read_file(filename:join(Datadir, "rsa_pub.pem")), + [{'SubjectPublicKeyInfo', _, _} = PubEntry0] = + public_key:pem_decode(RSAPubPem), + RSAPubKey = public_key:pem_entry_decode(PubEntry0), + true = check_entry_type(RSAPubKey, 'RSAPublicKey'), + PubEntry0 = public_key:pem_entry_encode('SubjectPublicKeyInfo', RSAPubKey), + RSAPubPemNoEndNewLines = strip_ending_newlines(RSAPubPem), + RSAPubPemNoEndNewLines = strip_ending_newlines(public_key:pem_encode([PubEntry0])), + + {ok, RSARawPem} = file:read_file(filename:join(Datadir, "rsa_pub_key.pem")), + [{'RSAPublicKey', _, _} = PubEntry1] = + public_key:pem_decode(RSARawPem), + RSAPubKey = public_key:pem_entry_decode(PubEntry1), + RSARawPemNoEndNewLines = strip_ending_newlines(RSARawPem), + RSARawPemNoEndNewLines = strip_ending_newlines(public_key:pem_encode([PubEntry1])). + %%-------------------------------------------------------------------- -decode_private_key(doc) -> - ["Check that private keys are decode to the expected key type."]; -decode_private_key(suite) -> + +encrypted_pem(doc) -> + [""]; +encrypted_pem(suite) -> []; -decode_private_key(Config) when is_list(Config) -> +encrypted_pem(Config) when is_list(Config) -> Datadir = ?config(data_dir, Config), - {ok,[DsaKey = {dsa_private_key, _DsaKey, _}]} = - public_key:pem_to_der(filename:join(Datadir, "dsa.pem")), - {ok,[RsaKey = {rsa_private_key, _RsaKey,_}]} = - public_key:pem_to_der(filename:join(Datadir, "client_key.pem")), - {ok,[ProtectedRsaKey1 = {rsa_private_key, _ProtectedRsaKey1,_}]} = - public_key:pem_to_der(filename:join(Datadir, "rsa.pem"), "abcd1234"), - {ok,[ProtectedRsaKey2 = {rsa_private_key, _ProtectedRsaKey2,_}]} = - public_key:pem_to_der(filename:join(Datadir, "rsa.pem")), - {ok, #'DSAPrivateKey'{}} = public_key:decode_private_key(DsaKey), - {ok, #'RSAPrivateKey'{}} = public_key:decode_private_key(RsaKey), - {ok, #'RSAPrivateKey'{}} = public_key:decode_private_key(ProtectedRsaKey1), - {ok, #'RSAPrivateKey'{}} = public_key:decode_private_key(ProtectedRsaKey2, "abcd1234"), - ok. + [{'RSAPrivateKey', DerRSAKey, not_encrypted}] = + erl_make_certs:pem_to_der(filename:join(Datadir, "client_key.pem")), + + RSAKey = public_key:der_decode('RSAPrivateKey', DerRSAKey), + + Salt0 = crypto:rand_bytes(8), + Entry0 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, + {{"DES-EDE3-CBC", Salt0}, "1234abcd"}), + RSAKey = public_key:pem_entry_decode(Entry0,"1234abcd"), + Des3KeyFile = filename:join(Datadir, "des3_client_key.pem"), + erl_make_certs:der_to_pem(Des3KeyFile, [Entry0]), + [{'RSAPrivateKey', _, {"DES-EDE3-CBC", Salt0}}] = + erl_make_certs:pem_to_der(Des3KeyFile), + + Salt1 = crypto:rand_bytes(8), + Entry1 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, + {{"DES-CBC", Salt1}, "4567efgh"}), + DesKeyFile = filename:join(Datadir, "des_client_key.pem"), + erl_make_certs:der_to_pem(DesKeyFile, [Entry1]), + [{'RSAPrivateKey', _, {"DES-CBC", Salt1}} =Entry2] = + erl_make_certs:pem_to_der(DesKeyFile), + true = check_entry_type(public_key:pem_entry_decode(Entry2, "4567efgh"), + 'RSAPrivateKey'). + +%%-------------------------------------------------------------------- + +dh_pem(doc) -> + [""]; +dh_pem(suite) -> + []; +dh_pem(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + [{'DHParameter', DerDH, not_encrypted} = Entry] = + erl_make_certs:pem_to_der(filename:join(Datadir, "dh.pem")), + + erl_make_certs:der_to_pem(filename:join(Datadir, "new_dh.pem"), [Entry]), + + DHParameter = public_key:der_decode('DHParameter', DerDH), + DHParameter = public_key:pem_entry_decode(Entry), + + Entry = public_key:pem_entry_encode('DHParameter', DHParameter). + +%%-------------------------------------------------------------------- +cert_pem(doc) -> + [""]; +cert_pem(suite) -> + []; +cert_pem(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + [Entry0] = + erl_make_certs:pem_to_der(filename:join(Datadir, "dsa.pem")), + + [{'Certificate', DerCert, not_encrypted} = Entry7] = + erl_make_certs:pem_to_der(filename:join(Datadir, "client_cert.pem")), + + Cert = public_key:der_decode('Certificate', DerCert), + Cert = public_key:pem_entry_decode(Entry7), + + CertEntries = [{'Certificate', _, not_encrypted} = CertEntry0, + {'Certificate', _, not_encrypted} = CertEntry1] = + erl_make_certs:pem_to_der(filename:join(Datadir, "cacerts.pem")), + + ok = erl_make_certs:der_to_pem(filename:join(Datadir, "wcacerts.pem"), CertEntries), + ok = erl_make_certs:der_to_pem(filename:join(Datadir, "wdsa.pem"), [Entry0]), + + NewCertEntries = erl_make_certs:pem_to_der(filename:join(Datadir, "wcacerts.pem")), + true = lists:member(CertEntry0, NewCertEntries), + true = lists:member(CertEntry1, NewCertEntries), + [Entry0] = erl_make_certs:pem_to_der(filename:join(Datadir, "wdsa.pem")). + +%%-------------------------------------------------------------------- +ssh_rsa_public_key(doc) -> + ""; +ssh_rsa_public_key(suite) -> + []; +ssh_rsa_public_key(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_pub")), + [{PubKey, Attributes1}] = public_key:ssh_decode(RSARawSsh2, public_key), + [{PubKey, Attributes1}] = public_key:ssh_decode(RSARawSsh2, rfc4716_public_key), + + {ok, RSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_rsa_pub")), + [{PubKey, Attributes2}] = public_key:ssh_decode(RSARawOpenSsh, public_key), + [{PubKey, Attributes2}] = public_key:ssh_decode(RSARawOpenSsh, openssh_public_key), + + %% Can not check EncodedSSh == RSARawSsh2 and EncodedOpenSsh + %% = RSARawOpenSsh as line breakpoints may differ + + EncodedSSh = public_key:ssh_encode([{PubKey, Attributes1}], rfc4716_public_key), + EncodedOpenSsh = public_key:ssh_encode([{PubKey, Attributes2}], openssh_public_key), + + [{PubKey, Attributes1}] = + public_key:ssh_decode(EncodedSSh, public_key), + [{PubKey, Attributes2}] = + public_key:ssh_decode(EncodedOpenSsh, public_key). + +%%-------------------------------------------------------------------- + +ssh_dsa_public_key(doc) -> + ""; +ssh_dsa_public_key(suite) -> + []; +ssh_dsa_public_key(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_pub")), + [{PubKey, Attributes1}] = public_key:ssh_decode(DSARawSsh2, public_key), + [{PubKey, Attributes1}] = public_key:ssh_decode(DSARawSsh2, rfc4716_public_key), + + {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_pub")), + [{PubKey, Attributes2}] = public_key:ssh_decode(DSARawOpenSsh, public_key), + [{PubKey, Attributes2}] = public_key:ssh_decode(DSARawOpenSsh, openssh_public_key), + + %% Can not check EncodedSSh == DSARawSsh2 and EncodedOpenSsh + %% = DSARawOpenSsh as line breakpoints may differ + + EncodedSSh = public_key:ssh_encode([{PubKey, Attributes1}], rfc4716_public_key), + EncodedOpenSsh = public_key:ssh_encode([{PubKey, Attributes2}], openssh_public_key), + + [{PubKey, Attributes1}] = + public_key:ssh_decode(EncodedSSh, public_key), + [{PubKey, Attributes2}] = + public_key:ssh_decode(EncodedOpenSsh, public_key). + +%%-------------------------------------------------------------------- +ssh_rfc4716_rsa_comment(doc) -> + "Test comment header and rsa key"; +ssh_rfc4716_rsa_comment(suite) -> + []; +ssh_rfc4716_rsa_comment(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_comment_pub")), + [{#'RSAPublicKey'{} = PubKey, Attributes}] = + public_key:ssh_decode(RSARawSsh2, public_key), + + Headers = proplists:get_value(headers, Attributes), + + Value = proplists:get_value("Comment", Headers, undefined), + true = Value =/= undefined, + RSARawSsh2 = public_key:ssh_encode([{PubKey, Attributes}], rfc4716_public_key). + +%%-------------------------------------------------------------------- +ssh_rfc4716_dsa_comment(doc) -> + "Test comment header and dsa key"; +ssh_rfc4716_dsa_comment(suite) -> + []; +ssh_rfc4716_dsa_comment(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_comment_pub")), + [{{_, #'Dss-Parms'{}} = PubKey, Attributes}] = + public_key:ssh_decode(DSARawSsh2, public_key), + + Headers = proplists:get_value(headers, Attributes), + + Value = proplists:get_value("Comment", Headers, undefined), + true = Value =/= undefined, + + %% Can not check Encoded == DSARawSsh2 as line continuation breakpoints may differ + Encoded = public_key:ssh_encode([{PubKey, Attributes}], rfc4716_public_key), + [{PubKey, Attributes}] = + public_key:ssh_decode(Encoded, public_key). + +%%-------------------------------------------------------------------- +ssh_rfc4716_rsa_subject(doc) -> + "Test another header value than comment"; +ssh_rfc4716_rsa_subject(suite) -> + []; +ssh_rfc4716_rsa_subject(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_subject_pub")), + [{#'RSAPublicKey'{} = PubKey, Attributes}] = + public_key:ssh_decode(RSARawSsh2, public_key), + + Headers = proplists:get_value(headers, Attributes), + + Value = proplists:get_value("Subject", Headers, undefined), + true = Value =/= undefined, + + %% Can not check Encoded == RSARawSsh2 as line continuation breakpoints may differ + Encoded = public_key:ssh_encode([{PubKey, Attributes}], rfc4716_public_key), + [{PubKey, Attributes}] = + public_key:ssh_decode(Encoded, public_key). + +%%-------------------------------------------------------------------- +ssh_known_hosts(doc) -> + ""; +ssh_known_hosts(suite) -> + []; +ssh_known_hosts(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "known_hosts")), + [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2}] = Decoded = + public_key:ssh_decode(SshKnownHosts, known_hosts), + + Value1 = proplists:get_value(hostnames, Attributes1, undefined), + Value2 = proplists:get_value(hostnames, Attributes2, undefined), + true = (Value1 =/= undefined) and (Value2 =/= undefined), + + Encoded = public_key:ssh_encode(Decoded, known_hosts), + Decoded = public_key:ssh_decode(Encoded, known_hosts). + +%%-------------------------------------------------------------------- + +ssh1_known_hosts(doc) -> + ""; +ssh1_known_hosts(suite) -> + []; +ssh1_known_hosts(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "ssh1_known_hosts")), + [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2}] = Decoded = + public_key:ssh_decode(SshKnownHosts, known_hosts), + + Value1 = proplists:get_value(hostnames, Attributes1, undefined), + Value2 = proplists:get_value(hostnames, Attributes2, undefined), + true = (Value1 =/= undefined) and (Value2 =/= undefined), + + Encoded = public_key:ssh_encode(Decoded, known_hosts), + Decoded = public_key:ssh_decode(Encoded, known_hosts). + +%%-------------------------------------------------------------------- +ssh_auth_keys(doc) -> + ""; +ssh_auth_keys(suite) -> + []; +ssh_auth_keys(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "auth_keys")), + [{#'RSAPublicKey'{}, Attributes1}, {{_, #'Dss-Parms'{}}, _Attributes2}] = Decoded = + public_key:ssh_decode(SshAuthKeys, auth_keys), + + Value1 = proplists:get_value(options, Attributes1, undefined), + true = Value1 =/= undefined, + + Encoded = public_key:ssh_encode(Decoded, auth_keys), + Decoded = public_key:ssh_decode(Encoded, auth_keys). + +%%-------------------------------------------------------------------- +ssh1_auth_keys(doc) -> + ""; +ssh1_auth_keys(suite) -> + []; +ssh1_auth_keys(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "ssh1_auth_keys")), + [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2}] = Decoded = + public_key:ssh_decode(SshAuthKeys, auth_keys), + + Value1 = proplists:get_value(bits, Attributes1, undefined), + Value2 = proplists:get_value(bits, Attributes2, undefined), + true = (Value1 =/= undefined) and (Value2 =/= undefined), + + Encoded = public_key:ssh_encode(Decoded, auth_keys), + Decoded = public_key:ssh_decode(Encoded, auth_keys). + +%%-------------------------------------------------------------------- +ssh_openssh_public_key_with_comment(doc) -> + "Test that emty lines and lines starting with # are ignored"; +ssh_openssh_public_key_with_comment(suite) -> + []; +ssh_openssh_public_key_with_comment(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_with_comment_pub")), + [{{_, #'Dss-Parms'{}}, _}] = public_key:ssh_decode(DSARawOpenSsh, openssh_public_key). + +%%-------------------------------------------------------------------- +ssh_openssh_public_key_long_header(doc) -> + "Test that long headers are handled"; +ssh_openssh_public_key_long_header(suite) -> + []; +ssh_openssh_public_key_long_header(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + + {ok,RSARawOpenSsh} = file:read_file(filename:join(Datadir, "ssh_rsa_long_header_pub")), + [{#'RSAPublicKey'{}, _}] = Decoded = public_key:ssh_decode(RSARawOpenSsh, public_key), + + Encoded = public_key:ssh_encode(Decoded, rfc4716_public_key), + Decoded = public_key:ssh_decode(Encoded, rfc4716_public_key). + %%-------------------------------------------------------------------- encrypt_decrypt(doc) -> [""]; encrypt_decrypt(suite) -> []; encrypt_decrypt(Config) when is_list(Config) -> - RSAPrivateKey = #'RSAPrivateKey'{publicExponent = 17, - modulus = 3233, - privateExponent = 2753, - prime1 = 61, - prime2 = 53, - version = 'two-prime'}, - Msg = <<0,123>>, - {ok, Encrypted} = public_key:encrypt(Msg, RSAPrivateKey, [{block_type, 2}]), - test_server:format("Expected 855, Encrypted ~p ~n", [Encrypted]), + {PrivateKey, _DerKey} = erl_make_certs:gen_rsa(64), + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = PrivateKey, + PublicKey = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey), + Msg = public_key:decrypt_public(RsaEncrypted, PublicKey), + Msg = public_key:decrypt_public(RsaEncrypted, PrivateKey), + RsaEncrypted2 = public_key:encrypt_public(Msg, PublicKey), + RsaEncrypted3 = public_key:encrypt_public(Msg, PrivateKey), + Msg = public_key:decrypt_private(RsaEncrypted2, PrivateKey), + Msg = public_key:decrypt_private(RsaEncrypted3, PrivateKey), + ok. + +%%-------------------------------------------------------------------- +rsa_sign_verify(doc) -> + ["Checks that we can sign and verify rsa signatures."]; +rsa_sign_verify(suite) -> + []; +rsa_sign_verify(Config) when is_list(Config) -> + Ca = {_, CaKey} = erl_make_certs:make_cert([]), + {Cert1, _} = erl_make_certs:make_cert([{key, dsa}, {issuer, Ca}]), + PrivateRSA = #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = + public_key:pem_entry_decode(CaKey), + PublicRSA = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, + true = public_key:pkix_verify(Cert1, PublicRSA), + + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + RSASign = public_key:sign(Msg, sha, PrivateRSA), + true = public_key:verify(Msg, sha, RSASign, PublicRSA), + false = public_key:verify(<<1:8, Msg/binary>>, sha, RSASign, PublicRSA), + false = public_key:verify(Msg, sha, <<1:8, RSASign/binary>>, PublicRSA), + + RSASign1 = public_key:sign(Msg, md5, PrivateRSA), + true = public_key:verify(Msg, md5, RSASign1, PublicRSA). +%%-------------------------------------------------------------------- +dsa_sign_verify(doc) -> + ["Checks that we can sign and verify dsa signatures."]; +dsa_sign_verify(suite) -> + []; +dsa_sign_verify(Config) when is_list(Config) -> + Ca = erl_make_certs:make_cert([]), + CertInfo = {_,CertKey1} = erl_make_certs:make_cert([{key, dsa}, {issuer, Ca}]), + {Cert2,_CertKey} = erl_make_certs:make_cert([{issuer, CertInfo}]), + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y, x=_X} = + public_key:pem_entry_decode(CertKey1), + true = public_key:pkix_verify(Cert2, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}), + Datadir = ?config(data_dir, Config), + [DsaKey = {'DSAPrivateKey', _, _}] = + erl_make_certs:pem_to_der(filename:join(Datadir, "dsa.pem")), + DSAPrivateKey = public_key:pem_entry_decode(DsaKey), + #'DSAPrivateKey'{p=P1, q=Q1, g=G1, y=Y1, x=_X1} = DSAPrivateKey, + Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")), + DSASign = public_key:sign(Msg, sha, DSAPrivateKey), + DSAPublicKey = Y1, + DSAParams = #'Dss-Parms'{p=P1, q=Q1, g=G1}, + true = public_key:verify(Msg, sha, DSASign, {DSAPublicKey, DSAParams}), + false = public_key:verify(<<1:8, Msg/binary>>, sha, DSASign, + {DSAPublicKey, DSAParams}), + false = public_key:verify(Msg, sha, <<1:8, DSASign/binary>>, + {DSAPublicKey, DSAParams}), + + Digest = crypto:sha(Msg), + DigestSign = public_key:sign(Digest, none, DSAPrivateKey), + true = public_key:verify(Digest, none, DigestSign, {DSAPublicKey, DSAParams}), + <<_:8, RestDigest/binary>> = Digest, + false = public_key:verify(<<1:8, RestDigest/binary>>, none, DigestSign, + {DSAPublicKey, DSAParams}), + false = public_key:verify(Digest, none, <<1:8, DigestSign/binary>>, + {DSAPublicKey, DSAParams}). +%%-------------------------------------------------------------------- +pkix(doc) -> + "Misc pkix tests not covered elsewhere"; +pkix(suite) -> + []; +pkix(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + Certs0 = erl_make_certs:pem_to_der(filename:join(Datadir, "cacerts.pem")), + Certs1 = erl_make_certs:pem_to_der(filename:join(Datadir, "client_cert.pem")), + TestTransform = fun({'Certificate', CertDer, not_encrypted}) -> + PlainCert = public_key:pkix_decode_cert(CertDer, plain), + OtpCert = public_key:pkix_decode_cert(CertDer, otp), + CertDer = + public_key:pkix_encode('OTPCertificate', OtpCert, otp), + CertDer = + public_key:pkix_encode('Certificate', PlainCert, plain), + OTPTBS = OtpCert#'OTPCertificate'.tbsCertificate, + OTPSubj = OTPTBS#'OTPTBSCertificate'.subject, + DNEncoded = public_key:pkix_encode('Name', OTPSubj, otp), + PlainTBS = PlainCert#'Certificate'.tbsCertificate, + Subj2 = PlainTBS#'TBSCertificate'.subject, + DNEncoded = public_key:pkix_encode('Name', Subj2, plain), + false = public_key:pkix_is_fixed_dh_cert(CertDer) + end, + [TestTransform(Cert) || Cert <- Certs0 ++ Certs1], + true = public_key:pkix_is_self_signed(element(2,hd(Certs0))), + false = public_key:pkix_is_self_signed(element(2,hd(Certs1))), -%% Datadir = ?config(data_dir, Config), -%% {ok,[{rsa_private_key, EncKey}]} = -%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")), -%% {ok, Key} = public_key:decode_private_key(EncKey, rsa), -%% RSAPublicKey = #'RSAPublicKey'{publicExponent = -%% Key#'RSAPrivateKey'.publicExponent, -%% modulus = Key#'RSAPrivateKey'.modulus}, -%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")), -%% Hash = crypto:sha(Msg), -%% {ok, Encrypted} = public_key:encrypt(Hash, Key, [{block_type, 2}]), -%% test_server:format("Encrypted ~p", [Encrypted]), -%% {ok, Decrypted} = public_key:decrypt(Encrypted, -%% RSAPublicKey, [{block_type, 1}]), -%% test_server:format("Encrypted ~p", [Decrypted]), -%% true = Encrypted == Decrypted. - + CaIds = [element(2, public_key:pkix_issuer_id(Cert, self)) || + {'Certificate', Cert, _} <- Certs0], + {ok, IssuerId = {_, _IssuerName}} = + public_key:pkix_issuer_id(element(2,hd(Certs1)), other), + + true = lists:member(IssuerId, CaIds), + + %% Should be normalized allready + TestStr = {rdnSequence, + [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"ERLANGCA"}}], + [{'AttributeTypeAndValue', {2,5,4,3},{printableString," erlang ca "}}]]}, + VerifyStr = {rdnSequence, + [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlang ca"}}], + [{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlangca"}}]]}, + VerifyStr = public_key:pkix_normalize_name(TestStr), + + ok. %%-------------------------------------------------------------------- -rsa_verify(doc) -> - ["Cheks that we can verify an rsa signature."]; -rsa_verify(suite) -> +pkix_path_validation(doc) -> + "Misc pkix tests not covered elsewhere"; +pkix_path_validation(suite) -> []; -rsa_verify(Config) when is_list(Config) -> - Datadir = ?config(data_dir, Config), +pkix_path_validation(Config) when is_list(Config) -> + CaK = {Trusted,_} = + erl_make_certs:make_cert([{key, dsa}, + {subject, [ + {name, "Public Key"}, + {?'id-at-name', {printableString, "public_key"}}, + {?'id-at-pseudonym', {printableString, "pubkey"}}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"} + ]} + ]), + ok = erl_make_certs:write_pem("./", "public_key_cacert", CaK), + + CertK1 = {Cert1, _} = erl_make_certs:make_cert([{issuer, CaK}]), + CertK2 = {Cert2,_} = erl_make_certs:make_cert([{issuer, CertK1}, + {digest, md5}, {extensions, false}]), + ok = erl_make_certs:write_pem("./", "public_key_cert", CertK2), - {ok,[{cert, DerCert}]} = - public_key:pem_to_der(filename:join(Datadir, "server_cert.pem")), + {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1], []), - {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp), + {error, {bad_cert,invalid_issuer}} = + public_key:pkix_path_validation(Trusted, [Cert2], []), - {0, Signature} = OTPCert#'Certificate'.signature, - TBSCert = OTPCert#'Certificate'.tbsCertificate, + {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1, Cert2], []), + {error, issuer_not_found} = public_key:pkix_issuer_id(Cert2, other), - #'TBSCertificate'{subjectPublicKeyInfo = Info} = TBSCert, - - #'SubjectPublicKeyInfo'{subjectPublicKey = RSAPublicKey} = Info, - - EncTBSCert = encoded_tbs_cert(DerCert), - Digest = crypto:sha(EncTBSCert), - - public_key:verify_signature(Digest, Signature, RSAPublicKey). - - -%% Signature is generated in the following way (in datadir): -%% openssl dgst -sha1 -binary -out rsa_signature -sign server_key.pem msg.txt -%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")), -%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")), -%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")), -%% Digest = crypto:sha(Msg), -%% {ok,[{rsa_private_key, EncKey}]} = -%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")), -%% {ok, Key} = public_key:decode_private_key(EncKey, rsa), -%% RSAPublicKey = #'RSAPublicKey'{publicExponent = -%% Key#'RSAPrivateKey'.publicExponent, -%% modulus = Key#'RSAPrivateKey'.modulus}, - -encoded_tbs_cert(Cert) -> - {ok, PKIXCert} = - 'OTP-PUB-KEY':decode_TBSCert_exclusive(Cert), - {'Certificate', - {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert, - EncodedTBSCert. + CertK3 = {Cert3,_} = erl_make_certs:make_cert([{issuer, CertK1}, + {extensions, [{basic_constraints, false}]}]), + {Cert4,_} = erl_make_certs:make_cert([{issuer, CertK3}]), + {error, {bad_cert,missing_basic_constraint}} = + public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], []), + + VerifyFunAndState0 = {fun(_,{bad_cert, missing_basic_constraint}, UserState) -> + {valid, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + {ok, _} = + public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], + [{verify_fun, VerifyFunAndState0}]), + + {error, {bad_cert, unknown_ca}} = + public_key:pkix_path_validation(unknown_ca, [Cert1, Cert3, Cert4], []), + + VerifyFunAndState1 = + {fun(_,{bad_cert, unknown_ca}, UserState) -> + {valid, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState} + end, []}, + + {ok, _} = + public_key:pkix_path_validation(unknown_ca, [Cert1], [{verify_fun, + VerifyFunAndState1}]), + ok. + +%%-------------------------------------------------------------------- +deprecated(doc) -> + ["Check deprecated functions."]; +deprecated(suite) -> + []; +deprecated(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + {ok, [DsaKey = {'DSAPrivateKey', _DsaKey, _}]} = + public_key:pem_to_der(filename:join(Datadir, "dsa.pem")), + {ok, [RsaKey = {'RSAPrivateKey', _RsaKey,_}]} = + public_key:pem_to_der(filename:join(Datadir, "client_key.pem")), + {ok, [ProtectedRsaKey = {'RSAPrivateKey', _ProtectedRsaKey,_}]} = + public_key:pem_to_der(filename:join(Datadir, "rsa.pem")), + + {ok, #'DSAPrivateKey'{}} = public_key:decode_private_key(DsaKey), + {ok, #'RSAPrivateKey'{}} = public_key:decode_private_key(RsaKey), + {ok, #'RSAPrivateKey'{}} = public_key:decode_private_key(ProtectedRsaKey, "abcd1234"), + ok. + +%%-------------------------------------------------------------------- + +check_entry_type(#'DSAPrivateKey'{}, 'DSAPrivateKey') -> + true; +check_entry_type(#'RSAPrivateKey'{}, 'RSAPrivateKey') -> + true; +check_entry_type(#'RSAPublicKey'{}, 'RSAPublicKey') -> + true; +check_entry_type({_Int, #'Dss-Parms'{}}, 'DSAPublicKey') when is_integer(_Int) -> + true; +check_entry_type(#'DHParameter'{}, 'DHParameter') -> + true; +check_entry_type(#'Certificate'{}, 'Certificate') -> + true; +check_entry_type(_,_) -> + false. +strip_ending_newlines(Bin) -> + string:strip(binary_to_list(Bin), right, 10). diff --git a/lib/public_key/test/public_key_SUITE_data/auth_keys b/lib/public_key/test/public_key_SUITE_data/auth_keys new file mode 100644 index 0000000000..0c4b47edde --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/auth_keys @@ -0,0 +1,3 @@ +command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmtisaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRuLDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368+dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNWjeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4fuKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV225JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRMIB+X+OTUUI8= dhopson@VMUbuntu-DSH + +ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH diff --git a/lib/public_key/test/public_key_SUITE_data/dsa_pub.pem b/lib/public_key/test/public_key_SUITE_data/dsa_pub.pem new file mode 100644 index 0000000000..d3635e5b20 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/dsa_pub.pem @@ -0,0 +1,12 @@ +-----BEGIN PUBLIC KEY----- +MIIBtzCCASwGByqGSM44BAEwggEfAoGBALez5tklY5CdFeTMos899pA6i4u4uCts +zgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95 +aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIB +VE/TChWYw2eRAhUA1R64VvcQ90XA8SOKVDmMA0dBzukCgYEAlLMYP0pbgBlgHQVO +3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+ +86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRb +yIpwb7MX1nsVdsKzALnZNMW27A0DgYQAAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb +6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq +19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJ +olHzIRpkLk45y4c= +-----END PUBLIC KEY----- diff --git a/lib/public_key/test/public_key_SUITE_data/known_hosts b/lib/public_key/test/public_key_SUITE_data/known_hosts new file mode 100644 index 0000000000..30fc3b1fe8 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/known_hosts @@ -0,0 +1,3 @@ +hostname.domain.com,192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM= + +|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM= [email protected] diff --git a/lib/public_key/test/public_key_SUITE_data/openssh_dsa_pub b/lib/public_key/test/public_key_SUITE_data/openssh_dsa_pub new file mode 100644 index 0000000000..a765ba8189 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/openssh_dsa_pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH diff --git a/lib/public_key/test/public_key_SUITE_data/openssh_dsa_with_comment_pub b/lib/public_key/test/public_key_SUITE_data/openssh_dsa_with_comment_pub new file mode 100644 index 0000000000..d5a34a3f78 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/openssh_dsa_with_comment_pub @@ -0,0 +1,3 @@ +#This should be ignored!! + +ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH diff --git a/lib/public_key/test/public_key_SUITE_data/openssh_rsa_pub b/lib/public_key/test/public_key_SUITE_data/openssh_rsa_pub new file mode 100644 index 0000000000..0a0838db40 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/openssh_rsa_pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmtisaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRuLDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368+dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNWjeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4fuKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV225JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRMIB+X+OTUUI8= dhopson@VMUbuntu-DSH diff --git a/lib/public_key/test/public_key_SUITE_data/rsa_pub.pem b/lib/public_key/test/public_key_SUITE_data/rsa_pub.pem new file mode 100644 index 0000000000..cbe81343f7 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/rsa_pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANRiyZg0uci74Nc6mnqZ8AoDl88aT7x6 +JA0MfgHIHzteEj7Qg+lE5QxMGAafurVE5vqoHkDfwk4uzzsCAJuz91MCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/lib/public_key/test/public_key_SUITE_data/rsa_pub_key.pem b/lib/public_key/test/public_key_SUITE_data/rsa_pub_key.pem new file mode 100644 index 0000000000..3b9d7568ff --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/rsa_pub_key.pem @@ -0,0 +1,4 @@ +-----BEGIN RSA PUBLIC KEY----- +MEgCQQDUYsmYNLnIu+DXOpp6mfAKA5fPGk+8eiQNDH4ByB87XhI+0IPpROUMTBgG +n7q1ROb6qB5A38JOLs87AgCbs/dTAgMBAAE= +-----END RSA PUBLIC KEY----- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh1_auth_keys b/lib/public_key/test/public_key_SUITE_data/ssh1_auth_keys new file mode 100644 index 0000000000..c91f4e4679 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh1_auth_keys @@ -0,0 +1,3 @@ +1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH + +command="dump /home",no-pty,no-port-forwarding 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH diff --git a/lib/public_key/test/public_key_SUITE_data/ssh1_known_hosts b/lib/public_key/test/public_key_SUITE_data/ssh1_known_hosts new file mode 100644 index 0000000000..ec668fe05b --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh1_known_hosts @@ -0,0 +1,2 @@ +hostname.domain.com,192.168.0.1 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH +hostname2.domain.com,192.168.0.2 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 diff --git a/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_comment_pub b/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_comment_pub new file mode 100644 index 0000000000..ca5089dbd7 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_comment_pub @@ -0,0 +1,13 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: This is my public key for use on \ +servers which I don't like. +AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET +W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH +YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c +vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf +J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA +vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB +AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS +n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5 +sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_pub b/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_pub new file mode 100644 index 0000000000..a5e38be81a --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh2_dsa_pub @@ -0,0 +1,12 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: DSA Public Key for use with MyIsp +AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET +W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH +YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c +vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf +J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA +vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB +AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS +n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5 +sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_comment_pub b/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_comment_pub new file mode 100644 index 0000000000..e4d446147c --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_comment_pub @@ -0,0 +1,7 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "1024-bit RSA, converted from OpenSSH by [email protected]" +x-command: /home/me/bin/lock-in-guest.sh +AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb +YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ +5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_pub b/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_pub new file mode 100644 index 0000000000..761088b517 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh2_rsa_pub @@ -0,0 +1,13 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o +39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS +7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmt +isaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2 +sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRu +LDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368 ++dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNW +jeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4f +uKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV22 +5JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRM +IB+X+OTUUI8= +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh2_subject_pub b/lib/public_key/test/public_key_SUITE_data/ssh2_subject_pub new file mode 100644 index 0000000000..8b8ccda8ba --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh2_subject_pub @@ -0,0 +1,8 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Subject: me +Comment: 1024-bit rsa, created by [email protected] Mon Jan 15 \ +08:31:24 2001 +AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 +596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 +soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_comment_pub b/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_comment_pub new file mode 100644 index 0000000000..7b42ced93e --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_comment_pub @@ -0,0 +1,9 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: This is an example of a very very very very looooooooooooo\ +ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\ +commment +x-command: /home/me/bin/lock-in-guest.sh +AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb +YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ +5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_header_pub b/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_header_pub new file mode 100644 index 0000000000..7b42ced93e --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ssh_rsa_long_header_pub @@ -0,0 +1,9 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: This is an example of a very very very very looooooooooooo\ +ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\ +commment +x-command: /home/me/bin/lock-in-guest.sh +AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb +YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ +5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index 8c4e4127b2..c99fd6fee1 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1,11 +1 @@ -PUBLIC_KEY_VSN = 0.7 - -TICKETS = OTP-8626 - -#TICKETS_0.6 = OTP-7046 \ -# OTP-8553 -#TICKETS_0.5 = OTP-8372 -#TICKETS_0.4 = OTP-8250 -#TICKETS_0.3 = OTP-8100 OTP-8142 -#TICKETS_0.2 = OTP-7860 -#TICKETS_0.1 = OTP-7637
\ No newline at end of file +PUBLIC_KEY_VSN = 0.11 |