Categorygithub.com/crypto-y/babble
modulepackage
0.0.0
Repository: https://github.com/crypto-y/babble.git
Documentation: pkg.go.dev

# README

Babble

Build Status codecov Go Report Card GoDoc

Babble is the Go implementation of the Noise Protocol Framework.

Being a framework, the essence is to have the ability to construct new protocols by applying any cryptographically secure functions. With extensibility in mind, babble makes it easy to add any new patterns, cipher functions, hash functions, and DH functions.

The current built-in components are summerized as follows,

  • DH curves: curve448, curve25519 and secp256k1.
  • Ciphers: ChaCha20-Poly1305 and AESGCM.
  • Hash functions: SHA256, SHA512, BLAKE2b and BLAKE2s.
  • Patterns: all the patterns defined here, with PSK mode supported.

Note: the current version doesn't implement the fallback mode.

Usage

To use, download the package,

go get -u "github.com/crypto-y/babble"

In addition to the main package babble, there are five packages which can be used for customization, see Extentable Components.

"github.com/crypto-y/babble/cipher"
"github.com/crypto-y/babble/dh"
"github.com/crypto-y/babble/hash"
"github.com/crypto-y/babble/rekey"
"github.com/crypto-y/babble/pattern"

WARNING: The Go's implementation of AESGCM might be vunerable to side channel attack, please read the documentation if you plan to use it.

There are two methods can be used for contructing new handshake state, NewProtocol and NewProtocolWithConfig.

NewProtocol

func NewProtocol(name, prologue string, initiator bool) (*HandshakeState, error)

The NewProtocol is used for quickly creating a new HandshakeState using the name and prologue, along with an initiator specifying whether the caller is an initiator or a responder.

// creates a new handshake state using pattern NN, curve 25519, cipher
// ChaChaPoly and hash function BLAKE2s.
p, _ := babble.NewProtocol("Noise_NN_25519_ChaChaPoly_BLAKE2s", "Demo", true)

This function uses a default rekeyer, which rotates the cipher key every 10000 encryption/decription and resets the nonce to be zero, read this documentation for detailed specs.

While it's convenient to create a protocol with three parameters, it's not achieved without a cost, in which only a limited number of patterns are supported when using NewProtocol. In particular,

  • The pre-message pattern is not supported, as it requires either one or both static public keys to be known before the handshake.
  • Pre-shared symmetric key, PSK, is not supported, as it required both parties to specify a PSK before the handshake.
// returns an error if the NewProtocol is used with pattern which has
// pre-message or psks.
_, err := babble.NewProtocol("Noise_N_25519_ChaChaPoly_BLAKE2s", "Demo", true)
fmt.Println(err)  // missing key: remote static key

_, err := babble.NewProtocol("Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s", "Demo", true)
fmt.Println(err)  // psk mode: expected to have 1 psks, got 0

Patterns supported using NewProtocol,

  • NN
  • NX, NX1
  • XN, X1N
  • IN, I1N
  • XX, X1X, XX1, X1X1
  • IX, I1X, IX1, I1X1

For full support, use NewProtocolWithConfig instead.

NewProtocolWithConfig

func NewProtocolWithConfig(config *ProtocolConfig) (*HandshakeState, error)

NewProtocolWithConfig takes a *ProtocolConfig to create a handshake state.

// create a config first
cfg := &babble.ProtocolConfig{
    Name: "Noise_NN_25519_ChaChaPoly_BLAKE2s",
    Initiator: true,
    Prologue: "Demo",
}

// use the config to construct a handshake state.
// this is equivalent of calling
// NewProtocol("Noise_NN_25519_ChaChaPoly_BLAKE2s", "Demo", true)
p, _ := babble.NewProtocolWithConfig(cfg)

Specifying a local static private key and a remote static public key,

// decode hex into binary, note that the local static key(s) is a private key, and the remote static key(rs) is a public key.
s, _ := hex.DecodeString(
  "a8abababababababababababababababababababababababababababababab6b")
rs, _ := hex.DecodeString(
  "c3c637648530e306e1115428acc44d0f0502615ee23ec1de0e59c5a148e9a30d")

cfg := &babble.ProtocolConfig{
    Name:            "Noise_KK_25519_ChaChaPoly_BLAKE2s",
    Initiator:       true,
    Prologue:        "Demo",
    LocalStaticPriv: s,
    RemoteStaticPub: rs,
}
p, _ := babble.NewProtocolWithConfig(cfg)

Specifying PSKs,

// decode hex into binary
psk0, _ := hex.DecodeString(
  "c3c637648530e306e1115428acc44d0f0502615ee23ec1de0e59c5a148e9a30d")
psk1, _ := hex.DecodeString(
  "2d0326b5ea11ba9330949dc4e816735615d718551aa9e777f25941c95d7899eb")
// put psks into a slice
psks := [][]byte{psk0, psk1}

cfg := &babble.ProtocolConfig{
    Name: "Noise_NNpsk0+psk1_25519_ChaChaPoly_BLAKE2s",
    Initiator: true,
    Prologue: "Demo",
    Psks: psks,
}
p, _:= babble.NewProtocolWithConfig(cfg)

Specifying default Rekey behavior,

// This config will set the rekeyer to rotate the cipher key every
// 1000 messages and won't reset the nonce.
rkCfg := &babble.DefaultRekeyerConfig{
    Interval: 1000,
    ResetNonce: false,
}

