blob: 8226264824d703f1bdb3a726f0a9dd4864194ff3 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package crypto
import (
"bytes"
"encoding/binary"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/salsa20/salsa"
"v.io/v23/verror"
)
// cbox implements a ControlCipher using go.crypto/nacl/box.
type cbox struct {
sharedKey [32]byte
channelBinding []byte
enc cboxStream
dec cboxStream
}
// cboxStream implements one stream of encryption or decryption.
type cboxStream struct {
counter uint64
nonce [24]byte
// buffer is a temporary used for in-place crypto.
buffer []byte
}
const (
cboxMACSize = box.Overhead
)
const pkgPath = "v.io/x/ref/runtime/internal/flow/crypto"
func reg(id, msg string) verror.IDAction {
return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
}
type BoxKey [32]byte
var (
// These errors are intended to be used as arguments to higher
// level errors and hence {1}{2} is omitted from their format
// strings to avoid repeating these n-times in the final error
// message visible to the user.
errMessageTooShort = reg(".errMessageTooShort", "control cipher: message is too short")
)
func (s *cboxStream) alloc(n int) []byte {
if len(s.buffer) < n {
s.buffer = make([]byte, n*2)
}
return s.buffer[:0]
}
func (s *cboxStream) currentNonce() *[24]byte {
return &s.nonce
}
func (s *cboxStream) advanceNonce() {
s.counter++
binary.LittleEndian.PutUint64(s.nonce[:], s.counter)
}
// setupXSalsa20 produces a sub-key and Salsa20 counter given a nonce and key.
//
// See, "Extending the Salsa20 nonce," by Daniel J. Bernsten, Department of
// Computer Science, University of Illinois at Chicago, 2008.
func setupXSalsa20(subKey *[32]byte, counter *[16]byte, nonce *[24]byte, key *[32]byte) {
// We use XSalsa20 for encryption so first we need to generate a
// key and nonce with HSalsa20.
var hNonce [16]byte
copy(hNonce[:], nonce[:])
salsa.HSalsa20(subKey, &hNonce, key, &salsa.Sigma)
// The final 8 bytes of the original nonce form the new nonce.
copy(counter[:], nonce[16:])
}
// NewControlCipher returns a ControlCipher for RPC versions greater than or equal to 11.
func NewControlCipherRPC11(myPublicKey, myPrivateKey, theirPublicKey *BoxKey) ControlCipher {
var c cbox
box.Precompute(&c.sharedKey, (*[32]byte)(theirPublicKey), (*[32]byte)(myPrivateKey))
// The stream is full-duplex, and we want the directions to use different
// nonces, so we set bit (1 << 64) in one stream, and leave it cleared in
// the other server stream. advanceNone touches only the first 8 bytes,
// so this change is permanent for the duration of the stream.
if bytes.Compare(myPublicKey[:], theirPublicKey[:]) < 0 {
c.enc.nonce[8] = 1
c.channelBinding = append(myPublicKey[:], theirPublicKey[:]...)
} else {
c.dec.nonce[8] = 1
c.channelBinding = append(theirPublicKey[:], myPublicKey[:]...)
}
return &c
}
// MACSize implements the ControlCipher method.
func (c *cbox) MACSize() int {
return cboxMACSize
}
// Seal implements the ControlCipher method.
func (c *cbox) Seal(data []byte) error {
n := len(data)
if n < cboxMACSize {
return verror.New(errMessageTooShort, nil)
}
tmp := c.enc.alloc(n)
nonce := c.enc.currentNonce()
out := box.SealAfterPrecomputation(tmp, data[:n-cboxMACSize], nonce, &c.sharedKey)
c.enc.advanceNonce()
copy(data, out)
return nil
}
// Open implements the ControlCipher method.
func (c *cbox) Open(data []byte) bool {
n := len(data)
if n < cboxMACSize {
return false
}
tmp := c.dec.alloc(n - cboxMACSize)
nonce := c.dec.currentNonce()
out, ok := box.OpenAfterPrecomputation(tmp, data, nonce, &c.sharedKey)
if !ok {
return false
}
c.dec.advanceNonce()
copy(data, out)
return true
}
// Encrypt implements the ControlCipher method.
func (c *cbox) Encrypt(data []byte) {
var subKey [32]byte
var counter [16]byte
nonce := c.enc.currentNonce()
setupXSalsa20(&subKey, &counter, nonce, &c.sharedKey)
c.enc.advanceNonce()
salsa.XORKeyStream(data, data, &counter, &subKey)
}
// Decrypt implements the ControlCipher method.
func (c *cbox) Decrypt(data []byte) {
var subKey [32]byte
var counter [16]byte
nonce := c.dec.currentNonce()
setupXSalsa20(&subKey, &counter, nonce, &c.sharedKey)
c.dec.advanceNonce()
salsa.XORKeyStream(data, data, &counter, &subKey)
}
func (c *cbox) ChannelBinding() []byte {
return c.channelBinding
}