README

WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
W                                                                             G
W   This is an experimental implementation. It might change and be broken     G
W   in unexpected ways. I know you read this on a lot of docs, but this       G
W   so far is really only a weekend project. It's also not standardized.      G
W                                                                             G
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

filippo.io/cpace
----------------

filippo.io/cpace is a Go implementation of the CPace PAKE,
instantiated with the ristretto255 group.

Usage
=====

https://pkg.go.dev/filippo.io/cpace

Details
=======

This implementation is loosely based on draft-haase-cpace-01, with ristretto255
swapped in, and a sprinkle of HKDF.

Using a properly abstracted prime order group such as ristretto255 allows us to
ignore most of the complexity of the spec, and is an excellent case study for
the value of a tight group abstraction.

1. Since the group has prime order, we don't need cofactor clearing and we don't
need any low order point checks.

2. The group natively provides encoding, decoding, and map to group.
   
3. Since the decoding function only works for valid encodings of valid elements,
we don't need any wrong curve checks.

4. Equivalent elliptic curve points are abstracted away by encoding and
decoding, so we don't need to worry about the quadratic twist at all.

5. There is no x-coordinate arithmetic to account for, so there's no need to
operate on a group modulo negation or to ever clear the sign.

6. We can probably even skip the identity element check, but we do it anyway.

The salt/sid is under-specified, and it's unclear what properties it needs to
have (random? unpredictable? not controlled by a MitM?). This implementation
always has the initiator generate it randomly and send it to the peer, as
allowed by the I-D. This seems safer than letting the user decide; if the higher
level protocol has a session ID, it should be included in the CI as additional
data, ideally along with a full transcript of the protocol that led to the
selection of this PAKE.

Simply concatenating variable-length, possibly attacker controlled values as the
I-D suggests is dangerous. For example, the (idA, idB) pairs ("ax", "b") and
("a", "xb") would result equivalent. Instead, this implementation uses HKDF to
separate secret material, salt, and context, and a uint16-length prefixed
serialization for CI.

The API allows two possible misuses that maybe should be blocked, depending on
how severely they would break security guarantees: identities can be empty; and
A's state can be used multiple times with different B messages.

There should probably be a higher level API that also incorporates an HMAC
verifier, checking that the peer does indeed have the password and agree on the
context. Such API should withold the key from B until getting A's HMAC back one
RTT later, or only expose it through a loud documented method for 0.5-RTT data.

It would be interesting to provide a symmetric API, but it's unclear how the
salt would be selected (and see above about my uncertainty on its properties).

Flow diagram
============

    A, B: ci = "cpace-r255" || idA || idB || ad
    A:    salt = random_bytes()
    A:    a = random_scalar()
    A:    A = a * hashToGroup(HKDF(pw, salt, ci))
    A->B: salt, A
       B: b = random_scalar()
       B: B = b * hashToGroup(HKDF(pw, salt, ci))
       B: K_b = HMAC(salt || A || B, b * A)
    A<-B: B
    A:    K_a = HMAC(salt || A || B, a * B)
Expand ▾ Collapse ▴

Documentation

Overview

    Package cpace implements the CPace password authenticated key exchange (PAKE) instantiated with the ristretto255 group.

    PAKEs allow two peers to establish a shared secret key if they agree on a password or similar low-entropy value, without letting eavesdropping or machine-in-the-middle attackers make multiple attempts at guessing the password value. CPace is a balanced PAKE, meaning that both peers need to know the password plaintext.

    This implementation is loosely based on draft-haase-cpace-01.

    Example
    Output:
    
    keyA == keyB: true
    

    Index

    Examples

    Constants

    This section is empty.

    Variables

    This section is empty.

    Functions

    func Exchange

    func Exchange(password string, c *ContextInfo, msgA []byte) (msgB, key []byte, err error)

      Exchange executes a PAKE exchange authenticated by password, processing msgA generated by a peer with Start, and returns the shared secret key and msgB. msgB should be sent to the peer, to be processed by (*State).Finish.

      If the two peers agree on the password and ContextInfo, they will derive the same key. Note that an error is NOT returned otherwise: the two peers will simply derive different keys.

      The returned key is suitable to be passed to hkdf.Expand.

      Types

      type ContextInfo

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

        ContextInfo captures the additional connection information that the two peers need to agree on for the key to be the same.

        func NewContextInfo

        func NewContextInfo(idA, idB string, ad []byte) *ContextInfo

          NewContextInfo returns a ContextInfo for use with Start or Exchange.

          idA represents the identity of the party that uses Start, idB of the party that uses Exchange. Identities could be MAC addresses, or IPs and ports.

          ad is any additional context the two parties share, and can be nil. Examples of values that could be included in ad to protect against protocol downgrade and mismatch attacks are the name and transcript of the higher level protocol, including any negotiation inputs that led to the use of this PAKE.

          type State

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

            State is a PAKE session in progress, where the initiating party is waiting for the peer response.

            func Start

            func Start(password string, c *ContextInfo) (msgA []byte, s *State, err error)

              Start initiates a new PAKE exchange authenticated by password. msgA should be sent to the peer, to be processed by Exchange, and s used to process the peer's response.

              func (*State) Finish

              func (s *State) Finish(msgB []byte) (key []byte, err error)

                Finish processes the peer's response, generated by Exchange, and returns the shared secret key.

                If the two peers agree on the password and ContextInfo, they will derive the same key. Note that an error is NOT returned otherwise: the two peers will simply derive different keys.

                The returned key is suitable to be passed to hkdf.Expand.

                Source Files