204 lines
4.9 KiB
Racket
204 lines
4.9 KiB
Racket
#lang scribble/manual
|
||
|
||
@(require scribble/example
|
||
(for-label racket/base
|
||
racket/contract
|
||
openssl))
|
||
|
||
@title{Self-Signed Certificate Utilities}
|
||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||
|
||
|
||
@defmodule[racket-self-signed-cert]
|
||
|
||
This module provides utilities for generating a self-signed X.509 certificate
|
||
together with a corresponding private key.
|
||
|
||
The implementation uses the @racketmodname[openssl] bindings that are
|
||
distributed with Racket. In other words, the module relies on the
|
||
OpenSSL library that ships with Racket and accesses it via Racket’s
|
||
FFI interface.
|
||
|
||
The generated certificate and key are returned in PEM format and can
|
||
be used directly with Racket networking libraries such as
|
||
@racketmodname[openssl] or TLS-enabled servers.
|
||
|
||
@section{OpenSSL Integration}
|
||
|
||
The module dynamically integrates with the OpenSSL library that is
|
||
present in the running Racket installation.
|
||
|
||
During initialization the module performs the following steps:
|
||
|
||
@itemlist[
|
||
@item{
|
||
It detects the major version of the OpenSSL library available through
|
||
Racket’s @racketmodname[openssl] bindings.
|
||
}
|
||
|
||
@item{
|
||
If OpenSSL version 3 is detected, the module raises an error because
|
||
the required FFI bindings currently support only the OpenSSL 1.x API.
|
||
}
|
||
|
||
@item{
|
||
The module determines which native OpenSSL library must be loaded for
|
||
FFI access. This allows the implementation to bind directly to the
|
||
required cryptographic primitives.
|
||
}
|
||
|
||
@item{
|
||
Platform-specific loading of the native OpenSSL library is performed
|
||
at runtime.
|
||
}
|
||
]
|
||
|
||
The implementation has been tested on the following platforms:
|
||
|
||
@itemlist[
|
||
@item{Windows}
|
||
@item{Linux}
|
||
]
|
||
|
||
Other platforms may work provided that a compatible OpenSSL library is
|
||
available through Racket.
|
||
|
||
|
||
@section{Data Structures}
|
||
|
||
@defstruct[self-signed-cert ([private-key string?]
|
||
[certificate string?])]{
|
||
|
||
Represents a generated self-signed certificate together with its
|
||
private key.
|
||
|
||
Both fields contain PEM encoded text.
|
||
|
||
@itemlist[
|
||
@item{@racket[private-key] — the RSA private key in PEM format.}
|
||
@item{@racket[certificate] — the X.509 certificate in PEM format.}
|
||
]
|
||
|
||
Instances of this structure are returned by
|
||
@racket[generate-self-signed-cert].
|
||
}
|
||
|
||
@defproc[(self-signed-cert? [v any/c]) boolean?]{
|
||
|
||
Returns @racket[#t] if @racket[v] is a
|
||
@racket[self-signed-cert] structure.
|
||
}
|
||
|
||
@section{Accessors}
|
||
|
||
@defproc[(private-key [ssc self-signed-cert?]) string?]{
|
||
|
||
Returns the private key stored in @racket[ssc].
|
||
|
||
The value is a PEM encoded RSA private key suitable for use with
|
||
TLS libraries or for writing to disk.
|
||
}
|
||
|
||
@defproc[(certificate [ssc self-signed-cert?]) string?]{
|
||
|
||
Returns the X.509 certificate stored in @racket[ssc].
|
||
|
||
The value is a PEM encoded certificate.
|
||
}
|
||
|
||
@defthing[x509-cert (-> self-signed-cert? string?)]{
|
||
|
||
Alias for @racket[certificate].
|
||
|
||
This name is provided for situations where the API user prefers the
|
||
term “X.509 certificate”.
|
||
}
|
||
|
||
@section{Certificate Generation}
|
||
|
||
@defproc[(generate-self-signed-cert
|
||
[bits integer?]
|
||
[duration-in-days integer?]
|
||
[hosts (or/c is-ip? is-dns? list-of-hosts?)]
|
||
[country string?]
|
||
[company string?])
|
||
self-signed-cert?]{
|
||
|
||
Generates a new self-signed RSA certificate and private key.
|
||
|
||
The implementation uses the OpenSSL functionality provided through
|
||
Racket’s @racketmodname[openssl] library.
|
||
|
||
@subsection{Arguments}
|
||
|
||
@itemlist[
|
||
@item{@racket[bits] — size of the RSA key in bits (for example
|
||
@racket[2048] or @racket[4096]).}
|
||
|
||
@item{@racket[duration-in-days] — number of days for which the
|
||
certificate remains valid.}
|
||
|
||
@item{@racket[hosts] — a host name, IP address, or a list of such
|
||
values. These values are written into the certificate’s
|
||
@italic{Subject Alternative Name} extension.}
|
||
|
||
@item{@racket[country] — value for the certificate subject’s
|
||
@tt{C} (country) attribute.}
|
||
|
||
@item{@racket[company] — value for the certificate subject’s
|
||
@tt{O} (organization) attribute.}
|
||
]
|
||
|
||
The first host in the list is used as the certificate’s
|
||
Common Name (CN).
|
||
|
||
@subsection{Result}
|
||
|
||
Returns a @racket[self-signed-cert] structure containing:
|
||
|
||
@itemlist[
|
||
@item{the private RSA key}
|
||
@item{the corresponding self-signed X.509 certificate}
|
||
]
|
||
|
||
Both values are returned as PEM encoded strings.
|
||
|
||
@subsection{Example}
|
||
|
||
@#reader scribble/comment-reader
|
||
[racketblock
|
||
(define cert
|
||
(generate-self-signed-cert
|
||
2048
|
||
365
|
||
'("localhost" "127.0.0.1")
|
||
"NL"
|
||
"Example Company"))
|
||
|
||
(private-key cert)
|
||
(certificate cert)
|
||
]
|
||
|
||
The returned values can be written to files or supplied directly
|
||
to TLS-enabled servers.
|
||
}
|
||
|
||
@section{Notes}
|
||
|
||
@itemlist[
|
||
@item{
|
||
This module relies on the OpenSSL library distributed with Racket and
|
||
accessed through the @racketmodname[openssl] package.
|
||
}
|
||
|
||
@item{
|
||
Certificates are generated entirely in memory and returned as PEM
|
||
strings.
|
||
}
|
||
|
||
@item{
|
||
The Subject Alternative Name (SAN) extension is automatically populated
|
||
from the provided host names and IP addresses.
|
||
}
|
||
]
|