cpace

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: BSD-3-Clause Imports: 11 Imported by: 0

README

CPace for Go

OpenSSF Scorecard OpenSSF Best Practices

This repository implements draft-irtf-cfrg-cpace-21 for the CPACE-RISTR255-SHA512 suite only.

Status: auditable draft implementation. This code has not had independent cryptographic review and is not production-ready.

Current work is release readiness, not new policy design. The public API and package-profile choices are frozen for review unless a new finding reopens a decision. ADR-0008 records a narrow public-lifecycle thaw for Initiator.Close and Responder.Close; ADR-0009 records a broad Caller input replacement whose authorization is narrowly limited to its follow-up Input implementation. All unrelated public-surface and package-profile choices remain frozen. Before any production-readiness claim, the release bar in docs/security-assessment.md must be satisfied.

The public API exposes only an initiator-responder flow with mandatory explicit key confirmation:

  1. Start returns initiator state and message A.
  2. Respond consumes message A and returns responder state and message B.
  3. Initiator.Finish verifies message B and returns message C plus Session.
  4. Responder.Finish verifies message C and returns Session.

Respond returning success is not authentication. Only successful Initiator.Finish and Responder.Finish calls return authenticated sessions. Treat message B as an unauthenticated protocol message until the finish calls complete.

This module is a package-specific cpace-go profile over draft-21. It builds CI internally from the draft version, suite, protocol roles, caller-provided party identities, and caller context. It also owns its binary wire framing; applications should treat message bytes as opaque and versioned by this module. The current wire format prefix byte is 0xc1.

Callers provide role-local Input. The initiator calls Start with SelfID set to the initiator identity and PeerID set to the responder identity; the responder calls Respond with SelfID set to the responder identity and PeerID set to the initiator identity. Password, Context, and SessionID are shared session values that both sides supply identically; LocalAssociatedData is each side's local associated data and may differ. Do not use only globally hardcoded role labels such as "client" and "server" for all users or deployments. Bind stable, application-meaningful party identities into SelfID and PeerID.

Provide a fresh, non-secret SessionID agreed by both parties for every session. Empty session IDs are rejected by default because they weaken replay and transcript separation properties; that failure wraps both ErrInvalidInput and ErrEmptySessionID. AllowEmptySessionID exists only for draft-21 compatibility tests or deliberately compatible profiles that accept the weaker empty-sid behavior. If an outer protocol negotiates PAKE versions, ciphersuites, or whether CPace is used at all, that negotiation needs its own downgrade protection; this package does not authenticate negotiation it never sees. See docs/integration-guidance.md for outer negotiation and downgrade-protection guidance.

Initiator.Finish and Responder.Finish are single-use calls. Passing a malformed message or a message that fails confirmation consumes the state and requires restarting the exchange. If an exchange might be abandoned after successful Start or Respond, call Initiator.Close or Responder.Close to release local single-use state secrets; it is safe to defer that close immediately after construction. Close after Finish is a no-op, and constructed value copies share the same terminal state.

Session.TranscriptID is the draft CPaceSidOutput for the confirmed CPace transcript, not a complete channel binding for outer protocol negotiation. Session.Export derives deterministic, domain-separated application key material from the confirmed ISK. It is not a source of fresh randomness; use specific labels and contexts for each exported key purpose. Session.Close performs best-effort cleanup of the session key material and makes future Export calls fail with ErrSessionClosed; non-secret metadata accessors remain available after close. Session.PeerAssociatedData returns the exact peer local associated data bound into the confirmed exchange. Session.PeerID returns the caller-configured peer identity that was bound into CI and confirmed by the exchange; it is not parsed from peer-controlled wire data.

Scalar randomness is always drawn from Go's crypto/rand.Reader. Callers do not provide randomness to Start or Respond.

Input and wire fields have package-owned per-field caps: passwords and party IDs are limited to 4 KiB, context and session IDs to 1 KiB, and local associated data to 64 KiB. Valid package-owned message shapes are governed by those per-field caps and exact public-share/tag lengths; malformed framed inputs also hit a 128 KiB aggregate decoder backstop before field parsing proceeds. Local associated data should bind protocol context, not carry large payloads; represent large external artifacts with a digest, Merkle root, exporter, or other fixed-size commitment.

