diff --git a/README.md b/README.md index a9dd0d1..cdf5f2f 100644 --- a/README.md +++ b/README.md @@ -82,5 +82,35 @@ certificate. See `examples/client` for an example. Gemini takes advantage of client certificates for authentication. -See `examples/auth` for an example server which authenticates its users with a +If a server responds with `StatusCertificateRequired`, clients will generate a +certificate for the site and resend the request with the provided certificate. +In order for this to work, clients must specify the fields `CertificateStore` +and `GetCertificate`: + +```go +// Initialize the certificate store. +client.CertificateStore = gmi.NewCertificateStore() +// GetCertificate is called when a server requests a certificate. +// The returned certificate, if not nil, will be used when resending the request. +client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { + // If the certificate is in the store, return it + if cert, ok := store[hostname]; ok { + return cert + } + // Otherwise, generate a certificate + duration := time.Hour + cert, err := gmi.NewCertificate(hostname, duration) + if err != nil { + return nil + } + // Store and return the certificate + store[hostname] = &cert + return &cert +} +``` + +Servers can then authenticate their clients with the fingerprint of their +certificates. + +See `examples/auth` for an example server which authenticates its users with username and password, and uses their client certificate to remember sessions. diff --git a/client.go b/client.go index a7bbbc5..f99735b 100644 --- a/client.go +++ b/client.go @@ -34,6 +34,7 @@ type Request struct { Host string // Certificate specifies the TLS certificate to use for the request. + // Request certificates take precedence over client certificates. // This field is ignored by the server. Certificate *tls.Certificate @@ -188,8 +189,10 @@ type Client struct { // CertificateStore contains all the certificates that the client has stored. CertificateStore CertificateStore - // GetCertificate, if not nil, will be called to determine which certificate - // to use when the server responds with CertificateRequired. + // GetCertificate, if not nil, will be called when a server requests a certificate. + // The returned certificate will be used when sending the request again. + // If the certificate is nil, the request will not be sent again and + // the response will be returned. GetCertificate func(hostname string, store CertificateStore) *tls.Certificate // TrustCertificate, if not nil, will be called to determine whether the @@ -205,6 +208,7 @@ func (c *Client) Send(req *Request) (*Response, error) { InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { + // Request certificates take precedence over client certificates if req.Certificate != nil { return req.Certificate, nil } diff --git a/examples/client/client.go b/examples/client/client.go index e8a16be..3e88c73 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -49,15 +49,17 @@ func init() { client.CertificateStore = gmi.NewCertificateStore() client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { + // If the certificate is in the store, return it if cert, ok := store[hostname]; ok { return cert } - // Generate a certificate + // Otherwise, generate a certificate duration := time.Hour cert, err := gmi.NewCertificate(hostname, duration) if err != nil { return nil } + // Store and return the certificate store[hostname] = &cert return &cert }