Help us shape the future of Go! Take the 2026 Go Developer Survey

srptls

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: BSD-3-Clause Imports: 23 Imported by: 0

README

go-tls-srp

GitHub Release Go Reference License Tests Interoperability Supply chain OpenSSF Scorecard

Package srptls is a TLS 1.2 client and server that sign in with SRP, the Secure Remote Password key exchange of RFC 5054. The client proves that it knows a password. The server stores only a verifier derived from the password, needs no certificate and proves to the client that it holds the verifier.

Go's crypto/tls has no SRP. This module is a trimmed copy of crypto/tls from Go 1.27.2 that keeps only the SRP handshake. It works with peers that already use TLS-SRP, such as EST clients built on libest or OpenSSL and EST servers that accept TLS-SRP.

What it supports

  • TLS 1.2 only on both sides. SRP has no TLS 1.3 cipher suites. Older versions are refused.
  • Cipher suites TLS_SRP_SHA_WITH_AES_256_CBC_SHA and TLS_SRP_SHA_WITH_AES_128_CBC_SHA, preferred in that order. TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA is used only when listed in Config.CipherSuites. The suites that also sign with a server certificate are not implemented.
  • Only the seven groups of RFC 5054, Appendix A, from 1024 to 8192 bits. The client accepts 2048 bits and more unless Config.MinGroupBits allows the smaller groups.
  • Extended Master Secret (RFC 7627). The client always offers it and the server uses it when offered. ConnectionState.ExportKeyingMaterial works only with it.
  • Secure renegotiation indication (RFC 5746). Renegotiation itself is refused.
  • SNI and tls-unique. The client sends Config.ServerName and both sides report it in ConnectionState.ServerName. ConnectionState.TLSUnique holds the channel binding.

Not included: TLS 1.3, session resumption and tickets, certificates, ALPN, encrypt-then-MAC, QUIC and ECH.

Server usage

Config.GetVerifier returns the stored verifier for the username in the ClientHello:

config := &srptls.Config{
	GetVerifier: func(info *srptls.ClientHelloInfo) (*srptls.Verifier, error) {
		if v, ok := store[info.Username]; ok {
			return v, nil
		}
		return srptls.SimulatedVerifier(seed, group, []byte(info.Username)), nil
	},
}
l, err := srptls.Listen("tcp", ":8443", config)

Create verifiers with NewVerifier and a salt from NewSalt. To use an OpenSSL verifier, such as an srpvfile entry, decode its salt and verifier to big-endian bytes without leading zeros and find the group with GroupByBits or GroupByParameters.

For an unknown username, return one of these:

  • SimulatedVerifier. The handshake fails like a wrong password, so the client cannot tell whether the username exists. The seed is a secret key. Generate at least 16 random bytes once and store them with the verifiers, so every server instance uses the same seed across restarts. Pass the group of the stored verifiers. Its documentation explains how another seed or group gives the username away.
  • ErrUnknownUsername. The server sends an unknown_psk_identity alert, which tells the client that the username does not exist.

To refuse a client before the costly SRP computation, for example one over a rate limit, return ErrAccessDenied or an error that wraps it. The client gets an access_denied alert.

With net/http

Request.TLS stays nil because net/http fills it only for crypto/tls connections. Save the connection with http.Server.ConnContext and read its ConnectionState in the handler. The handshake runs on the first read, so ReadHeaderTimeout also limits it. See Example_httpServer in example_test.go.

Client usage

Client wraps a connection. Config.Username is required:

raw, err := net.DialTimeout("tcp", "est.example:8443", 10*time.Second)
if err != nil {
	return err
}
conn := srptls.Client(raw, &srptls.Config{
	Username:   "device-1",
	Password:   password,
	ServerName: "est.example",
})
if err := conn.HandshakeContext(ctx); err != nil {
	raw.Close()
	return err
}
tlsUnique := conn.ConnectionState().TLSUnique

The handshake runs in Handshake, HandshakeContext or the first Read or Write. It fails before anything is sent when Username is empty or longer than 255 bytes. The username and password are hashed as given, without SASLprep, as OpenSSL does.

The client fails the handshake when the server's Finished message does not verify, which means the server does not hold the user's verifier. It also refuses groups outside RFC 5054 or below MinGroupBits and a server value B that is zero modulo N.

An EST client puts TLSUnique in the challenge password of its CSR (RFC 7030, Section 3.5). With net/http, run the handshake in http.Transport.DialTLSContext and read the state from the connection, because Response.TLS stays nil. See Example_httpClient in example_test.go.

Errors

Cause Server error Alert to the client Client error wraps
Wrong password or a SimulatedVerifier wraps ErrWrongPassword bad_record_mac ErrWrongPassword
GetVerifier returns ErrUnknownUsername the error from GetVerifier unknown_psk_identity ErrUnknownUsername
GetVerifier returns ErrAccessDenied the error from GetVerifier access_denied ErrAccessDenied