Validation

This repository uses Taskfile.yml as the local validation facade:

task quick
task check
task check:changed
task lint:golangci
task docs:check
task bench
FUZZTIME=30s PARALLEL=2 task fuzz
FUZZ_RACE=0 GOMAXPROCS=4 FUZZ_TEST_PARALLEL=2 FUZZTIME=8m PARALLEL=2 task fuzz

task check requires the normal Go/tooling prerequisites plus jq, because the full gate runs release-helper smoke tests that validate CycloneDX SBOM JSON. task lint:golangci runs a curated advisory analyzer set and is not part of the required local gate.

The fuzz-target registry lives in .github/fuzz-targets.json and records each target function, package, and OSS-Fuzz-compatible binary name; go test ./... includes a drift check that keeps it in sync with defined fuzz functions and ossfuzz/build.sh. GitHub-hosted pull-request CI is intentionally light because it runs untrusted fork code: code changes set up Go, run go test ./..., and run the evidence baseline validator; Markdown-only PRs normally run docs validation without Go, except changes to docs/evidence-baseline.md, docs/evidence-baseline-summary-docs.txt, docs/evidence/**, or summary docs listed in docs/evidence-baseline-summary-docs.txt also run the evidence baseline validator. Scheduled hosted lanes run govulncheck, advisory gosec, curated golangci-lint, and a 5-minute-per-target fuzz regression pass as background signal. Run the full local gate, longer maintainer-machine fuzzing, vulnerability scan, advisory gosec scan, and curated golangci-lint scan locally before release-oriented changes. The default fuzz lane keeps the race detector on for smoke runs; use FUZZ_RACE=0 for longer campaigns after task check has already covered race-instrumented tests. See docs/ci-policy.md for the hosted and self-hosted runner threat model.

Release-readiness work should record exact evidence: commit SHA, command or workflow, duration for fuzzing, target count, and residual risks. Dependency review evidence lives in docs/dependency-review.md; fuzz campaign evidence lives in docs/fuzz-evidence.md; security/spec audit evidence lives in docs/security-spec-audit.md; Capslock capability-analysis evidence lives in docs/capslock-report.md; local benchmark guidance lives in docs/performance.md. External reviewer scope and review questions are summarized in docs/external-review-handoff.md; security boundaries are summarized in docs/threat-model.md. Downstream release verification instructions live in docs/release-verification.md. Project governance, security-gate, and VEX policies live in docs/governance.md, docs/security-gates.md, and docs/vex.md.

Current pinned evidence baselines and freshness caveats are indexed in docs/evidence-baseline.md.

initiator, msgA, err := cpace.Start(initCfg)
defer initiator.Close()
responder, msgB, err := cpace.Respond(respCfg, msgA)
defer responder.Close()
msgC, initSession, err := initiator.Finish(msgB)
defer initSession.Close()
respSession, err := responder.Finish(msgC)
defer respSession.Close()
key, err := initSession.Export([]byte("application key"), nil, 32)

Release policy: keep tags in the v0.x range until independent review is complete and the release bar in docs/security-assessment.md is satisfied. Use docs/release-checklist.md for future release candidates. See CONTRIBUTING.md before opening public issues or pull requests; commits must include DCO signoffs.

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package cpace implements an auditable draft-irtf-cfrg-cpace-21 CPACE-RISTR255-SHA512 initiator-responder flow.

This module is an Internet-Draft implementation. It is not independently audited and must not be treated as production-ready cryptographic software.

The public API intentionally exposes only an initiator-responder flow with mandatory explicit key confirmation. A session is returned only after both sides have confirmed possession of the same intermediate session key. Respond success alone is not authentication.

Scalar randomness always comes from crypto/rand.Reader; the package does not accept caller-supplied randomness through the public API. Input and wire fields have package-owned per-field size caps; associated data is capped at 64 KiB and smaller identity/context fields are capped more tightly.

Input.SessionID must be non-empty by default. Input.AllowEmptySessionID is only for draft-21 compatibility tests or deliberately compatible profiles that accept weaker replay and transcript separation.

Initiator and Responder are single-use state. Finish consumes them on success and failure; Close releases their local persistent secret material when an exchange is abandoned before Finish. Close is nil-safe and idempotent, and value copies share terminal state.

Session.Close performs best-effort cleanup of session key material and makes future Export calls fail. PeerAssociatedData and PeerID expose copied, non-secret metadata bound into the confirmed exchange.

Callers provide role-local Input: Start maps SelfID to the initiator identity and PeerID to the responder identity, while Respond maps SelfID to the responder identity and PeerID to the initiator identity. Applications that negotiate PAKE versions, suites, or protocol modes outside this package must provide their own downgrade protection for that outer negotiation.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/the-sarge/cpace"
)

func main() {
	initCfg := exampleInitiatorInput("session-1234")
	initCfg.LocalAssociatedData = []byte("client hello")
	initiator, msgA, err := cpace.Start(initCfg)
	if err != nil {
		panic(err)
	}
	defer closeExampleInitiator(initiator)

	respCfg := exampleResponderInput("session-1234")
	respCfg.LocalAssociatedData = []byte("server hello")
	responder, msgB, err := cpace.Respond(respCfg, msgA)
	if err != nil {
		panic(err)
	}
	defer closeExampleResponder(responder)

	msgC, initSession, err := initiator.Finish(msgB)
	if err != nil {
		panic(err)
	}
	respSession, err := responder.Finish(msgC)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := initSession.Close(); err != nil {
			panic(err)
		}
	}()
	defer func() {
		if err := respSession.Close(); err != nil {
			panic(err)
		}
	}()

	initKey, _ := initSession.Export([]byte("application key"), nil, 32)
	respKey, _ := respSession.Export([]byte("application key"), nil, 32)
	fmt.Println(bytes.Equal(initKey, respKey))
	fmt.Println(bytes.Equal(initSession.TranscriptID(), respSession.TranscriptID()))
}

func exampleInitiatorInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("client@example"),
		PeerID:    []byte("server@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleResponderInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("server@example"),
		PeerID:    []byte("client@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func closeExampleInitiator(initiator *cpace.Initiator) {
	if err := initiator.Close(); err != nil {
		panic(err)
	}
}

func closeExampleResponder(responder *cpace.Responder) {
	if err := responder.Close(); err != nil {
		panic(err)
	}
}
Output:
true
true

Index

Examples

Constants

View Source
const (
	// DraftVersion identifies the CPace Internet-Draft revision implemented by
	// this package.
	DraftVersion = "draft-irtf-cfrg-cpace-21"
)

Variables

View Source
var (
	// ErrInvalidInput reports invalid local configuration or parameters.
	ErrInvalidInput = errors.New("cpace: invalid input")

	// ErrEmptySessionID reports an empty Input.SessionID without the explicit
	// AllowEmptySessionID compatibility opt-in. The returned error also wraps
	// ErrInvalidInput.
	ErrEmptySessionID = errors.New("cpace: empty session id")

	// ErrRandomness reports randomness-related failures, including random
	// source read failures and repeated unusable scalar samples.
	ErrRandomness = errors.New("cpace: randomness failure")

	// ErrMessage reports malformed or unexpected wire messages.
	ErrMessage = errors.New("cpace: invalid message")

	// ErrStateUsed reports an attempt to reuse a single-use protocol state.
	ErrStateUsed = errors.New("cpace: state already used")

	// ErrSessionClosed reports an attempt to export key material from a closed
	// Session.
	ErrSessionClosed = errors.New("cpace: session closed")

	// ErrAbort reports a draft abort condition such as an invalid point or
	// neutral-element Diffie-Hellman result.
	ErrAbort = errors.New("cpace: protocol abort")

	// ErrPeerShareEncoding reports a peer public share that is not a
	// canonical Ristretto255 encoding. The returned error also wraps
	// ErrAbort.
	ErrPeerShareEncoding = errors.New("cpace: peer share encoding")

	// ErrPeerShareIdentity reports a peer public share that decoded to the
	// Ristretto255 identity element. The returned error also wraps ErrAbort.
	ErrPeerShareIdentity = errors.New("cpace: peer share identity")

	// ErrConfirmationFailed reports failed explicit key confirmation.
	ErrConfirmationFailed = errors.New("cpace: key confirmation failed")
)

Functions

This section is empty.

Types

type Initiator

type Initiator struct {
	// contains filtered or unexported fields
}

Initiator is a single-use initiator state returned by Start.

func Start

func Start(input Input) (*Initiator, []byte, error)

Start creates initiator state and message A.

func (*Initiator) Close added in v0.1.3

func (i *Initiator) Close() error

Close releases the persistent secret material held by the initiator state when an exchange is abandoned before Finish. Close is idempotent and nil-safe; calling Close on a nil *Initiator returns nil. Copies of an Initiator share the same terminal state, so closing one copy closes them all.

func (*Initiator) Finish

func (i *Initiator) Finish(messageB []byte) ([]byte, *Session, error)

Finish consumes message B, verifies the responder confirmation tag, and returns message C plus an authenticated session. The initiator state is consumed even when message parsing or confirmation fails.

Example (ConfirmationFailure)
package main

import (
	"errors"
	"fmt"

	"github.com/the-sarge/cpace"
)

func main() {
	initCfg := exampleInitiatorInput("example-confirmation-failure")
	initCfg.LocalAssociatedData = []byte("client hello")
	initiator, msgA, err := cpace.Start(initCfg)
	if err != nil {
		panic(err)
	}
	defer closeExampleInitiator(initiator)

	respCfg := exampleResponderInput("example-confirmation-failure")
	respCfg.Context = []byte("different protocol context")
	respCfg.LocalAssociatedData = []byte("server hello")
	responder, msgB, err := cpace.Respond(respCfg, msgA)
	if err != nil {
		panic(err)
	}
	defer closeExampleResponder(responder)

	_, _, err = initiator.Finish(msgB)
	fmt.Println(errors.Is(err, cpace.ErrConfirmationFailed))
}

func exampleInitiatorInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("client@example"),
		PeerID:    []byte("server@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleResponderInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("server@example"),
		PeerID:    []byte("client@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func closeExampleInitiator(initiator *cpace.Initiator) {
	if err := initiator.Close(); err != nil {
		panic(err)
	}
}

func closeExampleResponder(responder *cpace.Responder) {
	if err := responder.Close(); err != nil {
		panic(err)
	}
}
Output:
true

type Input added in v0.1.3

type Input struct {
	Password            []byte
	SelfID              []byte
	PeerID              []byte
	Context             []byte
	SessionID           []byte
	LocalAssociatedData []byte
	AllowEmptySessionID bool
}

Input contains the local inputs for one CPace role.

Password, SelfID, and PeerID must be non-empty. Context and LocalAssociatedData may be empty. Password, Context, and SessionID are shared session values both parties supply identically. SelfID, PeerID, and LocalAssociatedData are role-local values: Start treats SelfID as the initiator identity and PeerID as the responder identity; Respond treats SelfID as the responder identity and PeerID as the initiator identity. SessionID must be a fresh, non-secret, parties-agree-on value for every session. Empty SessionID values are rejected by default because they weaken replay and transcript separation properties. Set AllowEmptySessionID only for draft-21 compatibility tests or profiles that have deliberately accepted the weaker empty-sid behavior. Scalar randomness always comes from crypto/rand.Reader. Field lengths are capped at 4 KiB for Password and IDs, 1 KiB for Context and SessionID, and 64 KiB for LocalAssociatedData. Inputs exceeding these caps are rejected before copying; accepted byte slices are copied by Start and Respond before use.

type Responder

type Responder struct {
	// contains filtered or unexported fields
}

Responder is a single-use responder state returned by Respond.

func Respond

func Respond(input Input, messageA []byte) (*Responder, []byte, error)

Respond consumes message A, creates responder state, and returns message B. Message B includes the responder's explicit key-confirmation tag. A nil error from Respond does not authenticate the initiator; authentication is established only by successful Finish calls.

func (*Responder) Close added in v0.1.3

func (r *Responder) Close() error

Close releases the persistent secret material held by the responder state when an exchange is abandoned before Finish. Close is idempotent and nil-safe; calling Close on a nil *Responder returns nil. Copies of a Responder share the same terminal state, so closing one copy closes them all.

func (*Responder) Finish

func (r *Responder) Finish(messageC []byte) (*Session, error)

Finish consumes message C, verifies the initiator confirmation tag, and returns an authenticated session. The responder state is consumed even when message parsing or confirmation fails.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is an explicitly confirmed CPace session. Copies of a Session share the same close state and secret key material.

func (*Session) Close

func (s *Session) Close() error

Close releases the secret key material held by the Session. Close is idempotent and nil-safe; calling Close on a nil *Session returns nil. It performs best-effort in-memory key cleanup, but Go does not provide guaranteed secure memory erasure and the runtime or compiler may make additional copies. Non-secret metadata such as TranscriptID, PeerAssociatedData, and PeerID remains available after Close.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/the-sarge/cpace"
)

func main() {
	initSession, respSession, err := exampleConfirmedSessions("example-close")
	if err != nil {
		panic(err)
	}
	defer closeExampleSession(respSession)

	if err := initSession.Close(); err != nil {
		panic(err)
	}
	_, err = initSession.Export([]byte("application key"), nil, 32)

	fmt.Println(errors.Is(err, cpace.ErrSessionClosed))
	fmt.Println(len(initSession.TranscriptID()) > 0)
}

func exampleInitiatorInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("client@example"),
		PeerID:    []byte("server@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleResponderInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("server@example"),
		PeerID:    []byte("client@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleConfirmedSessions(sessionID string) (*cpace.Session, *cpace.Session, error) {
	initCfg := exampleInitiatorInput(sessionID)
	initCfg.LocalAssociatedData = []byte("client hello")
	initiator, msgA, err := cpace.Start(initCfg)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleInitiator(initiator)

	respCfg := exampleResponderInput(sessionID)
	respCfg.LocalAssociatedData = []byte("server hello")
	responder, msgB, err := cpace.Respond(respCfg, msgA)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleResponder(responder)

	msgC, initSession, err := initiator.Finish(msgB)
	if err != nil {
		return nil, nil, err
	}
	respSession, err := responder.Finish(msgC)
	if err != nil {
		_ = initSession.Close()
		return nil, nil, err
	}
	return initSession, respSession, nil
}

func closeExampleSession(session *cpace.Session) {
	if err := session.Close(); err != nil {
		panic(err)
	}
}

func closeExampleInitiator(initiator *cpace.Initiator) {
	if err := initiator.Close(); err != nil {
		panic(err)
	}
}

func closeExampleResponder(responder *cpace.Responder) {
	if err := responder.Close(); err != nil {
		panic(err)
	}
}
Output:
true
true

func (*Session) Export

func (s *Session) Export(label, context []byte, length int) ([]byte, error)

Export derives deterministic application key material from the confirmed ISK using HKDF-SHA512. The label and context are prefix-free encoded into HKDF info. Export output is not fresh randomness or a randomness pool; use separate, domain-specific labels and contexts for each application purpose. length must be in the range [0, 16320] (255 * 64, the HKDF-SHA512 maximum). A length of zero returns a result with length 0; callers must not distinguish nil from empty output for this case. Negative values and values exceeding the maximum are rejected with a wrapped ErrInvalidInput.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/the-sarge/cpace"
)

func main() {
	initSession, respSession, err := exampleConfirmedSessions("example-export")
	if err != nil {
		panic(err)
	}
	defer closeExampleSession(initSession)
	defer closeExampleSession(respSession)

	trafficKey, err := initSession.Export([]byte("traffic key"), []byte("initiator to responder"), 32)
	if err != nil {
		panic(err)
	}
	headerKey, err := initSession.Export([]byte("header key"), []byte("initiator to responder"), 32)
	if err != nil {
		panic(err)
	}
	peerTrafficKey, err := respSession.Export([]byte("traffic key"), []byte("initiator to responder"), 32)
	if err != nil {
		panic(err)
	}

	fmt.Println(len(trafficKey))
	fmt.Println(bytes.Equal(trafficKey, headerKey))
	fmt.Println(bytes.Equal(trafficKey, peerTrafficKey))
}

func exampleInitiatorInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("client@example"),
		PeerID:    []byte("server@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleResponderInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("server@example"),
		PeerID:    []byte("client@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleConfirmedSessions(sessionID string) (*cpace.Session, *cpace.Session, error) {
	initCfg := exampleInitiatorInput(sessionID)
	initCfg.LocalAssociatedData = []byte("client hello")
	initiator, msgA, err := cpace.Start(initCfg)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleInitiator(initiator)

	respCfg := exampleResponderInput(sessionID)
	respCfg.LocalAssociatedData = []byte("server hello")
	responder, msgB, err := cpace.Respond(respCfg, msgA)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleResponder(responder)

	msgC, initSession, err := initiator.Finish(msgB)
	if err != nil {
		return nil, nil, err
	}
	respSession, err := responder.Finish(msgC)
	if err != nil {
		_ = initSession.Close()
		return nil, nil, err
	}
	return initSession, respSession, nil
}

func closeExampleSession(session *cpace.Session) {
	if err := session.Close(); err != nil {
		panic(err)
	}
}

func closeExampleInitiator(initiator *cpace.Initiator) {
	if err := initiator.Close(); err != nil {
		panic(err)
	}
}

func closeExampleResponder(responder *cpace.Responder) {
	if err := responder.Close(); err != nil {
		panic(err)
	}
}
Output:
32
false
true

func (*Session) PeerAssociatedData

func (s *Session) PeerAssociatedData() []byte

PeerAssociatedData returns the peer associated data that was bound into the confirmed exchange. The returned slice is a copy and remains available after Close.

func (*Session) PeerID

func (s *Session) PeerID() []byte

PeerID returns the caller-configured peer identity that was bound into CI and confirmed by the completed exchange. The value is copied from Input; it is not parsed from peer-controlled wire data. The returned slice is a copy and remains available after Close.

func (*Session) TranscriptID

func (s *Session) TranscriptID() []byte

TranscriptID returns the draft CPaceSidOutput value for the confirmed initiator-responder CPace transcript. It is not a complete channel binding for any outer version, suite, or application-protocol negotiation. TranscriptID remains available after Close.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/the-sarge/cpace"
)

func main() {
	initSession, respSession, err := exampleConfirmedSessions("example-transcript")
	if err != nil {
		panic(err)
	}
	defer closeExampleSession(initSession)
	defer closeExampleSession(respSession)

	fmt.Println(len(initSession.TranscriptID()))
	fmt.Println(bytes.Equal(initSession.TranscriptID(), respSession.TranscriptID()))
}

func exampleInitiatorInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("client@example"),
		PeerID:    []byte("server@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleResponderInput(sessionID string) cpace.Input {
	return cpace.Input{
		Password:  []byte("correct horse battery staple"),
		SelfID:    []byte("server@example"),
		PeerID:    []byte("client@example"),
		Context:   []byte("example protocol v1"),
		SessionID: []byte(sessionID),
	}
}

func exampleConfirmedSessions(sessionID string) (*cpace.Session, *cpace.Session, error) {
	initCfg := exampleInitiatorInput(sessionID)
	initCfg.LocalAssociatedData = []byte("client hello")
	initiator, msgA, err := cpace.Start(initCfg)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleInitiator(initiator)

	respCfg := exampleResponderInput(sessionID)
	respCfg.LocalAssociatedData = []byte("server hello")
	responder, msgB, err := cpace.Respond(respCfg, msgA)
	if err != nil {
		return nil, nil, err
	}
	defer closeExampleResponder(responder)

	msgC, initSession, err := initiator.Finish(msgB)
	if err != nil {
		return nil, nil, err
	}
	respSession, err := responder.Finish(msgC)
	if err != nil {
		_ = initSession.Close()
		return nil, nil, err
	}
	return initSession, respSession, nil
}

func closeExampleSession(session *cpace.Session) {
	if err := session.Close(); err != nil {
		panic(err)
	}
}

func closeExampleInitiator(initiator *cpace.Initiator) {
	if err := initiator.Close(); err != nil {
		panic(err)
	}
}

func closeExampleResponder(responder *cpace.Responder) {
	if err := responder.Close(); err != nil {
		panic(err)
	}
}
Output:
64
true

Jump to

Keyboard shortcuts

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