racket/collects/openssl/openssl.scrbl
2012-11-21 12:39:18 -05:00

603 lines
23 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/bnf
(for-label openssl
scheme
openssl/sha1))
@title{OpenSSL: Secure Communication}
@defmodule[openssl]
The @racketmodname[openssl] library provides glue for the OpenSSL
library with the Racket port system. It provides functions nearly
identically to the standard TCP subsystem in Racket, plus a
generic @racket[ports->ssl-ports] interface.
To use this library, you will need OpenSSL installed on your machine,
but
@itemize[
@item{for Windows, the Racket distribution for Windows includes
the necessary DLLs.}
@item{for Mac OS X, version 10.2 and later provides the necessary
OpenSSL libraries.}
@item{for Unix, @filepath{libssl.so} and @filepath{libcrypto.so} are
likely to be installed on your machine, already.}
]
@defthing[ssl-available? boolean?]{
A boolean value which says whether the system openssl library was
successfully loaded. Calling @racket[ssl-connect], @|etc| when this
value is @racket[#f] (library not loaded) will raise an exception.}
@defthing[ssl-load-fail-reason (or/c false/c string?)]{
Either @racket[#f] (when @racket[ssl-available?] is @racket[#t]) or an
error string (when @racket[ssl-available?] is @racket[#f]).}
@; ----------------------------------------------------------------------
@section{Using SSL Securely}
SSL and TLS are client-server cryptographic protocols that enable
secure communication with remote hosts (called ``peers''). But SSL
must be properly configured in order to be secure.
The security of client programs using SSL generally depends
on the authentication of the server credentials, which requires proper
initialization of this library's client contexts. To use SSL securely,
a client program must at a minimum take the following steps:
@itemlist[
@item{create an SSL client context using @racket[ssl-make-client-context]}
@item{tell the context what certificate authorities to trust using
@racket[ssl-load-verify-root-certificates!]}
@item{turn on certificate verification using @racket[ssl-set-verify!]
(or check each connection individually using @racket[ssl-peer-verified?])}
@item{turn on hostname verification using
@racket[ssl-set-verify-hostname!] (or check each connection
individually using @racket[ssl-peer-check-hostname])}
]
@; ----------------------------------------------------------------------
@section{TCP-like Client Procedures}
@defproc[(ssl-connect (hostname string?)
(port-no (integer-in 1 65535))
(client-protocol
(or/c ssl-client-context? symbol?) 'sslv2-or-v3))
(values input-port? output-port?)]{
Connect to the host given by @racket[hostname], on the port given by
@racket[port-no]. This connection will be encrypted using SSL. The
return values are as for @racket[tcp-connect]: an input port and an
output port.
The optional @racket[client-protocol] argument determines which
encryption protocol is used, whether the server's certificate is
checked, etc. The argument can be either a client context created by
@racket[ssl-make-client-context], or one of the following symbols:
@racket['sslv2-or-v3] (the default), @racket['sslv2], @racket['sslv3],
or @racket['tls]; see @racket[ssl-make-client-context] for further
details (including the meanings of the protocol symbols).
Closing the resulting output port does not send a shutdown message to
the server. See also @racket[ports->ssl-ports].
If hostname verification is enabled (see
@racket[ssl-set-verify-hostname!]), the peer's certificate is checked
against @racket[hostname].
@;{
See `enforce-retry?' in "mzssl.rkt", currently set to #f so that this
paragraph does not apply:
Beware that the SSL protocol allows reading or writing in only one
direction at a time. If you request data from the input port, then
data cannot be written to the output port (i.e., attempting to write
will block) until the other end of the connection responds to the
read. Even merely checking for input data --- using
@racket[byte-ready?], for example --- commits the connection to
reading, and the other end must respond with a (possibly zero-length)
answer. Protocols that work with SSL, such as IMAP, have a
well-defined communication pattern, where theres no question of
whether the other end is supposed to be sending or reading data.
}
}
@defproc[(ssl-connect/enable-break
(hostname string?)
(port-no (integer-in 1 65535))
(client-protocol
(or/c ssl-client-context? symbol?) 'sslv2-or-v3))
(values input-port? output-port?)]{
Like @racket[ssl-connect], but breaking is enabled while trying to
connect.}
@defproc[(ssl-make-client-context (protocol symbol? 'sslv2-or-v3))
ssl-client-context?]{
Creates a context to be supplied to @racket[ssl-connect]. The context
identifies a communication protocol (as selected by
@racket[protocol]), and also holds certificate information (i.e., the
client's identity, its trusted certificate authorities, etc.). See the
section @secref["cert-procs"] below for more information on
certificates.
The @racket[protocol] must be one of the following:
@itemize[
@item{@racket['sslv2-or-v3] : SSL protocol versions 2 or 3, as
appropriate (this is the default)}
@item{@racket['sslv2] : SSL protocol version 2}
@item{@racket['sslv3] : SSL protocol version 3}
@item{@racket['tls] : the TLS protocol version 1}
]
Note that SSL protocol version 2 is deprecated on some platforms and may not be
present in your system libraries. The use of SSLv2 may also compromise security;
thus, using SSLv3 is recommended.
By default, the context returned by @racket[ssl-make-client-context] does not
request verification of a server's certificate. Use @racket[ssl-set-verify!]
to enable such verification.}
@defproc[(ssl-secure-client-context)
ssl-client-context?]{
Returns a client context (using @racket['tls]) that verifies
certificates using the root certificates located in
@racket[(ssl-default-root-certificate-locations)], verifies hostnames,
and avoids using weak ciphers. The context is sealed to prevent
further modification, and the context is cached, so different calls to
@racket[ssl-secure-client-context] return the same context unless
@racket[(ssl-default-root-certificate-locations)] has changed.
}
@defproc[(ssl-client-context? (v any/c)) boolean?]{
Returns @racket[#t] if @racket[v] is a value produced by
@racket[ssl-make-client-context], @racket[#f] otherwise.}
@; ----------------------------------------------------------------------
@section{TCP-like Server Procedures}
@defproc[(ssl-listen
(port-no (integer-in 1 65535))
[queue-k exact-nonnegative-integer?]
[reuse? any/c #f]
[hostname-or-#f (or/c string? false/c) #f]
[server-protocol
(or/c ssl-server-context? symbol?) 'sslv2-or-v3])
ssl-listener?]{
Like @racket[tcp-listen], but the result is an SSL listener. The extra optional
@racket[server-protocol] is as for @racket[ssl-connect], except that a
context must be a server context instead of a client context.
Call @racket[ssl-load-certificate-chain!] and
@racket[ssl-load-private-key!] to avoid a @emph{no shared cipher}
error on accepting connections. The file @filepath{test.pem} in the
@filepath{openssl} collection is a suitable argument for both calls
when testing. Since @filepath{test.pem} is public, however, such a
test configuration obviously provides no security.
An SSL listener is a synchronizable value (see @racket[sync]). It is
ready---with itself as its value---when the underlying TCP listener is
ready. At that point, however, accepting a connection with
@racket[ssl-accept] may not complete immediately, because
further communication is needed to establish the connection.}
@deftogether[(
@defproc[(ssl-close (listener ssl-listener?)) void?]
@defproc[(ssl-listener? (v any/c)) boolean?])]{
Analogous to @racket[tcp-close] and @racket[tcp-listener?].}
@deftogether[(
@defproc[(ssl-accept (listener ssl-listener?))
(values input-port? output-port?)]
@defproc[(ssl-accept/enable-break (listener ssl-listener?))
(values input-port? output-port?)])]{
Analogous to @racket[tcp-accept].
Closing the resulting output port does not send a shutdown message to
the client. See also @racket[ports->ssl-ports].
See also @racket[ssl-connect] about the limitations of reading and
writing to an SSL connection (i.e., one direction at a time).
The @racket[ssl-accept/enable-break] procedure is analogous to
@racket[tcp-accept/enable-break].}
@defproc[(ssl-abandon-port [in (and/c ssl-port? output-port?)]) void?]{
Analogous to @racket[tcp-abandon-port].}
@defproc[(ssl-addresses [p (or/c ssl-port? ssl-listener?)][port-numbers? any/c #f]) void?]{
Analogous to @racket[tcp-addresses].}
@defproc[(ssl-port? [v any/c]) boolean?]{
Returns @racket[#t] of @racket[v] is an SSL port produced by
@racket[ssl-connect], @racket[ssl-connect/enable-break],
@racket[ssl-accept], @racket[ssl-accept/enable-break], or
@racket[ports->ssl-ports].}
@defproc[(ssl-make-server-context (protocol symbol?))
ssl-server-context?]{
Like @racket[ssl-make-client-context], but creates a server context.}
@defproc[(ssl-server-context? (v any/c)) boolean?]{
Returns @racket[#t] if @racket[v] is a value produced by
@racket[ssl-make-server-context], @racket[#f] otherwise.}
@; ----------------------------------------------------------------------
@section{SSL-wrapper Interface}
@defproc[(ports->ssl-ports
(input-port input-port?)
(output-port output-port?)
[#:mode mode symbol? 'accept]
[#:context context (or/c ssl-client-context? ssl-server-context?)
((if (eq? mode 'accept)
ssl-make-server-context
ssl-make-client-context)
protocol)]
[#:encrypt protocol symbol? 'sslv2-or-v3]
[#:close-original? close-original? boolean? #f]
[#:shutdown-on-close? shutdown-on-close? boolean? #f]
[#:error/ssl error procedure? error]
[#:hostname hostname (or/c string? #f) #f])
(values input-port? output-port?)]{
Returns two values---an input port and an output port---that
implement the SSL protocol over the given input and output port. (The
given ports should be connected to another process that runs the SSL
protocol.)
The @racket[mode] argument can be @racket['connect] or
@racket['accept]. The mode determines how the SSL protocol is
initialized over the ports, either as a client or as a server. As with
@racket[ssl-listen], in @racket['accept] mode, supply a
@racket[context] that has been initialized with
@racket[ssl-load-certificate-chain!] and
@racket[ssl-load-private-key!] to avoid a @emph{no shared cipher}
error.
The @racket[context] argument should be a client context for
@racket['connect] mode or a server context for @racket['accept]
mode. If it is not supplied, a context is created using the protocol
specified by a @racket[protocol] argument.
If the @racket[protocol] argument is not supplied, it defaults to
@racket['sslv2-or-v3]. See @racket[ssl-make-client-context] for
further details (including all options and the meanings of the
protocol symbols). This argument is ignored if a @racket[context]
argument is supplied.
If @racket[close-original?] is true, then when both SSL ports are
closed, the given input and output ports are automatically closed.
If @racket[shutdown-on-close?] is true, then when the output SSL port
is closed, it sends a shutdown message to the other end of the SSL
connection. When shutdown is enabled, closing the
output port can fail if the given output port becomes unwritable
(e.g., because the other end of the given port has been closed by
another process).
The @racket[error] argument is an error procedure to use for raising
communication errors. The default is @racket[error], which raises
@racket[exn:fail]; in contrast, @racket[ssl-accept] and
@racket[ssl-connect] use an error function that raises
@racket[exn:fail:network].
See also @racket[ssl-connect] about the limitations of reading and
writing to an SSL connection (i.e., one direction at a time).
If hostname verification is enabled (see
@racket[ssl-set-verify-hostname!]), the peer's certificate is checked
against @racket[hostname].
}
@; ----------------------------------------------------------------------
@section[#:tag "cert-procs"]{Context Procedures}
@defproc[(ssl-load-certificate-chain!
(context-or-listener (or/c ssl-client-context? ssl-server-context?
ssl-listener?))
(pathname path-string?))
void?]{
Loads a PEM-format certification chain file for connections to made
with the given context (created by @racket[ssl-make-client-context] or
@racket[ssl-make-server-context]) or listener (created by
@racket[ssl-listen]).
This chain is used to identify the client or server when it connects
or accepts connections. Loading a chain overwrites the old chain. Also
call @racket[ssl-load-private-key!] to load the certificate's
corresponding key.
You can use the file @filepath{test.pem} of the @filepath{openssl}
collection for testing purposes. Since @filepath{test.pem} is public,
such a test configuration obviously provides no security.}
@defproc[(ssl-load-private-key!
(context-or-listener (or/c ssl-client-context? ssl-server-context?
ssl-listener?))
(pathname path-string?)
[rsa? boolean? #t]
[asn1? boolean? #f])
void?]{
Loads the first private key from @racket[pathname] for the given
context or listener. The key goes with the certificate that identifies
the client or server.
If @racket[rsa?] is @racket[#t] (the default), the first RSA key is
read (i.e., non-RSA keys are skipped). If @racket[asn1?] is
@racket[#t], the file is parsed as ASN1 format instead of PEM.
You can use the file @filepath{test.pem} of the @filepath{openssl}
collection for testing purposes. Since @filepath{test.pem} is public,
such a test configuration obviously provides no security.}
@defproc[(ssl-load-verify-root-certificates!
(context-or-listener (or/c ssl-client-context? ssl-server-context?
ssl-listener?))
(pathname path-string?))
void?]{
Loads a PEM-format file containing trusted certificates that are used
to verify the certificates of a connection peer. Call this procedure
multiple times to load multiple sets of trusted certificates.
If @racket[pathname] is a file, its contents are immediately
loaded. If @racket[pathname] is a directory, it should contain hashed
certificates names (see the @tt{openssl c_rehash} utility); the
directory is searched only when a certificate needs verification.
You can use the file @filepath{test.pem} of the @filepath{openssl}
collection for testing purposes. Since @filepath{test.pem} is public,
such a test configuration obviously provides no security.
}
@defparam[ssl-default-root-certificate-locations paths
(listof path-string?)]{
Holds a list of paths of root certificate authority certificates, used
by @racket[ssl-load-default-verify-root-certificates!]. The list of
paths may refer to both files and directories, and nonexistent paths
are allowed.
The initial values are determined by the @tt{SSL_CERT_FILE} and
@tt{SSL_CERT_DIR} environment variables, if the variables are set, or
the system-wide default locations otherwise.
}
@defproc[(ssl-load-default-verify-root-certificates!
[context (or/c ssl-client-context? ssl-server-context?)])
void?]{
Loads the default root certificates, as determined by the
@racket[ssl-default-root-certificate-locations] parameter, into
@racket[context]. Nonexistent paths are skipped.
}
@defproc[(ssl-load-suggested-certificate-authorities!
(context-or-listener (or/c ssl-client-context? ssl-server-context?
ssl-listener?))
(pathname path-string?))
void?]{
Loads a PEM-format file containing certificates that are used by a
server. The certificate list is sent to a client when the server
requests a certificate as an indication of which certificates the
server trusts.
Loading the suggested certificates does not imply trust, however; any
certificate presented by the client will be checked using the trusted
roots loaded by @racket[ssl-load-verify-root-certificates!].
You can use the file @filepath{test.pem} of the @filepath{openssl}
collection for testing purposes where the peer identifies itself using
@filepath{test.pem}.}
@defproc[(ssl-set-ciphers! [context (or/c ssl-client-context? ssl-server-context?)]
[cipher-spec string?])
void?]{
Specifies the cipher suites that can be used in connections created
with @racket[context]. The meaning of @racket[cipher-spec] is the same
as for the
@hyperlink["http://www.openssl.org/docs/apps/ciphers.html"]{@tt{openssl
ciphers} command}.
}
@defproc[(ssl-seal-context! [context (or/c ssl-client-context? ssl-server-context?)])
void?]{
Seals @racket[context], preventing further modifications. After a
context is sealed, passing it to functions such as
@racket[ssl-set-verify!] and
@racket[ssl-load-verify-root-certificates!] results in an error.}
@; ----------------------------------------------------------------------
@section[#:tag "peer-verif"]{Peer Verification}
@defproc[(ssl-set-verify! [clp (or/c ssl-client-context? ssl-server-context?
ssl-listener?
ssl-port?)]
[on? any/c]) void?]{
Requires certificate verification on the peer SSL connection when
@racket[on?] is @racket[#t]. If @racket[clp] is an SSL port, then the
connection is immediately renegotiated, and an exception is raised
immediately if certificate verification fails. If @racket[clp] is a
context or listener, certification verification happens on each
subsequent connection using the context or listener.
Enabling verification also requires, at a minimum, designating trusted
certificate authorities with
@racket[ssl-load-verify-root-certificates!].
Verifying the certificate is not sufficient to prevent attacks by
active adversaries, such as
@hyperlink["http://en.wikipedia.org/wiki/Man-in-the-middle_attack"]{man-in-the-middle
attacks}. See also @racket[ssl-set-verify-hostname!].
}
@defproc[(ssl-try-verify! [clp (or/c ssl-client-context? ssl-server-context?
ssl-listener?
ssl-port?)]
[on? any/c]) void?]{
Like @racket[ssl-set-verify!], but when peer certificate verification fails,
then connection continues to work. Use @racket[ssl-peer-verified?] to determine
whether verification succeeded.}
@defproc[(ssl-peer-verified? [p ssl-port?]) boolean?]{
Returns @racket[#t] if the peer of SSL port @racket[p] has presented a
valid and verified certificate, @racket[#f] otherwise.}
@defproc[(ssl-set-verify-hostname! [ctx (or/c ssl-client-context? ssl-server-context?)]
[on? any/c])
void?]{
Requires hostname verification of SSL peers of connections made using
@racket[ctx] when @racket[on?] is @racket[#t]. When hostname
verification is enabled, the hostname associated with a connection
(see @racket[ssl-connect] or @racket[ports->ssl-ports]) is checked
against the hostnames listed in the peer's certificate. If the peer
certificate does not contain an entry matching the hostname, or if the
peer does not present a certificate, the connection is rejected and an
exception is raised.
Hostname verification does not imply certificate verification. To
verify the certificate itself, also call @racket[ssl-set-verify!].
}
@defproc[(ssl-peer-certificate-hostnames [p ssl-port?])
(listof string?)]{
Returns the list of hostnames for which the certificate of
@racket[p]'s peer is valid according to
@hyperlink["http://www.ietf.org/rfc/rfc2818.txt"]{RFC 2818}. If the
peer has not presented a certificate, @racket['()] is returned.
The result list may contain both hostnames such as
@racket["www.racket-lang.org"] and hostname patterns such as
@racket["*.racket-lang.org"].
}
@defproc[(ssl-peer-check-hostname [p ssl-port?] [hostname string?])
boolean?]{
Returns @racket[#t] if the peer certificate of @racket[p] is valid for
@racket[hostname] according to
@hyperlink["http://www.ietf.org/rfc/rfc2818.txt"]{RFC 2818}.
}
@defproc[(ssl-peer-subject-name [p ssl-port?]) (or/c bytes? #f)]{
If @racket[ssl-peer-verified?] would return @racket[#t] for
@racket[p], the result is a byte string for the subject field of
the certificate presented by the SSL port's peer, otherwise the result
is @racket[#f].
Use @racket[ssl-peer-check-hostname] or
@racket[ssl-peer-certificate-hostnames] instead to check the validity
of an SSL connection.
}
@defproc[(ssl-peer-issuer-name [p ssl-port?]) (or/c bytes? #f)]{
If @racket[ssl-peer-verified?] would return @racket[#t] for
@racket[p], the result is a byte string for the issuer field of
the certificate presented by the SSL port's peer, otherwise the result
is @racket[#f].}
@; ----------------------------------------------------------------------
@section{SHA-1 Hashing}
@defmodule[openssl/sha1]{The @racketmodname[openssl/sha1] library
provides a Racket wrapper for the OpenSSL library's SHA-1 hashing
functions. If the OpenSSL library cannot be opened, this library logs
a warning and falls back to the implementation in
@racketmodname[file/sha1].}
@defproc[(sha1 [in input-port]) string?]{
Returns a 40-character string that represents the SHA-1 hash (in
hexadecimal notation) of the content from @racket[in], consuming all
of the input from @racket[in] until an end-of-file.
The @racket[sha1] function composes @racket[bytes->hex-string] with
@racket[sha1-bytes].}
@defproc[(sha1-bytes [in input-port]) bytes?]{
Returns a 20-byte byte string that represents the SHA-1 hash of the
content from @racket[in], consuming all of the input from @racket[in]
until an end-of-file.}
@defproc[(bytes->hex-string [bstr bytes?]) string?]{
Converts the given byte string to a string representation, where each
byte in @racket[bstr] is converted to its two-digit hexadecimal
representation in the resulting string.}
@; ----------------------------------------------------------------------
@section{Implementation Notes}
For Windows, @racketmodname[openssl] relies on @filepath{libeay32.dll}
and @filepath{ssleay32.dll}, where the DLLs are located in the same
place as @filepath{libmzsch@nonterm{vers}.dll} (where @nonterm{vers}
is either @tt{xxxxxxx} or a mangling of Racket's version
number). The DLLs are distributed as part of Racket.
For Unix variants, @racketmodname[openssl] relies on
@filepath{libcrypto.so} and @filepath{libssl.so}, which must be
installed in a standard library location, or in a directory listed by
@envvar{LD_LIBRARY_PATH}.
For Mac OS X, @racketmodname[openssl] relies on
@filepath{libssl.dylib} and @filepath{libcrypto.dylib}, which are part
of the OS distribution for Mac OS X 10.2 and later.