Skip to content
Collection of ssl verification functions for Erlang
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
test
.gitignore
.travis.yml
LICENSE
Makefile
README.md
elvis
elvis.config
mix.exs
rebar.config
rebar.lock
rebar3

README.md

SSL verification for Erlang

Hex.pm Hex.pm Build Status

Note: all examples use {reuse_sessions, false} to make sure session won't be reused and ssl:connect will give you different result when changing fingerprints/hostnames, etc. Perhaps this should be removed in production.

Resources

Certificate fingerprint validation / pinning

OWASP link

1> ssl:connect("github.com", 443, [{verify_fun,
				 {fun ssl_verify_fingerprint:verify_fun/3,
				  [{check_fingerprint, {sha, "D79F076110B39293E349AC89845B0380C19E2F8B"} }]}},
				{verify, verify_none},
				{reuse_sessions, false}]).   
{ok,{sslsocket,{gen_tcp,#Port<0.1499>,tls_connection,
                        undefined},
               <0.53.0>}}

2> ssl:connect("google.com", 443, [{verify_fun,
				 {fun ssl_verify_fingerprint:verify_fun/3,
				  [{check_fingerprint, {sha, "D79F076110B39293E349AC89845B0380C19E2F8B"} }]}},
				{verify, verify_none},
				{reuse_sessions, false}]).
=ERROR REPORT==== 10-Mar-2016::16:13:54 ===
SSL: certify: ssl_handshake.erl:1492:Fatal error: handshake failure
{error,{tls_alert,"handshake failure"}}

Public Key validation / pinning

OWASP link

We can pin public key using its hex or base64 representation as well as fingerprint

Using github.com as example lets extract public key

openssl x509 -inform DER  -pubkey -noout -in /tmp/github.com.der

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA54hc8pZclxgcupjiA/F/
OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPwbAvUaLP//T79Jc+1WXMpxMiz9PK6
yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS/YTpQSelTuq+Fuwts5v6aAweNyMc
YD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or/4jSyZwzxu9kdmBlBzeHMvsqdH8S
X9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd02T0ux9Yzd5ezkMxukqEAQ7AKIIi
jvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBPT+ZG1uv0QQP9LuY/joO+FKDOTler
4wIDAQAB
-----END PUBLIC KEY-----

Openssl prints public key encoded using base64 format. It can be used like that:

ssl:connect("github.com", 443, [{verify_fun,
                                {fun ssl_verify_pk:verify_fun/3,
                                 [{check_pk, {base64,
                                              "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA54hc8pZclxgcupjiA/F/"++
                                              "OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPwbAvUaLP//T79Jc+1WXMpxMiz9PK6"++
                                              "yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS/YTpQSelTuq+Fuwts5v6aAweNyMc"++
                                              "YD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or/4jSyZwzxu9kdmBlBzeHMvsqdH8S"++
                                              "X9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd02T0ux9Yzd5ezkMxukqEAQ7AKIIi"++
                                              "jvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBPT+ZG1uv0QQP9LuY/joO+FKDOTler"++
                                              "4wIDAQAB" } }]}},
                                {verify, verify_none},
                                {reuse_sessions, false}]).      
{ok,{sslsocket,{gen_tcp,#Port<0.2167>,tls_connection,
                        undefined},
               <0.60.0>}}

If you don't want to expose public ceritificate or just want to save space, you can validate public key fingerprint. Ubuntu der viewer

ssl:connect("github.com", 443, [{verify_fun,
                                {fun ssl_verify_pk:verify_fun/3,
                                 [{check_pk, {sha,
                                              "D4EE9D2A6712B3614C272D158B04FCC8CA08A0B6" } }]}},
                                {verify, verify_none},
                                {reuse_sessions, false}]).
{ok,{sslsocket,{gen_tcp,#Port<0.2744>,tls_connection,
	                      undefined},
               <0.73.0>}}

As you can see I just copy-pasted Key SHA1 Fingerprint value and removed spaces. It's that easy!

Hostname validation

Excerpt from RFC (http://tools.ietf.org/html/rfc6125)


6.4.3.  Checking of Wildcard Certificates

   1.  The client SHOULD NOT attempt to match a presented identifier in
   which the wildcard character comprises a label other than the
   left-most label (e.g., do not match bar.*.example.net).

   2.  If the wildcard character is the only character of the left-most
   label in the presented identifier, the client SHOULD NOT compare
   against anything but the left-most label of the reference
   identifier (e.g., *.example.com would match foo.example.com but
   not bar.foo.example.com or example.com).

   3.  The client MAY match a presented identifier in which the wildcard
   character is not the only character of the label (e.g.,
   baz*.example.net and *baz.example.net and b*z.example.net would
   be taken to match baz1.example.net and foobaz.example.net and
   buzz.example.net, respectively).  However, the client SHOULD NOT
   attempt to match a presented identifier where the wildcard
   character is embedded within an A-label or U-label [IDNA-DEFS] of
   an internationalized domain name [IDNA-PROTO].

6.4.4.  Checking of Common Names

   As noted, a client MUST NOT seek a match for a reference identifier
   of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
   URI-ID, or any application-specific identifier types supported by the
   client.

   Therefore, if and only if the presented identifiers do not include a
   DNS-ID, SRV-ID, URI-ID, or any application-specific identifier types
   supported by the client, then the client MAY as a last resort check
   for a string whose form matches that of a fully qualified DNS domain
   name in a Common Name field of the subject field (i.e., a CN-ID).  If
   the client chooses to compare a reference identifier of type CN-ID
   against that string, it MUST follow the comparison rules for the DNS
   domain name portion of an identifier of type DNS-ID, SRV-ID, or
   URI-ID, as described under Section 6.4.1, Section 6.4.2, and
   Section 6.4.3.

####Usage###

  • With SSL lib or HTTP client you can use provided verify_fun/3, do not forget to add check_hostname key to user state:
CACertFile = "..../my-ca.pem".
ssl:connect("tv.eurosport.com", 443, [{verify_fun,
                                       {fun ssl_verify_hostname:verify_fun/3,
                                        [{check_hostname, "tv.eurosport.com"}]}},
                                      {cacertfile, CACertFile },
                                      {server_name_indication, "tv.eurosport.com"},
                                      {reuse_sessions, false},
                                      {verify, verify_peer},
                                      {depth, 99}]).

=ERROR REPORT==== 9-Oct-2014::03:34:41 ===
SSL: certify: ..../ssl_handshake.erl:1403:Fatal error: handshake failure
{error,{tls_alert,"handshake failure"}}

ssl:connect("tv.eurosport.com", 443, [{verify_fun,
                                       {fun ssl_verify_hostname:verify_fun/3, []}},
                                      {cacertfile, CACertFile },
                                      {server_name_indication, "tv.eurosport.com"},
                                      {reuse_sessions, false},
                                      {verify, verify_peer},
                                      {depth, 99}]).

{ok,{sslsocket,{gen_tcp,#Port<0.1565>,tls_connection,
                        undefined},
                        <0.53.0>}}

Unfortunately as you can see OTP SSL error reporting not so informative (in fact it ignores everything user-provided verify_fun returns as failure reason (8 October 2014))

path_validation_alert(Reason) ->
    ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
  • With custom verify_fun: Call verify_cert_hostname/2 with Certificate and Hostname.
You can’t perform that action at this time.