apiac_auth_mtls v1.0.0 APIacAuthMTLS View Source
An APIac.Authenticator
plug implementing section 2 of
OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
(RFC8705)
Using this scheme, authentication is performed thanks to 2 elements:
- TLS client certificate authentication
- the
client_id
parameter of theapplication/x-www-form-urlencoded
body
TLS client certificate authentication may be performed thanks to two methods:
authentication with a certificate issued by a Certificate Authority (CA) which is called PKI Mutual-TLS Method. In this case, one of the following certificate attribute is checked against this attribute registered for the
client_id
:- Distinguished name
- SAN DNS
- SAN URI
- SAN IP address
- SAN email
- authentication with a self-signed, self-issued certificate which is called
Self-Signed Certificate Mutual-TLS Method.
In this case, the certificate is checked against the subject public key info
of the registered certificates of the
client_id
Plug options
:allowed_methods
: one of:pki
,:selfsigned
or:both
. No default value, mandatory option:pki_callback
: a(String.t -> String.t | {tls_client_auth_subject_value(), String.t()} | nil)
function that takes theclient_id
as a parameter and returns its DN as aString.t()
or{tls_client_auth_subject_value(), String.t()}
ornil
if no DN is registered for that client. When notls_client_auth_subject_value/0
is specified, defaults to:tls_client_auth_subject_dn
:selfsigned_callback
: a(String.t -> binary() | [binary()] | nil)
function that takes theclient_id
as a parameter and returns the certificate or the list of the certificate forthe client_id
, ornil
if no certificate is registered for that client. Certificates can be returned in DER-encoded format, or native OTP certificate structure:cert_data_origin
: origin of the peer cert data. Can be set to::native
: the peer certificate data is retrieved from the connection. Only works when this plug is used at the TLS termination endpoint. This is the default value{:header_param, "Header-Name"}
: the peer certificate data, and more specifically the parameter upon which the decision is to be made, is retrieved from an HTTP header. When using this feature, make sure that this header is filtered by a n upstream system (reverse-proxy...) so that malicious users cannot inject the value themselves. For instance, the configuration could be set to:{:header_param, "SSL_CLIENT_DN"}
. If there are several values for the parameter (for instance severaldNSName
), they must be sent in separate headers. Not compatible with self-signed certiticate authentication:header_cert
: the whole certificate is forwarded in the"Client-Cert"
HTTP header as a Base64 encoded value of the certificate's DER serialization, in conformance with Client-Cert HTTP Header: Conveying Client Certificate Information from TLS Terminating Reverse Proxies to Origin Server Applications (draft-bdc-something-something-certificate-01){:header_cert, "Header-Name"}
: the whole certificate is forwarded in the "Header-Name" HTTP header as a Base64 encoded value of the certificate's DER serialization{:header_cert_pem, "Header-Name"}
: the whole certificate is forwarded in the "Header-Name" as a PEM-encoded string and retrieved by this plug
:set_error_response
: function called when authentication failed. Defaults toAPIacAuthMTLS.send_error_response/3
:error_response_verbosity
: one of:debug
,:normal
or:minimal
. Defaults to:normal
Example
plug APIacAuthMTLS, allowed_methods: :both,
selfsigned_callback: &selfsigned_certs/1,
pki_callback: &get_dn/1
# further
defp selfsigned_certs(client_id) do
:ets.lookup_element(:clients, :client_id, 5)
end
defp get_dn("client-1") do
"/C=US/ST=ARI/L=Chicago/O=Agora/CN=API access certificate"
end
defp get_dn(_), do: nil
Configuring TLS for APIacAuthMTLS
authentication
Plugs can authenticate requests on elements contained in the HTTP request.
Mutual TLS authentication, however, occurs on the TLS layer and the authentication
context is only then passed to the plug (peer_data
).
Usually, when using TLS, only the server is authenticated by the client. But client authentication by the server can also be activated on an TLS-enabled server: in this case, both the server and the clients authenticate to each other. Client authentication can either be optional or mandatory.
When a TLS-enabled server authenticates a client, it checks the client's certificate against its list of known certificate authorities (CA). CAs are trusted root certificates. The list of CAs can be changed through configuration.
Note that by default, the Erlang TLS stack does not accept self-signed certificate.
All TLS options are documented in the Erlang SSL module documentation.
Enabling TLS client authentication
This table summarizes which options are to be activated on the server:
Use-case | TLS options |
---|---|
No client authentication (default) | (no specific option to set) |
Optional client authentication | -verify: :verify_peer |
Mandatory client authentication | -verify: :verify_peer - fail_if_no_peer_cert: true |
Example with plug_cowboy
To enable optional TLS client authentication:
Plug.Cowboy.https(MyPlug, [],
port: 8443,
keyfile: "priv/ssl/key.pem",
certfile: "priv/ssl/cer.pem",
verify: :verify_peer)
To enable mandatory TLS client authentication:
Plug.Cowboy.https(MyPlug, [],
port: 8443,
keyfile: "priv/ssl/key.pem",
certfile: "priv/ssl/cer.pem",
verify: :verify_peer,
fail_if_no_peer_cert: true)
Allowing TLS connection of clients with self-signed certificates
By default, Erlang's TLS stack rejects self-signed client certificates. To allow it,
use the verify_fun
TLS parameter with the following function:
defp verify_fun_selfsigned_cert(_, {:bad_cert, :selfsigned_peer}, user_state),
do: {:valid, user_state}
defp verify_fun_selfsigned_cert(_, {:bad_cert, _} = reason, _),
do: {:fail, reason}
defp verify_fun_selfsigned_cert(_, {:extension, _}, user_state),
do: {:unkown, user_state}
defp verify_fun_selfsigned_cert(_, :valid, user_state),
do: {:valid, user_state}
defp verify_fun_selfsigned_cert(_, :valid_peer, user_state),
do: {:valid, user_state}
Example with plug_cowboy
:
Plug.Cowboy.https(MyPlug, [],
port: 8443,
keyfile: "priv/ssl/key.pem",
certfile: "priv/ssl/cer.pem",
verify: :verify_peer,
verify_fun: {&verify_fun_selfsigned_cert/3, []})
Security considerations
In addition to the security considerations listed in the RFC, consider that:
- Before TLS1.3, client authentication may leak information (further information)
- Any CA can signe for any DN (as for any other certificate attribute). Though this is a well-known security limitation of the X509 infrastructure, issuing certificate with rogue DNs may be more difficult to detect (because less monitored)
Other considerations
When activating TLS client authentication, be aware that some browser user interfaces may prompt the user, in a unpredictable manner, for certificate selection. You may want to consider starting a TLS-authentication-enabled endpoint on another port (i.e. one port for web browsing, another one for API access).
Link to this section Summary
Functions
APIac.Authenticator
credential extractor callback
Saves failure in a Plug.Conn.t()
's private field and returns the conn
Implementation of the APIac.Authenticator
callback
APIac.Authenticator
credential validator callback
Link to this section Types
tls_client_auth_subject_value()
View Sourcetls_client_auth_subject_value() :: :tls_client_auth_subject_dn | :tls_client_auth_san_dns | :tls_client_auth_san_uri | :tls_client_auth_san_ip | :tls_client_auth_san_email
Link to this section Functions
APIac.Authenticator
credential extractor callback
The returned credentials is a {String.t, binary}
tuple where:
- the first parameter is the
client_id
- the second parameter is the raw DER-encoded certificate
save_authentication_failure_response(conn, error, opts)
View Sourcesave_authentication_failure_response( Plug.Conn.t(), %APIac.Authenticator.Unauthorized{ __exception__: term(), authenticator: term(), reason: term() }, any() ) :: Plug.Conn.t()
Saves failure in a Plug.Conn.t()
's private field and returns the conn
See the APIac.AuthFailureResponseData
module for more information.
Implementation of the APIac.Authenticator
callback
Verbosity
The following elements in the HTTP response are set depending on the value
of the :error_response_verbosity
option:
Error response verbosity | HTTP Status | Headers | Body |
---|---|---|---|
:debug | Unauthorized (401) | APIac.Authenticator.Unauthorized exception's message |
|
:normal | Unauthorized (401) | ||
:minimal | Unauthorized (401) |
APIac.Authenticator
credential validator callback
The credentials parameter must be an %X509.Certificate{}
struct