Skip to content

Getting started

This page creates a verifier, starts a server and signs in to it with a client in one program. The code is Example in example_test.go, which go test runs and checks against its output.

Install

go get github.com/misiektoja/go-tls-srp

The module needs Go 1.27.3 or newer.

Create a verifier

The server never sees the password. It stores a verifier made from the username, the password, a random salt and one of the RFC 5054 groups:

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)
}

A real server creates the verifier once when the user is enrolled. It stores the group size, the salt and the value. Verifiers and groups describes what to store and how to import verifiers made by OpenSSL.

Start the server

Config.GetVerifier returns the stored verifier for the username in the ClientHello. Listen returns a net.Listener whose connections are *srptls.Conn:

l, err := srptls.Listen("tcp", "127.0.0.1:0", &srptls.Config{
    GetVerifier: func(info *srptls.ClientHelloInfo) (*srptls.Verifier, error) {
        if info.Username == "device-1" {
            return stored, nil
        }
        return nil, srptls.ErrUnknownUsername
    },
})
if err != nil {
    log.Fatal(err)
}

go func() {
    c, err := l.Accept()
    if err != nil {
        return
    }
    defer c.Close()
    // The library sets no handshake deadline.
    c.SetDeadline(time.Now().Add(10 * time.Second))
    conn := c.(*srptls.Conn)
    if err := conn.Handshake(); err != nil {
        log.Print(err)
        return
    }
    fmt.Fprintf(conn, "hello %s\n", conn.ConnectionState().SRPUsername)
}()

Returning ErrUnknownUsername tells the client that the username does not exist. Unknown usernames and refusals shows how to hide that instead.

Connect a client

Client wraps a connection you dialed. The handshake checks the server's group and its Finished message, so it succeeds only when the server holds the verifier of this username and password:

raw, err := net.DialTimeout("tcp", l.Addr().String(), 10*time.Second)
if err != nil {
    log.Fatal(err)
}
conn := srptls.Client(raw, &srptls.Config{
    Username:   "device-1",
    Password:   "secret",
    ServerName: "localhost",
})
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err = conn.HandshakeContext(ctx)
cancel()
if err != nil {
    log.Fatal(err)
}

greeting, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
    log.Fatal(err)
}
state := conn.ConnectionState()
fmt.Print(greeting)
fmt.Println(srptls.CipherSuiteName(state.CipherSuite))
fmt.Println("extended master secret:", state.ExtendedMasterSecret)
fmt.Println("tls-unique bytes:", len(state.TLSUnique))
conn.Close()

The program prints:

hello device-1
TLS_SRP_SHA_WITH_AES_256_CBC_SHA
extended master secret: true
tls-unique bytes: 12

To run it from a checkout of the repository:

go test -run '^Example$' -v .

What to change for production

  • Store verifiers instead of passwords and protect them like password hashes. Whoever reads a verifier can test password guesses offline and can pose as the server to that user (RFC 5054, Section 3.3).
  • Decide how to answer unknown usernames. SimulatedVerifier makes them fail like a wrong password, as Unknown usernames and refusals describes.
  • Set a deadline on every accepted connection before its handshake and limit the connections a client can open. The library sets no handshake deadline. See Running a server.
  • Limit password guessing by counting failed handshakes and refusing clients over the limit in GetVerifier. See Limiting password guessing.
  • With net/http, use the setup on Running a server and Connecting as a client, because Request.TLS and Response.TLS stay nil for these connections.