otr: add package
otr implements the Off-the-Record messaging protocol from http://www.cypherpunks.ca/otr/. It's often used on IRC and Jabber for private conversations. R=golang-dev, rsc, dchest CC=golang-dev https://golang.org/cl/6300064
This commit is contained in:
Родитель
f8bd48becc
Коммит
929dedf39d
|
@ -0,0 +1,171 @@
|
|||
// 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 code can be compiled and used to test the otr package against libotr.
|
||||
// See otr_test.go.
|
||||
|
||||
// +build ignore
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <proto.h>
|
||||
#include <message.h>
|
||||
|
||||
static int g_session_established = 0;
|
||||
|
||||
OtrlPolicy policy(void *opdata, ConnContext *context) {
|
||||
return OTRL_POLICY_ALWAYS;
|
||||
}
|
||||
|
||||
int is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void inject_message(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message) {
|
||||
printf("%s\n", message);
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "libotr helper sent: %s\n", message);
|
||||
}
|
||||
|
||||
void notify(void *opdata, OtrlNotifyLevel level, const char *accountname, const char *protocol, const char *username, const char *title, const char *primary, const char *secondary) {
|
||||
fprintf(stderr, "NOTIFY: %s %s %s %s\n", username, title, primary, secondary);
|
||||
}
|
||||
|
||||
int display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg) {
|
||||
fprintf(stderr, "MESSAGE: %s %s\n", username, msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void update_context_list(void *opdata) {
|
||||
}
|
||||
|
||||
const char *protocol_name(void *opdata, const char *protocol) {
|
||||
return "PROTOCOL";
|
||||
}
|
||||
|
||||
void protocol_name_free(void *opdata, const char *protocol_name) {
|
||||
}
|
||||
|
||||
void new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) {
|
||||
fprintf(stderr, "NEW FINGERPRINT\n");
|
||||
g_session_established = 1;
|
||||
}
|
||||
|
||||
void write_fingerprints(void *opdata) {
|
||||
}
|
||||
|
||||
void gone_secure(void *opdata, ConnContext *context) {
|
||||
}
|
||||
|
||||
void gone_insecure(void *opdata, ConnContext *context) {
|
||||
}
|
||||
|
||||
void still_secure(void *opdata, ConnContext *context, int is_reply) {
|
||||
}
|
||||
|
||||
void log_message(void *opdata, const char *message) {
|
||||
fprintf(stderr, "MESSAGE: %s\n", message);
|
||||
}
|
||||
|
||||
int max_message_size(void *opdata, ConnContext *context) {
|
||||
return 99999;
|
||||
}
|
||||
|
||||
const char *account_name(void *opdata, const char *account, const char *protocol) {
|
||||
return "ACCOUNT";
|
||||
}
|
||||
|
||||
void account_name_free(void *opdata, const char *account_name) {
|
||||
}
|
||||
|
||||
OtrlMessageAppOps uiops = {
|
||||
policy,
|
||||
NULL,
|
||||
is_logged_in,
|
||||
inject_message,
|
||||
notify,
|
||||
display_otr_message,
|
||||
update_context_list,
|
||||
protocol_name,
|
||||
protocol_name_free,
|
||||
new_fingerprint,
|
||||
write_fingerprints,
|
||||
gone_secure,
|
||||
gone_insecure,
|
||||
still_secure,
|
||||
log_message,
|
||||
max_message_size,
|
||||
account_name,
|
||||
account_name_free,
|
||||
};
|
||||
|
||||
static const char kPrivateKeyData[] = "(privkeys (account (name \"account\") (protocol proto) (private-key (dsa (p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#) (q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#) (g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#) (y #0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A3C0FF501E3DC673B76D7BABF349009B6ECF#) (x #14D0345A3562C480A039E3C72764F72D79043216#)))))\n";
|
||||
|
||||
int
|
||||
main() {
|
||||
OTRL_INIT;
|
||||
|
||||
// We have to write the private key information to a file because the libotr
|
||||
// API demands a filename to read from.
|
||||
const char *tmpdir = "/tmp";
|
||||
if (getenv("TMP")) {
|
||||
tmpdir = getenv("TMP");
|
||||
}
|
||||
|
||||
char private_key_file[256];
|
||||
snprintf(private_key_file, sizeof(private_key_file), "%s/libotr_test_helper_privatekeys-XXXXXX", tmpdir);
|
||||
int fd = mkstemp(private_key_file);
|
||||
if (fd == -1) {
|
||||
perror("creating temp file");
|
||||
}
|
||||
write(fd, kPrivateKeyData, sizeof(kPrivateKeyData)-1);
|
||||
close(fd);
|
||||
|
||||
OtrlUserState userstate = otrl_userstate_create();
|
||||
otrl_privkey_read(userstate, private_key_file);
|
||||
unlink(private_key_file);
|
||||
|
||||
fprintf(stderr, "libotr helper started\n");
|
||||
|
||||
char buf[4096];
|
||||
|
||||
for (;;) {
|
||||
char* message = fgets(buf, sizeof(buf), stdin);
|
||||
if (strlen(message) == 0) {
|
||||
break;
|
||||
}
|
||||
message[strlen(message) - 1] = 0;
|
||||
fprintf(stderr, "libotr helper got: %s\n", message);
|
||||
|
||||
char *newmessage = NULL;
|
||||
OtrlTLV *tlvs;
|
||||
int ignore_message = otrl_message_receiving(userstate, &uiops, NULL, "account", "proto", "peer", message, &newmessage, &tlvs, NULL, NULL);
|
||||
if (tlvs) {
|
||||
otrl_tlv_free(tlvs);
|
||||
}
|
||||
|
||||
if (newmessage != NULL) {
|
||||
fprintf(stderr, "libotr got: %s\n", newmessage);
|
||||
otrl_message_free(newmessage);
|
||||
|
||||
gcry_error_t err;
|
||||
char *newmessage = NULL;
|
||||
|
||||
err = otrl_message_sending(userstate, &uiops, NULL, "account", "proto", "peer", "test message", NULL, &newmessage, NULL, NULL);
|
||||
if (newmessage == NULL) {
|
||||
fprintf(stderr, "libotr didn't encrypt message\n");
|
||||
return 1;
|
||||
}
|
||||
write(1, newmessage, strlen(newmessage));
|
||||
write(1, "\n", 1);
|
||||
g_session_established = 0;
|
||||
otrl_message_free(newmessage);
|
||||
write(1, "?OTRv2?\n", 8);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,354 @@
|
|||
// 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, QueryMessage)
|
||||
|
||||
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[:])
|
||||
}
|
||||
|
||||
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(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,572 @@
|
|||
// 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 otr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"hash"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
Загрузка…
Ссылка в новой задаче