This section describes examples of how to use the Public Key API. Keys and certificates used in the following sections are generated only for testing the Public Key application.
Some shell printouts in the following examples are abbreviated for increased readability.
Public-key data (keys, certificates, and so on) can be stored in Privacy Enhanced Mail (PEM) format. The PEM files have the following structure:
<text>
-----BEGIN <SOMETHING>-----
<Attribute> : <Value>
<Base64 encoded DER data>
-----END <SOMETHING>-----
<text>
A file can contain several
A DSA private key can look as follows:
File handling is not done by the Public Key application.
1> {ok, PemBin} = file:read_file("dsa.pem").
{ok,<<"-----BEGIN DSA PRIVATE KEY-----\nMIIBuw"...>>}
The following PEM file has only one entry, a private DSA key:
2> [DSAEntry] = public_key:pem_decode(PemBin).
[{'DSAPrivateKey',<<48,130,1,187,2,1,0,2,129,129,0,183,
179,230,217,37,99,144,157,21,228,204,
162,207,61,246,...>>,
not_encrypted}]
3> Key = public_key:pem_entry_decode(DSAEntry).
#'DSAPrivateKey'{version = 0,
p = 12900045185019966618...6593,
q = 1216700114794736143432235288305776850295620488937,
g = 10442040227452349332...47213,
y = 87256807980030509074...403143,
x = 510968529856012146351317363807366575075645839654}
An RSA private key encrypted with a password can look as follows:
1> {ok, PemBin} = file:read_file("rsa.pem").
{ok,<<"Bag Attribut"...>>}
The following PEM file has only one entry, a private RSA key:
2>[RSAEntry] = public_key:pem_decode(PemBin).
[{'RSAPrivateKey',<<224,108,117,203,152,40,15,77,128,126,
221,195,154,249,85,208,202,251,109,
119,120,57,29,89,19,9,...>>,
{"DES-EDE3-CBC",<<"kÙeø¼pµL">>}}]
In this following example, the password is
3> Key = public_key:pem_entry_decode(RSAEntry, "abcd1234").
#'RSAPrivateKey'{version = 'two-prime',
modulus = 1112355156729921663373...2737107,
publicExponent = 65537,
privateExponent = 58064406231183...2239766033,
prime1 = 11034766614656598484098...7326883017,
prime2 = 10080459293561036618240...77738643771,
exponent1 = 77928819327425934607...22152984217,
exponent2 = 36287623121853605733...20588523793,
coefficient = 924840412626098444...41820968343,
otherPrimeInfos = asn1_NOVALUE}
The following is an example of X509 certificates:
1> {ok, PemBin} = file:read_file("cacerts.pem").
{ok,<<"-----BEGIN CERTIFICATE-----\nMIIC7jCCAl"...>>}
The following file includes two certificates:
2> [CertEntry1, CertEntry2] = public_key:pem_decode(PemBin).
[{'Certificate',<<48,130,2,238,48,130,2,87,160,3,2,1,2,2,
9,0,230,145,97,214,191,2,120,150,48,13,
...>>,
not_encrypted},
{'Certificate',<<48,130,3,200,48,130,3,49,160,3,2,1,2,2,1,
1,48,13,6,9,42,134,72,134,247,...>>,
not_encrypted}]
Certificates can be decoded as usual:
2> Cert = public_key:pem_entry_decode(CertEntry1).
#'Certificate'{
tbsCertificate =
#'TBSCertificate'{
version = v3,serialNumber = 16614168075301976214,
signature =
#'AlgorithmIdentifier'{
algorithm = {1,2,840,113549,1,1,5},
parameters = <<5,0>>},
issuer =
{rdnSequence,
[[#'AttributeTypeAndValue'{
type = {2,5,4,3},
value = <<19,8,101,114,108,97,110,103,67,65>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,11},
value = <<19,10,69,114,108,97,110,103,32,79,84,80>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,10},
value = <<19,11,69,114,105,99,115,115,111,110,32,65,66>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,7},
value = <<19,9,83,116,111,99,107,104,111,108,109>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,6},
value = <<19,2,83,69>>}],
[#'AttributeTypeAndValue'{
type = {1,2,840,113549,1,9,1},
value = <<22,22,112,101,116,101,114,64,101,114,...>>}]]},
validity =
#'Validity'{
notBefore = {utcTime,"080109082929Z"},
notAfter = {utcTime,"080208082929Z"}},
subject =
{rdnSequence,
[[#'AttributeTypeAndValue'{
type = {2,5,4,3},
value = <<19,8,101,114,108,97,110,103,67,65>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,11},
value = <<19,10,69,114,108,97,110,103,32,79,84,80>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,10},
value = <<19,11,69,114,105,99,115,115,111,110,32,...>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,7},
value = <<19,9,83,116,111,99,107,104,111,108,...>>}],
[#'AttributeTypeAndValue'{
type = {2,5,4,6},
value = <<19,2,83,69>>}],
[#'AttributeTypeAndValue'{
type = {1,2,840,113549,1,9,1},
value = <<22,22,112,101,116,101,114,64,...>>}]]},
subjectPublicKeyInfo =
#'SubjectPublicKeyInfo'{
algorithm =
#'AlgorithmIdentifier'{
algorithm = {1,2,840,113549,1,1,1},
parameters = <<5,0>>},
subjectPublicKey =
{0,<<48,129,137,2,129,129,0,203,209,187,77,73,231,90,...>>}},
issuerUniqueID = asn1_NOVALUE,
subjectUniqueID = asn1_NOVALUE,
extensions =
[#'Extension'{
extnID = {2,5,29,19},
critical = true,
extnValue = [48,3,1,1,255]},
#'Extension'{
extnID = {2,5,29,15},
critical = false,
extnValue = [3,2,1,6]},
#'Extension'{
extnID = {2,5,29,14},
critical = false,
extnValue = [4,20,27,217,65,152,6,30,142|...]},
#'Extension'{
extnID = {2,5,29,17},
critical = false,
extnValue = [48,24,129,22,112,101,116,101|...]}]},
signatureAlgorithm =
#'AlgorithmIdentifier'{
algorithm = {1,2,840,113549,1,1,5},
parameters = <<5,0>>},
signature =
<<163,186,7,163,216,152,63,47,154,234,139,73,154,96,120,
165,2,52,196,195,109,167,192,...>>}
Parts of certificates can be decoded with
public_key:der_decode('X520CommonName', <<19,8,101,114,108,97,110,103,67,65>>).
{printableString,"erlangCA"}
However, certificates can also be decoded using
3>{_, DerCert, _} = CertEntry1.
4> public_key:pkix_decode_cert(DerCert, otp).
#'OTPCertificate'{
tbsCertificate =
#'OTPTBSCertificate'{
version = v3,serialNumber = 16614168075301976214,
signature =
#'SignatureAlgorithm'{
algorithm = {1,2,840,113549,1,1,5},
parameters = 'NULL'},
issuer =
{rdnSequence,
[[#'AttributeTypeAndValue'{
type = {2,5,4,3},
value = {printableString,"erlangCA"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,11},
value = {printableString,"Erlang OTP"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,10},
value = {printableString,"Ericsson AB"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,7},
value = {printableString,"Stockholm"}}],
[#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}],
[#'AttributeTypeAndValue'{
type = {1,2,840,113549,1,9,1},
value = "peter@erix.ericsson.se"}]]},
validity =
#'Validity'{
notBefore = {utcTime,"080109082929Z"},
notAfter = {utcTime,"080208082929Z"}},
subject =
{rdnSequence,
[[#'AttributeTypeAndValue'{
type = {2,5,4,3},
value = {printableString,"erlangCA"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,11},
value = {printableString,"Erlang OTP"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,10},
value = {printableString,"Ericsson AB"}}],
[#'AttributeTypeAndValue'{
type = {2,5,4,7},
value = {printableString,"Stockholm"}}],
[#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}],
[#'AttributeTypeAndValue'{
type = {1,2,840,113549,1,9,1},
value = "peter@erix.ericsson.se"}]]},
subjectPublicKeyInfo =
#'OTPSubjectPublicKeyInfo'{
algorithm =
#'PublicKeyAlgorithm'{
algorithm = {1,2,840,113549,1,1,1},
parameters = 'NULL'},
subjectPublicKey =
#'RSAPublicKey'{
modulus =
1431267547247997...37419,
publicExponent = 65537}},
issuerUniqueID = asn1_NOVALUE,
subjectUniqueID = asn1_NOVALUE,
extensions =
[#'Extension'{
extnID = {2,5,29,19},
critical = true,
extnValue =
#'BasicConstraints'{
cA = true,pathLenConstraint = asn1_NOVALUE}},
#'Extension'{
extnID = {2,5,29,15},
critical = false,
extnValue = [keyCertSign,cRLSign]},
#'Extension'{
extnID = {2,5,29,14},
critical = false,
extnValue = [27,217,65,152,6,30,142,132,245|...]},
#'Extension'{
extnID = {2,5,29,17},
critical = false,
extnValue = [{rfc822Name,"peter@erix.ericsson.se"}]}]},
signatureAlgorithm =
#'SignatureAlgorithm'{
algorithm = {1,2,840,113549,1,1,5},
parameters = 'NULL'},
signature =
<<163,186,7,163,216,152,63,47,154,234,139,73,154,96,120,
165,2,52,196,195,109,167,192,...>>}
This call is equivalent to
5> public_key:pkix_decode_cert(DerCert, plain).
#'Certificate'{ ...}
If you have public-key data and want to create a PEM file
this can be done by calling functions
The second element of the PEM-entry is the ASN.1
1> PemEntry = public_key:pem_entry_encode('RSAPublicKey', RSAPubKey).
{'RSAPublicKey', <<48,72,...>>, not_encrypted}
2> PemBin = public_key:pem_encode([PemEntry]).
<<"-----BEGIN RSA PUBLIC KEY-----\nMEgC...>>
3> file:write_file("rsa_pub_key.pem", PemBin).
ok
or:
1> PemEntry = public_key:pem_entry_encode('SubjectPublicKeyInfo', RSAPubKey).
{'SubjectPublicKeyInfo', <<48,92...>>, not_encrypted}
2> PemBin = public_key:pem_encode([PemEntry]).
<<"-----BEGIN PUBLIC KEY-----\nMFw...>>
3> file:write_file("pub_key.pem", PemBin).
ok
Suppose you have the following private key and a corresponding public key:
Then you can proceed as follows:
Encrypt with the private key:
RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey),
Msg = public_key:decrypt_public(RsaEncrypted, PublicKey),
Encrypt with the public key:
RsaEncrypted = public_key:encrypt_public(Msg, PublicKey),
Msg = public_key:decrypt_private(RsaEncrypted, PrivateKey),
You normally do only one of the encrypt or decrypt operations, and the peer does the other. This normaly used in legacy applications as a primitive digital signature.
Suppose you have the following private key and a corresponding public key:
Then you can proceed as follows:
Signature = public_key:sign(Msg, sha, PrivateKey),
true = public_key:verify(Msg, sha, Signature, PublicKey),
You normally do only one of the sign or verify operations, and the peer does the other.
It can be appropriate to calculate the message digest before
calling
Digest = crypto:sha(Msg),
Signature = public_key:sign(Digest, none, PrivateKey),
true = public_key:verify(Digest, none, Signature, PublicKey),
When a client checks a server certificate there are a number of checks available like checks that the certificate is not revoked, not forged or not out-of-date.
There are however attacks that are not detected by those checks. Suppose a bad guy has succeded with a DNS infection. Then the client could belive it is connecting to one host but ends up at another but evil one. Though it is evil, it could have a perfectly legal certificate! The certificate has a valid signature, it is not revoked, the certificate chain is not faked and has a trusted root and so on.
To detect that the server is not the intended one, the client must additionaly perform
a hostname verification. This procedure is described in
There is a default hostname matching procedure defined in
Some terminology is needed: the certificate presents hostname(s) on which it is valid. Those are called Presented IDs. The hostname(s) the client belives it connects to are called Reference IDs. The matching rules aims to verify that there is at least one of the Reference IDs that matches one of the Presented IDs. If not, the verification fails.
The IDs contains normal fully qualified domain names like e.g
Internationalized domain names are not supported.
Traditionally the Presented IDs were found in the
$ openssl x509 -text < cert.pem
...
Subject: C=SE, CN=example.com, CN=*.example.com, O=erlang.org
...
The example
In the example the Presented IDs are
In case where the Presented IDs are fetched from the
There may only be one wildcard character and that is in the first label, for example:
There may be label characters before or/and after the wildcard. For example:
In the previous example there is no indication of which protocols are expected. So a client
has no indication of whether it is a web server, an ldap server or maybe a sip server it is
connected to.
There are fields in the certificate that can indicate this. To be more exact, the rfc
introduces the usage of the
$ openssl x509 -text < cert.pem
...
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:kb.example.org, URI:https://www.example.org
...
Here
The next example has both
$ openssl x509 -text < cert.pem
...
Subject: C=SE, CN=example.com, CN=*.example.com, O=erlang.org
...
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:kb.example.org, URI:https://www.example.org
...
The RFC states that if a certificate defines Reference IDs in a
Other applications like ssl/tls or https might have options that are passed
down to the
Suppose our client expects to connect to the web server https://www.example.net. This URI is therefore the Reference IDs of the client. The call will be:
public_key:pkix_verify_hostname(CertFromHost,
[{uri_id, "https://www.example.net"}
]).
The call will return
The caller can use own extraction and matching rules. This is done with the two options
The
To "teach" the function how to extract, you can give a fun which replaces the default
extraction function.
The
...
Extract = fun({uri_id, "myspecial://"++HostName}) -> HostName;
(_Else) -> default
end,
...
public_key:pkix_verify_hostname(CertFromHost, RefIDs,
[{fqdn_fun, Extract}])
...
The default matching handles dns_id and uri_id. In an uri_id the value is tested for
equality with a value from the
The
...
Match = fun({uri_id,"myspecial://"++A},
{uniformResourceIdentifier,"myspecial://"++B}) ->
my_match(A,B);
(_RefID, _PresentedID) ->
default
end,
...
public_key:pkix_verify_hostname(CertFromHost, RefIDs,
[{match_fun, Match}]),
...
In case of a match operation between a ReferenceID and a CN value from the
The default matching transformes the ascii values in strings to lowercase before comparing.
The
The
"The act of establishing a cached name association between the application service's certificate and one of the client's reference identifiers, despite the fact that none of the presented identifiers matches the given reference identifier. ..."
The purpose is to have a mechanism for a human to accept an otherwise faulty Certificate. In for example a web browser, you could get a question like
Warning: you wanted to visit the site www.example.com, but the certificate is for shop.example.com. Accept anyway (yes/no)?"
This could be accomplished with the option
-include_lib("public_key/include/public_key.hrl"). % Record def
...
Fail = fun(#'OTPCertificate'{}=C) ->
case in_my_cache(C) orelse my_accept(C) of
true ->
enter_my_cache(C),
true;
false ->
false
end,
...
public_key:pkix_verify_hostname(CertFromHost, RefIDs,
[{fail_callback, Fail}]),
...
SSH typically uses PEM files for private keys but has its
own file format for storing public keys. The
RFC 4716 SSH files looks confusingly like PEM files, but there are some differences:
1> {ok, SshBin} = file:read_file("ssh2_rsa_pub").
{ok, <<"---- BEGIN SSH2 PUBLIC KEY ----\nAAAA"...>>}
This is equivalent to calling
2> public_key:ssh_decode(SshBin, public_key).
[{#'RSAPublicKey'{modulus = 794430685...91663,
publicExponent = 35}, []}]
OpenSSH public-key format looks as follows:
1> {ok, SshBin} = file:read_file("openssh_dsa_pub").
{ok,<<"ssh-dss AAAAB3Nza"...>>}
This is equivalent to calling
2> public_key:ssh_decode(SshBin, public_key).
[{{15642692...694280725,
#'Dss-Parms'{p = 17291273936...696123221,
q = 1255626590179665817295475654204371833735706001853,
g = 10454211196...480338645}},
[{comment,"dhopson@VMUbuntu-DSH"}]}]
Known hosts - OpenSSH format looks as follows:
1> {ok, SshBin} = file:read_file("known_hosts").
{ok,<<"hostname.domain.com,192.168.0.1 ssh-rsa AAAAB...>>}
Returns a list of public keys and their related attributes. Each pair of key and attribute corresponds to one entry in the known hosts file:
2> public_key:ssh_decode(SshBin, known_hosts).
[{#'RSAPublicKey'{modulus = 1498979460408...72721699,
publicExponent = 35},
[{hostnames,["hostname.domain.com","192.168.0.1"]}]},
{#'RSAPublicKey'{modulus = 14989794604088...2721699,
publicExponent = 35},
[{comment,"foo@bar.com"},
{hostnames,["|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA="]}]}]
Authorized keys - OpenSSH format looks as follows:
1> {ok, SshBin} = file:read_file("auth_keys").
{ok, <<"command=\"dump /home\",no-pty,no-port-forwarding ssh-rsa AAA...>>}
Returns a list of public keys and their related attributes. Each pair of key and attribute corresponds to one entry in the authorized key file:
2> public_key:ssh_decode(SshBin, auth_keys).
[{#'RSAPublicKey'{modulus = 794430685...691663,
publicExponent = 35},
[{comment,"dhopson@VMUbuntu-DSH"},
{options,["command=\"dump/home\"","no-pty",
"no-port-forwarding"]}]},
{{1564269258491...607694280725,
#'Dss-Parms'{p = 17291273936185...763696123221,
q = 1255626590179665817295475654204371833735706001853,
g = 10454211195705...60511039590076780999046480338645}},
[{comment,"dhopson@VMUbuntu-DSH"}]}]
If you got a public key
N> SshBin = public_key:ssh_encode([{PubKey, Attributes}], openssh_public_key),
<<"ssh-rsa "...>>
N+1> file:write_file("id_rsa.pub", SshBin).
ok