--- /dev/null
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ Negotiation of TLS connections.
+ Part of Scryer Prolog.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+:- module(tls, [tls_client_context/2, % -Context, +Options
+ tls_client_negotiate/3, % +Context, +Stream0, -Stream
+ tls_server_context/2, % -Context, +Options
+ tls_server_negotiate/3 % +Context, +Stream0, -Stream
+ ]).
+
+:- use_module(library(lists)).
+:- use_module(library(error)).
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ TLS Clients
+ ===========
+
+ Use tls_client_context/2 to create a TLS context, for example with:
+
+ tls_client_context(Context, [hostname("metalevel.at")])
+
+ Using the context and an existing stream S0 (for example, the
+ result of socket_client_open/3), a TLS stream S can be negotiated
+ with:
+
+ tls_client_negotiate(Context, S0, S)
+
+ S will be an encrypted and authenticated stream with the server.
+
+ The advantage of separating the creation of the client context from
+ negotiating a connection is that the context can be created only once,
+ and quickly reused if needed. This is currently not implemented: In
+ the present implementation, a new internal "Connector" is created for
+ every connection, using the specified hostname.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+tls_client_context(tls_context(Host), Options) :-
+ must_be(list, Options),
+ ( member(hostname(Host), Options) ->
+ must_be(chars, Host)
+ ; Host = ""
+ ).
+
+tls_client_negotiate(tls_context(Host), S0, S) :-
+ '$tls_client_connect'(Host, S0, S).
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ TLS Servers
+ ===========
+
+ Use tls_server_context/2 to create a TLS context, for example with:
+
+ tls_server_context(Context, [pkcs12(Chars)])
+
+ where Chars is a list of characters with the contents of a
+ DER-formatted PKCS #12 archive. The option password(Ps) can be used
+ to specify the password Ps (also a string) for decrypting the key.
+ On some versions of OSX, and potentially also on other platforms,
+ empty passwords are not supported.
+
+ The archive should contain a leaf certificate and its private key,
+ as well any intermediate certificates that should be sent to
+ clients to allow them to build a chain to a trusted root. The chain
+ certificates should be in order from the leaf certificate towards
+ the root.
+
+ PKCS #12 archives typically have the file extension .p12 or .pfx,
+ and can be created with the OpenSSL pkcs12 tool:
+
+ $ openssl pkcs12 -export -out identity.pfx \
+ -inkey key.pem -in cert.pem -certfile chain_certs.pem
+
+
+ You can use phrase_from_file/3 from library(pio) and seq//1 from
+ library(dcgs) to read the contents of "identity.pfx" into a string:
+
+ phrase_from_file(seq(Chars), "identity.pfx", [type(binary)])
+
+ The obtained context should be treated as an opaque Prolog term.
+
+ Using the context and an existing stream S0 (for example, the
+ result of socket_server_accept/4), a TLS stream S can be negotiated
+ by a Prolog-based server with:
+
+ tls_server_negotiate(Context, S0, S)
+
+ S will be an encrypted and authenticated stream with the client.
+
+ The advantage of separating the creation of the server context from
+ negotiating a connection is that the context can be created only
+ once, and quickly cloned for every incoming connection. This is
+ currently not implemented: In the present implementation, a new context
+ is created for every connection, using the specified parameters.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+tls_server_context(tls_context(Cert,Password), Options) :-
+ ( member(pcks12(Cert), Options) ->
+ must_be(chars, Cert)
+ ; domain_error(contains_pcks12, Options, tls_server_context/2)
+ ),
+ ( member(password(Password), Options) ->
+ must_be(chars, Password)
+ ; Password = ""
+ ).
+
+tls_server_negotiate(tls_context(Cert,Password), S0, S) :-
+ '$tls_accept_client'(Cert, Password, S0, S).
+