Commit 86deda05 authored by Sascha Steinbiss's avatar Sascha Steinbiss

New upstream version 0.0~git20161015.0.744856d

parents
export GO_CURRENT_PROJECT=otr3
export GO_CURRENT_PACKAGE=github.com/twstrike
export GLOBAL_GOPATH=$HOME/.gopath
export LOCAL_GOPATH=$HOME/.gopkgs/$GO_CURRENT_PROJECT
export GOPATH=$LOCAL_GOPATH:$GLOBAL_GOPATH
PATH_add $LOCAL_GOPATH/bin
mkdir -p $LOCAL_GOPATH
mkdir -p $LOCAL_GOPATH/src
mkdir -p $LOCAL_GOPATH/pkg
mkdir -p $LOCAL_GOPATH/bin
mkdir -p $LOCAL_GOPATH/src/$GO_CURRENT_PACKAGE
language: go
go:
- tip
- 1.6
- 1.5.3
- 1.4.3
matrix:
allow_failures:
- go: tip
addons:
apt:
packages:
- automake
- libtool
- libgcrypt11-dev
- libgpg-error-dev
install: make deps
script: make ci
cache:
apt:
Fan Jiang (fanjiang@thoughtworks.com)
Iván Pazmiño (ipazmino@thoughtworks.com)
Ola Bini (obini@thoughtworks.com)
Reinaldo De Souza Junior (rjunior@thoughtworks.com)
Tania Silva (tsilva@thoughtworks.com)
This diff is collapsed.
GO_VERSION=$(shell go version | grep -o 'go[[:digit:]]\.[[:digit:]]')
default: deps lint test
lint:
ifeq ($(GO_VERSION), go1.3)
echo "Your version of Go is too old for running lint"
else
ifeq ($(GO_VERSION), go1.4)
echo "Your version of Go is too old for running lint"
else
golint ./...
endif
endif
test:
go test -cover -v ./...
test-slow:
make -C ./compat libotr-compat
ci: lint test test-slow
deps:
ifeq ($(GO_VERSION), go1.3)
else
ifeq ($(GO_VERSION), go1.4)
else
go get github.com/golang/lint/golint
endif
endif
go get golang.org/x/tools/cmd/cover
cover:
go test . -coverprofile=coverage.out
go tool cover -html=coverage.out
# OTR3 [![Build Status](https://travis-ci.org/twstrike/otr3.svg?branch=master)](https://travis-ci.org/twstrike/otr3)
Implements version 3 of the OTR standard. Implements feature parity with libotr 4.1.0.
## API Documentation
[![GoDoc](https://godoc.org/github.com/twstrike/otr3?status.svg)](https://godoc.org/github.com/twstrike/otr3)
## Developing
Before doing any work, if you want to separate out your GOPATH from other projects, install direnv
```
$ brew update
$ brew install direnv
$ echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
```
Then, create a symbolic link to the OTR3 repository
```
ln -s /PathToMyGoPackages/.gopkgs/otr3/src/github.com/twstrike/ .
```
Install all dependencies:
``
./deps.sh
``
# Security Assumptions and Caveats
Here we list all the assumptions and caveats from a security perspective for the code in OTR3.
1. This code has not been audited, and there are no guarantees that it will fulfill the security properties of the OTR protocol.
2. Zeroing `byte` slices wipes the value from memory in the Golang VM.
3. `byte` slices and `big.Int` instances are not likely to be copied to other places in memory by the Golang GC.
4. Assigning 0 to a `big.Int` wipes the previous value from memory.
5. Modular exponentiation and other similar `big.Int` operations don't leak enough timing information to be useful for side channel attacks. (Or OTR provides enough blinding to counter act this). The libotr implementation uses MPIs from libgcrypt, that seem to be implemented in a similar manner to `big.Int` operations.
package otr3
import (
"crypto/hmac"
"crypto/subtle"
"io"
"math/big"
"time"
)
type ake struct {
secretExponent *big.Int
ourPublicValue *big.Int
theirPublicValue *big.Int
// TODO: why this number here?
r [16]byte
encryptedGx []byte
// SIZE: this should always be version.hash2Length
xhashedGx []byte
revealKey akeKeys
sigKey akeKeys
state authState
keys keyManagementContext
lastStateChange time.Time
}
func (c *Conversation) ensureAKE() {
if c.ake != nil {
return
}
c.initAKE()
}
func (c *Conversation) initAKE() {
c.ake = &ake{
state: authStateNone{},
}
}
func (c *Conversation) calcAKEKeys(s *big.Int) {
c.ssid, c.ake.revealKey, c.ake.sigKey = calculateAKEKeys(s, c.version)
}
func (c *Conversation) setSecretExponent(val *big.Int) {
c.ake.secretExponent = new(big.Int).Set(val)
c.ake.ourPublicValue = modExp(g1, val)
}
func (c *Conversation) calcDHSharedSecret() *big.Int {
return modExp(c.ake.theirPublicValue, c.ake.secretExponent)
}
func (c *Conversation) generateEncryptedSignature(key *akeKeys) ([]byte, error) {
verifyData := appendAll(c.ake.ourPublicValue, c.ake.theirPublicValue, c.ourCurrentKey.PublicKey(), c.ake.keys.ourKeyID)
mb := sumHMAC(key.m1, verifyData, c.version)
xb, err := c.calcXb(key, mb)
if err != nil {
return nil, err
}
return appendData(nil, xb), nil
}
func appendAll(one, two *big.Int, publicKey PublicKey, keyID uint32) []byte {
return appendWord(append(appendMPI(appendMPI(nil, one), two), publicKey.serialize()...), keyID)
}
func fixedSize(s int, v []byte) []byte {
if len(v) < s {
vv := make([]byte, s)
copy(vv, v)
return vv
}
return v
}
func (c *Conversation) calcXb(key *akeKeys, mb []byte) ([]byte, error) {
xb := c.ourCurrentKey.PublicKey().serialize()
xb = appendWord(xb, c.ake.keys.ourKeyID)
sigb, err := c.ourCurrentKey.Sign(c.rand(), mb)
if err == io.ErrUnexpectedEOF {
return nil, errShortRandomRead
}
if err != nil {
return nil, err
}
// this error can't happen, since key.c is fixed to the correct size
xb, _ = encrypt(fixedSize(c.version.keyLength(), key.c), append(xb, sigb...))
return xb, nil
}
// dhCommitMessage = bob = x
// Bob ---- DH Commit -----------> Alice
func (c *Conversation) dhCommitMessage() ([]byte, error) {
c.initAKE()
c.ake.keys.ourKeyID = 0
// TODO: where does this 40 come from?
x, err := c.randMPI(make([]byte, 40))
if err != nil {
return nil, err
}
c.setSecretExponent(x)
wipeBigInt(x)
if err := c.randomInto(c.ake.r[:]); err != nil {
return nil, err
}
// this can't return an error, since ake.r is of a fixed size that is always correct
c.ake.encryptedGx, _ = encrypt(c.ake.r[:], appendMPI(nil, c.ake.ourPublicValue))
return c.serializeDHCommit(c.ake.ourPublicValue), nil
}
func (c *Conversation) serializeDHCommit(public *big.Int) []byte {
dhCommitMsg := dhCommit{
encryptedGx: c.ake.encryptedGx,
yhashedGx: c.version.hash2(appendMPI(nil, public)),
}
return dhCommitMsg.serialize()
}
// dhKeyMessage = alice = y
// Alice -- DH Key --------------> Bob
func (c *Conversation) dhKeyMessage() ([]byte, error) {
c.initAKE()
// TODO: where does this 40 come from?
y, err := c.randMPI(make([]byte, 40)[:])
if err != nil {
return nil, err
}
c.setSecretExponent(y)
wipeBigInt(y)
return c.serializeDHKey(), nil
}
func (c *Conversation) serializeDHKey() []byte {
dhKeyMsg := dhKey{
gy: c.ake.ourPublicValue,
}
return dhKeyMsg.serialize()
}
// revealSigMessage = bob = x
// Bob ---- Reveal Signature ----> Alice
func (c *Conversation) revealSigMessage() ([]byte, error) {
c.calcAKEKeys(c.calcDHSharedSecret())
c.ake.keys.ourKeyID++
encryptedSig, err := c.generateEncryptedSignature(&c.ake.revealKey)
if err != nil {
return nil, err
}
macSig := sumHMAC(c.ake.revealKey.m2, encryptedSig, c.version)
revealSigMsg := revealSig{
r: c.ake.r,
encryptedSig: encryptedSig,
macSig: macSig,
}
return revealSigMsg.serialize(c.version), nil
}
// sigMessage = alice = y
// Alice -- Signature -----------> Bob
func (c *Conversation) sigMessage() ([]byte, error) {
c.ake.keys.ourKeyID++
encryptedSig, err := c.generateEncryptedSignature(&c.ake.sigKey)
if err != nil {
return nil, err
}
macSig := sumHMAC(c.ake.sigKey.m2, encryptedSig, c.version)
sigMsg := sig{
encryptedSig: encryptedSig,
macSig: macSig,
}
return sigMsg.serialize(c.version), nil
}
// processDHCommit = alice = y
// Bob ---- DH Commit -----------> Alice
func (c *Conversation) processDHCommit(msg []byte) error {
dhCommitMsg := dhCommit{}
err := dhCommitMsg.deserialize(msg)
if err != nil {
return err
}
c.ake.encryptedGx = dhCommitMsg.encryptedGx
c.ake.xhashedGx = dhCommitMsg.yhashedGx
return err
}
// processDHKey = bob = x
// Alice -- DH Key --------------> Bob
func (c *Conversation) processDHKey(msg []byte) (isSame bool, err error) {
dhKeyMsg := dhKey{}
err = dhKeyMsg.deserialize(msg)
if err != nil {
return false, err
}
if !isGroupElement(dhKeyMsg.gy) {
return false, newOtrError("DH value out of range")
}
//If receive same public key twice, just retransmit the previous Reveal Signature
if c.ake.theirPublicValue != nil {
isSame = eq(c.ake.theirPublicValue, dhKeyMsg.gy)
return
}
c.ake.theirPublicValue = dhKeyMsg.gy
return
}
// processRevealSig = alice = y
// Bob ---- Reveal Signature ----> Alice
func (c *Conversation) processRevealSig(msg []byte) (err error) {
revealSigMsg := revealSig{}
err = revealSigMsg.deserialize(msg, c.version)
if err != nil {
return
}
r := revealSigMsg.r[:]
theirMAC := revealSigMsg.macSig
encryptedSig := revealSigMsg.encryptedSig
decryptedGx := make([]byte, len(c.ake.encryptedGx))
if err = decrypt(r, decryptedGx, c.ake.encryptedGx); err != nil {
return
}
if err = checkDecryptedGx(decryptedGx, c.ake.xhashedGx, c.version); err != nil {
return
}
if c.ake.theirPublicValue, err = extractGx(decryptedGx); err != nil {
return
}
c.calcAKEKeys(c.calcDHSharedSecret())
if err = c.processEncryptedSig(encryptedSig, theirMAC, &c.ake.revealKey); err != nil {
return newOtrError("in reveal signature message: " + err.Error())
}
return nil
}
// processSig = bob = x
// Alice -- Signature -----------> Bob
func (c *Conversation) processSig(msg []byte) (err error) {
sigMsg := sig{}
err = sigMsg.deserialize(msg)
if err != nil {
return
}
theirMAC := sigMsg.macSig
encryptedSig := sigMsg.encryptedSig
if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.ake.sigKey); err != nil {
return newOtrError("in signature message: " + err.Error())
}
return nil
}
func (c *Conversation) checkedSignatureVerification(mb, sig []byte) error {
rest, ok := c.theirKey.Verify(mb, sig)
if !ok {
return newOtrError("bad signature in encrypted signature")
}
if len(rest) > 0 {
return errCorruptEncryptedSignature
}
return nil
}
func verifyEncryptedSignatureMAC(encryptedSig []byte, theirMAC []byte, keys *akeKeys, v otrVersion) error {
tomac := appendData(nil, encryptedSig)
myMAC := sumHMAC(keys.m2, tomac, v)[:v.truncateLength()]
if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
return newOtrError("bad signature MAC in encrypted signature")
}
return nil
}
func (c *Conversation) parseTheirKey(key []byte) (sig []byte, keyID uint32, err error) {
var rest []byte
var ok1 bool
rest, ok1, c.theirKey = ParsePublicKey(key)
sig, keyID, ok2 := extractWord(rest)
if !ok1 || !ok2 {
return nil, 0, errCorruptEncryptedSignature
}
return
}
func (c *Conversation) expectedMessageHMAC(keyID uint32, keys *akeKeys) []byte {
verifyData := appendAll(c.ake.theirPublicValue, c.ake.ourPublicValue, c.theirKey, keyID)
return sumHMAC(keys.m1, verifyData, c.version)
}
func (c *Conversation) processEncryptedSig(encryptedSig []byte, theirMAC []byte, keys *akeKeys) error {
if err := verifyEncryptedSignatureMAC(encryptedSig, theirMAC, keys, c.version); err != nil {
return err
}
decryptedSig := encryptedSig
if err := decrypt(fixedSize(c.version.keyLength(), keys.c), decryptedSig, encryptedSig); err != nil {
return err
}
sig, keyID, err := c.parseTheirKey(decryptedSig)
if err != nil {
return err
}
mb := c.expectedMessageHMAC(keyID, keys)
if err := c.checkedSignatureVerification(mb, sig); err != nil {
return err
}
c.ake.keys.theirKeyID = keyID
return nil
}
func extractGx(decryptedGx []byte) (*big.Int, error) {
newData, gx, ok := extractMPI(decryptedGx)
if !ok || len(newData) > 0 {
return gx, newOtrError("gx corrupt after decryption")
}
if !isGroupElement(gx) {
return gx, newOtrError("DH value out of range")
}
return gx, nil
}
func sumHMAC(key, data []byte, v otrVersion) []byte {
mac := hmac.New(v.hash2Instance, key)
mac.Write(data)
return mac.Sum(nil)
}
func checkDecryptedGx(decryptedGx, hashedGx []byte, v otrVersion) error {
digest := v.hash2(decryptedGx)
if subtle.ConstantTimeCompare(digest[:], hashedGx[:]) == 0 {
return newOtrError("bad commit MAC in reveal signature message")
}
return nil
}
This diff is collapsed.
package otr3
import (
"bytes"
"time"
)
const minimumMessageLength = 3 // length of protocol version (SHORT) and message type (BYTE)
func (c *Conversation) generateNewDHKeyPair() error {
return c.keys.generateNewDHKeyPair(c.rand())
}
func (c *Conversation) akeHasFinished() error {
c.keys.wipe()
c.keys = c.ake.keys
c.ake.wipe(false)
previousMsgState := c.msgState
c.lastMessageStateChange = time.Now()
c.msgState = encrypted
defer c.signalSecurityEventIf(previousMsgState != encrypted, GoneSecure)
defer c.signalSecurityEventIf(previousMsgState == encrypted, StillSecure)
if c.ourCurrentKey.PublicKey().IsSame(c.theirKey) {
c.messageEvent(MessageEventMessageReflected)
}
return c.generateNewDHKeyPair()
}
func (c *Conversation) processAKE(msgType byte, msg []byte) (toSend []messageWithHeader, err error) {
c.ensureAKE()
var toSendSingle messageWithHeader
var toSendExtra []messageWithHeader
switch msgType {
case msgTypeDHCommit:
c.ake.state, toSendSingle, err = c.ake.state.receiveDHCommitMessage(c, msg)
case msgTypeDHKey:
c.ake.state, toSendSingle, err = c.ake.state.receiveDHKeyMessage(c, msg)
case msgTypeRevealSig:
c.ake.state, toSendSingle, err = c.ake.state.receiveRevealSigMessage(c, msg)
toSendExtra, _ = c.maybeRetransmit()
case msgTypeSig:
c.ake.state, toSendSingle, err = c.ake.state.receiveSigMessage(c, msg)
toSendExtra, _ = c.maybeRetransmit()
default:
err = newOtrErrorf("unknown message type 0x%X", msgType)
}
c.ake.lastStateChange = time.Now()
messages := append([]messageWithHeader{toSendSingle}, toSendExtra...)
toSend = compactMessagesWithHeader(messages...)
return
}
type authStateBase struct{}
type authStateNone struct{ authStateBase }
type authStateAwaitingDHKey struct{ authStateBase }
type authStateAwaitingRevealSig struct{ authStateBase }
type authStateAwaitingSig struct {
authStateBase
// revealSigMsg is only used to store the message so we can re-transmit it if needed
revealSigMsg messageWithHeader
}
type authState interface {
receiveDHCommitMessage(*Conversation, []byte) (authState, messageWithHeader, error)
receiveDHKeyMessage(*Conversation, []byte) (authState, messageWithHeader, error)
receiveRevealSigMessage(*Conversation, []byte) (authState, messageWithHeader, error)
receiveSigMessage(*Conversation, []byte) (authState, messageWithHeader, error)
identity() int
identityString() string
}
func (authStateBase) receiveDHCommitMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
return authStateNone{}.receiveDHCommitMessage(c, msg)
}
func (s authStateNone) receiveDHCommitMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
c.ake.wipe(true)
dhKeyMsg, err := c.dhKeyMessage()
if err != nil {
return s, nil, err
}
dhKeyMsg, err = c.wrapMessageHeader(msgTypeDHKey, dhKeyMsg)
if err != nil {
return s, nil, err
}
if err = c.processDHCommit(msg); err != nil {
return s, nil, err
}
return authStateAwaitingRevealSig{}, dhKeyMsg, nil
}
func (s authStateAwaitingRevealSig) receiveDHCommitMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
//As per spec, we forget the old DH-commit (received before we sent the DH-Key)
//and use this one, so we forget all the keys
c.ake.keys = c.ake.keys.wipeAndKeepRevealKeys()
c.ake.wipeGX()
if err := c.processDHCommit(msg); err != nil {
return s, nil, err
}
dhKeyMsg, err := c.wrapMessageHeader(msgTypeDHKey, c.serializeDHKey())
if err != nil {
return s, nil, err
}
return authStateAwaitingRevealSig{}, dhKeyMsg, nil
}
func (s authStateAwaitingDHKey) receiveDHCommitMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
newMsg, _, ok1 := extractData(msg)
_, theirHashedGx, ok2 := extractData(newMsg)
if !ok1 || !ok2 {
return s, nil, errInvalidOTRMessage
}
gxMPI := appendMPI(nil, c.ake.ourPublicValue)
hashedGx := c.version.hash2(gxMPI)
//If yours is the higher hash value:
//Ignore the incoming D-H Commit message, but resend your D-H Commit message.
if bytes.Compare(hashedGx[:], theirHashedGx) == 1 {
dhCommitMsg, err := c.wrapMessageHeader(msgTypeDHCommit, c.serializeDHCommit(c.ake.ourPublicValue))
if err != nil {
return s, nil, err
}
return authStateAwaitingRevealSig{}, dhCommitMsg, nil
}
//Otherwise:
//Forget your old gx value that you sent (encrypted) earlier, and pretend you're in AUTHSTATE_NONE; i.e. reply with a D-H Key Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.
//This is done as part of receiving a DHCommit message in AUTHSTATE_NONE
return authStateNone{}.receiveDHCommitMessage(c, msg)
}
func (s authStateNone) receiveDHKeyMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
return s, nil, nil
}
func (s authStateAwaitingRevealSig) receiveDHKeyMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
return s, nil, nil
}
func (s authStateAwaitingDHKey) receiveDHKeyMessage(c *Conversation, msg []byte) (authState, messageWithHeader, error) {
_, err := c.processDHKey(msg)
if err != nil {
return s, nil, err
}