cfg := &babble.ProtocolConfig{
    Name: "Noise_NN_25519_ChaChaPoly_BLAKE2s",
    Initiator: true,
    Prologue: "Demo",
    RekeyerConfig: rkCfg,
}
p, _ := babble.NewProtocolWithConfig(cfg)

You can also specify a customized rekeyer by defining your own rules on when and how the cipher key should be reset. Read this documentation for more details.

Check here for the full list of parameters in the ProtocolConfig.

GetInfo

Once created, the GetInfo method can be handy to monitor the internal state of the current handshake.

// GetInfo will return the internal state info of the handshake
info, _ := p.GetInfo()
fmt.Printf("%s", info)

which prints the following result,

{
	"chaining_key": "38a1b63073db5d5a3a4007b51e83c41598ea2f67e2389e121c56f3a1462d98aa",
	"cipher_key": "0000000000000000000000000000000000000000000000000000000000000000",
	"digest": "ec62085a3ed4240d70240150cc3f98a170fd781cf11c151c65776b04e67be173",
	"finished": false,
	"initiator": true,
	"key_pair": {
		"local_static_priv": "",
		"local_static_pub": "",
		"local_ephemeral_priv": "",
		"local_ephemeral_pub": "",
		"remote_ephemeral_pub": "",
		"remote_static_pub": ""
	},
	"nonce": 0,
	"pattern": {
		"psk_mode": {
			"mode": false,
			"psks": {}
		},
		"name": "NN",
		"pre_message": {},
		"message": {
			"0": "->, e",
			"1": "<-, e, ee"
		},
		"index_processed": -1
	},
	"prologue": "Demo",
	"send_cipher": {
		"key": "",
		"nonce": 0
	},
	"recv_cipher": {
		"key": "",
		"nonce": 0
	},
	"rekey": {
		"interval": 10000,
		"reset_nonce": true
	}
}

Performing handshakes

The following code gives an example for how two participants, Alice and Bob, performs a handshake using the handshake pattern NN.

// Pattern used here is NN,
// -> e,
// <- e, ee

// alice is the initiator
alice, _ := babble.NewProtocol("Noise_NN_25519_ChaChaPoly_BLAKE2s", "Demo", true)
// bob is the responder
bob, _ := babble.NewProtocol("Noise_NN_25519_ChaChaPoly_BLAKE2s", "Demo", false)

// alice writes the first message, -> e
ciphertext, err := alice.WriteMessage(nil)
if err != nil {
    fmt.Println("alice: -> e, gives an error", err)
}
// bob reads the first message, ->
_, err = bob.ReadMessage(ciphertext)
if err != nil {
    fmt.Println("bob: -> e, gives an error", err)
}

// bob writes the second message, <- e, ee
ciphertext, err = bob.WriteMessage(nil)
if err != nil {
    fmt.Println("bob: <- e, ee, gives an error", err)
}
// alice reads the second message, <- e, ee
_, err = alice.ReadMessage(ciphertext)
if err != nil {
    fmt.Println("alice: <- e, ee, gives an error", err)
}

// the handshake is finished, we can verify that,
fmt.Println("alice's handshake is finished: ", alice.Finished())
fmt.Println("bob's handshake is finished: ", bob.Finished())

The full example can be found at examples/handshake.

Extentable Components

Aside from the built-in components, it's pretty straightforward to add new components to the framework using the Register method defined in each component's package. For instance, to add a new pattern,

import "github.com/crypto-y/babble/pattern"

// Register a dumb pattern
name := "YY"
rawPattern := `
  -> e
  <- e, ee, es`

// Register will validate the pattern, if invalid, an error is returned.
_ := pattern.Register(name, rawPattern)

// Now "YY" is a valid pattern name, and it can be used in the protocol name as,
p, _ := babble.NewProtocol("Noise_YY_25519_ChaChaPoly_BLAKE2s", "Demo", true)

You can check the package documentation for details on how to implement new components.

  • Cipher. To add a new cipher, implement the AEAD interface.
  • Hash. To add a new hash, implement the Hash interface.
  • DH. To add a new DHKE, implement the Curve interface.
  • Pattern. To add a new pattern, simply provide the pattern in string.

Vector tests

See vector documentation for more details.

# Packages

Package cipher implements the cipher functions specified in the noise protocol.
Package dh implements the DH functions specified in the noise protocol.
No description provided by the author
Package hash implements the hash functions specified in the noise protocol.
Package pattern implements the noise handshake pattern.
Package rekey defines the rekey functions to be used in the babbel package.
Package vectors is created to help the vector test.

# Functions

NewProtocol creates a new handshake state with the specified name prologue, and initiator.
NewProtocolWithConfig creates a handshake state with parameters from a ProtocolConfig.

# Constants

CipherKeySize defines the byte length of the key used for cipher.
NoisePrefix is the mandatory prefix defined by the noise protocol framework.

# Variables

ErrInvalidRekeyInterval is returned when interval is 0.
ErrMissingConfig is returned when no config file is provided.
ErrProtocolInvalidName is returned when protocol name is wrong.
ZEROLEN is a zero-length byte sequence.
ZEROS is a 32-byte array filled with zeros.

# Structs

CipherState contains key and nonce variables, which it uses to encrypt and decrypt ciphertext.
DefaultRekeyerConfig is used for creating the default rekey manager.
HandshakeState object contains a symmetricState plus DH variables (s, e, rs, re) and a variable representing the handshake pattern.
ProtocolConfig is used for constructing a new handshake state.