Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemare authored and bytemare committed Nov 21, 2020
1 parent d986f82 commit a45864b
Show file tree
Hide file tree
Showing 8 changed files with 1,288 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

.idea
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CPace

CPace implements the CFRG recommended balanced Password Authentication Key Exchange.

**!!! WARNING : THIS IMPLEMENTATION IS PROOF OF CONCEPT AND BASED ON THE LATEST INTERNET DRAFT.
THERE ARE ABSOLUTELY NO WARRANTIES. !!!**

CPace allows two parties sharing a common secret or password to securely agree on a session key for secure communication.
It's a dead simple protocol with only two messages, yet state of the art key exchange based on a shared secret.

Note: The registration of the secret password is not in the scope of the protocol or this implementation.

# Get it

go get github.com/bytemare/cpace

# Use it

The API is really minimal and very easy to use.

- New() returns the structure through which the steps are made. It is the same struct type for the client and the server.
- Authenticate() takes a message and returns one. If this function returns a message, it should be sent to the peer.
- SessionKey() returns the fresh session key, if everything went fine.

Messages are of type message.Kex, from the companion pake package. These message have built-in methods for encoding and decoding to different formats like Gob and Json.

<details>
<summary>Example:</summary>

```
serverID := []byte("server")
username := []byte("client")
password := []byte("password")
var ad = []byte("myAuth")
// Set up the initiator, let's call it the client
client, err := New(pake.Initiator, username, serverID, password, nil, ad, nil)
if err != nil { panic(err) }
// Start the protocol.
// message1 must then be sent to the responder
message1, err := client.Authenticate(nil)
if err != nil { panic(err) }
// Set up the responder, let's call it the server
server, err := New(pake.Responder, serverID, username, password, nil, ad, nil)
if err != nil { panic(err) }
// Handle the initiator's message, and send back message2. At this point the session key can already be derived.
message2, err := server.Authenticate(message1)
if err != nil { panic(err) }
// Give the initiator the responder's answer. Since we're in implicit authentication, no message comes out here.
// After this, the initiator can derive the session key.
_, err = client.Authenticate(message2)
if err != nil { panic(err) }
```
</details>

# Under the hood

All cryptographic operations can be found in the pake package, which itself uses either the standard library or tested and proved external libraries.
The Ristretto255 group is used for the mathematical heavy lifting and performance.

Hash operations use SHA3 and Shake.

Default password key derivation is Argon2id.

# Deploy it

Don't, yet.

## Work on it

WIP
246 changes: 246 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Package cpace provides an easy to use CPace implementation
package cpace

import (
"errors"
"fmt"

"golang.org/x/crypto/cryptobyte"

"github.com/bytemare/cryptotools"
"github.com/bytemare/cryptotools/encoding"
"github.com/bytemare/cryptotools/hashtogroup"
"github.com/bytemare/cryptotools/utils"
"github.com/bytemare/pake"
"github.com/bytemare/pake/message"
)

const (
Protocol = "CPace"
Version = "0.0.0"
maxIDLength = 1<<16 - 1
)

var errInternalKexAssertion = errors.New("internal: something went wrong in type assertion to Kex message")

// Parameters groups a party's input parameters
type Parameters struct {
// ID is own identity
ID []byte

// PeerID identifies the remote peer
PeerID []byte

// Secret is the shared secret, e.g. the password
Secret []byte

// SID is the session identifier, a unique random byte array of at least 16 bytes
SID []byte

// AD
AD []byte

// Encoding specifies which encoding should be used for outgoing and incoming messages
Encoding encoding.Encoding
}

// CPace wraps the core CPace session and state info and enriches them with a more abstract API
type CPace struct {
session
state
}

func validateCI(id, peerID, ad []byte) error {
switch {
case len(id) > maxIDLength:
return errSetupLongID
case len(peerID) > maxIDLength:
return errSetupLongPeerID
case len(ad) > maxIDLength:
return errSetupLongAD
default:
return nil
}
}

func assembleCI(role pake.Role, id, peerID, ad []byte) ([]byte, error) {
ci := make([]byte, 0, len(id)+len(peerID)+len(ad))
b := cryptobyte.NewBuilder(ci)

switch role {
case pake.Initiator:
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(id) })
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(peerID) })
case pake.Responder:
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(peerID) })
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(id) })
default:
return nil, fmt.Errorf("assembleCI : %w", errInternalInvalidRole)
}

b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(ad) })

return b.BytesOrPanic(), nil
}

func buildDST(identifier hashtogroup.Ciphersuite, in int) []byte {
return []byte(fmt.Sprintf("%s%s-%d", Protocol, identifier, in))
}

func newCPace(role pake.Role, parameters *Parameters, csp *cryptotools.Parameters) (*CPace, error) {
if err := parameters.Encoding.Available(); err != nil {
return nil, err
}

// Verify session id and generate one for the initiator if none provided
switch l := len(parameters.SID); {
case l == 0:
// If none is given for the Responder, we'll take it from the initiator's first message
if role == pake.Initiator {
parameters.SID = utils.RandomBytes(minSidLength)
}
case l < minSidLength:
return nil, errSetupSIDTooShort
}

if err := validateCI(parameters.ID, parameters.PeerID, parameters.AD); err != nil {
return nil, err
}

ci, err := assembleCI(role, parameters.ID, parameters.PeerID, parameters.AD)
if err != nil {
return nil, err
}

// meta := pake.MetaData()

pakeCore, err := pake.KeyExchange.New(Protocol, Version, parameters.Encoding, csp, role, parameters.PeerID)
if err != nil {
return nil, err
}

return &CPace{
session: session{
role: role,
sid: parameters.SID,
cid: ci,
},
state: state{
password: parameters.Secret,
core: pakeCore,
dsi1: buildDST(pakeCore.Crypto.Parameters.Group, 1),
dsi2: buildDST(pakeCore.Crypto.Parameters.Group, 2),
},
}, nil
}

// Client returns a new CPace client instance
func Client(parameters *Parameters, csp *cryptotools.Parameters) (pake.Pake, error) {
return newCPace(pake.Initiator, parameters, csp)
}

// Server returns a new CPace server instance
func Server(parameters *Parameters, csp *cryptotools.Parameters) (pake.Pake, error) {
return newCPace(pake.Responder, parameters, csp)
}

// AuthenticateKex interprets the key exchange message and operates the consecutive steps of the protocol.
func (c *CPace) AuthenticateKex(m *message.Kex) (*message.Kex, error) {
switch c.role {
case pake.Initiator:
if c.core.Expect != message.StageResponse {
return nil, errInternalUnexpectedMessage
}

// We should only enter here when initialising the initiator, i.e. with a nil message
if m == nil {
// If own public element has already been set, it means we've already initiated the client
if c.publicElement != nil {
return nil, errInitReInit
}

// We want to start the protocol
elem, sid, err := c.initiate()
if err != nil {
return nil, err
}

return &message.Kex{
Element: elem,
Auth: sid,
}, nil
}

return nil, c.finish(m.Element)

case pake.Responder:
if c.core.Expect != message.StageStart {
return nil, errInternalUnexpectedMessage
}

if m == nil {
return nil, errRespNilMsg
}

elem, err := c.response(m.Element, m.Auth)
if err != nil {
return nil, err
}

return &message.Kex{
Element: elem,
Auth: nil,
}, nil
}

return nil, errInternalInvalidRole
}

// Authenticate decodes the payload as a Kex message and operates the consecutive steps of the protocol.
func (c *CPace) Authenticate(m []byte) ([]byte, error) {
kex, err := decodeKeyExchange(m, c.core.Encoding())
if err != nil {
return nil, errPeerEncoding // todo : should we wrap the error ?
}

r, err := c.AuthenticateKex(kex)
if err != nil {
return nil, err
}

// On success, the initiator returns nil
if r == nil {
return nil, nil
}

return r.Encode(c.core.Encoding())
}

// SessionKey returns the session's intermediary secret session key
func (c *CPace) SessionKey() []byte {
return c.iSessionKey
}

// EncodedParameters returns the 4-byte encoding of the ciphersuite parameters
func (c *CPace) EncodedParameters() cryptotools.CiphersuiteEncoding {
return c.core.Crypto.Parameters.Encode()
}

// decodeKeyExchange decodes messages in the simple Kex format, returns nil on nil message.
func decodeKeyExchange(m []byte, enc encoding.Encoding) (*message.Kex, error) {
if m == nil {
return nil, nil
}

k, err := enc.Decode(m, &message.Kex{})
if err != nil {
return nil, err
}

kex, ok := k.(*message.Kex)
if !ok {
return nil, errInternalKexAssertion
}

return kex, nil
}
Loading

0 comments on commit a45864b

Please sign in to comment.