sendafriend/smp.go

2069 lines
50 KiB
Go

/*
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//
// original: https://github.com/golang/crypto/tree/e3636079e1a4c1f337f212cc5cd2aca108f6c900/otr
//
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements the Socialist Millionaires Protocol as described in
// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html. The protocol
// specification is required in order to understand this code and, where
// possible, the variable names in the code match up with the spec.
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/dsa"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/subtle"
libpeer "cwtch.im/cwtch/peer"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"math/big"
"os"
"strconv"
)
func arr2tlv(data []byte) tlv {
var typ, len uint16
typ = (uint16(data[0]) << 8) + uint16(data[1])
len = (uint16(data[2]) << 8) + uint16(data[3])
return tlv{typ, len, data[4:]}
}
func tlv2arr(t tlv) []byte {
ret := make([]byte, 4+len(t.data))
ret[0] = byte(t.typ >> 8)
ret[1] = byte(t.typ & 0xFF)
ret[2] = byte(t.length >> 8)
ret[3] = byte(t.length & 0xFF)
for i := 0; i < len(t.data); i++ {
ret[4+i] = t.data[i]
}
return ret
}
func doSMP(longtermpeer libpeer.CwtchPeer, onion string, secret string, friendname string) {
var alice Conversation
secretInt := new(big.Int)
secretInt.SetBytes([]byte(secret))
alice.smp.secret = secretInt
peer, err := libpeer.NewCwtchPeer("doSMP", "doSMPpass", "")
if err != nil {
fmt.Println("couldn't create an ephemeral onion :(")
os.Exit(1)
}
processData := func(onion string, data []byte) []byte {
tlvPacket := arr2tlv(data)
out, complete, err := alice.processSMP(tlvPacket)
if tlvPacket.typ == 42 {
fmt.Printf("found them! adding %v <%s> to contact list\n", friendname, tlvPacket.data)
addContactEntry(longtermpeer, os.Args[2], string(tlvPacket.data))
longtermpeer.Save()
os.Exit(0)
}
if err != nil {
return nil
} else if complete {
finalPacket := tlv{42, uint16(len(longtermpeer.GetProfile().Onion)), []byte(longtermpeer.GetProfile().Onion)}
return tlv2arr(finalPacket)
}
return tlv2arr(out)
}
peer.SetPeerDataHandler(processData)
connection := peer.PeerWithOnion(onion)
tlvList := alice.startSMP("")
for i := range tlvList {
connection.SendPacket(tlv2arr(tlvList[i]))
}
}
type smpFailure string
func (s smpFailure) Error() string {
return string(s)
}
var smpFailureError = smpFailure("otr: SMP protocol failed")
var smpSecretMissingError = smpFailure("otr: mutual secret needed")
const smpVersion = 1
const (
smpState1 = iota
smpState2
smpState3
smpState4
)
type smpState struct {
state int
a2, a3, b2, b3, pb, qb *big.Int
g2a, g3a *big.Int
g2, g3 *big.Int
g3b, papb, qaqb, ra *big.Int
saved *tlv
secret *big.Int
question string
}
func (c *Conversation) startSMP(question string) (tlvs []tlv) {
if c.smp.state != smpState1 {
tlvs = append(tlvs, c.generateSMPAbort())
}
tlvs = append(tlvs, c.generateSMP1(question))
c.smp.question = ""
c.smp.state = smpState2
return
}
func (c *Conversation) resetSMP() {
c.smp.state = smpState1
c.smp.secret = nil
c.smp.question = ""
}
func (c *Conversation) processSMP(in tlv) (out tlv, complete bool, err error) {
data := in.data
switch in.typ {
case tlvTypeSMPAbort:
if c.smp.state != smpState1 {
err = smpFailureError
}
c.resetSMP()
return
case tlvTypeSMP1WithQuestion:
// We preprocess this into a SMP1 message.
nulPos := bytes.IndexByte(data, 0)
if nulPos == -1 {
err = errors.New("otr: SMP message with question didn't contain a NUL byte")
return
}
c.smp.question = string(data[:nulPos])
data = data[nulPos+1:]
}
numMPIs, data, ok := getU32(data)
if !ok || numMPIs > 20 {
err = errors.New("otr: corrupt SMP message")
return
}
mpis := make([]*big.Int, numMPIs)
for i := range mpis {
var ok bool
mpis[i], data, ok = getMPI(data)
if !ok {
err = errors.New("otr: corrupt SMP message")
return
}
}
switch in.typ {
case tlvTypeSMP1, tlvTypeSMP1WithQuestion:
if c.smp.state != smpState1 {
c.resetSMP()
out = c.generateSMPAbort()
return
}
if c.smp.secret == nil {
err = smpSecretMissingError
return
}
if err = c.processSMP1(mpis); err != nil {
return
}
c.smp.state = smpState3
out = c.generateSMP2()
case tlvTypeSMP2:
if c.smp.state != smpState2 {
c.resetSMP()
out = c.generateSMPAbort()
return
}
if out, err = c.processSMP2(mpis); err != nil {
out = c.generateSMPAbort()
return
}
c.smp.state = smpState4
case tlvTypeSMP3:
if c.smp.state != smpState3 {
c.resetSMP()
out = c.generateSMPAbort()
return
}
if out, err = c.processSMP3(mpis); err != nil {
return
}
c.smp.state = smpState1
c.smp.secret = nil
complete = true
case tlvTypeSMP4:
if c.smp.state != smpState4 {
c.resetSMP()
out = c.generateSMPAbort()
return
}
if err = c.processSMP4(mpis); err != nil {
out = c.generateSMPAbort()
return
}
c.smp.state = smpState1
c.smp.secret = nil
complete = true
default:
panic("unknown SMP message")
}
return
}
func (c *Conversation) calcSMPSecret(mutualSecret []byte, weStarted bool) {
h := sha256.New()
h.Write([]byte{smpVersion})
if weStarted {
h.Write(c.PrivateKey.PublicKey.Fingerprint())
h.Write(c.TheirPublicKey.Fingerprint())
} else {
h.Write(c.TheirPublicKey.Fingerprint())
h.Write(c.PrivateKey.PublicKey.Fingerprint())
}
h.Write(c.SSID[:])
h.Write(mutualSecret)
c.smp.secret = new(big.Int).SetBytes(h.Sum(nil))
}
func (c *Conversation) generateSMP1(question string) tlv {
var randBuf [16]byte
c.smp.a2 = c.randMPI(randBuf[:])
c.smp.a3 = c.randMPI(randBuf[:])
g2a := new(big.Int).Exp(g, c.smp.a2, p)
g3a := new(big.Int).Exp(g, c.smp.a3, p)
h := sha256.New()
r2 := c.randMPI(randBuf[:])
r := new(big.Int).Exp(g, r2, p)
c2 := new(big.Int).SetBytes(hashMPIs(h, 1, r))
d2 := new(big.Int).Mul(c.smp.a2, c2)
d2.Sub(r2, d2)
d2.Mod(d2, q)
if d2.Sign() < 0 {
d2.Add(d2, q)
}
r3 := c.randMPI(randBuf[:])
r.Exp(g, r3, p)
c3 := new(big.Int).SetBytes(hashMPIs(h, 2, r))
d3 := new(big.Int).Mul(c.smp.a3, c3)
d3.Sub(r3, d3)
d3.Mod(d3, q)
if d3.Sign() < 0 {
d3.Add(d3, q)
}
var ret tlv
if len(question) > 0 {
ret.typ = tlvTypeSMP1WithQuestion
ret.data = append(ret.data, question...)
ret.data = append(ret.data, 0)
} else {
ret.typ = tlvTypeSMP1
}
ret.data = appendU32(ret.data, 6)
ret.data = appendMPIs(ret.data, g2a, c2, d2, g3a, c3, d3)
return ret
}
func (c *Conversation) processSMP1(mpis []*big.Int) error {
if len(mpis) != 6 {
return errors.New("otr: incorrect number of arguments in SMP1 message")
}
g2a := mpis[0]
c2 := mpis[1]
d2 := mpis[2]
g3a := mpis[3]
c3 := mpis[4]
d3 := mpis[5]
h := sha256.New()
r := new(big.Int).Exp(g, d2, p)
s := new(big.Int).Exp(g2a, c2, p)
r.Mul(r, s)
r.Mod(r, p)
t := new(big.Int).SetBytes(hashMPIs(h, 1, r))
if c2.Cmp(t) != 0 {
return errors.New("otr: ZKP c2 incorrect in SMP1 message")
}
r.Exp(g, d3, p)
s.Exp(g3a, c3, p)
r.Mul(r, s)
r.Mod(r, p)
t.SetBytes(hashMPIs(h, 2, r))
if c3.Cmp(t) != 0 {
return errors.New("otr: ZKP c3 incorrect in SMP1 message")
}
c.smp.g2a = g2a
c.smp.g3a = g3a
return nil
}
func (c *Conversation) generateSMP2() tlv {
var randBuf [16]byte
b2 := c.randMPI(randBuf[:])
c.smp.b3 = c.randMPI(randBuf[:])
r2 := c.randMPI(randBuf[:])
r3 := c.randMPI(randBuf[:])
r4 := c.randMPI(randBuf[:])
r5 := c.randMPI(randBuf[:])
r6 := c.randMPI(randBuf[:])
g2b := new(big.Int).Exp(g, b2, p)
g3b := new(big.Int).Exp(g, c.smp.b3, p)
r := new(big.Int).Exp(g, r2, p)
h := sha256.New()
c2 := new(big.Int).SetBytes(hashMPIs(h, 3, r))
d2 := new(big.Int).Mul(b2, c2)
d2.Sub(r2, d2)
d2.Mod(d2, q)
if d2.Sign() < 0 {
d2.Add(d2, q)
}
r.Exp(g, r3, p)
c3 := new(big.Int).SetBytes(hashMPIs(h, 4, r))
d3 := new(big.Int).Mul(c.smp.b3, c3)
d3.Sub(r3, d3)
d3.Mod(d3, q)
if d3.Sign() < 0 {
d3.Add(d3, q)
}
c.smp.g2 = new(big.Int).Exp(c.smp.g2a, b2, p)
c.smp.g3 = new(big.Int).Exp(c.smp.g3a, c.smp.b3, p)
c.smp.pb = new(big.Int).Exp(c.smp.g3, r4, p)
c.smp.qb = new(big.Int).Exp(g, r4, p)
r.Exp(c.smp.g2, c.smp.secret, p)
c.smp.qb.Mul(c.smp.qb, r)
c.smp.qb.Mod(c.smp.qb, p)
s := new(big.Int)
s.Exp(c.smp.g2, r6, p)
r.Exp(g, r5, p)
s.Mul(r, s)
s.Mod(s, p)
r.Exp(c.smp.g3, r5, p)
cp := new(big.Int).SetBytes(hashMPIs(h, 5, r, s))
// D5 = r5 - r4 cP mod q and D6 = r6 - y cP mod q
s.Mul(r4, cp)
r.Sub(r5, s)
d5 := new(big.Int).Mod(r, q)
if d5.Sign() < 0 {
d5.Add(d5, q)
}
s.Mul(c.smp.secret, cp)
r.Sub(r6, s)
d6 := new(big.Int).Mod(r, q)
if d6.Sign() < 0 {
d6.Add(d6, q)
}
var ret tlv
ret.typ = tlvTypeSMP2
ret.data = appendU32(ret.data, 11)
ret.data = appendMPIs(ret.data, g2b, c2, d2, g3b, c3, d3, c.smp.pb, c.smp.qb, cp, d5, d6)
return ret
}
func (c *Conversation) processSMP2(mpis []*big.Int) (out tlv, err error) {
if len(mpis) != 11 {
err = errors.New("otr: incorrect number of arguments in SMP2 message")
return
}
g2b := mpis[0]
c2 := mpis[1]
d2 := mpis[2]
g3b := mpis[3]
c3 := mpis[4]
d3 := mpis[5]
pb := mpis[6]
qb := mpis[7]
cp := mpis[8]
d5 := mpis[9]
d6 := mpis[10]
h := sha256.New()
r := new(big.Int).Exp(g, d2, p)
s := new(big.Int).Exp(g2b, c2, p)
r.Mul(r, s)
r.Mod(r, p)
s.SetBytes(hashMPIs(h, 3, r))
if c2.Cmp(s) != 0 {
err = errors.New("otr: ZKP c2 failed in SMP2 message")
return
}
r.Exp(g, d3, p)
s.Exp(g3b, c3, p)
r.Mul(r, s)
r.Mod(r, p)
s.SetBytes(hashMPIs(h, 4, r))
if c3.Cmp(s) != 0 {
err = errors.New("otr: ZKP c3 failed in SMP2 message")
return
}
c.smp.g2 = new(big.Int).Exp(g2b, c.smp.a2, p)
c.smp.g3 = new(big.Int).Exp(g3b, c.smp.a3, p)
r.Exp(g, d5, p)
s.Exp(c.smp.g2, d6, p)
r.Mul(r, s)
s.Exp(qb, cp, p)
r.Mul(r, s)
r.Mod(r, p)
s.Exp(c.smp.g3, d5, p)
t := new(big.Int).Exp(pb, cp, p)
s.Mul(s, t)
s.Mod(s, p)
t.SetBytes(hashMPIs(h, 5, s, r))
if cp.Cmp(t) != 0 {
err = errors.New("otr: ZKP cP failed in SMP2 message")
return
}
var randBuf [16]byte
r4 := c.randMPI(randBuf[:])
r5 := c.randMPI(randBuf[:])
r6 := c.randMPI(randBuf[:])
r7 := c.randMPI(randBuf[:])
pa := new(big.Int).Exp(c.smp.g3, r4, p)
r.Exp(c.smp.g2, c.smp.secret, p)
qa := new(big.Int).Exp(g, r4, p)
qa.Mul(qa, r)
qa.Mod(qa, p)
r.Exp(g, r5, p)
s.Exp(c.smp.g2, r6, p)
r.Mul(r, s)
r.Mod(r, p)
s.Exp(c.smp.g3, r5, p)
cp.SetBytes(hashMPIs(h, 6, s, r))
r.Mul(r4, cp)
d5 = new(big.Int).Sub(r5, r)
d5.Mod(d5, q)
if d5.Sign() < 0 {
d5.Add(d5, q)
}
r.Mul(c.smp.secret, cp)
d6 = new(big.Int).Sub(r6, r)
d6.Mod(d6, q)
if d6.Sign() < 0 {
d6.Add(d6, q)
}
r.ModInverse(qb, p)
qaqb := new(big.Int).Mul(qa, r)
qaqb.Mod(qaqb, p)
ra := new(big.Int).Exp(qaqb, c.smp.a3, p)
r.Exp(qaqb, r7, p)
s.Exp(g, r7, p)
cr := new(big.Int).SetBytes(hashMPIs(h, 7, s, r))
r.Mul(c.smp.a3, cr)
d7 := new(big.Int).Sub(r7, r)
d7.Mod(d7, q)
if d7.Sign() < 0 {
d7.Add(d7, q)
}
c.smp.g3b = g3b
c.smp.qaqb = qaqb
r.ModInverse(pb, p)
c.smp.papb = new(big.Int).Mul(pa, r)
c.smp.papb.Mod(c.smp.papb, p)
c.smp.ra = ra
out.typ = tlvTypeSMP3
out.data = appendU32(out.data, 8)
out.data = appendMPIs(out.data, pa, qa, cp, d5, d6, ra, cr, d7)
return
}
func (c *Conversation) processSMP3(mpis []*big.Int) (out tlv, err error) {
if len(mpis) != 8 {
err = errors.New("otr: incorrect number of arguments in SMP3 message")
return
}
pa := mpis[0]
qa := mpis[1]
cp := mpis[2]
d5 := mpis[3]
d6 := mpis[4]
ra := mpis[5]
cr := mpis[6]
d7 := mpis[7]
h := sha256.New()
r := new(big.Int).Exp(g, d5, p)
s := new(big.Int).Exp(c.smp.g2, d6, p)
r.Mul(r, s)
s.Exp(qa, cp, p)
r.Mul(r, s)
r.Mod(r, p)
s.Exp(c.smp.g3, d5, p)
t := new(big.Int).Exp(pa, cp, p)
s.Mul(s, t)
s.Mod(s, p)
t.SetBytes(hashMPIs(h, 6, s, r))
if t.Cmp(cp) != 0 {
err = errors.New("otr: ZKP cP failed in SMP3 message")
return
}
r.ModInverse(c.smp.qb, p)
qaqb := new(big.Int).Mul(qa, r)
qaqb.Mod(qaqb, p)
r.Exp(qaqb, d7, p)
s.Exp(ra, cr, p)
r.Mul(r, s)
r.Mod(r, p)
s.Exp(g, d7, p)
t.Exp(c.smp.g3a, cr, p)
s.Mul(s, t)
s.Mod(s, p)
t.SetBytes(hashMPIs(h, 7, s, r))
if t.Cmp(cr) != 0 {
err = errors.New("otr: ZKP cR failed in SMP3 message")
return
}
var randBuf [16]byte
r7 := c.randMPI(randBuf[:])
rb := new(big.Int).Exp(qaqb, c.smp.b3, p)
r.Exp(qaqb, r7, p)
s.Exp(g, r7, p)
cr = new(big.Int).SetBytes(hashMPIs(h, 8, s, r))
r.Mul(c.smp.b3, cr)
d7 = new(big.Int).Sub(r7, r)
d7.Mod(d7, q)
if d7.Sign() < 0 {
d7.Add(d7, q)
}
out.typ = tlvTypeSMP4
out.data = appendU32(out.data, 3)
out.data = appendMPIs(out.data, rb, cr, d7)
r.ModInverse(c.smp.pb, p)
r.Mul(pa, r)
r.Mod(r, p)
s.Exp(ra, c.smp.b3, p)
if r.Cmp(s) != 0 {
err = smpFailureError
}
return
}
func (c *Conversation) processSMP4(mpis []*big.Int) error {
if len(mpis) != 3 {
return errors.New("otr: incorrect number of arguments in SMP4 message")
}
rb := mpis[0]
cr := mpis[1]
d7 := mpis[2]
h := sha256.New()
r := new(big.Int).Exp(c.smp.qaqb, d7, p)
s := new(big.Int).Exp(rb, cr, p)
r.Mul(r, s)
r.Mod(r, p)
s.Exp(g, d7, p)
t := new(big.Int).Exp(c.smp.g3b, cr, p)
s.Mul(s, t)
s.Mod(s, p)
t.SetBytes(hashMPIs(h, 8, s, r))
if t.Cmp(cr) != 0 {
return errors.New("otr: ZKP cR failed in SMP4 message")
}
r.Exp(rb, c.smp.a3, p)
if r.Cmp(c.smp.papb) != 0 {
return smpFailureError
}
return nil
}
func (c *Conversation) generateSMPAbort() tlv {
return tlv{typ: tlvTypeSMPAbort}
}
func hashMPIs(h hash.Hash, magic byte, mpis ...*big.Int) []byte {
if h != nil {
h.Reset()
} else {
h = sha256.New()
}
h.Write([]byte{magic})
for _, mpi := range mpis {
h.Write(appendMPI(nil, mpi))
}
return h.Sum(nil)
}
// SecurityChange describes a change in the security state of a Conversation.
type SecurityChange int
const (
NoChange SecurityChange = iota
// NewKeys indicates that a key exchange has completed. This occurs
// when a conversation first becomes encrypted, and when the keys are
// renegotiated within an encrypted conversation.
NewKeys
// SMPSecretNeeded indicates that the peer has started an
// authentication and that we need to supply a secret. Call SMPQuestion
// to get the optional, human readable challenge and then Authenticate
// to supply the matching secret.
SMPSecretNeeded
// SMPComplete indicates that an authentication completed. The identity
// of the peer has now been confirmed.
SMPComplete
// SMPFailed indicates that an authentication failed.
SMPFailed
// ConversationEnded indicates that the peer ended the secure
// conversation.
ConversationEnded
)
// QueryMessage can be sent to a peer to start an OTR conversation.
var QueryMessage = "?OTRv2?"
// ErrorPrefix can be used to make an OTR error by appending an error message
// to it.
var ErrorPrefix = "?OTR Error:"
var (
fragmentPartSeparator = []byte(",")
fragmentPrefix = []byte("?OTR,")
msgPrefix = []byte("?OTR:")
queryMarker = []byte("?OTR")
)
// isQuery attempts to parse an OTR query from msg and returns the greatest
// common version, or 0 if msg is not an OTR query.
func isQuery(msg []byte) (greatestCommonVersion int) {
pos := bytes.Index(msg, queryMarker)
if pos == -1 {
return 0
}
for i, c := range msg[pos+len(queryMarker):] {
if i == 0 {
if c == '?' {
// Indicates support for version 1, but we don't
// implement that.
continue
}
if c != 'v' {
// Invalid message
return 0
}
continue
}
if c == '?' {
// End of message
return
}
if c == ' ' || c == '\t' {
// Probably an invalid message
return 0
}
if c == '2' {
greatestCommonVersion = 2
}
}
return 0
}
const (
statePlaintext = iota
stateEncrypted
stateFinished
)
const (
authStateNone = iota
authStateAwaitingDHKey
authStateAwaitingRevealSig
authStateAwaitingSig
)
const (
msgTypeDHCommit = 2
msgTypeData = 3
msgTypeDHKey = 10
msgTypeRevealSig = 17
msgTypeSig = 18
)
const (
// If the requested fragment size is less than this, it will be ignored.
minFragmentSize = 18
// Messages are padded to a multiple of this number of bytes.
paddingGranularity = 256
// The number of bytes in a Diffie-Hellman private value (320-bits).
dhPrivateBytes = 40
// The number of bytes needed to represent an element of the DSA
// subgroup (160-bits).
dsaSubgroupBytes = 20
// The number of bytes of the MAC that are sent on the wire (160-bits).
macPrefixBytes = 20
)
// These are the global, common group parameters for OTR.
var (
p *big.Int // group prime
g *big.Int // group generator
q *big.Int // group order
pMinus2 *big.Int
)
func init() {
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", 16)
q, _ = new(big.Int).SetString("7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA36046511B993FFFFFFFFFFFFFFFF", 16)
g = new(big.Int).SetInt64(2)
pMinus2 = new(big.Int).Sub(p, g)
}
// Conversation represents a relation with a peer. The zero value is a valid
// Conversation, although PrivateKey must be set.
//
// When communicating with a peer, all inbound messages should be passed to
// Conversation.Receive and all outbound messages to Conversation.Send. The
// Conversation will take care of maintaining the encryption state and
// negotiating encryption as needed.
type Conversation struct {
// PrivateKey contains the private key to use to sign key exchanges.
PrivateKey *PrivateKey
// Rand can be set to override the entropy source. Otherwise,
// crypto/rand will be used.
Rand io.Reader
// If FragmentSize is set, all messages produced by Receive and Send
// will be fragmented into messages of, at most, this number of bytes.
FragmentSize int
// Once Receive has returned NewKeys once, the following fields are
// valid.
SSID [8]byte
TheirPublicKey PublicKey
state, authState int
r [16]byte
x, y *big.Int
gx, gy *big.Int
gxBytes []byte
digest [sha256.Size]byte
revealKeys, sigKeys akeKeys
myKeyId uint32
myCurrentDHPub *big.Int
myCurrentDHPriv *big.Int
myLastDHPub *big.Int
myLastDHPriv *big.Int
theirKeyId uint32
theirCurrentDHPub *big.Int
theirLastDHPub *big.Int
keySlots [4]keySlot
myCounter [8]byte
theirLastCtr [8]byte
oldMACs []byte
k, n int // fragment state
frag []byte
smp smpState
}
// A keySlot contains key material for a specific (their keyid, my keyid) pair.
type keySlot struct {
// used is true if this slot is valid. If false, it's free for reuse.
used bool
theirKeyId uint32
myKeyId uint32
sendAESKey, recvAESKey []byte
sendMACKey, recvMACKey []byte
theirLastCtr [8]byte
}
// akeKeys are generated during key exchange. There's one set for the reveal
// signature message and another for the signature message. In the protocol
// spec the latter are indicated with a prime mark.
type akeKeys struct {
c [16]byte
m1, m2 [32]byte
}
func (c *Conversation) rand() io.Reader {
if c.Rand != nil {
return c.Rand
}
return rand.Reader
}
func (c *Conversation) randMPI(buf []byte) *big.Int {
_, err := io.ReadFull(c.rand(), buf)
if err != nil {
panic("otr: short read from random source")
}
return new(big.Int).SetBytes(buf)
}
// tlv represents the type-length value from the protocol.
type tlv struct {
typ, length uint16
data []byte
}
const (
tlvTypePadding = 0
tlvTypeDisconnected = 1
tlvTypeSMP1 = 2
tlvTypeSMP2 = 3
tlvTypeSMP3 = 4
tlvTypeSMP4 = 5
tlvTypeSMPAbort = 6
tlvTypeSMP1WithQuestion = 7
)
// Receive handles a message from a peer. It returns a human readable message,
// an indicator of whether that message was encrypted, a hint about the
// encryption state and zero or more messages to send back to the peer.
// These messages do not need to be passed to Send before transmission.
func (c *Conversation) Receive(in []byte) (out []byte, encrypted bool, change SecurityChange, toSend [][]byte, err error) {
if bytes.HasPrefix(in, fragmentPrefix) {
in, err = c.processFragment(in)
if in == nil || err != nil {
return
}
}
if bytes.HasPrefix(in, msgPrefix) && in[len(in)-1] == '.' {
in = in[len(msgPrefix) : len(in)-1]
} else if version := isQuery(in); version > 0 {
c.authState = authStateAwaitingDHKey
c.reset()
toSend = c.encode(c.generateDHCommit())
return
} else {
// plaintext message
out = in
return
}
msg := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
msgLen, err := base64.StdEncoding.Decode(msg, in)
if err != nil {
err = errors.New("otr: invalid base64 encoding in message")
return
}
msg = msg[:msgLen]
// The first two bytes are the protocol version (2)
if len(msg) < 3 || msg[0] != 0 || msg[1] != 2 {
err = errors.New("otr: invalid OTR message")
return
}
msgType := int(msg[2])
msg = msg[3:]
switch msgType {
case msgTypeDHCommit:
switch c.authState {
case authStateNone:
c.authState = authStateAwaitingRevealSig
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
return
case authStateAwaitingDHKey:
// This is a 'SYN-crossing'. The greater digest wins.
var cmp int
if cmp, err = c.compareToDHCommit(msg); err != nil {
return
}
if cmp > 0 {
// We win. Retransmit DH commit.
toSend = c.encode(c.serializeDHCommit())
return
} else {
// They win. We forget about our DH commit.
c.authState = authStateAwaitingRevealSig
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
return
}
case authStateAwaitingRevealSig:
if err = c.processDHCommit(msg); err != nil {
return
}
toSend = c.encode(c.serializeDHKey())
case authStateAwaitingSig:
if err = c.processDHCommit(msg); err != nil {
return
}
c.reset()
toSend = c.encode(c.generateDHKey())
c.authState = authStateAwaitingRevealSig
default:
panic("bad state")
}
case msgTypeDHKey:
switch c.authState {
case authStateAwaitingDHKey:
var isSame bool
if isSame, err = c.processDHKey(msg); err != nil {
return
}
if isSame {
err = errors.New("otr: unexpected duplicate DH key")
return
}
toSend = c.encode(c.generateRevealSig())
c.authState = authStateAwaitingSig
case authStateAwaitingSig:
var isSame bool
if isSame, err = c.processDHKey(msg); err != nil {
return
}
if isSame {
toSend = c.encode(c.serializeDHKey())
}
}
case msgTypeRevealSig:
if c.authState != authStateAwaitingRevealSig {
return
}
if err = c.processRevealSig(msg); err != nil {
return
}
toSend = c.encode(c.generateSig())
c.authState = authStateNone
c.state = stateEncrypted
change = NewKeys
case msgTypeSig:
if c.authState != authStateAwaitingSig {
return
}
if err = c.processSig(msg); err != nil {
return
}
c.authState = authStateNone
c.state = stateEncrypted
change = NewKeys
case msgTypeData:
if c.state != stateEncrypted {
err = errors.New("otr: encrypted message received without encrypted session established")
return
}
var tlvs []tlv
out, tlvs, err = c.processData(msg)
encrypted = true
EachTLV:
for _, inTLV := range tlvs {
switch inTLV.typ {
case tlvTypeDisconnected:
change = ConversationEnded
c.state = stateFinished
break EachTLV
case tlvTypeSMP1, tlvTypeSMP2, tlvTypeSMP3, tlvTypeSMP4, tlvTypeSMPAbort, tlvTypeSMP1WithQuestion:
var reply tlv
var complete bool
reply, complete, err = c.processSMP(inTLV)
if err == smpSecretMissingError {
err = nil
change = SMPSecretNeeded
c.smp.saved = &inTLV
return
}
if err == smpFailureError {
err = nil
change = SMPFailed
} else if complete {
change = SMPComplete
}
if reply.typ != 0 {
toSend = c.encode(c.generateData(nil, &reply))
}
break EachTLV
default:
// skip unknown TLVs
}
}
default:
err = errors.New("otr: unknown message type " + strconv.Itoa(msgType))
}
return
}
// Send takes a human readable message from the local user, possibly encrypts
// it and returns zero one or more messages to send to the peer.
func (c *Conversation) Send(msg []byte) ([][]byte, error) {
switch c.state {
case statePlaintext:
return [][]byte{msg}, nil
case stateEncrypted:
return c.encode(c.generateData(msg, nil)), nil
case stateFinished:
return nil, errors.New("otr: cannot send message because secure conversation has finished")
}
return nil, errors.New("otr: cannot send message in current state")
}
// SMPQuestion returns the human readable challenge question from the peer.
// It's only valid after Receive has returned SMPSecretNeeded.
func (c *Conversation) SMPQuestion() string {
return c.smp.question
}
// Authenticate begins an authentication with the peer. Authentication involves
// an optional challenge message and a shared secret. The authentication
// proceeds until either Receive returns SMPComplete, SMPSecretNeeded (which
// indicates that a new authentication is happening and thus this one was
// aborted) or SMPFailed.
func (c *Conversation) Authenticate(question string, mutualSecret []byte) (toSend [][]byte, err error) {
if c.state != stateEncrypted {
err = errors.New("otr: can't authenticate a peer without a secure conversation established")
return
}
if c.smp.saved != nil {
c.calcSMPSecret(mutualSecret, false /* they started it */)
var out tlv
var complete bool
out, complete, err = c.processSMP(*c.smp.saved)
if complete {
panic("SMP completed on the first message")
}
c.smp.saved = nil
if out.typ != 0 {
toSend = c.encode(c.generateData(nil, &out))
}
return
}
c.calcSMPSecret(mutualSecret, true /* we started it */)
outs := c.startSMP(question)
for _, out := range outs {
toSend = append(toSend, c.encode(c.generateData(nil, &out))...)
}
return
}
// End ends a secure conversation by generating a termination message for
// the peer and switches to unencrypted communication.
func (c *Conversation) End() (toSend [][]byte) {
switch c.state {
case statePlaintext:
return nil
case stateEncrypted:
c.state = statePlaintext
return c.encode(c.generateData(nil, &tlv{typ: tlvTypeDisconnected}))
case stateFinished:
c.state = statePlaintext
return nil
}
panic("unreachable")
}
// IsEncrypted returns true if a message passed to Send would be encrypted
// before transmission. This result remains valid until the next call to
// Receive or End, which may change the state of the Conversation.
func (c *Conversation) IsEncrypted() bool {
return c.state == stateEncrypted
}
var fragmentError = errors.New("otr: invalid OTR fragment")
// processFragment processes a fragmented OTR message and possibly returns a
// complete message. Fragmented messages look like "?OTR,k,n,msg," where k is
// the fragment number (starting from 1), n is the number of fragments in this
// message and msg is a substring of the base64 encoded message.
func (c *Conversation) processFragment(in []byte) (out []byte, err error) {
in = in[len(fragmentPrefix):] // remove "?OTR,"
parts := bytes.Split(in, fragmentPartSeparator)
if len(parts) != 4 || len(parts[3]) != 0 {
return nil, fragmentError
}
k, err := strconv.Atoi(string(parts[0]))
if err != nil {
return nil, fragmentError
}
n, err := strconv.Atoi(string(parts[1]))
if err != nil {
return nil, fragmentError
}
if k < 1 || n < 1 || k > n {
return nil, fragmentError
}
if k == 1 {
c.frag = append(c.frag[:0], parts[2]...)
c.k, c.n = k, n
} else if n == c.n && k == c.k+1 {
c.frag = append(c.frag, parts[2]...)
c.k++
} else {
c.frag = c.frag[:0]
c.n, c.k = 0, 0
}
if c.n > 0 && c.k == c.n {
c.n, c.k = 0, 0
return c.frag, nil
}
return nil, nil
}
func (c *Conversation) generateDHCommit() []byte {
_, err := io.ReadFull(c.rand(), c.r[:])
if err != nil {
panic("otr: short read from random source")
}
var xBytes [dhPrivateBytes]byte
c.x = c.randMPI(xBytes[:])
c.gx = new(big.Int).Exp(g, c.x, p)
c.gy = nil
c.gxBytes = appendMPI(nil, c.gx)
h := sha256.New()
h.Write(c.gxBytes)
h.Sum(c.digest[:0])
aesCipher, err := aes.NewCipher(c.r[:])
if err != nil {
panic(err.Error())
}
var iv [aes.BlockSize]byte
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(c.gxBytes, c.gxBytes)
return c.serializeDHCommit()
}
func (c *Conversation) serializeDHCommit() []byte {
var ret []byte
ret = appendU16(ret, 2) // protocol version
ret = append(ret, msgTypeDHCommit)
ret = appendData(ret, c.gxBytes)
ret = appendData(ret, c.digest[:])
return ret
}
func (c *Conversation) processDHCommit(in []byte) error {
var ok1, ok2 bool
c.gxBytes, in, ok1 = getData(in)
digest, in, ok2 := getData(in)
if !ok1 || !ok2 || len(in) > 0 {
return errors.New("otr: corrupt DH commit message")
}
copy(c.digest[:], digest)
return nil
}
func (c *Conversation) compareToDHCommit(in []byte) (int, error) {
_, in, ok1 := getData(in)
digest, in, ok2 := getData(in)
if !ok1 || !ok2 || len(in) > 0 {
return 0, errors.New("otr: corrupt DH commit message")
}
return bytes.Compare(c.digest[:], digest), nil
}
func (c *Conversation) generateDHKey() []byte {
var yBytes [dhPrivateBytes]byte
c.y = c.randMPI(yBytes[:])
c.gy = new(big.Int).Exp(g, c.y, p)
return c.serializeDHKey()
}
func (c *Conversation) serializeDHKey() []byte {
var ret []byte
ret = appendU16(ret, 2) // protocol version
ret = append(ret, msgTypeDHKey)
ret = appendMPI(ret, c.gy)
return ret
}
func (c *Conversation) processDHKey(in []byte) (isSame bool, err error) {
gy, in, ok := getMPI(in)
if !ok {
err = errors.New("otr: corrupt DH key message")
return
}
if gy.Cmp(g) < 0 || gy.Cmp(pMinus2) > 0 {
err = errors.New("otr: DH value out of range")
return
}
if c.gy != nil {
isSame = c.gy.Cmp(gy) == 0
return
}
c.gy = gy
return
}
func (c *Conversation) generateEncryptedSignature(keys *akeKeys, xFirst bool) ([]byte, []byte) {
var xb []byte
xb = c.PrivateKey.PublicKey.Serialize(xb)
var verifyData []byte
if xFirst {
verifyData = appendMPI(verifyData, c.gx)
verifyData = appendMPI(verifyData, c.gy)
} else {
verifyData = appendMPI(verifyData, c.gy)
verifyData = appendMPI(verifyData, c.gx)
}
verifyData = append(verifyData, xb...)
verifyData = appendU32(verifyData, c.myKeyId)
mac := hmac.New(sha256.New, keys.m1[:])
mac.Write(verifyData)
mb := mac.Sum(nil)
xb = appendU32(xb, c.myKeyId)
xb = append(xb, c.PrivateKey.Sign(c.rand(), mb)...)
aesCipher, err := aes.NewCipher(keys.c[:])
if err != nil {
panic(err.Error())
}
var iv [aes.BlockSize]byte
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(xb, xb)
mac = hmac.New(sha256.New, keys.m2[:])
encryptedSig := appendData(nil, xb)
mac.Write(encryptedSig)
return encryptedSig, mac.Sum(nil)
}
func (c *Conversation) generateRevealSig() []byte {
s := new(big.Int).Exp(c.gy, c.x, p)
c.calcAKEKeys(s)
c.myKeyId++
encryptedSig, mac := c.generateEncryptedSignature(&c.revealKeys, true /* gx comes first */)
c.myCurrentDHPub = c.gx
c.myCurrentDHPriv = c.x
c.rotateDHKeys()
incCounter(&c.myCounter)
var ret []byte
ret = appendU16(ret, 2)
ret = append(ret, msgTypeRevealSig)
ret = appendData(ret, c.r[:])
ret = append(ret, encryptedSig...)
ret = append(ret, mac[:20]...)
return ret
}
func (c *Conversation) processEncryptedSig(encryptedSig, theirMAC []byte, keys *akeKeys, xFirst bool) error {
mac := hmac.New(sha256.New, keys.m2[:])
mac.Write(appendData(nil, encryptedSig))
myMAC := mac.Sum(nil)[:20]
if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
return errors.New("bad signature MAC in encrypted signature")
}
aesCipher, err := aes.NewCipher(keys.c[:])
if err != nil {
panic(err.Error())
}
var iv [aes.BlockSize]byte
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(encryptedSig, encryptedSig)
sig := encryptedSig
sig, ok1 := c.TheirPublicKey.Parse(sig)
keyId, sig, ok2 := getU32(sig)
if !ok1 || !ok2 {
return errors.New("otr: corrupt encrypted signature")
}
var verifyData []byte
if xFirst {
verifyData = appendMPI(verifyData, c.gx)
verifyData = appendMPI(verifyData, c.gy)
} else {
verifyData = appendMPI(verifyData, c.gy)
verifyData = appendMPI(verifyData, c.gx)
}
verifyData = c.TheirPublicKey.Serialize(verifyData)
verifyData = appendU32(verifyData, keyId)
mac = hmac.New(sha256.New, keys.m1[:])
mac.Write(verifyData)
mb := mac.Sum(nil)
sig, ok1 = c.TheirPublicKey.Verify(mb, sig)
if !ok1 {
return errors.New("bad signature in encrypted signature")
}
if len(sig) > 0 {
return errors.New("corrupt encrypted signature")
}
c.theirKeyId = keyId
zero(c.theirLastCtr[:])
return nil
}
func (c *Conversation) processRevealSig(in []byte) error {
r, in, ok1 := getData(in)
encryptedSig, in, ok2 := getData(in)
theirMAC := in
if !ok1 || !ok2 || len(theirMAC) != 20 {
return errors.New("otr: corrupt reveal signature message")
}
aesCipher, err := aes.NewCipher(r)
if err != nil {
return errors.New("otr: cannot create AES cipher from reveal signature message: " + err.Error())
}
var iv [aes.BlockSize]byte
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(c.gxBytes, c.gxBytes)
h := sha256.New()
h.Write(c.gxBytes)
digest := h.Sum(nil)
if len(digest) != len(c.digest) || subtle.ConstantTimeCompare(digest, c.digest[:]) == 0 {
return errors.New("otr: bad commit MAC in reveal signature message")
}
var rest []byte
c.gx, rest, ok1 = getMPI(c.gxBytes)
if !ok1 || len(rest) > 0 {
return errors.New("otr: gx corrupt after decryption")
}
if c.gx.Cmp(g) < 0 || c.gx.Cmp(pMinus2) > 0 {
return errors.New("otr: DH value out of range")
}
s := new(big.Int).Exp(c.gx, c.y, p)
c.calcAKEKeys(s)
if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.revealKeys, true /* gx comes first */); err != nil {
return errors.New("otr: in reveal signature message: " + err.Error())
}
c.theirCurrentDHPub = c.gx
c.theirLastDHPub = nil
return nil
}
func (c *Conversation) generateSig() []byte {
c.myKeyId++
encryptedSig, mac := c.generateEncryptedSignature(&c.sigKeys, false /* gy comes first */)
c.myCurrentDHPub = c.gy
c.myCurrentDHPriv = c.y
c.rotateDHKeys()
incCounter(&c.myCounter)
var ret []byte
ret = appendU16(ret, 2)
ret = append(ret, msgTypeSig)
ret = append(ret, encryptedSig...)
ret = append(ret, mac[:macPrefixBytes]...)
return ret
}
func (c *Conversation) processSig(in []byte) error {
encryptedSig, in, ok1 := getData(in)
theirMAC := in
if !ok1 || len(theirMAC) != macPrefixBytes {
return errors.New("otr: corrupt signature message")
}
if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.sigKeys, false /* gy comes first */); err != nil {
return errors.New("otr: in signature message: " + err.Error())
}
c.theirCurrentDHPub = c.gy
c.theirLastDHPub = nil
return nil
}
func (c *Conversation) rotateDHKeys() {
// evict slots using our retired key id
for i := range c.keySlots {
slot := &c.keySlots[i]
if slot.used && slot.myKeyId == c.myKeyId-1 {
slot.used = false
c.oldMACs = append(c.oldMACs, slot.recvMACKey...)
}
}
c.myLastDHPriv = c.myCurrentDHPriv
c.myLastDHPub = c.myCurrentDHPub
var xBytes [dhPrivateBytes]byte
c.myCurrentDHPriv = c.randMPI(xBytes[:])
c.myCurrentDHPub = new(big.Int).Exp(g, c.myCurrentDHPriv, p)
c.myKeyId++
}
func (c *Conversation) processData(in []byte) (out []byte, tlvs []tlv, err error) {
origIn := in
flags, in, ok1 := getU8(in)
theirKeyId, in, ok2 := getU32(in)
myKeyId, in, ok3 := getU32(in)
y, in, ok4 := getMPI(in)
counter, in, ok5 := getNBytes(in, 8)
encrypted, in, ok6 := getData(in)
macedData := origIn[:len(origIn)-len(in)]
theirMAC, in, ok7 := getNBytes(in, macPrefixBytes)
_, in, ok8 := getData(in)
if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 || !ok7 || !ok8 || len(in) > 0 {
err = errors.New("otr: corrupt data message")
return
}
ignoreErrors := flags&1 != 0
slot, err := c.calcDataKeys(myKeyId, theirKeyId)
if err != nil {
if ignoreErrors {
err = nil
}
return
}
mac := hmac.New(sha1.New, slot.recvMACKey)
mac.Write([]byte{0, 2, 3})
mac.Write(macedData)
myMAC := mac.Sum(nil)
if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
if !ignoreErrors {
err = errors.New("otr: bad MAC on data message")
}
return
}
if bytes.Compare(counter, slot.theirLastCtr[:]) <= 0 {
err = errors.New("otr: counter regressed")
return
}
copy(slot.theirLastCtr[:], counter)
var iv [aes.BlockSize]byte
copy(iv[:], counter)
aesCipher, err := aes.NewCipher(slot.recvAESKey)
if err != nil {
panic(err.Error())
}
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(encrypted, encrypted)
decrypted := encrypted
if myKeyId == c.myKeyId {
c.rotateDHKeys()
}
if theirKeyId == c.theirKeyId {
// evict slots using their retired key id
for i := range c.keySlots {
slot := &c.keySlots[i]
if slot.used && slot.theirKeyId == theirKeyId-1 {
slot.used = false
c.oldMACs = append(c.oldMACs, slot.recvMACKey...)
}
}
c.theirLastDHPub = c.theirCurrentDHPub
c.theirKeyId++
c.theirCurrentDHPub = y
}
if nulPos := bytes.IndexByte(decrypted, 0); nulPos >= 0 {
out = decrypted[:nulPos]
tlvData := decrypted[nulPos+1:]
for len(tlvData) > 0 {
var t tlv
var ok1, ok2, ok3 bool
t.typ, tlvData, ok1 = getU16(tlvData)
t.length, tlvData, ok2 = getU16(tlvData)
t.data, tlvData, ok3 = getNBytes(tlvData, int(t.length))
if !ok1 || !ok2 || !ok3 {
err = errors.New("otr: corrupt tlv data")
return
}
tlvs = append(tlvs, t)
}
} else {
out = decrypted
}
return
}
func (c *Conversation) generateData(msg []byte, extra *tlv) []byte {
slot, err := c.calcDataKeys(c.myKeyId-1, c.theirKeyId)
if err != nil {
panic("otr: failed to generate sending keys: " + err.Error())
}
var plaintext []byte
plaintext = append(plaintext, msg...)
plaintext = append(plaintext, 0)
padding := paddingGranularity - ((len(plaintext) + 4) % paddingGranularity)
plaintext = appendU16(plaintext, tlvTypePadding)
plaintext = appendU16(plaintext, uint16(padding))
for i := 0; i < padding; i++ {
plaintext = append(plaintext, 0)
}
if extra != nil {
plaintext = appendU16(plaintext, extra.typ)
plaintext = appendU16(plaintext, uint16(len(extra.data)))
plaintext = append(plaintext, extra.data...)
}
encrypted := make([]byte, len(plaintext))
var iv [aes.BlockSize]byte
copy(iv[:], c.myCounter[:])
aesCipher, err := aes.NewCipher(slot.sendAESKey)
if err != nil {
panic(err.Error())
}
ctr := cipher.NewCTR(aesCipher, iv[:])
ctr.XORKeyStream(encrypted, plaintext)
var ret []byte
ret = appendU16(ret, 2)
ret = append(ret, msgTypeData)
ret = append(ret, 0 /* flags */)
ret = appendU32(ret, c.myKeyId-1)
ret = appendU32(ret, c.theirKeyId)
ret = appendMPI(ret, c.myCurrentDHPub)
ret = append(ret, c.myCounter[:]...)
ret = appendData(ret, encrypted)
mac := hmac.New(sha1.New, slot.sendMACKey)
mac.Write(ret)
ret = append(ret, mac.Sum(nil)[:macPrefixBytes]...)
ret = appendData(ret, c.oldMACs)
c.oldMACs = nil
incCounter(&c.myCounter)
return ret
}
func incCounter(counter *[8]byte) {
for i := 7; i >= 0; i-- {
counter[i]++
if counter[i] > 0 {
break
}
}
}
// calcDataKeys computes the keys used to encrypt a data message given the key
// IDs.
func (c *Conversation) calcDataKeys(myKeyId, theirKeyId uint32) (slot *keySlot, err error) {
// Check for a cache hit.
for i := range c.keySlots {
slot = &c.keySlots[i]
if slot.used && slot.theirKeyId == theirKeyId && slot.myKeyId == myKeyId {
return
}
}
// Find an empty slot to write into.
slot = nil
for i := range c.keySlots {
if !c.keySlots[i].used {
slot = &c.keySlots[i]
break
}
}
if slot == nil {
return nil, errors.New("otr: internal error: no more key slots")
}
var myPriv, myPub, theirPub *big.Int
if myKeyId == c.myKeyId {
myPriv = c.myCurrentDHPriv
myPub = c.myCurrentDHPub
} else if myKeyId == c.myKeyId-1 {
myPriv = c.myLastDHPriv
myPub = c.myLastDHPub
} else {
err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when I'm on " + strconv.FormatUint(uint64(c.myKeyId), 10))
return
}
if theirKeyId == c.theirKeyId {
theirPub = c.theirCurrentDHPub
} else if theirKeyId == c.theirKeyId-1 && c.theirLastDHPub != nil {
theirPub = c.theirLastDHPub
} else {
err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when they're on " + strconv.FormatUint(uint64(c.myKeyId), 10))
return
}
var sendPrefixByte, recvPrefixByte [1]byte
if myPub.Cmp(theirPub) > 0 {
// we're the high end
sendPrefixByte[0], recvPrefixByte[0] = 1, 2
} else {
// we're the low end
sendPrefixByte[0], recvPrefixByte[0] = 2, 1
}
s := new(big.Int).Exp(theirPub, myPriv, p)
sBytes := appendMPI(nil, s)
h := sha1.New()
h.Write(sendPrefixByte[:])
h.Write(sBytes)
slot.sendAESKey = h.Sum(slot.sendAESKey[:0])[:16]
h.Reset()
h.Write(slot.sendAESKey)
slot.sendMACKey = h.Sum(slot.sendMACKey[:0])
h.Reset()
h.Write(recvPrefixByte[:])
h.Write(sBytes)
slot.recvAESKey = h.Sum(slot.recvAESKey[:0])[:16]
h.Reset()
h.Write(slot.recvAESKey)
slot.recvMACKey = h.Sum(slot.recvMACKey[:0])
slot.theirKeyId = theirKeyId
slot.myKeyId = myKeyId
slot.used = true
zero(slot.theirLastCtr[:])
return
}
func (c *Conversation) calcAKEKeys(s *big.Int) {
mpi := appendMPI(nil, s)
h := sha256.New()
var cBytes [32]byte
hashWithPrefix(c.SSID[:], 0, mpi, h)
hashWithPrefix(cBytes[:], 1, mpi, h)
copy(c.revealKeys.c[:], cBytes[:16])
copy(c.sigKeys.c[:], cBytes[16:])
hashWithPrefix(c.revealKeys.m1[:], 2, mpi, h)
hashWithPrefix(c.revealKeys.m2[:], 3, mpi, h)
hashWithPrefix(c.sigKeys.m1[:], 4, mpi, h)
hashWithPrefix(c.sigKeys.m2[:], 5, mpi, h)
}
func hashWithPrefix(out []byte, prefix byte, in []byte, h hash.Hash) {
h.Reset()
var p [1]byte
p[0] = prefix
h.Write(p[:])
h.Write(in)
if len(out) == h.Size() {
h.Sum(out[:0])
} else {
digest := h.Sum(nil)
copy(out, digest)
}
}
func (c *Conversation) encode(msg []byte) [][]byte {
b64 := make([]byte, base64.StdEncoding.EncodedLen(len(msg))+len(msgPrefix)+1)
base64.StdEncoding.Encode(b64[len(msgPrefix):], msg)
copy(b64, msgPrefix)
b64[len(b64)-1] = '.'
if c.FragmentSize < minFragmentSize || len(b64) <= c.FragmentSize {
// We can encode this in a single fragment.
return [][]byte{b64}
}
// We have to fragment this message.
var ret [][]byte
bytesPerFragment := c.FragmentSize - minFragmentSize
numFragments := (len(b64) + bytesPerFragment) / bytesPerFragment
for i := 0; i < numFragments; i++ {
frag := []byte("?OTR," + strconv.Itoa(i+1) + "," + strconv.Itoa(numFragments) + ",")
todo := bytesPerFragment
if todo > len(b64) {
todo = len(b64)
}
frag = append(frag, b64[:todo]...)
b64 = b64[todo:]
frag = append(frag, ',')
ret = append(ret, frag)
}
return ret
}
func (c *Conversation) reset() {
c.myKeyId = 0
for i := range c.keySlots {
c.keySlots[i].used = false
}
}
type PublicKey struct {
dsa.PublicKey
}
func (pk *PublicKey) Parse(in []byte) ([]byte, bool) {
var ok bool
var pubKeyType uint16
if pubKeyType, in, ok = getU16(in); !ok || pubKeyType != 0 {
return nil, false
}
if pk.P, in, ok = getMPI(in); !ok {
return nil, false
}
if pk.Q, in, ok = getMPI(in); !ok {
return nil, false
}
if pk.G, in, ok = getMPI(in); !ok {
return nil, false
}
if pk.Y, in, ok = getMPI(in); !ok {
return nil, false
}
return in, true
}
func (pk *PublicKey) Serialize(in []byte) []byte {
in = appendU16(in, 0)
in = appendMPI(in, pk.P)
in = appendMPI(in, pk.Q)
in = appendMPI(in, pk.G)
in = appendMPI(in, pk.Y)
return in
}
// Fingerprint returns the 20-byte, binary fingerprint of the PublicKey.
func (pk *PublicKey) Fingerprint() []byte {
b := pk.Serialize(nil)
h := sha1.New()
h.Write(b[2:])
return h.Sum(nil)
}
func (pk *PublicKey) Verify(hashed, sig []byte) ([]byte, bool) {
if len(sig) != 2*dsaSubgroupBytes {
return nil, false
}
r := new(big.Int).SetBytes(sig[:dsaSubgroupBytes])
s := new(big.Int).SetBytes(sig[dsaSubgroupBytes:])
ok := dsa.Verify(&pk.PublicKey, hashed, r, s)
return sig[dsaSubgroupBytes*2:], ok
}
type PrivateKey struct {
PublicKey
dsa.PrivateKey
}
func (priv *PrivateKey) Sign(rand io.Reader, hashed []byte) []byte {
r, s, err := dsa.Sign(rand, &priv.PrivateKey, hashed)
if err != nil {
panic(err.Error())
}
rBytes := r.Bytes()
sBytes := s.Bytes()
if len(rBytes) > dsaSubgroupBytes || len(sBytes) > dsaSubgroupBytes {
panic("DSA signature too large")
}
out := make([]byte, 2*dsaSubgroupBytes)
copy(out[dsaSubgroupBytes-len(rBytes):], rBytes)
copy(out[len(out)-len(sBytes):], sBytes)
return out
}
func (priv *PrivateKey) Serialize(in []byte) []byte {
in = priv.PublicKey.Serialize(in)
in = appendMPI(in, priv.PrivateKey.X)
return in
}
func (priv *PrivateKey) Parse(in []byte) ([]byte, bool) {
in, ok := priv.PublicKey.Parse(in)
if !ok {
return in, ok
}
priv.PrivateKey.PublicKey = priv.PublicKey.PublicKey
priv.PrivateKey.X, in, ok = getMPI(in)
return in, ok
}
func (priv *PrivateKey) Generate(rand io.Reader) {
if err := dsa.GenerateParameters(&priv.PrivateKey.PublicKey.Parameters, rand, dsa.L1024N160); err != nil {
panic(err.Error())
}
if err := dsa.GenerateKey(&priv.PrivateKey, rand); err != nil {
panic(err.Error())
}
priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey
}
func notHex(r rune) bool {
if r >= '0' && r <= '9' ||
r >= 'a' && r <= 'f' ||
r >= 'A' && r <= 'F' {
return false
}
return true
}
// Import parses the contents of a libotr private key file.
func (priv *PrivateKey) Import(in []byte) bool {
mpiStart := []byte(" #")
mpis := make([]*big.Int, 5)
for i := 0; i < len(mpis); i++ {
start := bytes.Index(in, mpiStart)
if start == -1 {
return false
}
in = in[start+len(mpiStart):]
end := bytes.IndexFunc(in, notHex)
if end == -1 {
return false
}
hexBytes := in[:end]
in = in[end:]
if len(hexBytes)&1 != 0 {
return false
}
mpiBytes := make([]byte, len(hexBytes)/2)
if _, err := hex.Decode(mpiBytes, hexBytes); err != nil {
return false
}
mpis[i] = new(big.Int).SetBytes(mpiBytes)
}
for _, mpi := range mpis {
if mpi.Sign() <= 0 {
return false
}
}
priv.PrivateKey.P = mpis[0]
priv.PrivateKey.Q = mpis[1]
priv.PrivateKey.G = mpis[2]
priv.PrivateKey.Y = mpis[3]
priv.PrivateKey.X = mpis[4]
priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey
a := new(big.Int).Exp(priv.PrivateKey.G, priv.PrivateKey.X, priv.PrivateKey.P)
return a.Cmp(priv.PrivateKey.Y) == 0
}
func getU8(in []byte) (uint8, []byte, bool) {
if len(in) < 1 {
return 0, in, false
}
return in[0], in[1:], true
}
func getU16(in []byte) (uint16, []byte, bool) {
if len(in) < 2 {
return 0, in, false
}
r := uint16(in[0])<<8 | uint16(in[1])
return r, in[2:], true
}
func getU32(in []byte) (uint32, []byte, bool) {
if len(in) < 4 {
return 0, in, false
}
r := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
return r, in[4:], true
}
func getMPI(in []byte) (*big.Int, []byte, bool) {
l, in, ok := getU32(in)
if !ok || uint32(len(in)) < l {
return nil, in, false
}
r := new(big.Int).SetBytes(in[:l])
return r, in[l:], true
}
func getData(in []byte) ([]byte, []byte, bool) {
l, in, ok := getU32(in)
if !ok || uint32(len(in)) < l {
return nil, in, false
}
return in[:l], in[l:], true
}
func getNBytes(in []byte, n int) ([]byte, []byte, bool) {
if len(in) < n {
return nil, in, false
}
return in[:n], in[n:], true
}
func appendU16(out []byte, v uint16) []byte {
out = append(out, byte(v>>8), byte(v))
return out
}
func appendU32(out []byte, v uint32) []byte {
out = append(out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
return out
}
func appendData(out, v []byte) []byte {
out = appendU32(out, uint32(len(v)))
out = append(out, v...)
return out
}
func appendMPI(out []byte, v *big.Int) []byte {
vBytes := v.Bytes()
out = appendU32(out, uint32(len(vBytes)))
out = append(out, vBytes...)
return out
}
func appendMPIs(out []byte, mpis ...*big.Int) []byte {
for _, mpi := range mpis {
out = appendMPI(out, mpi)
}
return out
}
func zero(b []byte) {
for i := range b {
b[i] = 0
}
}