An alert from the peer is a *net.OpError with Op set to "remote error". After a failed handshake, Conn.Handshake returns the same error again.

Security notes

  • SRP in TLS uses SHA-1 for the SRP values, as RFC 5054 requires, and CBC with HMAC-SHA1 for the records. The CBC code keeps the partial Lucky13 countermeasures of crypto/tls.
  • The big-number arithmetic of both sides uses filippo.io/bigmod, the constant-time modular arithmetic of the Go standard library exported as a module.
  • After GetVerifier returns, a simulated verifier costs the server the same work as a real one. The time of the lookup itself is up to the caller.
  • Once GetVerifier returns a verifier, the server does one modular exponentiation before the client proves anything. A ClientHello of about 100 bytes is enough to cause it. The cost grows with the square of the group size. The library sets no handshake deadline. Limit the connections a client can open and set a deadline on each accepted connection before its handshake with SetDeadline or HandshakeContext. A reverse proxy can enforce both.
  • To limit password guessing, count a failed sign-in when the handshake error wraps ErrWrongPassword and check the count in GetVerifier, returning ErrAccessDenied once it is used up.

Tested clients

  • OpenSSL 1.1.1k s_client and curl 7.61.1 on Rocky Linux 8.
  • libssl 3.5.9 and 4.0.3 through the SRP API (SSL_CTX_set_srp_username and SSL_CTX_set_srp_password).

openssl s_client from OpenSSL 3.0 onward ignores -srppass and uses a wrong password (openssl/openssl#32565). Test with OpenSSL 1.1.1, curl or a program that calls libssl directly.

Tested servers

  • OpenSSL 1.1.1w s_server with -srpvfile and verifiers made by openssl srp.

openssl s_server from OpenSSL 3.0.22, 3.5.9, 3.6.5 and 4.0.3 closes the connection during its SRP user lookup, also with its own s_client.

make test-interop builds OpenSSL 1.1.1w from source under bin/ and runs the client against its s_server. To use another OpenSSL 1.1.1 binary, set SRPTLS_TEST_OPENSSL to it and run go test -run TestOpenSSLServer.

Contributing

CONTRIBUTING.md describes the development checks, how the copy of crypto/tls follows Go releases and the release process.

Licence

BSD-3-Clause, the licence of the Go project. See LICENSE.

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

Index

Examples

Constants

View Source
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.

View Source
const (
	VersionTLS10 = 0x0301
	VersionTLS11 = 0x0302
	VersionTLS12 = 0x0303
)

Variables

View Source
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.

View Source
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.

View Source
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

func CipherSuiteName(id uint16) string

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

func Listen(network, laddr string, config *Config) (net.Listener, error)

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

func NewListener(inner net.Listener, config *Config) net.Listener

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.

func NewSalt

func NewSalt(rand io.Reader) ([]byte, error)

NewSalt returns a random salt of 20 bytes without a leading zero byte. OpenSSL clients treat the salt as a number and drop leading zeros, so such a salt would give them another x.

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

func Client(conn net.Conn, config *Config) *Conn

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

func Server(conn net.Conn, config *Config) *Conn

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

func (c *Conn) Close() error

Close closes the connection.

func (*Conn) CloseWrite

func (c *Conn) CloseWrite() error

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

func (c *Conn) Handshake() error

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

func (c *Conn) HandshakeContext(ctx context.Context) error

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

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Conn) NetConn

func (c *Conn) NetConn() net.Conn

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

func (c *Conn) Read(b []byte) (int, error)

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

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

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

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the read deadline on the underlying connection. A zero value for t means Conn.Read will not time out.

func (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

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

func (c *Conn) Write(b []byte) (int, error)

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

func GroupByBits(bits int) (*Group, error)

GroupByBits returns the RFC 5054 group with a prime of the given size, such as 1536 or 3072.

func GroupByParameters

func GroupByParameters(n, g []byte) (*Group, error)

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.

func (*Group) Bits

func (g *Group) Bits() int

Bits returns the size of the group's prime in bits.

func (*Group) Generator

func (g *Group) Generator() []byte

Generator returns a copy of the group's generator in big-endian bytes.

func (*Group) N

func (g *Group) N() []byte

N returns a copy of the group's prime in big-endian bytes.

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

func NewVerifier(group *Group, username, password, salt []byte) (*Verifier, error)

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

func SimulatedVerifier(seed []byte, group *Group, username []byte) *Verifier

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.

func (*Verifier) Validate

func (v *Verifier) Validate() error

Validate checks the limits RFC 5054 and the group place on a verifier.

Notes

Bugs

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL