Files
crypto/otr/otr_test.go
T
Adam Langley c9c0e06eed go.crypto/otr: expose IsEncrypted.
It's useful to expose this bit of state because, although callers can
keep track of it themselves, it's already in the Conversation so it
seems wasteful to make them do so.

R=golang-dev, ioerror
CC=golang-dev
https://golang.org/cl/6724056
2012-10-17 14:58:16 -04:00

369 lines
12 KiB
Go

// 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.
package otr
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/hex"
"math/big"
"os"
"os/exec"
"testing"
)
var isQueryTests = []struct {
msg string
expectedVersion int
}{
{"foo", 0},
{"?OtR", 0},
{"?OtR?", 0},
{"?OTR?", 0},
{"?OTRv?", 0},
{"?OTRv1?", 0},
{"?OTR?v1?", 0},
{"?OTR?v?", 0},
{"?OTR?v2?", 2},
{"?OTRv2?", 2},
{"?OTRv23?", 2},
{"?OTRv23 ?", 0},
}
func TestIsQuery(t *testing.T) {
for i, test := range isQueryTests {
version := isQuery([]byte(test.msg))
if version != test.expectedVersion {
t.Errorf("#%d: got %d, want %d", i, version, test.expectedVersion)
}
}
}
var alicePrivateKeyHex = "000000000080c81c2cb2eb729b7e6fd48e975a932c638b3a9055478583afa46755683e30102447f6da2d8bec9f386bbb5da6403b0040fee8650b6ab2d7f32c55ab017ae9b6aec8c324ab5844784e9a80e194830d548fb7f09a0410df2c4d5c8bc2b3e9ad484e65412be689cf0834694e0839fb2954021521ffdffb8f5c32c14dbf2020b3ce7500000014da4591d58def96de61aea7b04a8405fe1609308d000000808ddd5cb0b9d66956e3dea5a915d9aba9d8a6e7053b74dadb2fc52f9fe4e5bcc487d2305485ed95fed026ad93f06ebb8c9e8baf693b7887132c7ffdd3b0f72f4002ff4ed56583ca7c54458f8c068ca3e8a4dfa309d1dd5d34e2a4b68e6f4338835e5e0fb4317c9e4c7e4806dafda3ef459cd563775a586dd91b1319f72621bf3f00000080b8147e74d8c45e6318c37731b8b33b984a795b3653c2cd1d65cc99efe097cb7eb2fa49569bab5aab6e8a1c261a27d0f7840a5e80b317e6683042b59b6dceca2879c6ffc877a465be690c15e4a42f9a7588e79b10faac11b1ce3741fcef7aba8ce05327a2c16d279ee1b3d77eb783fb10e3356caa25635331e26dd42b8396c4d00000001420bec691fea37ecea58a5c717142f0b804452f57"
var aliceFingerprintHex = "0bb01c360424522e94ee9c346ce877a1a4288b2f"
var bobPrivateKeyHex = "000000000080a5138eb3d3eb9c1d85716faecadb718f87d31aaed1157671d7fee7e488f95e8e0ba60ad449ec732710a7dec5190f7182af2e2f98312d98497221dff160fd68033dd4f3a33b7c078d0d9f66e26847e76ca7447d4bab35486045090572863d9e4454777f24d6706f63e02548dfec2d0a620af37bbc1d24f884708a212c343b480d00000014e9c58f0ea21a5e4dfd9f44b6a9f7f6a9961a8fa9000000803c4d111aebd62d3c50c2889d420a32cdf1e98b70affcc1fcf44d59cca2eb019f6b774ef88153fb9b9615441a5fe25ea2d11b74ce922ca0232bd81b3c0fcac2a95b20cb6e6c0c5c1ace2e26f65dc43c751af0edbb10d669890e8ab6beea91410b8b2187af1a8347627a06ecea7e0f772c28aae9461301e83884860c9b656c722f0000008065af8625a555ea0e008cd04743671a3cda21162e83af045725db2eb2bb52712708dc0cc1a84c08b3649b88a966974bde27d8612c2861792ec9f08786a246fcadd6d8d3a81a32287745f309238f47618c2bd7612cb8b02d940571e0f30b96420bcd462ff542901b46109b1e5ad6423744448d20a57818a8cbb1647d0fea3b664e0000001440f9f2eb554cb00d45a5826b54bfa419b6980e48"
func TestKeySerialization(t *testing.T) {
var priv PrivateKey
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
rest, ok := priv.Parse(alicePrivateKey)
if !ok {
t.Error("failed to parse private key")
}
if len(rest) > 0 {
t.Error("data remaining after parsing private key")
}
out := priv.Serialize(nil)
if !bytes.Equal(alicePrivateKey, out) {
t.Errorf("serialization (%x) is not equal to original (%x)", out, alicePrivateKey)
}
aliceFingerprint, _ := hex.DecodeString(aliceFingerprintHex)
fingerprint := priv.PublicKey.Fingerprint()
if !bytes.Equal(aliceFingerprint, fingerprint) {
t.Errorf("fingerprint (%x) is not equal to expected value (%x)", fingerprint, aliceFingerprint)
}
}
const libOTRPrivateKey = `(privkeys
(account
(name "foo@example.com")
(protocol prpl-jabber)
(private-key
(dsa
(p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
(q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
(g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
(y #0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A3C0FF501E3DC673B76D7BABF349009B6ECF#)
(x #14D0345A3562C480A039E3C72764F72D79043216#)
)
)
)
)`
func TestParseLibOTRPrivateKey(t *testing.T) {
var priv PrivateKey
if !priv.Import([]byte(libOTRPrivateKey)) {
t.Fatalf("Failed to import sample private key")
}
}
func TestSignVerify(t *testing.T) {
var priv PrivateKey
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
_, ok := priv.Parse(alicePrivateKey)
if !ok {
t.Error("failed to parse private key")
}
var msg [32]byte
rand.Reader.Read(msg[:])
sig := priv.Sign(rand.Reader, msg[:])
rest, ok := priv.PublicKey.Verify(msg[:], sig)
if !ok {
t.Errorf("signature (%x) of %x failed to verify", sig, msg[:])
} else if len(rest) > 0 {
t.Error("signature data remains after verification")
}
sig[10] ^= 80
_, ok = priv.PublicKey.Verify(msg[:], sig)
if ok {
t.Errorf("corrupted signature (%x) of %x verified", sig, msg[:])
}
}
func TestConversation(t *testing.T) {
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
bobPrivateKey, _ := hex.DecodeString(bobPrivateKeyHex)
var alice, bob Conversation
alice.PrivateKey = new(PrivateKey)
bob.PrivateKey = new(PrivateKey)
alice.PrivateKey.Parse(alicePrivateKey)
bob.PrivateKey.Parse(bobPrivateKey)
alice.FragmentSize = 100
bob.FragmentSize = 100
var alicesMessage, bobsMessage [][]byte
var out []byte
var aliceChange, bobChange SecurityChange
var err error
alicesMessage = append(alicesMessage, []byte(QueryMessage))
if alice.IsEncrypted() {
t.Error("Alice believes that the conversation is secure before we've started")
}
if bob.IsEncrypted() {
t.Error("Bob believes that the conversation is secure before we've started")
}
for round := 0; len(alicesMessage) > 0 || len(bobsMessage) > 0; round++ {
bobsMessage = nil
for i, msg := range alicesMessage {
out, _, bobChange, bobsMessage, err = bob.Receive(msg)
if len(out) > 0 {
t.Errorf("Bob generated output during key exchange, round %d, message %d", round, i)
}
if err != nil {
t.Fatalf("Bob returned an error, round %d, message %d (%x): %s", round, i, msg, err)
}
if len(bobsMessage) > 0 && i != len(alicesMessage)-1 {
t.Errorf("Bob produced output while processing a fragment, round %d, message %d", round, i)
}
}
alicesMessage = nil
for i, msg := range bobsMessage {
out, _, aliceChange, alicesMessage, err = alice.Receive(msg)
if len(out) > 0 {
t.Errorf("Alice generated output during key exchange, round %d, message %d", round, i)
}
if err != nil {
t.Fatalf("Alice returned an error, round %d, message %d (%x): %s", round, i, msg, err)
}
if len(alicesMessage) > 0 && i != len(bobsMessage)-1 {
t.Errorf("Alice produced output while processing a fragment, round %d, message %d", round, i)
}
}
}
if aliceChange != NewKeys {
t.Errorf("Alice terminated without signaling new keys")
}
if bobChange != NewKeys {
t.Errorf("Bob terminated without signaling new keys")
}
if !bytes.Equal(alice.SSID[:], bob.SSID[:]) {
t.Errorf("Session identifiers don't match. Alice has %x, Bob has %x", alice.SSID[:], bob.SSID[:])
}
if !alice.IsEncrypted() {
t.Error("Alice doesn't believe that the conversation is secure")
}
if !bob.IsEncrypted() {
t.Error("Bob doesn't believe that the conversation is secure")
}
var testMessage = []byte("hello Bob")
alicesMessage, err = alice.Send(testMessage)
for i, msg := range alicesMessage {
out, encrypted, _, _, err := bob.Receive(msg)
if err != nil {
t.Errorf("Error generated while processing test message: %s", err.Error())
}
if len(out) > 0 {
if i != len(alicesMessage)-1 {
t.Fatal("Bob produced a message while processing a fragment of Alice's")
}
if !encrypted {
t.Errorf("Message was not marked as encrypted")
}
if !bytes.Equal(out, testMessage) {
t.Errorf("Message corrupted: got %x, want %x", out, testMessage)
}
}
}
}
func TestGoodSMP(t *testing.T) {
var alice, bob Conversation
alice.smp.secret = new(big.Int).SetInt64(42)
bob.smp.secret = alice.smp.secret
var alicesMessages, bobsMessages []tlv
var aliceComplete, bobComplete bool
var err error
var out tlv
alicesMessages = alice.startSMP("")
for round := 0; len(alicesMessages) > 0 || len(bobsMessages) > 0; round++ {
bobsMessages = bobsMessages[:0]
for i, msg := range alicesMessages {
out, bobComplete, err = bob.processSMP(msg)
if err != nil {
t.Errorf("Error from Bob in round %d: %s", round, err)
}
if bobComplete && i != len(alicesMessages)-1 {
t.Errorf("Bob returned a completed signal before processing all of Alice's messages in round %d", round)
}
if out.typ != 0 {
bobsMessages = append(bobsMessages, out)
}
}
alicesMessages = alicesMessages[:0]
for i, msg := range bobsMessages {
out, aliceComplete, err = alice.processSMP(msg)
if err != nil {
t.Errorf("Error from Alice in round %d: %s", round, err)
}
if aliceComplete && i != len(bobsMessages)-1 {
t.Errorf("Alice returned a completed signal before processing all of Bob's messages in round %d", round)
}
if out.typ != 0 {
alicesMessages = append(alicesMessages, out)
}
}
}
if !aliceComplete || !bobComplete {
t.Errorf("SMP completed without both sides reporting success: alice: %v, bob: %v\n", aliceComplete, bobComplete)
}
}
func TestBadSMP(t *testing.T) {
var alice, bob Conversation
alice.smp.secret = new(big.Int).SetInt64(42)
bob.smp.secret = new(big.Int).SetInt64(43)
var alicesMessages, bobsMessages []tlv
alicesMessages = alice.startSMP("")
for round := 0; len(alicesMessages) > 0 || len(bobsMessages) > 0; round++ {
bobsMessages = bobsMessages[:0]
for _, msg := range alicesMessages {
out, complete, _ := bob.processSMP(msg)
if complete {
t.Errorf("Bob signaled completion in round %d", round)
}
if out.typ != 0 {
bobsMessages = append(bobsMessages, out)
}
}
alicesMessages = alicesMessages[:0]
for _, msg := range bobsMessages {
out, complete, _ := alice.processSMP(msg)
if complete {
t.Errorf("Alice signaled completion in round %d", round)
}
if out.typ != 0 {
alicesMessages = append(alicesMessages, out)
}
}
}
}
func TestAgainstLibOTR(t *testing.T) {
// This test requires otr.c.test to be built as /tmp/a.out.
// If enabled, this tests runs forever performing OTR handshakes in a
// loop.
return
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
var alice Conversation
alice.PrivateKey = new(PrivateKey)
alice.PrivateKey.Parse(alicePrivateKey)
cmd := exec.Command("/tmp/a.out")
cmd.Stderr = os.Stderr
out, err := cmd.StdinPipe()
if err != nil {
t.Fatal(err)
}
defer out.Close()
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
in := bufio.NewReader(stdout)
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
out.Write([]byte(QueryMessage))
out.Write([]byte("\n"))
var expectedText = []byte("test message")
for {
line, isPrefix, err := in.ReadLine()
if isPrefix {
t.Fatal("line from subprocess too long")
}
if err != nil {
t.Fatal(err)
}
text, encrypted, change, alicesMessage, err := alice.Receive(line)
if err != nil {
t.Fatal(err)
}
for _, msg := range alicesMessage {
out.Write(msg)
out.Write([]byte("\n"))
}
if change == NewKeys {
alicesMessage, err := alice.Send([]byte("Go -> libotr test message"))
if err != nil {
t.Errorf("error sending message: %s", err.Error())
} else {
for _, msg := range alicesMessage {
out.Write(msg)
out.Write([]byte("\n"))
}
}
}
if len(text) > 0 {
if !bytes.Equal(text, expectedText) {
t.Errorf("expected %x, but got %x", expectedText, text)
}
if !encrypted {
t.Error("message wasn't encrypted")
}
}
}
}