Documentation
¶
Overview ¶
Package srptls is a TLS 1.2 client and server that authenticate with SRP, the Secure Remote Password key exchange of RFC 5054. The client proves that it knows a password and the server proves that it holds the verifier made from it, without certificates. Use Server or Listen for the server side and Client for the client side. It is a trimmed copy of Go's crypto/tls with the SRP cipher suites only. It has no TLS 1.3, no session resumption, no renegotiation and no certificate authentication.
Example (HttpClient) ¶
Example_httpClient sends an HTTPS request over TLS-SRP. The transport runs the handshake in DialTLSContext. net/http leaves Response.TLS nil for connections that are not crypto/tls, so the connection state, such as the tls-unique value an EST client puts in its CSR, comes from the Conn.
package main
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"time"
srptls "github.com/misiektoja/go-tls-srp"
)
func main() {
config := &srptls.Config{
Username: "device-1",
Password: "secret",
ServerName: "est.example",
}
transport := &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
raw, err := new(net.Dialer).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
conn := srptls.Client(raw, config)
if err := conn.HandshakeContext(ctx); err != nil {
raw.Close()
if errors.Is(err, srptls.ErrWrongPassword) {
return nil, fmt.Errorf("wrong username or password: %w", err)
}
return nil, err
}
log.Printf("tls-unique %x", conn.ConnectionState().TLSUnique)
return conn, nil
},
}
client := &http.Client{Transport: transport, Timeout: 30 * time.Second}
resp, err := client.Get("https://est.example:8443/.well-known/est/cacerts")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
}
Output:
Example (HttpServer) ¶
Example_httpServer serves HTTPS over TLS-SRP. net/http leaves Request.TLS nil for connections that are not crypto/tls, so the handler reads the connection state from the request context instead.
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"net"
"net/http"
"os"
"time"
srptls "github.com/misiektoja/go-tls-srp"
)
type connKey struct{}
func main() {
group, err := srptls.GroupByBits(3072)
if err != nil {
log.Fatal(err)
}
salt, err := srptls.NewSalt(rand.Reader)
if err != nil {
log.Fatal(err)
}
stored, err := srptls.NewVerifier(group, []byte("device-1"), []byte("secret"), salt)
if err != nil {
log.Fatal(err)
}
// The seed is a secret. Generate at least 16 random bytes once, store them
// with the verifiers and use the same value on every instance and across
// restarts.
seed, err := hex.DecodeString(os.Getenv("SRP_SIMULATED_SEED"))
if err != nil || len(seed) < 16 {
log.Fatal("SRP_SIMULATED_SEED must hold at least 32 hexadecimal digits")
}
config := &srptls.Config{
GetVerifier: func(info *srptls.ClientHelloInfo) (*srptls.Verifier, error) {
if info.Username == "device-1" {
return stored, nil
}
return srptls.SimulatedVerifier(seed, group, []byte(info.Username)), nil
},
}
l, err := srptls.Listen("tcp", "127.0.0.1:0", config)
if err != nil {
log.Fatal(err)
}
srv := &http.Server{
// The handshake runs in the first read, so this also bounds it.
ReadHeaderTimeout: 10 * time.Second,
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, connKey{}, c)
},
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state := r.Context().Value(connKey{}).(*srptls.Conn).ConnectionState()
fmt.Fprintf(w, "hello %s\n", state.SRPUsername)
}),
}
go srv.Serve(l)
defer srv.Close()
}
Output:
Index ¶
- Constants
- Variables
- func CipherSuiteName(id uint16) string
- func Listen(network, laddr string, config *Config) (net.Listener, error)
- func NewListener(inner net.Listener, config *Config) net.Listener
- func NewSalt(rand io.Reader) ([]byte, error)
- type ClientHelloInfo
- type Config
- type Conn
- func (c *Conn) Close() error
- func (c *Conn) CloseWrite() error
- func (c *Conn) ConnectionState() ConnectionState
- func (c *Conn) Handshake() error
- func (c *Conn) HandshakeContext(ctx context.Context) error
- func (c *Conn) LocalAddr() net.Addr
- func (c *Conn) NetConn() net.Conn
- func (c *Conn) Read(b []byte) (int, error)
- func (c *Conn) RemoteAddr() net.Addr
- func (c *Conn) SetDeadline(t time.Time) error
- func (c *Conn) SetReadDeadline(t time.Time) error
- func (c *Conn) SetWriteDeadline(t time.Time) error
- func (c *Conn) Write(b []byte) (int, error)
- type ConnectionState
- type Group
- type RecordHeaderError
- type Verifier
- Bugs
Examples ¶
Constants ¶
const ( TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc01a TLS_SRP_SHA_WITH_AES_128_CBC_SHA uint16 = 0xc01d TLS_SRP_SHA_WITH_AES_256_CBC_SHA uint16 = 0xc020 )
SRP cipher suite IDs from RFC 5054, Section 2.7. The suites that also sign with a server certificate are not implemented.
const ( VersionTLS10 = 0x0301 VersionTLS11 = 0x0302 VersionTLS12 = 0x0303 )
Variables ¶
var ErrAccessDenied = errors.New("srptls: access denied")
ErrAccessDenied may be returned by Config.GetVerifier, also wrapped, to refuse a client, for example one over a rate limit. The server then aborts the handshake with an access_denied alert before it does the costly SRP computation. A client handshake error wraps ErrAccessDenied when the server sends that alert.
var ErrUnknownUsername = errors.New("srptls: unknown SRP username")
ErrUnknownUsername may be returned by Config.GetVerifier. The server then aborts the handshake with an unknown_psk_identity alert, which tells the client that the username does not exist. Return a SimulatedVerifier instead to hide which usernames exist. A client handshake error wraps ErrUnknownUsername when the server sends that alert.
var ErrWrongPassword = errors.New("srptls: wrong SRP password")
ErrWrongPassword is wrapped by the handshake error when the client's Finished message does not decrypt or does not verify. With SRP that means the client used another password than the verifier was made from, or the username is unknown and a simulated verifier answered. An attacker who alters the client's records on the way causes the same error. On the client it is wrapped when the server answers the client's Finished message with a bad_record_mac alert.
Functions ¶
func CipherSuiteName ¶
CipherSuiteName returns the standard name for the passed cipher suite ID, or a hexadecimal fallback if the suite is not one of this package's.
func Listen ¶
Listen creates a TLS listener accepting connections on the given network address using net.Listen. The configuration must be non-nil and must set GetVerifier.
func NewListener ¶
NewListener creates a Listener which accepts connections from an inner Listener and wraps each connection with Server. The configuration must be non-nil and must set GetVerifier.
Types ¶
type ClientHelloInfo ¶
type ClientHelloInfo struct {
// Username is the SRP username from the ClientHello, as sent. It is
// between 1 and 255 bytes and may not be valid UTF-8.
Username string
// ServerName indicates the name of the server requested by the client.
// It is only set if the client is using SNI.
ServerName string
// CipherSuites lists the cipher suites offered by the client.
CipherSuites []uint16
// Conn is the underlying net.Conn for the connection. Do not read
// from, or write to, this connection; that will cause the TLS
// connection to fail.
Conn net.Conn
// contains filtered or unexported fields
}
ClientHelloInfo contains information from a ClientHello message to guide the GetVerifier callback.
func (*ClientHelloInfo) Context ¶
func (c *ClientHelloInfo) Context() context.Context
Context returns the context of the handshake that is in progress. This context is a child of the context passed to HandshakeContext, if any, and is canceled when the handshake concludes.
type Config ¶
type Config struct {
// GetVerifier returns the SRP verifier for the username in the
// ClientHello. It is required on the server and not used by the client.
// Any error other than ErrUnknownUsername and ErrAccessDenied aborts the
// handshake with an internal_error alert.
GetVerifier func(*ClientHelloInfo) (*Verifier, error)
// Username is the SRP username the client sends in the SRP extension of
// its ClientHello, RFC 5054 Section 2.8.1. It is required on the client,
// must be 1 to 255 bytes and is not used by the server. Like Password it
// is hashed as given, without SASLprep.
Username string
// Password is the client's SRP password. It is not used by the server.
Password string
// ServerName is the host name the client sends in the Server Name
// Indication extension. A trailing dot is removed. An IP address is not
// sent. It is not used by the server.
ServerName string
// MinGroupBits is the prime size in bits of the smallest RFC 5054 group
// the client accepts from the server. If zero, it is 2048, so the 1024
// and 1536 bit groups must be allowed explicitly. It must not exceed 8192,
// the largest group. It is not used by the server, which uses the group of
// the verifier.
MinGroupBits int
// CipherSuites is the preference order of SRP cipher suites. The server
// picks the first one the client offers. The client offers them in this
// order. If nil, both use TLS_SRP_SHA_WITH_AES_256_CBC_SHA and then
// TLS_SRP_SHA_WITH_AES_128_CBC_SHA. TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA is
// used only when listed here.
CipherSuites []uint16
// Rand provides the source of entropy for nonces and the SRP private
// values of the client and the server. If Rand is nil, crypto/rand is
// used.
Rand io.Reader
// KeyLogWriter optionally specifies a destination for TLS master secrets
// in NSS key log format that can be used to allow external programs
// such as Wireshark to decrypt TLS connections. It works on both sides.
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
// Use of KeyLogWriter compromises security and should only be
// used for debugging.
KeyLogWriter io.Writer
}
A Config structure is used to configure a TLS-SRP client or server. Some fields apply to one side only, as their documentation says. After one has been passed to a TLS function it must not be modified. A Config may be reused.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
A Conn represents a secured connection. It implements the net.Conn interface.
func Client ¶ added in v0.2.0
Client returns a new TLS-SRP client side connection using conn as the underlying transport. The configuration must be non-nil and must set Username. Otherwise the handshake fails before anything is sent. Like the server, the client runs the handshake on the first Read or Write, or on Handshake or HandshakeContext.
func Server ¶
Server returns a new TLS-SRP server side connection using conn as the underlying transport. The configuration must be non-nil and must set GetVerifier.
func (*Conn) CloseWrite ¶
CloseWrite shuts down the writing side of the connection. It should only be called once the handshake has completed and does not call CloseWrite on the underlying connection. Most callers should just use Conn.Close.
func (*Conn) ConnectionState ¶
func (c *Conn) ConnectionState() ConnectionState
ConnectionState returns basic TLS details about the connection.
The returned ConnectionState is only meaningful after the handshake has completed, as reported by ConnectionState.HandshakeComplete; before then its fields are not populated. The handshake is run automatically by the first Conn.Read or Conn.Write, or it can be triggered explicitly with Conn.Handshake.
func (*Conn) Handshake ¶
Handshake runs the client or server handshake if it has not yet been run.
Most uses of this package need not call Handshake explicitly: the first Conn.Read or Conn.Write will call it automatically.
For control over canceling or setting a timeout on a handshake, use Conn.HandshakeContext.
func (*Conn) HandshakeContext ¶
HandshakeContext runs the client or server handshake if it has not yet been run.
The provided Context must be non-nil. If the context is canceled before the handshake is complete, the handshake is interrupted and an error is returned. Once the handshake has completed, cancellation of the context will not affect the connection.
Most uses of this package need not call HandshakeContext explicitly: the first Conn.Read or Conn.Write will call it automatically.
func (*Conn) NetConn ¶
NetConn returns the underlying connection that is wrapped by c. Note that writing to or reading from this connection directly will corrupt the TLS session.
func (*Conn) Read ¶
Read reads data from the connection.
As Read calls Conn.Handshake, in order to prevent indefinite blocking a deadline must be set for both Read and Conn.Write before Read is called when the handshake has not yet completed. See Conn.SetDeadline, Conn.SetReadDeadline, and Conn.SetWriteDeadline.
func (*Conn) RemoteAddr ¶
RemoteAddr returns the remote network address.
func (*Conn) SetDeadline ¶
SetDeadline sets the read and write deadlines associated with the connection. A zero value for t means Conn.Read and Conn.Write will not time out. After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (*Conn) SetReadDeadline ¶
SetReadDeadline sets the read deadline on the underlying connection. A zero value for t means Conn.Read will not time out.
func (*Conn) SetWriteDeadline ¶
SetWriteDeadline sets the write deadline on the underlying connection. A zero value for t means Conn.Write will not time out. After a Conn.Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (*Conn) Write ¶
Write writes data to the connection.
As Write calls Conn.Handshake, in order to prevent indefinite blocking a deadline must be set for both Conn.Read and Write before Write is called when the handshake has not yet completed. See Conn.SetDeadline, Conn.SetReadDeadline, and Conn.SetWriteDeadline.
type ConnectionState ¶
type ConnectionState struct {
// Version is the TLS version used by the connection. It is always VersionTLS12.
Version uint16
// HandshakeComplete is true if the handshake has concluded.
HandshakeComplete bool
// CipherSuite is the cipher suite negotiated for the connection, such as
// TLS_SRP_SHA_WITH_AES_256_CBC_SHA.
CipherSuite uint16
// ServerName is the value of the Server Name Indication extension sent by
// the client. On a client connection it is the name the client sent, which
// is empty when Config.ServerName is empty or an IP address.
ServerName string
// SRPUsername is the username the client proved it holds the password of.
// On a client connection it is Config.Username.
SRPUsername string
// ExtendedMasterSecret is true if the client and server used the extended
// master secret of RFC 7627.
ExtendedMasterSecret bool
// TLSUnique contains the "tls-unique" channel binding value (see RFC 5929,
// Section 3). It is the client's Finished message because every handshake
// is a full handshake.
TLSUnique []byte
// contains filtered or unexported fields
}
ConnectionState records basic TLS details about the connection.
func (*ConnectionState) ExportKeyingMaterial ¶
func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error)
ExportKeyingMaterial returns length bytes of exported key material in a new slice as defined in RFC 5705. If context is nil, it is not used as part of the seed. It returns an error unless the connection used Extended Master Secret.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group is one of the seven SRP groups of RFC 5054 Appendix A. OpenSSL clients accept only these groups, so the package offers no way to define another one.
func GroupByBits ¶
GroupByBits returns the RFC 5054 group with a prime of the given size, such as 1536 or 3072.
func GroupByParameters ¶
GroupByParameters returns the RFC 5054 group whose prime and generator match. Leading zero bytes are ignored, so values from any encoding of the numbers match.
type RecordHeaderError ¶
type RecordHeaderError struct {
// Msg contains a human readable string that describes the error.
Msg string
// RecordHeader contains the five bytes of TLS record header that
// triggered the error.
RecordHeader [5]byte
// Conn provides the underlying net.Conn in the case that a client
// sent an initial handshake that didn't look like TLS.
// It is nil if there's already been a handshake or a TLS alert has
// been written to the connection.
Conn net.Conn
}
RecordHeaderError is returned when a TLS record header is invalid.
func (RecordHeaderError) Error ¶
func (e RecordHeaderError) Error() string
Error returns the error description.
type Verifier ¶
type Verifier struct {
Group *Group
// Salt is sent to the client exactly as stored. OpenSSL computes x from the salt as a number,
// so a salt imported from an OpenSSL srpv file must not keep a leading zero byte.
Salt []byte
// Value is v in big-endian bytes, with or without leading zeros.
Value []byte
}
Verifier is the stored SRP record of one username: the group, the salt and the verifier v = g^x mod N. The server needs no password.
func NewVerifier ¶
NewVerifier computes the verifier of a username and password with RFC 5054 Section 2.4: x = SHA1(s | SHA1(I | ":" | P)) and v = g^x mod N. The bytes are hashed as given, without SASLprep, as OpenSSL does.
func SimulatedVerifier ¶
SimulatedVerifier returns a verifier for a username the server does not know, as RFC 5054 Section 2.5.1.3 suggests. The same seed and username always give the same salt, and the handshake fails like a wrong password. The verifier is a pseudo-random number below N rather than g^x, because the client never sees it and an exponentiation would make unknown usernames slower than known ones.
The seed is a secret key. Generate at least 16 random bytes once, store them with the verifiers and use the same seed on every server that answers the same clients. A seed that differs between servers or restarts lets a client spot an unknown username by its salt changing, and anyone who knows the seed can compute the salt of any unknown username. The group must be the group of the stored verifiers, because the server sends it in the clear and a different group would give the username away as well.
Notes ¶
Bugs ¶
The crypto/tls package only implements some countermeasures against Lucky13 attacks on CBC-mode encryption, and only on SHA1 variants. See http://www.isg.rhul.ac.uk/tls/TLStiming.pdf and https://www.imperialviolet.org/2013/02/04/luckythirteen.html.