Documentation
¶
Overview ¶
Package cpace implements CPace, the balanced password-authenticated key exchange of draft-irtf-cfrg-cpace-21, in its CPACE-RISTRETTO255-SHA512 cipher suite.
Two parties who share a low-entropy password -- a short code read out over the phone -- agree on a strong shared key over a channel they do not trust. Someone in the middle gets one guess at the password per attempt, learns nothing it can test offline, and makes the exchange fail on both ends.
The exchange ¶
This package runs the initiator-responder setting of the draft, with the explicit key confirmation of its section 10.4, in three messages:
initiator responder
NewInitiator -- start: Ya, ADa --> NewResponder
<-- reply: Yb, ADb, Tb --
Finish -- confirm: Ta --> Finish
The messages are opaque bytes; carry them however you like. The password (PRS), the channel identifier (CI) and the session identifier (SID) never travel; the associated data (AD) travels in the clear and is authenticated. A party that finishes knows that the other holds the same PRS, CI and SID, and has the other's AD as it was sent. Confirmation tags are checked before anything is returned, so a wrong password fails on both ends: the initiator on the reply, the responder on the confirmation.
What it rests on ¶
The group is ristretto255 (RFC 9496) from github.com/gtank/ristretto255, on filippo.io/edwards25519; this package does no arithmetic of its own. Every step -- prepend_len, lv_cat, the generator string, the generator, Ya, Yb, K, transcript_ir, ISK, and the handling of invalid points -- is checked byte for byte against the draft's test vectors (appendices A.1 and B.3), and so is a whole exchange run with the draft's scalars.
Using it well ¶
- Limit attempts and show every failure: with a 4-digit password, each attempt is one chance in 10,000 for an attacker.
- Put both parties' identities in CI when both know them in advance (initiator first); otherwise put each party's identity, or public key, in its own AD and check what Result.PeerAD says (section 10.1).
- Use a SID unique to the session that both parties know, such as randomness both contributed (section 10.9).
- Run Result.ISK through a KDF before use (section 10.3).
Example ¶
package main
import (
"bytes"
"fmt"
"github.com/mendsec/cpace"
)
func main() {
ci := []byte("example.org remote support")
sid := []byte("3f6c9a1d2b7e4058")
// The party who typed the code starts.
alice := cpace.Config{PRS: []byte("5071"), CI: ci, SID: sid, AD: []byte("alice's public key")}
ini, start, err := cpace.NewInitiator(alice)
if err != nil {
panic(err)
}
// The party who read it out answers. start, reply and confirm are what
// travel between them.
bob := cpace.Config{PRS: []byte("5071"), CI: ci, SID: sid, AD: []byte("bob's public key")}
res, reply, err := cpace.NewResponder(bob, start)
if err != nil {
panic(err)
}
confirm, a, err := ini.Finish(reply)
if err != nil {
panic(err) // ErrAuthFailed: a wrong code, or someone in the middle
}
b, err := res.Finish(confirm)
if err != nil {
panic(err)
}
fmt.Println(bytes.Equal(a.ISK, b.ISK))
fmt.Println(string(a.PeerAD))
fmt.Println(string(b.PeerAD))
}
Output: true bob's public key alice's public key
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAuthFailed means the other party did not prove it holds the same // password, channel identifier and session identifier: the password // was mistyped, or someone in the middle tried to take part. The two // cases cannot be told apart, on purpose. ErrAuthFailed = errors.New("cpace: the other party does not hold the same password") // ErrMalformed means a message is not one of this protocol's, or is // out of place. ErrMalformed = errors.New("cpace: malformed message") // ErrNoPassword means a Config has no password. ErrNoPassword = errors.New("cpace: no password") )
Errors a caller can act on.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// PRS is the password-related string both parties share. It never
// travels.
PRS []byte
// CI is the channel identifier: what this exchange is for, and the
// parties' identities when both know them in advance, initiator first
// (draft section 10.1). It is hashed, never sent.
CI []byte
// SID is the session identifier. It should be unique to the session
// and known to both parties (draft section 10.9); it may be empty.
SID []byte
// AD is this party's associated data. It is sent in the clear and
// authenticated: the other party learns it as Result.PeerAD only if
// the exchange succeeds. A party's own identity or public key goes
// here when CI cannot carry it.
AD []byte
}
Config is one party's inputs. Both parties must give the same PRS, CI and SID, or the exchange fails as a wrong password does.
type Initiator ¶
type Initiator struct {
// contains filtered or unexported fields
}
Initiator is the party that speaks first.
func NewInitiator ¶
NewInitiator starts an exchange and returns the first message, for the responder.
type Responder ¶
type Responder struct {
// contains filtered or unexported fields
}
Responder is the party that answers.
func NewResponder ¶
NewResponder answers the initiator's first message and returns the reply, for the initiator. The exchange is not authenticated until Finish accepts the initiator's confirmation.