We can do that now as we're no longer carrying archive/tar.
Note that latest vndr removes vendor/ subdir so we don't have to,
thus the change in hack/validate/vendor.
While at it, re-run a new vndr version to make sure everything
that should be there is.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
| ... | ... |
@@ -9,10 +9,7 @@ validate_vendor_diff(){
|
| 9 | 9 |
unset IFS |
| 10 | 10 |
|
| 11 | 11 |
if [ ${#files[@]} -gt 0 ]; then
|
| 12 |
- # Remove vendor/ first so that anything not included in vendor.conf will |
|
| 13 |
- # cause the validation to fail. |
|
| 14 |
- ls -d vendor/* | xargs rm -rf |
|
| 15 |
- # run vndr to recreate vendor/ |
|
| 12 |
+ # recreate vendor/ |
|
| 16 | 13 |
vndr |
| 17 | 14 |
# check if any files have changed |
| 18 | 15 |
diffs="$(git status --porcelain -- vendor 2>/dev/null)" |
| 19 | 16 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,197 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// This code can be compiled and used to test the otr package against libotr. |
|
| 5 |
+// See otr_test.go. |
|
| 6 |
+ |
|
| 7 |
+// +build ignore |
|
| 8 |
+ |
|
| 9 |
+#include <stdio.h> |
|
| 10 |
+#include <stdlib.h> |
|
| 11 |
+#include <unistd.h> |
|
| 12 |
+ |
|
| 13 |
+#include <proto.h> |
|
| 14 |
+#include <message.h> |
|
| 15 |
+#include <privkey.h> |
|
| 16 |
+ |
|
| 17 |
+static int g_session_established = 0; |
|
| 18 |
+ |
|
| 19 |
+OtrlPolicy policy(void *opdata, ConnContext *context) {
|
|
| 20 |
+ return OTRL_POLICY_ALWAYS; |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+int is_logged_in(void *opdata, const char *accountname, const char *protocol, |
|
| 24 |
+ const char *recipient) {
|
|
| 25 |
+ return 1; |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+void inject_message(void *opdata, const char *accountname, const char *protocol, |
|
| 29 |
+ const char *recipient, const char *message) {
|
|
| 30 |
+ printf("%s\n", message);
|
|
| 31 |
+ fflush(stdout); |
|
| 32 |
+ fprintf(stderr, "libotr helper sent: %s\n", message); |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+void update_context_list(void *opdata) {}
|
|
| 36 |
+ |
|
| 37 |
+void new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, |
|
| 38 |
+ const char *protocol, const char *username, |
|
| 39 |
+ unsigned char fingerprint[20]) {
|
|
| 40 |
+ fprintf(stderr, "NEW FINGERPRINT\n"); |
|
| 41 |
+ g_session_established = 1; |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+void write_fingerprints(void *opdata) {}
|
|
| 45 |
+ |
|
| 46 |
+void gone_secure(void *opdata, ConnContext *context) {}
|
|
| 47 |
+ |
|
| 48 |
+void gone_insecure(void *opdata, ConnContext *context) {}
|
|
| 49 |
+ |
|
| 50 |
+void still_secure(void *opdata, ConnContext *context, int is_reply) {}
|
|
| 51 |
+ |
|
| 52 |
+int max_message_size(void *opdata, ConnContext *context) { return 99999; }
|
|
| 53 |
+ |
|
| 54 |
+const char *account_name(void *opdata, const char *account, |
|
| 55 |
+ const char *protocol) {
|
|
| 56 |
+ return "ACCOUNT"; |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+void account_name_free(void *opdata, const char *account_name) {}
|
|
| 60 |
+ |
|
| 61 |
+const char *error_message(void *opdata, ConnContext *context, |
|
| 62 |
+ OtrlErrorCode err_code) {
|
|
| 63 |
+ return "ERR"; |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+void error_message_free(void *opdata, const char *msg) {}
|
|
| 67 |
+ |
|
| 68 |
+void resent_msg_prefix_free(void *opdata, const char *prefix) {}
|
|
| 69 |
+ |
|
| 70 |
+void handle_smp_event(void *opdata, OtrlSMPEvent smp_event, |
|
| 71 |
+ ConnContext *context, unsigned short progress_event, |
|
| 72 |
+ char *question) {}
|
|
| 73 |
+ |
|
| 74 |
+void handle_msg_event(void *opdata, OtrlMessageEvent msg_event, |
|
| 75 |
+ ConnContext *context, const char *message, |
|
| 76 |
+ gcry_error_t err) {
|
|
| 77 |
+ fprintf(stderr, "msg event: %d %s\n", msg_event, message); |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+OtrlMessageAppOps uiops = {
|
|
| 81 |
+ policy, |
|
| 82 |
+ NULL, |
|
| 83 |
+ is_logged_in, |
|
| 84 |
+ inject_message, |
|
| 85 |
+ update_context_list, |
|
| 86 |
+ new_fingerprint, |
|
| 87 |
+ write_fingerprints, |
|
| 88 |
+ gone_secure, |
|
| 89 |
+ gone_insecure, |
|
| 90 |
+ still_secure, |
|
| 91 |
+ max_message_size, |
|
| 92 |
+ account_name, |
|
| 93 |
+ account_name_free, |
|
| 94 |
+ NULL, /* received_symkey */ |
|
| 95 |
+ error_message, |
|
| 96 |
+ error_message_free, |
|
| 97 |
+ NULL, /* resent_msg_prefix */ |
|
| 98 |
+ resent_msg_prefix_free, |
|
| 99 |
+ handle_smp_event, |
|
| 100 |
+ handle_msg_event, |
|
| 101 |
+ NULL /* create_instag */, |
|
| 102 |
+ NULL /* convert_msg */, |
|
| 103 |
+ NULL /* convert_free */, |
|
| 104 |
+ NULL /* timer_control */, |
|
| 105 |
+}; |
|
| 106 |
+ |
|
| 107 |
+static const char kPrivateKeyData[] = |
|
| 108 |
+ "(privkeys (account (name \"account\") (protocol proto) (private-key (dsa " |
|
| 109 |
+ "(p " |
|
| 110 |
+ "#00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F" |
|
| 111 |
+ "30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E" |
|
| 112 |
+ "5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB" |
|
| 113 |
+ "8C031D3561FECEE72EBB4A090D450A9B7A857#) (q " |
|
| 114 |
+ "#00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#) (g " |
|
| 115 |
+ "#535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F" |
|
| 116 |
+ "1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F" |
|
| 117 |
+ "6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57" |
|
| 118 |
+ "597766A2F9CE3857D7ACE3E1E3BC1FC6F26#) (y " |
|
| 119 |
+ "#0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF" |
|
| 120 |
+ "2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93" |
|
| 121 |
+ "454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A" |
|
| 122 |
+ "3C0FF501E3DC673B76D7BABF349009B6ECF#) (x " |
|
| 123 |
+ "#14D0345A3562C480A039E3C72764F72D79043216#)))))\n"; |
|
| 124 |
+ |
|
| 125 |
+int main() {
|
|
| 126 |
+ OTRL_INIT; |
|
| 127 |
+ |
|
| 128 |
+ // We have to write the private key information to a file because the libotr |
|
| 129 |
+ // API demands a filename to read from. |
|
| 130 |
+ const char *tmpdir = "/tmp"; |
|
| 131 |
+ if (getenv("TMP")) {
|
|
| 132 |
+ tmpdir = getenv("TMP");
|
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ char private_key_file[256]; |
|
| 136 |
+ snprintf(private_key_file, sizeof(private_key_file), |
|
| 137 |
+ "%s/libotr_test_helper_privatekeys-XXXXXX", tmpdir); |
|
| 138 |
+ int fd = mkstemp(private_key_file); |
|
| 139 |
+ if (fd == -1) {
|
|
| 140 |
+ perror("creating temp file");
|
|
| 141 |
+ } |
|
| 142 |
+ write(fd, kPrivateKeyData, sizeof(kPrivateKeyData) - 1); |
|
| 143 |
+ close(fd); |
|
| 144 |
+ |
|
| 145 |
+ OtrlUserState userstate = otrl_userstate_create(); |
|
| 146 |
+ otrl_privkey_read(userstate, private_key_file); |
|
| 147 |
+ unlink(private_key_file); |
|
| 148 |
+ |
|
| 149 |
+ fprintf(stderr, "libotr helper started\n"); |
|
| 150 |
+ |
|
| 151 |
+ char buf[4096]; |
|
| 152 |
+ |
|
| 153 |
+ for (;;) {
|
|
| 154 |
+ char *message = fgets(buf, sizeof(buf), stdin); |
|
| 155 |
+ if (strlen(message) == 0) {
|
|
| 156 |
+ break; |
|
| 157 |
+ } |
|
| 158 |
+ message[strlen(message) - 1] = 0; |
|
| 159 |
+ fprintf(stderr, "libotr helper got: %s\n", message); |
|
| 160 |
+ |
|
| 161 |
+ char *newmessage = NULL; |
|
| 162 |
+ OtrlTLV *tlvs; |
|
| 163 |
+ int ignore_message = otrl_message_receiving( |
|
| 164 |
+ userstate, &uiops, NULL, "account", "proto", "peer", message, |
|
| 165 |
+ &newmessage, &tlvs, NULL, NULL, NULL); |
|
| 166 |
+ if (tlvs) {
|
|
| 167 |
+ otrl_tlv_free(tlvs); |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ if (newmessage != NULL) {
|
|
| 171 |
+ fprintf(stderr, "libotr got: %s\n", newmessage); |
|
| 172 |
+ otrl_message_free(newmessage); |
|
| 173 |
+ |
|
| 174 |
+ gcry_error_t err; |
|
| 175 |
+ char *newmessage = NULL; |
|
| 176 |
+ |
|
| 177 |
+ err = otrl_message_sending(userstate, &uiops, NULL, "account", "proto", |
|
| 178 |
+ "peer", 0, "test message", NULL, &newmessage, |
|
| 179 |
+ OTRL_FRAGMENT_SEND_SKIP, NULL, NULL, NULL); |
|
| 180 |
+ if (newmessage == NULL) {
|
|
| 181 |
+ fprintf(stderr, "libotr didn't encrypt message\n"); |
|
| 182 |
+ return 1; |
|
| 183 |
+ } |
|
| 184 |
+ write(1, newmessage, strlen(newmessage)); |
|
| 185 |
+ write(1, "\n", 1); |
|
| 186 |
+ fprintf(stderr, "libotr sent: %s\n", newmessage); |
|
| 187 |
+ otrl_message_free(newmessage); |
|
| 188 |
+ |
|
| 189 |
+ g_session_established = 0; |
|
| 190 |
+ write(1, "?OTRv2?\n", 8); |
|
| 191 |
+ fprintf(stderr, "libotr sent: ?OTRv2\n"); |
|
| 192 |
+ } |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ return 0; |
|
| 196 |
+} |
| 0 | 197 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1415 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package otr implements the Off The Record protocol as specified in |
|
| 5 |
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html |
|
| 6 |
+package otr // import "golang.org/x/crypto/otr" |
|
| 7 |
+ |
|
| 8 |
+import ( |
|
| 9 |
+ "bytes" |
|
| 10 |
+ "crypto/aes" |
|
| 11 |
+ "crypto/cipher" |
|
| 12 |
+ "crypto/dsa" |
|
| 13 |
+ "crypto/hmac" |
|
| 14 |
+ "crypto/rand" |
|
| 15 |
+ "crypto/sha1" |
|
| 16 |
+ "crypto/sha256" |
|
| 17 |
+ "crypto/subtle" |
|
| 18 |
+ "encoding/base64" |
|
| 19 |
+ "encoding/hex" |
|
| 20 |
+ "errors" |
|
| 21 |
+ "hash" |
|
| 22 |
+ "io" |
|
| 23 |
+ "math/big" |
|
| 24 |
+ "strconv" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// SecurityChange describes a change in the security state of a Conversation. |
|
| 28 |
+type SecurityChange int |
|
| 29 |
+ |
|
| 30 |
+const ( |
|
| 31 |
+ NoChange SecurityChange = iota |
|
| 32 |
+ // NewKeys indicates that a key exchange has completed. This occurs |
|
| 33 |
+ // when a conversation first becomes encrypted, and when the keys are |
|
| 34 |
+ // renegotiated within an encrypted conversation. |
|
| 35 |
+ NewKeys |
|
| 36 |
+ // SMPSecretNeeded indicates that the peer has started an |
|
| 37 |
+ // authentication and that we need to supply a secret. Call SMPQuestion |
|
| 38 |
+ // to get the optional, human readable challenge and then Authenticate |
|
| 39 |
+ // to supply the matching secret. |
|
| 40 |
+ SMPSecretNeeded |
|
| 41 |
+ // SMPComplete indicates that an authentication completed. The identity |
|
| 42 |
+ // of the peer has now been confirmed. |
|
| 43 |
+ SMPComplete |
|
| 44 |
+ // SMPFailed indicates that an authentication failed. |
|
| 45 |
+ SMPFailed |
|
| 46 |
+ // ConversationEnded indicates that the peer ended the secure |
|
| 47 |
+ // conversation. |
|
| 48 |
+ ConversationEnded |
|
| 49 |
+) |
|
| 50 |
+ |
|
| 51 |
+// QueryMessage can be sent to a peer to start an OTR conversation. |
|
| 52 |
+var QueryMessage = "?OTRv2?" |
|
| 53 |
+ |
|
| 54 |
+// ErrorPrefix can be used to make an OTR error by appending an error message |
|
| 55 |
+// to it. |
|
| 56 |
+var ErrorPrefix = "?OTR Error:" |
|
| 57 |
+ |
|
| 58 |
+var ( |
|
| 59 |
+ fragmentPartSeparator = []byte(",")
|
|
| 60 |
+ fragmentPrefix = []byte("?OTR,")
|
|
| 61 |
+ msgPrefix = []byte("?OTR:")
|
|
| 62 |
+ queryMarker = []byte("?OTR")
|
|
| 63 |
+) |
|
| 64 |
+ |
|
| 65 |
+// isQuery attempts to parse an OTR query from msg and returns the greatest |
|
| 66 |
+// common version, or 0 if msg is not an OTR query. |
|
| 67 |
+func isQuery(msg []byte) (greatestCommonVersion int) {
|
|
| 68 |
+ pos := bytes.Index(msg, queryMarker) |
|
| 69 |
+ if pos == -1 {
|
|
| 70 |
+ return 0 |
|
| 71 |
+ } |
|
| 72 |
+ for i, c := range msg[pos+len(queryMarker):] {
|
|
| 73 |
+ if i == 0 {
|
|
| 74 |
+ if c == '?' {
|
|
| 75 |
+ // Indicates support for version 1, but we don't |
|
| 76 |
+ // implement that. |
|
| 77 |
+ continue |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ if c != 'v' {
|
|
| 81 |
+ // Invalid message |
|
| 82 |
+ return 0 |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ continue |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ if c == '?' {
|
|
| 89 |
+ // End of message |
|
| 90 |
+ return |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ if c == ' ' || c == '\t' {
|
|
| 94 |
+ // Probably an invalid message |
|
| 95 |
+ return 0 |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ if c == '2' {
|
|
| 99 |
+ greatestCommonVersion = 2 |
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ return 0 |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+const ( |
|
| 107 |
+ statePlaintext = iota |
|
| 108 |
+ stateEncrypted |
|
| 109 |
+ stateFinished |
|
| 110 |
+) |
|
| 111 |
+ |
|
| 112 |
+const ( |
|
| 113 |
+ authStateNone = iota |
|
| 114 |
+ authStateAwaitingDHKey |
|
| 115 |
+ authStateAwaitingRevealSig |
|
| 116 |
+ authStateAwaitingSig |
|
| 117 |
+) |
|
| 118 |
+ |
|
| 119 |
+const ( |
|
| 120 |
+ msgTypeDHCommit = 2 |
|
| 121 |
+ msgTypeData = 3 |
|
| 122 |
+ msgTypeDHKey = 10 |
|
| 123 |
+ msgTypeRevealSig = 17 |
|
| 124 |
+ msgTypeSig = 18 |
|
| 125 |
+) |
|
| 126 |
+ |
|
| 127 |
+const ( |
|
| 128 |
+ // If the requested fragment size is less than this, it will be ignored. |
|
| 129 |
+ minFragmentSize = 18 |
|
| 130 |
+ // Messages are padded to a multiple of this number of bytes. |
|
| 131 |
+ paddingGranularity = 256 |
|
| 132 |
+ // The number of bytes in a Diffie-Hellman private value (320-bits). |
|
| 133 |
+ dhPrivateBytes = 40 |
|
| 134 |
+ // The number of bytes needed to represent an element of the DSA |
|
| 135 |
+ // subgroup (160-bits). |
|
| 136 |
+ dsaSubgroupBytes = 20 |
|
| 137 |
+ // The number of bytes of the MAC that are sent on the wire (160-bits). |
|
| 138 |
+ macPrefixBytes = 20 |
|
| 139 |
+) |
|
| 140 |
+ |
|
| 141 |
+// These are the global, common group parameters for OTR. |
|
| 142 |
+var ( |
|
| 143 |
+ p *big.Int // group prime |
|
| 144 |
+ g *big.Int // group generator |
|
| 145 |
+ q *big.Int // group order |
|
| 146 |
+ pMinus2 *big.Int |
|
| 147 |
+) |
|
| 148 |
+ |
|
| 149 |
+func init() {
|
|
| 150 |
+ p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", 16)
|
|
| 151 |
+ q, _ = new(big.Int).SetString("7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA36046511B993FFFFFFFFFFFFFFFF", 16)
|
|
| 152 |
+ g = new(big.Int).SetInt64(2) |
|
| 153 |
+ pMinus2 = new(big.Int).Sub(p, g) |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+// Conversation represents a relation with a peer. The zero value is a valid |
|
| 157 |
+// Conversation, although PrivateKey must be set. |
|
| 158 |
+// |
|
| 159 |
+// When communicating with a peer, all inbound messages should be passed to |
|
| 160 |
+// Conversation.Receive and all outbound messages to Conversation.Send. The |
|
| 161 |
+// Conversation will take care of maintaining the encryption state and |
|
| 162 |
+// negotiating encryption as needed. |
|
| 163 |
+type Conversation struct {
|
|
| 164 |
+ // PrivateKey contains the private key to use to sign key exchanges. |
|
| 165 |
+ PrivateKey *PrivateKey |
|
| 166 |
+ |
|
| 167 |
+ // Rand can be set to override the entropy source. Otherwise, |
|
| 168 |
+ // crypto/rand will be used. |
|
| 169 |
+ Rand io.Reader |
|
| 170 |
+ // If FragmentSize is set, all messages produced by Receive and Send |
|
| 171 |
+ // will be fragmented into messages of, at most, this number of bytes. |
|
| 172 |
+ FragmentSize int |
|
| 173 |
+ |
|
| 174 |
+ // Once Receive has returned NewKeys once, the following fields are |
|
| 175 |
+ // valid. |
|
| 176 |
+ SSID [8]byte |
|
| 177 |
+ TheirPublicKey PublicKey |
|
| 178 |
+ |
|
| 179 |
+ state, authState int |
|
| 180 |
+ |
|
| 181 |
+ r [16]byte |
|
| 182 |
+ x, y *big.Int |
|
| 183 |
+ gx, gy *big.Int |
|
| 184 |
+ gxBytes []byte |
|
| 185 |
+ digest [sha256.Size]byte |
|
| 186 |
+ |
|
| 187 |
+ revealKeys, sigKeys akeKeys |
|
| 188 |
+ |
|
| 189 |
+ myKeyId uint32 |
|
| 190 |
+ myCurrentDHPub *big.Int |
|
| 191 |
+ myCurrentDHPriv *big.Int |
|
| 192 |
+ myLastDHPub *big.Int |
|
| 193 |
+ myLastDHPriv *big.Int |
|
| 194 |
+ |
|
| 195 |
+ theirKeyId uint32 |
|
| 196 |
+ theirCurrentDHPub *big.Int |
|
| 197 |
+ theirLastDHPub *big.Int |
|
| 198 |
+ |
|
| 199 |
+ keySlots [4]keySlot |
|
| 200 |
+ |
|
| 201 |
+ myCounter [8]byte |
|
| 202 |
+ theirLastCtr [8]byte |
|
| 203 |
+ oldMACs []byte |
|
| 204 |
+ |
|
| 205 |
+ k, n int // fragment state |
|
| 206 |
+ frag []byte |
|
| 207 |
+ |
|
| 208 |
+ smp smpState |
|
| 209 |
+} |
|
| 210 |
+ |
|
| 211 |
+// A keySlot contains key material for a specific (their keyid, my keyid) pair. |
|
| 212 |
+type keySlot struct {
|
|
| 213 |
+ // used is true if this slot is valid. If false, it's free for reuse. |
|
| 214 |
+ used bool |
|
| 215 |
+ theirKeyId uint32 |
|
| 216 |
+ myKeyId uint32 |
|
| 217 |
+ sendAESKey, recvAESKey []byte |
|
| 218 |
+ sendMACKey, recvMACKey []byte |
|
| 219 |
+ theirLastCtr [8]byte |
|
| 220 |
+} |
|
| 221 |
+ |
|
| 222 |
+// akeKeys are generated during key exchange. There's one set for the reveal |
|
| 223 |
+// signature message and another for the signature message. In the protocol |
|
| 224 |
+// spec the latter are indicated with a prime mark. |
|
| 225 |
+type akeKeys struct {
|
|
| 226 |
+ c [16]byte |
|
| 227 |
+ m1, m2 [32]byte |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+func (c *Conversation) rand() io.Reader {
|
|
| 231 |
+ if c.Rand != nil {
|
|
| 232 |
+ return c.Rand |
|
| 233 |
+ } |
|
| 234 |
+ return rand.Reader |
|
| 235 |
+} |
|
| 236 |
+ |
|
| 237 |
+func (c *Conversation) randMPI(buf []byte) *big.Int {
|
|
| 238 |
+ _, err := io.ReadFull(c.rand(), buf) |
|
| 239 |
+ if err != nil {
|
|
| 240 |
+ panic("otr: short read from random source")
|
|
| 241 |
+ } |
|
| 242 |
+ |
|
| 243 |
+ return new(big.Int).SetBytes(buf) |
|
| 244 |
+} |
|
| 245 |
+ |
|
| 246 |
+// tlv represents the type-length value from the protocol. |
|
| 247 |
+type tlv struct {
|
|
| 248 |
+ typ, length uint16 |
|
| 249 |
+ data []byte |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+const ( |
|
| 253 |
+ tlvTypePadding = 0 |
|
| 254 |
+ tlvTypeDisconnected = 1 |
|
| 255 |
+ tlvTypeSMP1 = 2 |
|
| 256 |
+ tlvTypeSMP2 = 3 |
|
| 257 |
+ tlvTypeSMP3 = 4 |
|
| 258 |
+ tlvTypeSMP4 = 5 |
|
| 259 |
+ tlvTypeSMPAbort = 6 |
|
| 260 |
+ tlvTypeSMP1WithQuestion = 7 |
|
| 261 |
+) |
|
| 262 |
+ |
|
| 263 |
+// Receive handles a message from a peer. It returns a human readable message, |
|
| 264 |
+// an indicator of whether that message was encrypted, a hint about the |
|
| 265 |
+// encryption state and zero or more messages to send back to the peer. |
|
| 266 |
+// These messages do not need to be passed to Send before transmission. |
|
| 267 |
+func (c *Conversation) Receive(in []byte) (out []byte, encrypted bool, change SecurityChange, toSend [][]byte, err error) {
|
|
| 268 |
+ if bytes.HasPrefix(in, fragmentPrefix) {
|
|
| 269 |
+ in, err = c.processFragment(in) |
|
| 270 |
+ if in == nil || err != nil {
|
|
| 271 |
+ return |
|
| 272 |
+ } |
|
| 273 |
+ } |
|
| 274 |
+ |
|
| 275 |
+ if bytes.HasPrefix(in, msgPrefix) && in[len(in)-1] == '.' {
|
|
| 276 |
+ in = in[len(msgPrefix) : len(in)-1] |
|
| 277 |
+ } else if version := isQuery(in); version > 0 {
|
|
| 278 |
+ c.authState = authStateAwaitingDHKey |
|
| 279 |
+ c.reset() |
|
| 280 |
+ toSend = c.encode(c.generateDHCommit()) |
|
| 281 |
+ return |
|
| 282 |
+ } else {
|
|
| 283 |
+ // plaintext message |
|
| 284 |
+ out = in |
|
| 285 |
+ return |
|
| 286 |
+ } |
|
| 287 |
+ |
|
| 288 |
+ msg := make([]byte, base64.StdEncoding.DecodedLen(len(in))) |
|
| 289 |
+ msgLen, err := base64.StdEncoding.Decode(msg, in) |
|
| 290 |
+ if err != nil {
|
|
| 291 |
+ err = errors.New("otr: invalid base64 encoding in message")
|
|
| 292 |
+ return |
|
| 293 |
+ } |
|
| 294 |
+ msg = msg[:msgLen] |
|
| 295 |
+ |
|
| 296 |
+ // The first two bytes are the protocol version (2) |
|
| 297 |
+ if len(msg) < 3 || msg[0] != 0 || msg[1] != 2 {
|
|
| 298 |
+ err = errors.New("otr: invalid OTR message")
|
|
| 299 |
+ return |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ msgType := int(msg[2]) |
|
| 303 |
+ msg = msg[3:] |
|
| 304 |
+ |
|
| 305 |
+ switch msgType {
|
|
| 306 |
+ case msgTypeDHCommit: |
|
| 307 |
+ switch c.authState {
|
|
| 308 |
+ case authStateNone: |
|
| 309 |
+ c.authState = authStateAwaitingRevealSig |
|
| 310 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 311 |
+ return |
|
| 312 |
+ } |
|
| 313 |
+ c.reset() |
|
| 314 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 315 |
+ return |
|
| 316 |
+ case authStateAwaitingDHKey: |
|
| 317 |
+ // This is a 'SYN-crossing'. The greater digest wins. |
|
| 318 |
+ var cmp int |
|
| 319 |
+ if cmp, err = c.compareToDHCommit(msg); err != nil {
|
|
| 320 |
+ return |
|
| 321 |
+ } |
|
| 322 |
+ if cmp > 0 {
|
|
| 323 |
+ // We win. Retransmit DH commit. |
|
| 324 |
+ toSend = c.encode(c.serializeDHCommit()) |
|
| 325 |
+ return |
|
| 326 |
+ } else {
|
|
| 327 |
+ // They win. We forget about our DH commit. |
|
| 328 |
+ c.authState = authStateAwaitingRevealSig |
|
| 329 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 330 |
+ return |
|
| 331 |
+ } |
|
| 332 |
+ c.reset() |
|
| 333 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 334 |
+ return |
|
| 335 |
+ } |
|
| 336 |
+ case authStateAwaitingRevealSig: |
|
| 337 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 338 |
+ return |
|
| 339 |
+ } |
|
| 340 |
+ toSend = c.encode(c.serializeDHKey()) |
|
| 341 |
+ case authStateAwaitingSig: |
|
| 342 |
+ if err = c.processDHCommit(msg); err != nil {
|
|
| 343 |
+ return |
|
| 344 |
+ } |
|
| 345 |
+ c.reset() |
|
| 346 |
+ toSend = c.encode(c.generateDHKey()) |
|
| 347 |
+ c.authState = authStateAwaitingRevealSig |
|
| 348 |
+ default: |
|
| 349 |
+ panic("bad state")
|
|
| 350 |
+ } |
|
| 351 |
+ case msgTypeDHKey: |
|
| 352 |
+ switch c.authState {
|
|
| 353 |
+ case authStateAwaitingDHKey: |
|
| 354 |
+ var isSame bool |
|
| 355 |
+ if isSame, err = c.processDHKey(msg); err != nil {
|
|
| 356 |
+ return |
|
| 357 |
+ } |
|
| 358 |
+ if isSame {
|
|
| 359 |
+ err = errors.New("otr: unexpected duplicate DH key")
|
|
| 360 |
+ return |
|
| 361 |
+ } |
|
| 362 |
+ toSend = c.encode(c.generateRevealSig()) |
|
| 363 |
+ c.authState = authStateAwaitingSig |
|
| 364 |
+ case authStateAwaitingSig: |
|
| 365 |
+ var isSame bool |
|
| 366 |
+ if isSame, err = c.processDHKey(msg); err != nil {
|
|
| 367 |
+ return |
|
| 368 |
+ } |
|
| 369 |
+ if isSame {
|
|
| 370 |
+ toSend = c.encode(c.serializeDHKey()) |
|
| 371 |
+ } |
|
| 372 |
+ } |
|
| 373 |
+ case msgTypeRevealSig: |
|
| 374 |
+ if c.authState != authStateAwaitingRevealSig {
|
|
| 375 |
+ return |
|
| 376 |
+ } |
|
| 377 |
+ if err = c.processRevealSig(msg); err != nil {
|
|
| 378 |
+ return |
|
| 379 |
+ } |
|
| 380 |
+ toSend = c.encode(c.generateSig()) |
|
| 381 |
+ c.authState = authStateNone |
|
| 382 |
+ c.state = stateEncrypted |
|
| 383 |
+ change = NewKeys |
|
| 384 |
+ case msgTypeSig: |
|
| 385 |
+ if c.authState != authStateAwaitingSig {
|
|
| 386 |
+ return |
|
| 387 |
+ } |
|
| 388 |
+ if err = c.processSig(msg); err != nil {
|
|
| 389 |
+ return |
|
| 390 |
+ } |
|
| 391 |
+ c.authState = authStateNone |
|
| 392 |
+ c.state = stateEncrypted |
|
| 393 |
+ change = NewKeys |
|
| 394 |
+ case msgTypeData: |
|
| 395 |
+ if c.state != stateEncrypted {
|
|
| 396 |
+ err = errors.New("otr: encrypted message received without encrypted session established")
|
|
| 397 |
+ return |
|
| 398 |
+ } |
|
| 399 |
+ var tlvs []tlv |
|
| 400 |
+ out, tlvs, err = c.processData(msg) |
|
| 401 |
+ encrypted = true |
|
| 402 |
+ |
|
| 403 |
+ EachTLV: |
|
| 404 |
+ for _, inTLV := range tlvs {
|
|
| 405 |
+ switch inTLV.typ {
|
|
| 406 |
+ case tlvTypeDisconnected: |
|
| 407 |
+ change = ConversationEnded |
|
| 408 |
+ c.state = stateFinished |
|
| 409 |
+ break EachTLV |
|
| 410 |
+ case tlvTypeSMP1, tlvTypeSMP2, tlvTypeSMP3, tlvTypeSMP4, tlvTypeSMPAbort, tlvTypeSMP1WithQuestion: |
|
| 411 |
+ var reply tlv |
|
| 412 |
+ var complete bool |
|
| 413 |
+ reply, complete, err = c.processSMP(inTLV) |
|
| 414 |
+ if err == smpSecretMissingError {
|
|
| 415 |
+ err = nil |
|
| 416 |
+ change = SMPSecretNeeded |
|
| 417 |
+ c.smp.saved = &inTLV |
|
| 418 |
+ return |
|
| 419 |
+ } |
|
| 420 |
+ if err == smpFailureError {
|
|
| 421 |
+ err = nil |
|
| 422 |
+ change = SMPFailed |
|
| 423 |
+ } else if complete {
|
|
| 424 |
+ change = SMPComplete |
|
| 425 |
+ } |
|
| 426 |
+ if reply.typ != 0 {
|
|
| 427 |
+ toSend = c.encode(c.generateData(nil, &reply)) |
|
| 428 |
+ } |
|
| 429 |
+ break EachTLV |
|
| 430 |
+ default: |
|
| 431 |
+ // skip unknown TLVs |
|
| 432 |
+ } |
|
| 433 |
+ } |
|
| 434 |
+ default: |
|
| 435 |
+ err = errors.New("otr: unknown message type " + strconv.Itoa(msgType))
|
|
| 436 |
+ } |
|
| 437 |
+ |
|
| 438 |
+ return |
|
| 439 |
+} |
|
| 440 |
+ |
|
| 441 |
+// Send takes a human readable message from the local user, possibly encrypts |
|
| 442 |
+// it and returns zero one or more messages to send to the peer. |
|
| 443 |
+func (c *Conversation) Send(msg []byte) ([][]byte, error) {
|
|
| 444 |
+ switch c.state {
|
|
| 445 |
+ case statePlaintext: |
|
| 446 |
+ return [][]byte{msg}, nil
|
|
| 447 |
+ case stateEncrypted: |
|
| 448 |
+ return c.encode(c.generateData(msg, nil)), nil |
|
| 449 |
+ case stateFinished: |
|
| 450 |
+ return nil, errors.New("otr: cannot send message because secure conversation has finished")
|
|
| 451 |
+ } |
|
| 452 |
+ |
|
| 453 |
+ return nil, errors.New("otr: cannot send message in current state")
|
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+// SMPQuestion returns the human readable challenge question from the peer. |
|
| 457 |
+// It's only valid after Receive has returned SMPSecretNeeded. |
|
| 458 |
+func (c *Conversation) SMPQuestion() string {
|
|
| 459 |
+ return c.smp.question |
|
| 460 |
+} |
|
| 461 |
+ |
|
| 462 |
+// Authenticate begins an authentication with the peer. Authentication involves |
|
| 463 |
+// an optional challenge message and a shared secret. The authentication |
|
| 464 |
+// proceeds until either Receive returns SMPComplete, SMPSecretNeeded (which |
|
| 465 |
+// indicates that a new authentication is happening and thus this one was |
|
| 466 |
+// aborted) or SMPFailed. |
|
| 467 |
+func (c *Conversation) Authenticate(question string, mutualSecret []byte) (toSend [][]byte, err error) {
|
|
| 468 |
+ if c.state != stateEncrypted {
|
|
| 469 |
+ err = errors.New("otr: can't authenticate a peer without a secure conversation established")
|
|
| 470 |
+ return |
|
| 471 |
+ } |
|
| 472 |
+ |
|
| 473 |
+ if c.smp.saved != nil {
|
|
| 474 |
+ c.calcSMPSecret(mutualSecret, false /* they started it */) |
|
| 475 |
+ |
|
| 476 |
+ var out tlv |
|
| 477 |
+ var complete bool |
|
| 478 |
+ out, complete, err = c.processSMP(*c.smp.saved) |
|
| 479 |
+ if complete {
|
|
| 480 |
+ panic("SMP completed on the first message")
|
|
| 481 |
+ } |
|
| 482 |
+ c.smp.saved = nil |
|
| 483 |
+ if out.typ != 0 {
|
|
| 484 |
+ toSend = c.encode(c.generateData(nil, &out)) |
|
| 485 |
+ } |
|
| 486 |
+ return |
|
| 487 |
+ } |
|
| 488 |
+ |
|
| 489 |
+ c.calcSMPSecret(mutualSecret, true /* we started it */) |
|
| 490 |
+ outs := c.startSMP(question) |
|
| 491 |
+ for _, out := range outs {
|
|
| 492 |
+ toSend = append(toSend, c.encode(c.generateData(nil, &out))...) |
|
| 493 |
+ } |
|
| 494 |
+ return |
|
| 495 |
+} |
|
| 496 |
+ |
|
| 497 |
+// End ends a secure conversation by generating a termination message for |
|
| 498 |
+// the peer and switches to unencrypted communication. |
|
| 499 |
+func (c *Conversation) End() (toSend [][]byte) {
|
|
| 500 |
+ switch c.state {
|
|
| 501 |
+ case statePlaintext: |
|
| 502 |
+ return nil |
|
| 503 |
+ case stateEncrypted: |
|
| 504 |
+ c.state = statePlaintext |
|
| 505 |
+ return c.encode(c.generateData(nil, &tlv{typ: tlvTypeDisconnected}))
|
|
| 506 |
+ case stateFinished: |
|
| 507 |
+ c.state = statePlaintext |
|
| 508 |
+ return nil |
|
| 509 |
+ } |
|
| 510 |
+ panic("unreachable")
|
|
| 511 |
+} |
|
| 512 |
+ |
|
| 513 |
+// IsEncrypted returns true if a message passed to Send would be encrypted |
|
| 514 |
+// before transmission. This result remains valid until the next call to |
|
| 515 |
+// Receive or End, which may change the state of the Conversation. |
|
| 516 |
+func (c *Conversation) IsEncrypted() bool {
|
|
| 517 |
+ return c.state == stateEncrypted |
|
| 518 |
+} |
|
| 519 |
+ |
|
| 520 |
+var fragmentError = errors.New("otr: invalid OTR fragment")
|
|
| 521 |
+ |
|
| 522 |
+// processFragment processes a fragmented OTR message and possibly returns a |
|
| 523 |
+// complete message. Fragmented messages look like "?OTR,k,n,msg," where k is |
|
| 524 |
+// the fragment number (starting from 1), n is the number of fragments in this |
|
| 525 |
+// message and msg is a substring of the base64 encoded message. |
|
| 526 |
+func (c *Conversation) processFragment(in []byte) (out []byte, err error) {
|
|
| 527 |
+ in = in[len(fragmentPrefix):] // remove "?OTR," |
|
| 528 |
+ parts := bytes.Split(in, fragmentPartSeparator) |
|
| 529 |
+ if len(parts) != 4 || len(parts[3]) != 0 {
|
|
| 530 |
+ return nil, fragmentError |
|
| 531 |
+ } |
|
| 532 |
+ |
|
| 533 |
+ k, err := strconv.Atoi(string(parts[0])) |
|
| 534 |
+ if err != nil {
|
|
| 535 |
+ return nil, fragmentError |
|
| 536 |
+ } |
|
| 537 |
+ |
|
| 538 |
+ n, err := strconv.Atoi(string(parts[1])) |
|
| 539 |
+ if err != nil {
|
|
| 540 |
+ return nil, fragmentError |
|
| 541 |
+ } |
|
| 542 |
+ |
|
| 543 |
+ if k < 1 || n < 1 || k > n {
|
|
| 544 |
+ return nil, fragmentError |
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ if k == 1 {
|
|
| 548 |
+ c.frag = append(c.frag[:0], parts[2]...) |
|
| 549 |
+ c.k, c.n = k, n |
|
| 550 |
+ } else if n == c.n && k == c.k+1 {
|
|
| 551 |
+ c.frag = append(c.frag, parts[2]...) |
|
| 552 |
+ c.k++ |
|
| 553 |
+ } else {
|
|
| 554 |
+ c.frag = c.frag[:0] |
|
| 555 |
+ c.n, c.k = 0, 0 |
|
| 556 |
+ } |
|
| 557 |
+ |
|
| 558 |
+ if c.n > 0 && c.k == c.n {
|
|
| 559 |
+ c.n, c.k = 0, 0 |
|
| 560 |
+ return c.frag, nil |
|
| 561 |
+ } |
|
| 562 |
+ |
|
| 563 |
+ return nil, nil |
|
| 564 |
+} |
|
| 565 |
+ |
|
| 566 |
+func (c *Conversation) generateDHCommit() []byte {
|
|
| 567 |
+ _, err := io.ReadFull(c.rand(), c.r[:]) |
|
| 568 |
+ if err != nil {
|
|
| 569 |
+ panic("otr: short read from random source")
|
|
| 570 |
+ } |
|
| 571 |
+ |
|
| 572 |
+ var xBytes [dhPrivateBytes]byte |
|
| 573 |
+ c.x = c.randMPI(xBytes[:]) |
|
| 574 |
+ c.gx = new(big.Int).Exp(g, c.x, p) |
|
| 575 |
+ c.gy = nil |
|
| 576 |
+ c.gxBytes = appendMPI(nil, c.gx) |
|
| 577 |
+ |
|
| 578 |
+ h := sha256.New() |
|
| 579 |
+ h.Write(c.gxBytes) |
|
| 580 |
+ h.Sum(c.digest[:0]) |
|
| 581 |
+ |
|
| 582 |
+ aesCipher, err := aes.NewCipher(c.r[:]) |
|
| 583 |
+ if err != nil {
|
|
| 584 |
+ panic(err.Error()) |
|
| 585 |
+ } |
|
| 586 |
+ |
|
| 587 |
+ var iv [aes.BlockSize]byte |
|
| 588 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 589 |
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes) |
|
| 590 |
+ |
|
| 591 |
+ return c.serializeDHCommit() |
|
| 592 |
+} |
|
| 593 |
+ |
|
| 594 |
+func (c *Conversation) serializeDHCommit() []byte {
|
|
| 595 |
+ var ret []byte |
|
| 596 |
+ ret = appendU16(ret, 2) // protocol version |
|
| 597 |
+ ret = append(ret, msgTypeDHCommit) |
|
| 598 |
+ ret = appendData(ret, c.gxBytes) |
|
| 599 |
+ ret = appendData(ret, c.digest[:]) |
|
| 600 |
+ return ret |
|
| 601 |
+} |
|
| 602 |
+ |
|
| 603 |
+func (c *Conversation) processDHCommit(in []byte) error {
|
|
| 604 |
+ var ok1, ok2 bool |
|
| 605 |
+ c.gxBytes, in, ok1 = getData(in) |
|
| 606 |
+ digest, in, ok2 := getData(in) |
|
| 607 |
+ if !ok1 || !ok2 || len(in) > 0 {
|
|
| 608 |
+ return errors.New("otr: corrupt DH commit message")
|
|
| 609 |
+ } |
|
| 610 |
+ copy(c.digest[:], digest) |
|
| 611 |
+ return nil |
|
| 612 |
+} |
|
| 613 |
+ |
|
| 614 |
+func (c *Conversation) compareToDHCommit(in []byte) (int, error) {
|
|
| 615 |
+ _, in, ok1 := getData(in) |
|
| 616 |
+ digest, in, ok2 := getData(in) |
|
| 617 |
+ if !ok1 || !ok2 || len(in) > 0 {
|
|
| 618 |
+ return 0, errors.New("otr: corrupt DH commit message")
|
|
| 619 |
+ } |
|
| 620 |
+ return bytes.Compare(c.digest[:], digest), nil |
|
| 621 |
+} |
|
| 622 |
+ |
|
| 623 |
+func (c *Conversation) generateDHKey() []byte {
|
|
| 624 |
+ var yBytes [dhPrivateBytes]byte |
|
| 625 |
+ c.y = c.randMPI(yBytes[:]) |
|
| 626 |
+ c.gy = new(big.Int).Exp(g, c.y, p) |
|
| 627 |
+ return c.serializeDHKey() |
|
| 628 |
+} |
|
| 629 |
+ |
|
| 630 |
+func (c *Conversation) serializeDHKey() []byte {
|
|
| 631 |
+ var ret []byte |
|
| 632 |
+ ret = appendU16(ret, 2) // protocol version |
|
| 633 |
+ ret = append(ret, msgTypeDHKey) |
|
| 634 |
+ ret = appendMPI(ret, c.gy) |
|
| 635 |
+ return ret |
|
| 636 |
+} |
|
| 637 |
+ |
|
| 638 |
+func (c *Conversation) processDHKey(in []byte) (isSame bool, err error) {
|
|
| 639 |
+ gy, in, ok := getMPI(in) |
|
| 640 |
+ if !ok {
|
|
| 641 |
+ err = errors.New("otr: corrupt DH key message")
|
|
| 642 |
+ return |
|
| 643 |
+ } |
|
| 644 |
+ if gy.Cmp(g) < 0 || gy.Cmp(pMinus2) > 0 {
|
|
| 645 |
+ err = errors.New("otr: DH value out of range")
|
|
| 646 |
+ return |
|
| 647 |
+ } |
|
| 648 |
+ if c.gy != nil {
|
|
| 649 |
+ isSame = c.gy.Cmp(gy) == 0 |
|
| 650 |
+ return |
|
| 651 |
+ } |
|
| 652 |
+ c.gy = gy |
|
| 653 |
+ return |
|
| 654 |
+} |
|
| 655 |
+ |
|
| 656 |
+func (c *Conversation) generateEncryptedSignature(keys *akeKeys, xFirst bool) ([]byte, []byte) {
|
|
| 657 |
+ var xb []byte |
|
| 658 |
+ xb = c.PrivateKey.PublicKey.Serialize(xb) |
|
| 659 |
+ |
|
| 660 |
+ var verifyData []byte |
|
| 661 |
+ if xFirst {
|
|
| 662 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 663 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 664 |
+ } else {
|
|
| 665 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 666 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 667 |
+ } |
|
| 668 |
+ verifyData = append(verifyData, xb...) |
|
| 669 |
+ verifyData = appendU32(verifyData, c.myKeyId) |
|
| 670 |
+ |
|
| 671 |
+ mac := hmac.New(sha256.New, keys.m1[:]) |
|
| 672 |
+ mac.Write(verifyData) |
|
| 673 |
+ mb := mac.Sum(nil) |
|
| 674 |
+ |
|
| 675 |
+ xb = appendU32(xb, c.myKeyId) |
|
| 676 |
+ xb = append(xb, c.PrivateKey.Sign(c.rand(), mb)...) |
|
| 677 |
+ |
|
| 678 |
+ aesCipher, err := aes.NewCipher(keys.c[:]) |
|
| 679 |
+ if err != nil {
|
|
| 680 |
+ panic(err.Error()) |
|
| 681 |
+ } |
|
| 682 |
+ var iv [aes.BlockSize]byte |
|
| 683 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 684 |
+ ctr.XORKeyStream(xb, xb) |
|
| 685 |
+ |
|
| 686 |
+ mac = hmac.New(sha256.New, keys.m2[:]) |
|
| 687 |
+ encryptedSig := appendData(nil, xb) |
|
| 688 |
+ mac.Write(encryptedSig) |
|
| 689 |
+ |
|
| 690 |
+ return encryptedSig, mac.Sum(nil) |
|
| 691 |
+} |
|
| 692 |
+ |
|
| 693 |
+func (c *Conversation) generateRevealSig() []byte {
|
|
| 694 |
+ s := new(big.Int).Exp(c.gy, c.x, p) |
|
| 695 |
+ c.calcAKEKeys(s) |
|
| 696 |
+ c.myKeyId++ |
|
| 697 |
+ |
|
| 698 |
+ encryptedSig, mac := c.generateEncryptedSignature(&c.revealKeys, true /* gx comes first */) |
|
| 699 |
+ |
|
| 700 |
+ c.myCurrentDHPub = c.gx |
|
| 701 |
+ c.myCurrentDHPriv = c.x |
|
| 702 |
+ c.rotateDHKeys() |
|
| 703 |
+ incCounter(&c.myCounter) |
|
| 704 |
+ |
|
| 705 |
+ var ret []byte |
|
| 706 |
+ ret = appendU16(ret, 2) |
|
| 707 |
+ ret = append(ret, msgTypeRevealSig) |
|
| 708 |
+ ret = appendData(ret, c.r[:]) |
|
| 709 |
+ ret = append(ret, encryptedSig...) |
|
| 710 |
+ ret = append(ret, mac[:20]...) |
|
| 711 |
+ return ret |
|
| 712 |
+} |
|
| 713 |
+ |
|
| 714 |
+func (c *Conversation) processEncryptedSig(encryptedSig, theirMAC []byte, keys *akeKeys, xFirst bool) error {
|
|
| 715 |
+ mac := hmac.New(sha256.New, keys.m2[:]) |
|
| 716 |
+ mac.Write(appendData(nil, encryptedSig)) |
|
| 717 |
+ myMAC := mac.Sum(nil)[:20] |
|
| 718 |
+ |
|
| 719 |
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
|
|
| 720 |
+ return errors.New("bad signature MAC in encrypted signature")
|
|
| 721 |
+ } |
|
| 722 |
+ |
|
| 723 |
+ aesCipher, err := aes.NewCipher(keys.c[:]) |
|
| 724 |
+ if err != nil {
|
|
| 725 |
+ panic(err.Error()) |
|
| 726 |
+ } |
|
| 727 |
+ var iv [aes.BlockSize]byte |
|
| 728 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 729 |
+ ctr.XORKeyStream(encryptedSig, encryptedSig) |
|
| 730 |
+ |
|
| 731 |
+ sig := encryptedSig |
|
| 732 |
+ sig, ok1 := c.TheirPublicKey.Parse(sig) |
|
| 733 |
+ keyId, sig, ok2 := getU32(sig) |
|
| 734 |
+ if !ok1 || !ok2 {
|
|
| 735 |
+ return errors.New("otr: corrupt encrypted signature")
|
|
| 736 |
+ } |
|
| 737 |
+ |
|
| 738 |
+ var verifyData []byte |
|
| 739 |
+ if xFirst {
|
|
| 740 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 741 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 742 |
+ } else {
|
|
| 743 |
+ verifyData = appendMPI(verifyData, c.gy) |
|
| 744 |
+ verifyData = appendMPI(verifyData, c.gx) |
|
| 745 |
+ } |
|
| 746 |
+ verifyData = c.TheirPublicKey.Serialize(verifyData) |
|
| 747 |
+ verifyData = appendU32(verifyData, keyId) |
|
| 748 |
+ |
|
| 749 |
+ mac = hmac.New(sha256.New, keys.m1[:]) |
|
| 750 |
+ mac.Write(verifyData) |
|
| 751 |
+ mb := mac.Sum(nil) |
|
| 752 |
+ |
|
| 753 |
+ sig, ok1 = c.TheirPublicKey.Verify(mb, sig) |
|
| 754 |
+ if !ok1 {
|
|
| 755 |
+ return errors.New("bad signature in encrypted signature")
|
|
| 756 |
+ } |
|
| 757 |
+ if len(sig) > 0 {
|
|
| 758 |
+ return errors.New("corrupt encrypted signature")
|
|
| 759 |
+ } |
|
| 760 |
+ |
|
| 761 |
+ c.theirKeyId = keyId |
|
| 762 |
+ zero(c.theirLastCtr[:]) |
|
| 763 |
+ return nil |
|
| 764 |
+} |
|
| 765 |
+ |
|
| 766 |
+func (c *Conversation) processRevealSig(in []byte) error {
|
|
| 767 |
+ r, in, ok1 := getData(in) |
|
| 768 |
+ encryptedSig, in, ok2 := getData(in) |
|
| 769 |
+ theirMAC := in |
|
| 770 |
+ if !ok1 || !ok2 || len(theirMAC) != 20 {
|
|
| 771 |
+ return errors.New("otr: corrupt reveal signature message")
|
|
| 772 |
+ } |
|
| 773 |
+ |
|
| 774 |
+ aesCipher, err := aes.NewCipher(r) |
|
| 775 |
+ if err != nil {
|
|
| 776 |
+ return errors.New("otr: cannot create AES cipher from reveal signature message: " + err.Error())
|
|
| 777 |
+ } |
|
| 778 |
+ var iv [aes.BlockSize]byte |
|
| 779 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 780 |
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes) |
|
| 781 |
+ h := sha256.New() |
|
| 782 |
+ h.Write(c.gxBytes) |
|
| 783 |
+ digest := h.Sum(nil) |
|
| 784 |
+ if len(digest) != len(c.digest) || subtle.ConstantTimeCompare(digest, c.digest[:]) == 0 {
|
|
| 785 |
+ return errors.New("otr: bad commit MAC in reveal signature message")
|
|
| 786 |
+ } |
|
| 787 |
+ var rest []byte |
|
| 788 |
+ c.gx, rest, ok1 = getMPI(c.gxBytes) |
|
| 789 |
+ if !ok1 || len(rest) > 0 {
|
|
| 790 |
+ return errors.New("otr: gx corrupt after decryption")
|
|
| 791 |
+ } |
|
| 792 |
+ if c.gx.Cmp(g) < 0 || c.gx.Cmp(pMinus2) > 0 {
|
|
| 793 |
+ return errors.New("otr: DH value out of range")
|
|
| 794 |
+ } |
|
| 795 |
+ s := new(big.Int).Exp(c.gx, c.y, p) |
|
| 796 |
+ c.calcAKEKeys(s) |
|
| 797 |
+ |
|
| 798 |
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.revealKeys, true /* gx comes first */); err != nil {
|
|
| 799 |
+ return errors.New("otr: in reveal signature message: " + err.Error())
|
|
| 800 |
+ } |
|
| 801 |
+ |
|
| 802 |
+ c.theirCurrentDHPub = c.gx |
|
| 803 |
+ c.theirLastDHPub = nil |
|
| 804 |
+ |
|
| 805 |
+ return nil |
|
| 806 |
+} |
|
| 807 |
+ |
|
| 808 |
+func (c *Conversation) generateSig() []byte {
|
|
| 809 |
+ c.myKeyId++ |
|
| 810 |
+ |
|
| 811 |
+ encryptedSig, mac := c.generateEncryptedSignature(&c.sigKeys, false /* gy comes first */) |
|
| 812 |
+ |
|
| 813 |
+ c.myCurrentDHPub = c.gy |
|
| 814 |
+ c.myCurrentDHPriv = c.y |
|
| 815 |
+ c.rotateDHKeys() |
|
| 816 |
+ incCounter(&c.myCounter) |
|
| 817 |
+ |
|
| 818 |
+ var ret []byte |
|
| 819 |
+ ret = appendU16(ret, 2) |
|
| 820 |
+ ret = append(ret, msgTypeSig) |
|
| 821 |
+ ret = append(ret, encryptedSig...) |
|
| 822 |
+ ret = append(ret, mac[:macPrefixBytes]...) |
|
| 823 |
+ return ret |
|
| 824 |
+} |
|
| 825 |
+ |
|
| 826 |
+func (c *Conversation) processSig(in []byte) error {
|
|
| 827 |
+ encryptedSig, in, ok1 := getData(in) |
|
| 828 |
+ theirMAC := in |
|
| 829 |
+ if !ok1 || len(theirMAC) != macPrefixBytes {
|
|
| 830 |
+ return errors.New("otr: corrupt signature message")
|
|
| 831 |
+ } |
|
| 832 |
+ |
|
| 833 |
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.sigKeys, false /* gy comes first */); err != nil {
|
|
| 834 |
+ return errors.New("otr: in signature message: " + err.Error())
|
|
| 835 |
+ } |
|
| 836 |
+ |
|
| 837 |
+ c.theirCurrentDHPub = c.gy |
|
| 838 |
+ c.theirLastDHPub = nil |
|
| 839 |
+ |
|
| 840 |
+ return nil |
|
| 841 |
+} |
|
| 842 |
+ |
|
| 843 |
+func (c *Conversation) rotateDHKeys() {
|
|
| 844 |
+ // evict slots using our retired key id |
|
| 845 |
+ for i := range c.keySlots {
|
|
| 846 |
+ slot := &c.keySlots[i] |
|
| 847 |
+ if slot.used && slot.myKeyId == c.myKeyId-1 {
|
|
| 848 |
+ slot.used = false |
|
| 849 |
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...) |
|
| 850 |
+ } |
|
| 851 |
+ } |
|
| 852 |
+ |
|
| 853 |
+ c.myLastDHPriv = c.myCurrentDHPriv |
|
| 854 |
+ c.myLastDHPub = c.myCurrentDHPub |
|
| 855 |
+ |
|
| 856 |
+ var xBytes [dhPrivateBytes]byte |
|
| 857 |
+ c.myCurrentDHPriv = c.randMPI(xBytes[:]) |
|
| 858 |
+ c.myCurrentDHPub = new(big.Int).Exp(g, c.myCurrentDHPriv, p) |
|
| 859 |
+ c.myKeyId++ |
|
| 860 |
+} |
|
| 861 |
+ |
|
| 862 |
+func (c *Conversation) processData(in []byte) (out []byte, tlvs []tlv, err error) {
|
|
| 863 |
+ origIn := in |
|
| 864 |
+ flags, in, ok1 := getU8(in) |
|
| 865 |
+ theirKeyId, in, ok2 := getU32(in) |
|
| 866 |
+ myKeyId, in, ok3 := getU32(in) |
|
| 867 |
+ y, in, ok4 := getMPI(in) |
|
| 868 |
+ counter, in, ok5 := getNBytes(in, 8) |
|
| 869 |
+ encrypted, in, ok6 := getData(in) |
|
| 870 |
+ macedData := origIn[:len(origIn)-len(in)] |
|
| 871 |
+ theirMAC, in, ok7 := getNBytes(in, macPrefixBytes) |
|
| 872 |
+ _, in, ok8 := getData(in) |
|
| 873 |
+ if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 || !ok7 || !ok8 || len(in) > 0 {
|
|
| 874 |
+ err = errors.New("otr: corrupt data message")
|
|
| 875 |
+ return |
|
| 876 |
+ } |
|
| 877 |
+ |
|
| 878 |
+ ignoreErrors := flags&1 != 0 |
|
| 879 |
+ |
|
| 880 |
+ slot, err := c.calcDataKeys(myKeyId, theirKeyId) |
|
| 881 |
+ if err != nil {
|
|
| 882 |
+ if ignoreErrors {
|
|
| 883 |
+ err = nil |
|
| 884 |
+ } |
|
| 885 |
+ return |
|
| 886 |
+ } |
|
| 887 |
+ |
|
| 888 |
+ mac := hmac.New(sha1.New, slot.recvMACKey) |
|
| 889 |
+ mac.Write([]byte{0, 2, 3})
|
|
| 890 |
+ mac.Write(macedData) |
|
| 891 |
+ myMAC := mac.Sum(nil) |
|
| 892 |
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
|
|
| 893 |
+ if !ignoreErrors {
|
|
| 894 |
+ err = errors.New("otr: bad MAC on data message")
|
|
| 895 |
+ } |
|
| 896 |
+ return |
|
| 897 |
+ } |
|
| 898 |
+ |
|
| 899 |
+ if bytes.Compare(counter, slot.theirLastCtr[:]) <= 0 {
|
|
| 900 |
+ err = errors.New("otr: counter regressed")
|
|
| 901 |
+ return |
|
| 902 |
+ } |
|
| 903 |
+ copy(slot.theirLastCtr[:], counter) |
|
| 904 |
+ |
|
| 905 |
+ var iv [aes.BlockSize]byte |
|
| 906 |
+ copy(iv[:], counter) |
|
| 907 |
+ aesCipher, err := aes.NewCipher(slot.recvAESKey) |
|
| 908 |
+ if err != nil {
|
|
| 909 |
+ panic(err.Error()) |
|
| 910 |
+ } |
|
| 911 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 912 |
+ ctr.XORKeyStream(encrypted, encrypted) |
|
| 913 |
+ decrypted := encrypted |
|
| 914 |
+ |
|
| 915 |
+ if myKeyId == c.myKeyId {
|
|
| 916 |
+ c.rotateDHKeys() |
|
| 917 |
+ } |
|
| 918 |
+ if theirKeyId == c.theirKeyId {
|
|
| 919 |
+ // evict slots using their retired key id |
|
| 920 |
+ for i := range c.keySlots {
|
|
| 921 |
+ slot := &c.keySlots[i] |
|
| 922 |
+ if slot.used && slot.theirKeyId == theirKeyId-1 {
|
|
| 923 |
+ slot.used = false |
|
| 924 |
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...) |
|
| 925 |
+ } |
|
| 926 |
+ } |
|
| 927 |
+ |
|
| 928 |
+ c.theirLastDHPub = c.theirCurrentDHPub |
|
| 929 |
+ c.theirKeyId++ |
|
| 930 |
+ c.theirCurrentDHPub = y |
|
| 931 |
+ } |
|
| 932 |
+ |
|
| 933 |
+ if nulPos := bytes.IndexByte(decrypted, 0); nulPos >= 0 {
|
|
| 934 |
+ out = decrypted[:nulPos] |
|
| 935 |
+ tlvData := decrypted[nulPos+1:] |
|
| 936 |
+ for len(tlvData) > 0 {
|
|
| 937 |
+ var t tlv |
|
| 938 |
+ var ok1, ok2, ok3 bool |
|
| 939 |
+ |
|
| 940 |
+ t.typ, tlvData, ok1 = getU16(tlvData) |
|
| 941 |
+ t.length, tlvData, ok2 = getU16(tlvData) |
|
| 942 |
+ t.data, tlvData, ok3 = getNBytes(tlvData, int(t.length)) |
|
| 943 |
+ if !ok1 || !ok2 || !ok3 {
|
|
| 944 |
+ err = errors.New("otr: corrupt tlv data")
|
|
| 945 |
+ return |
|
| 946 |
+ } |
|
| 947 |
+ tlvs = append(tlvs, t) |
|
| 948 |
+ } |
|
| 949 |
+ } else {
|
|
| 950 |
+ out = decrypted |
|
| 951 |
+ } |
|
| 952 |
+ |
|
| 953 |
+ return |
|
| 954 |
+} |
|
| 955 |
+ |
|
| 956 |
+func (c *Conversation) generateData(msg []byte, extra *tlv) []byte {
|
|
| 957 |
+ slot, err := c.calcDataKeys(c.myKeyId-1, c.theirKeyId) |
|
| 958 |
+ if err != nil {
|
|
| 959 |
+ panic("otr: failed to generate sending keys: " + err.Error())
|
|
| 960 |
+ } |
|
| 961 |
+ |
|
| 962 |
+ var plaintext []byte |
|
| 963 |
+ plaintext = append(plaintext, msg...) |
|
| 964 |
+ plaintext = append(plaintext, 0) |
|
| 965 |
+ |
|
| 966 |
+ padding := paddingGranularity - ((len(plaintext) + 4) % paddingGranularity) |
|
| 967 |
+ plaintext = appendU16(plaintext, tlvTypePadding) |
|
| 968 |
+ plaintext = appendU16(plaintext, uint16(padding)) |
|
| 969 |
+ for i := 0; i < padding; i++ {
|
|
| 970 |
+ plaintext = append(plaintext, 0) |
|
| 971 |
+ } |
|
| 972 |
+ |
|
| 973 |
+ if extra != nil {
|
|
| 974 |
+ plaintext = appendU16(plaintext, extra.typ) |
|
| 975 |
+ plaintext = appendU16(plaintext, uint16(len(extra.data))) |
|
| 976 |
+ plaintext = append(plaintext, extra.data...) |
|
| 977 |
+ } |
|
| 978 |
+ |
|
| 979 |
+ encrypted := make([]byte, len(plaintext)) |
|
| 980 |
+ |
|
| 981 |
+ var iv [aes.BlockSize]byte |
|
| 982 |
+ copy(iv[:], c.myCounter[:]) |
|
| 983 |
+ aesCipher, err := aes.NewCipher(slot.sendAESKey) |
|
| 984 |
+ if err != nil {
|
|
| 985 |
+ panic(err.Error()) |
|
| 986 |
+ } |
|
| 987 |
+ ctr := cipher.NewCTR(aesCipher, iv[:]) |
|
| 988 |
+ ctr.XORKeyStream(encrypted, plaintext) |
|
| 989 |
+ |
|
| 990 |
+ var ret []byte |
|
| 991 |
+ ret = appendU16(ret, 2) |
|
| 992 |
+ ret = append(ret, msgTypeData) |
|
| 993 |
+ ret = append(ret, 0 /* flags */) |
|
| 994 |
+ ret = appendU32(ret, c.myKeyId-1) |
|
| 995 |
+ ret = appendU32(ret, c.theirKeyId) |
|
| 996 |
+ ret = appendMPI(ret, c.myCurrentDHPub) |
|
| 997 |
+ ret = append(ret, c.myCounter[:]...) |
|
| 998 |
+ ret = appendData(ret, encrypted) |
|
| 999 |
+ |
|
| 1000 |
+ mac := hmac.New(sha1.New, slot.sendMACKey) |
|
| 1001 |
+ mac.Write(ret) |
|
| 1002 |
+ ret = append(ret, mac.Sum(nil)[:macPrefixBytes]...) |
|
| 1003 |
+ ret = appendData(ret, c.oldMACs) |
|
| 1004 |
+ c.oldMACs = nil |
|
| 1005 |
+ incCounter(&c.myCounter) |
|
| 1006 |
+ |
|
| 1007 |
+ return ret |
|
| 1008 |
+} |
|
| 1009 |
+ |
|
| 1010 |
+func incCounter(counter *[8]byte) {
|
|
| 1011 |
+ for i := 7; i >= 0; i-- {
|
|
| 1012 |
+ counter[i]++ |
|
| 1013 |
+ if counter[i] > 0 {
|
|
| 1014 |
+ break |
|
| 1015 |
+ } |
|
| 1016 |
+ } |
|
| 1017 |
+} |
|
| 1018 |
+ |
|
| 1019 |
+// calcDataKeys computes the keys used to encrypt a data message given the key |
|
| 1020 |
+// IDs. |
|
| 1021 |
+func (c *Conversation) calcDataKeys(myKeyId, theirKeyId uint32) (slot *keySlot, err error) {
|
|
| 1022 |
+ // Check for a cache hit. |
|
| 1023 |
+ for i := range c.keySlots {
|
|
| 1024 |
+ slot = &c.keySlots[i] |
|
| 1025 |
+ if slot.used && slot.theirKeyId == theirKeyId && slot.myKeyId == myKeyId {
|
|
| 1026 |
+ return |
|
| 1027 |
+ } |
|
| 1028 |
+ } |
|
| 1029 |
+ |
|
| 1030 |
+ // Find an empty slot to write into. |
|
| 1031 |
+ slot = nil |
|
| 1032 |
+ for i := range c.keySlots {
|
|
| 1033 |
+ if !c.keySlots[i].used {
|
|
| 1034 |
+ slot = &c.keySlots[i] |
|
| 1035 |
+ break |
|
| 1036 |
+ } |
|
| 1037 |
+ } |
|
| 1038 |
+ if slot == nil {
|
|
| 1039 |
+ return nil, errors.New("otr: internal error: no more key slots")
|
|
| 1040 |
+ } |
|
| 1041 |
+ |
|
| 1042 |
+ var myPriv, myPub, theirPub *big.Int |
|
| 1043 |
+ |
|
| 1044 |
+ if myKeyId == c.myKeyId {
|
|
| 1045 |
+ myPriv = c.myCurrentDHPriv |
|
| 1046 |
+ myPub = c.myCurrentDHPub |
|
| 1047 |
+ } else if myKeyId == c.myKeyId-1 {
|
|
| 1048 |
+ myPriv = c.myLastDHPriv |
|
| 1049 |
+ myPub = c.myLastDHPub |
|
| 1050 |
+ } else {
|
|
| 1051 |
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when I'm on " + strconv.FormatUint(uint64(c.myKeyId), 10))
|
|
| 1052 |
+ return |
|
| 1053 |
+ } |
|
| 1054 |
+ |
|
| 1055 |
+ if theirKeyId == c.theirKeyId {
|
|
| 1056 |
+ theirPub = c.theirCurrentDHPub |
|
| 1057 |
+ } else if theirKeyId == c.theirKeyId-1 && c.theirLastDHPub != nil {
|
|
| 1058 |
+ theirPub = c.theirLastDHPub |
|
| 1059 |
+ } else {
|
|
| 1060 |
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when they're on " + strconv.FormatUint(uint64(c.myKeyId), 10))
|
|
| 1061 |
+ return |
|
| 1062 |
+ } |
|
| 1063 |
+ |
|
| 1064 |
+ var sendPrefixByte, recvPrefixByte [1]byte |
|
| 1065 |
+ |
|
| 1066 |
+ if myPub.Cmp(theirPub) > 0 {
|
|
| 1067 |
+ // we're the high end |
|
| 1068 |
+ sendPrefixByte[0], recvPrefixByte[0] = 1, 2 |
|
| 1069 |
+ } else {
|
|
| 1070 |
+ // we're the low end |
|
| 1071 |
+ sendPrefixByte[0], recvPrefixByte[0] = 2, 1 |
|
| 1072 |
+ } |
|
| 1073 |
+ |
|
| 1074 |
+ s := new(big.Int).Exp(theirPub, myPriv, p) |
|
| 1075 |
+ sBytes := appendMPI(nil, s) |
|
| 1076 |
+ |
|
| 1077 |
+ h := sha1.New() |
|
| 1078 |
+ h.Write(sendPrefixByte[:]) |
|
| 1079 |
+ h.Write(sBytes) |
|
| 1080 |
+ slot.sendAESKey = h.Sum(slot.sendAESKey[:0])[:16] |
|
| 1081 |
+ |
|
| 1082 |
+ h.Reset() |
|
| 1083 |
+ h.Write(slot.sendAESKey) |
|
| 1084 |
+ slot.sendMACKey = h.Sum(slot.sendMACKey[:0]) |
|
| 1085 |
+ |
|
| 1086 |
+ h.Reset() |
|
| 1087 |
+ h.Write(recvPrefixByte[:]) |
|
| 1088 |
+ h.Write(sBytes) |
|
| 1089 |
+ slot.recvAESKey = h.Sum(slot.recvAESKey[:0])[:16] |
|
| 1090 |
+ |
|
| 1091 |
+ h.Reset() |
|
| 1092 |
+ h.Write(slot.recvAESKey) |
|
| 1093 |
+ slot.recvMACKey = h.Sum(slot.recvMACKey[:0]) |
|
| 1094 |
+ |
|
| 1095 |
+ slot.theirKeyId = theirKeyId |
|
| 1096 |
+ slot.myKeyId = myKeyId |
|
| 1097 |
+ slot.used = true |
|
| 1098 |
+ |
|
| 1099 |
+ zero(slot.theirLastCtr[:]) |
|
| 1100 |
+ return |
|
| 1101 |
+} |
|
| 1102 |
+ |
|
| 1103 |
+func (c *Conversation) calcAKEKeys(s *big.Int) {
|
|
| 1104 |
+ mpi := appendMPI(nil, s) |
|
| 1105 |
+ h := sha256.New() |
|
| 1106 |
+ |
|
| 1107 |
+ var cBytes [32]byte |
|
| 1108 |
+ hashWithPrefix(c.SSID[:], 0, mpi, h) |
|
| 1109 |
+ |
|
| 1110 |
+ hashWithPrefix(cBytes[:], 1, mpi, h) |
|
| 1111 |
+ copy(c.revealKeys.c[:], cBytes[:16]) |
|
| 1112 |
+ copy(c.sigKeys.c[:], cBytes[16:]) |
|
| 1113 |
+ |
|
| 1114 |
+ hashWithPrefix(c.revealKeys.m1[:], 2, mpi, h) |
|
| 1115 |
+ hashWithPrefix(c.revealKeys.m2[:], 3, mpi, h) |
|
| 1116 |
+ hashWithPrefix(c.sigKeys.m1[:], 4, mpi, h) |
|
| 1117 |
+ hashWithPrefix(c.sigKeys.m2[:], 5, mpi, h) |
|
| 1118 |
+} |
|
| 1119 |
+ |
|
| 1120 |
+func hashWithPrefix(out []byte, prefix byte, in []byte, h hash.Hash) {
|
|
| 1121 |
+ h.Reset() |
|
| 1122 |
+ var p [1]byte |
|
| 1123 |
+ p[0] = prefix |
|
| 1124 |
+ h.Write(p[:]) |
|
| 1125 |
+ h.Write(in) |
|
| 1126 |
+ if len(out) == h.Size() {
|
|
| 1127 |
+ h.Sum(out[:0]) |
|
| 1128 |
+ } else {
|
|
| 1129 |
+ digest := h.Sum(nil) |
|
| 1130 |
+ copy(out, digest) |
|
| 1131 |
+ } |
|
| 1132 |
+} |
|
| 1133 |
+ |
|
| 1134 |
+func (c *Conversation) encode(msg []byte) [][]byte {
|
|
| 1135 |
+ b64 := make([]byte, base64.StdEncoding.EncodedLen(len(msg))+len(msgPrefix)+1) |
|
| 1136 |
+ base64.StdEncoding.Encode(b64[len(msgPrefix):], msg) |
|
| 1137 |
+ copy(b64, msgPrefix) |
|
| 1138 |
+ b64[len(b64)-1] = '.' |
|
| 1139 |
+ |
|
| 1140 |
+ if c.FragmentSize < minFragmentSize || len(b64) <= c.FragmentSize {
|
|
| 1141 |
+ // We can encode this in a single fragment. |
|
| 1142 |
+ return [][]byte{b64}
|
|
| 1143 |
+ } |
|
| 1144 |
+ |
|
| 1145 |
+ // We have to fragment this message. |
|
| 1146 |
+ var ret [][]byte |
|
| 1147 |
+ bytesPerFragment := c.FragmentSize - minFragmentSize |
|
| 1148 |
+ numFragments := (len(b64) + bytesPerFragment) / bytesPerFragment |
|
| 1149 |
+ |
|
| 1150 |
+ for i := 0; i < numFragments; i++ {
|
|
| 1151 |
+ frag := []byte("?OTR," + strconv.Itoa(i+1) + "," + strconv.Itoa(numFragments) + ",")
|
|
| 1152 |
+ todo := bytesPerFragment |
|
| 1153 |
+ if todo > len(b64) {
|
|
| 1154 |
+ todo = len(b64) |
|
| 1155 |
+ } |
|
| 1156 |
+ frag = append(frag, b64[:todo]...) |
|
| 1157 |
+ b64 = b64[todo:] |
|
| 1158 |
+ frag = append(frag, ',') |
|
| 1159 |
+ ret = append(ret, frag) |
|
| 1160 |
+ } |
|
| 1161 |
+ |
|
| 1162 |
+ return ret |
|
| 1163 |
+} |
|
| 1164 |
+ |
|
| 1165 |
+func (c *Conversation) reset() {
|
|
| 1166 |
+ c.myKeyId = 0 |
|
| 1167 |
+ |
|
| 1168 |
+ for i := range c.keySlots {
|
|
| 1169 |
+ c.keySlots[i].used = false |
|
| 1170 |
+ } |
|
| 1171 |
+} |
|
| 1172 |
+ |
|
| 1173 |
+type PublicKey struct {
|
|
| 1174 |
+ dsa.PublicKey |
|
| 1175 |
+} |
|
| 1176 |
+ |
|
| 1177 |
+func (pk *PublicKey) Parse(in []byte) ([]byte, bool) {
|
|
| 1178 |
+ var ok bool |
|
| 1179 |
+ var pubKeyType uint16 |
|
| 1180 |
+ |
|
| 1181 |
+ if pubKeyType, in, ok = getU16(in); !ok || pubKeyType != 0 {
|
|
| 1182 |
+ return nil, false |
|
| 1183 |
+ } |
|
| 1184 |
+ if pk.P, in, ok = getMPI(in); !ok {
|
|
| 1185 |
+ return nil, false |
|
| 1186 |
+ } |
|
| 1187 |
+ if pk.Q, in, ok = getMPI(in); !ok {
|
|
| 1188 |
+ return nil, false |
|
| 1189 |
+ } |
|
| 1190 |
+ if pk.G, in, ok = getMPI(in); !ok {
|
|
| 1191 |
+ return nil, false |
|
| 1192 |
+ } |
|
| 1193 |
+ if pk.Y, in, ok = getMPI(in); !ok {
|
|
| 1194 |
+ return nil, false |
|
| 1195 |
+ } |
|
| 1196 |
+ |
|
| 1197 |
+ return in, true |
|
| 1198 |
+} |
|
| 1199 |
+ |
|
| 1200 |
+func (pk *PublicKey) Serialize(in []byte) []byte {
|
|
| 1201 |
+ in = appendU16(in, 0) |
|
| 1202 |
+ in = appendMPI(in, pk.P) |
|
| 1203 |
+ in = appendMPI(in, pk.Q) |
|
| 1204 |
+ in = appendMPI(in, pk.G) |
|
| 1205 |
+ in = appendMPI(in, pk.Y) |
|
| 1206 |
+ return in |
|
| 1207 |
+} |
|
| 1208 |
+ |
|
| 1209 |
+// Fingerprint returns the 20-byte, binary fingerprint of the PublicKey. |
|
| 1210 |
+func (pk *PublicKey) Fingerprint() []byte {
|
|
| 1211 |
+ b := pk.Serialize(nil) |
|
| 1212 |
+ h := sha1.New() |
|
| 1213 |
+ h.Write(b[2:]) |
|
| 1214 |
+ return h.Sum(nil) |
|
| 1215 |
+} |
|
| 1216 |
+ |
|
| 1217 |
+func (pk *PublicKey) Verify(hashed, sig []byte) ([]byte, bool) {
|
|
| 1218 |
+ if len(sig) != 2*dsaSubgroupBytes {
|
|
| 1219 |
+ return nil, false |
|
| 1220 |
+ } |
|
| 1221 |
+ r := new(big.Int).SetBytes(sig[:dsaSubgroupBytes]) |
|
| 1222 |
+ s := new(big.Int).SetBytes(sig[dsaSubgroupBytes:]) |
|
| 1223 |
+ ok := dsa.Verify(&pk.PublicKey, hashed, r, s) |
|
| 1224 |
+ return sig[dsaSubgroupBytes*2:], ok |
|
| 1225 |
+} |
|
| 1226 |
+ |
|
| 1227 |
+type PrivateKey struct {
|
|
| 1228 |
+ PublicKey |
|
| 1229 |
+ dsa.PrivateKey |
|
| 1230 |
+} |
|
| 1231 |
+ |
|
| 1232 |
+func (priv *PrivateKey) Sign(rand io.Reader, hashed []byte) []byte {
|
|
| 1233 |
+ r, s, err := dsa.Sign(rand, &priv.PrivateKey, hashed) |
|
| 1234 |
+ if err != nil {
|
|
| 1235 |
+ panic(err.Error()) |
|
| 1236 |
+ } |
|
| 1237 |
+ rBytes := r.Bytes() |
|
| 1238 |
+ sBytes := s.Bytes() |
|
| 1239 |
+ if len(rBytes) > dsaSubgroupBytes || len(sBytes) > dsaSubgroupBytes {
|
|
| 1240 |
+ panic("DSA signature too large")
|
|
| 1241 |
+ } |
|
| 1242 |
+ |
|
| 1243 |
+ out := make([]byte, 2*dsaSubgroupBytes) |
|
| 1244 |
+ copy(out[dsaSubgroupBytes-len(rBytes):], rBytes) |
|
| 1245 |
+ copy(out[len(out)-len(sBytes):], sBytes) |
|
| 1246 |
+ return out |
|
| 1247 |
+} |
|
| 1248 |
+ |
|
| 1249 |
+func (priv *PrivateKey) Serialize(in []byte) []byte {
|
|
| 1250 |
+ in = priv.PublicKey.Serialize(in) |
|
| 1251 |
+ in = appendMPI(in, priv.PrivateKey.X) |
|
| 1252 |
+ return in |
|
| 1253 |
+} |
|
| 1254 |
+ |
|
| 1255 |
+func (priv *PrivateKey) Parse(in []byte) ([]byte, bool) {
|
|
| 1256 |
+ in, ok := priv.PublicKey.Parse(in) |
|
| 1257 |
+ if !ok {
|
|
| 1258 |
+ return in, ok |
|
| 1259 |
+ } |
|
| 1260 |
+ priv.PrivateKey.PublicKey = priv.PublicKey.PublicKey |
|
| 1261 |
+ priv.PrivateKey.X, in, ok = getMPI(in) |
|
| 1262 |
+ return in, ok |
|
| 1263 |
+} |
|
| 1264 |
+ |
|
| 1265 |
+func (priv *PrivateKey) Generate(rand io.Reader) {
|
|
| 1266 |
+ if err := dsa.GenerateParameters(&priv.PrivateKey.PublicKey.Parameters, rand, dsa.L1024N160); err != nil {
|
|
| 1267 |
+ panic(err.Error()) |
|
| 1268 |
+ } |
|
| 1269 |
+ if err := dsa.GenerateKey(&priv.PrivateKey, rand); err != nil {
|
|
| 1270 |
+ panic(err.Error()) |
|
| 1271 |
+ } |
|
| 1272 |
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey |
|
| 1273 |
+} |
|
| 1274 |
+ |
|
| 1275 |
+func notHex(r rune) bool {
|
|
| 1276 |
+ if r >= '0' && r <= '9' || |
|
| 1277 |
+ r >= 'a' && r <= 'f' || |
|
| 1278 |
+ r >= 'A' && r <= 'F' {
|
|
| 1279 |
+ return false |
|
| 1280 |
+ } |
|
| 1281 |
+ |
|
| 1282 |
+ return true |
|
| 1283 |
+} |
|
| 1284 |
+ |
|
| 1285 |
+// Import parses the contents of a libotr private key file. |
|
| 1286 |
+func (priv *PrivateKey) Import(in []byte) bool {
|
|
| 1287 |
+ mpiStart := []byte(" #")
|
|
| 1288 |
+ |
|
| 1289 |
+ mpis := make([]*big.Int, 5) |
|
| 1290 |
+ |
|
| 1291 |
+ for i := 0; i < len(mpis); i++ {
|
|
| 1292 |
+ start := bytes.Index(in, mpiStart) |
|
| 1293 |
+ if start == -1 {
|
|
| 1294 |
+ return false |
|
| 1295 |
+ } |
|
| 1296 |
+ in = in[start+len(mpiStart):] |
|
| 1297 |
+ end := bytes.IndexFunc(in, notHex) |
|
| 1298 |
+ if end == -1 {
|
|
| 1299 |
+ return false |
|
| 1300 |
+ } |
|
| 1301 |
+ hexBytes := in[:end] |
|
| 1302 |
+ in = in[end:] |
|
| 1303 |
+ |
|
| 1304 |
+ if len(hexBytes)&1 != 0 {
|
|
| 1305 |
+ return false |
|
| 1306 |
+ } |
|
| 1307 |
+ |
|
| 1308 |
+ mpiBytes := make([]byte, len(hexBytes)/2) |
|
| 1309 |
+ if _, err := hex.Decode(mpiBytes, hexBytes); err != nil {
|
|
| 1310 |
+ return false |
|
| 1311 |
+ } |
|
| 1312 |
+ |
|
| 1313 |
+ mpis[i] = new(big.Int).SetBytes(mpiBytes) |
|
| 1314 |
+ } |
|
| 1315 |
+ |
|
| 1316 |
+ for _, mpi := range mpis {
|
|
| 1317 |
+ if mpi.Sign() <= 0 {
|
|
| 1318 |
+ return false |
|
| 1319 |
+ } |
|
| 1320 |
+ } |
|
| 1321 |
+ |
|
| 1322 |
+ priv.PrivateKey.P = mpis[0] |
|
| 1323 |
+ priv.PrivateKey.Q = mpis[1] |
|
| 1324 |
+ priv.PrivateKey.G = mpis[2] |
|
| 1325 |
+ priv.PrivateKey.Y = mpis[3] |
|
| 1326 |
+ priv.PrivateKey.X = mpis[4] |
|
| 1327 |
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey |
|
| 1328 |
+ |
|
| 1329 |
+ a := new(big.Int).Exp(priv.PrivateKey.G, priv.PrivateKey.X, priv.PrivateKey.P) |
|
| 1330 |
+ return a.Cmp(priv.PrivateKey.Y) == 0 |
|
| 1331 |
+} |
|
| 1332 |
+ |
|
| 1333 |
+func getU8(in []byte) (uint8, []byte, bool) {
|
|
| 1334 |
+ if len(in) < 1 {
|
|
| 1335 |
+ return 0, in, false |
|
| 1336 |
+ } |
|
| 1337 |
+ return in[0], in[1:], true |
|
| 1338 |
+} |
|
| 1339 |
+ |
|
| 1340 |
+func getU16(in []byte) (uint16, []byte, bool) {
|
|
| 1341 |
+ if len(in) < 2 {
|
|
| 1342 |
+ return 0, in, false |
|
| 1343 |
+ } |
|
| 1344 |
+ r := uint16(in[0])<<8 | uint16(in[1]) |
|
| 1345 |
+ return r, in[2:], true |
|
| 1346 |
+} |
|
| 1347 |
+ |
|
| 1348 |
+func getU32(in []byte) (uint32, []byte, bool) {
|
|
| 1349 |
+ if len(in) < 4 {
|
|
| 1350 |
+ return 0, in, false |
|
| 1351 |
+ } |
|
| 1352 |
+ r := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3]) |
|
| 1353 |
+ return r, in[4:], true |
|
| 1354 |
+} |
|
| 1355 |
+ |
|
| 1356 |
+func getMPI(in []byte) (*big.Int, []byte, bool) {
|
|
| 1357 |
+ l, in, ok := getU32(in) |
|
| 1358 |
+ if !ok || uint32(len(in)) < l {
|
|
| 1359 |
+ return nil, in, false |
|
| 1360 |
+ } |
|
| 1361 |
+ r := new(big.Int).SetBytes(in[:l]) |
|
| 1362 |
+ return r, in[l:], true |
|
| 1363 |
+} |
|
| 1364 |
+ |
|
| 1365 |
+func getData(in []byte) ([]byte, []byte, bool) {
|
|
| 1366 |
+ l, in, ok := getU32(in) |
|
| 1367 |
+ if !ok || uint32(len(in)) < l {
|
|
| 1368 |
+ return nil, in, false |
|
| 1369 |
+ } |
|
| 1370 |
+ return in[:l], in[l:], true |
|
| 1371 |
+} |
|
| 1372 |
+ |
|
| 1373 |
+func getNBytes(in []byte, n int) ([]byte, []byte, bool) {
|
|
| 1374 |
+ if len(in) < n {
|
|
| 1375 |
+ return nil, in, false |
|
| 1376 |
+ } |
|
| 1377 |
+ return in[:n], in[n:], true |
|
| 1378 |
+} |
|
| 1379 |
+ |
|
| 1380 |
+func appendU16(out []byte, v uint16) []byte {
|
|
| 1381 |
+ out = append(out, byte(v>>8), byte(v)) |
|
| 1382 |
+ return out |
|
| 1383 |
+} |
|
| 1384 |
+ |
|
| 1385 |
+func appendU32(out []byte, v uint32) []byte {
|
|
| 1386 |
+ out = append(out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) |
|
| 1387 |
+ return out |
|
| 1388 |
+} |
|
| 1389 |
+ |
|
| 1390 |
+func appendData(out, v []byte) []byte {
|
|
| 1391 |
+ out = appendU32(out, uint32(len(v))) |
|
| 1392 |
+ out = append(out, v...) |
|
| 1393 |
+ return out |
|
| 1394 |
+} |
|
| 1395 |
+ |
|
| 1396 |
+func appendMPI(out []byte, v *big.Int) []byte {
|
|
| 1397 |
+ vBytes := v.Bytes() |
|
| 1398 |
+ out = appendU32(out, uint32(len(vBytes))) |
|
| 1399 |
+ out = append(out, vBytes...) |
|
| 1400 |
+ return out |
|
| 1401 |
+} |
|
| 1402 |
+ |
|
| 1403 |
+func appendMPIs(out []byte, mpis ...*big.Int) []byte {
|
|
| 1404 |
+ for _, mpi := range mpis {
|
|
| 1405 |
+ out = appendMPI(out, mpi) |
|
| 1406 |
+ } |
|
| 1407 |
+ return out |
|
| 1408 |
+} |
|
| 1409 |
+ |
|
| 1410 |
+func zero(b []byte) {
|
|
| 1411 |
+ for i := range b {
|
|
| 1412 |
+ b[i] = 0 |
|
| 1413 |
+ } |
|
| 1414 |
+} |
| 0 | 1415 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,572 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// This file implements the Socialist Millionaires Protocol as described in |
|
| 5 |
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html. The protocol |
|
| 6 |
+// specification is required in order to understand this code and, where |
|
| 7 |
+// possible, the variable names in the code match up with the spec. |
|
| 8 |
+ |
|
| 9 |
+package otr |
|
| 10 |
+ |
|
| 11 |
+import ( |
|
| 12 |
+ "bytes" |
|
| 13 |
+ "crypto/sha256" |
|
| 14 |
+ "errors" |
|
| 15 |
+ "hash" |
|
| 16 |
+ "math/big" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+type smpFailure string |
|
| 20 |
+ |
|
| 21 |
+func (s smpFailure) Error() string {
|
|
| 22 |
+ return string(s) |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+var smpFailureError = smpFailure("otr: SMP protocol failed")
|
|
| 26 |
+var smpSecretMissingError = smpFailure("otr: mutual secret needed")
|
|
| 27 |
+ |
|
| 28 |
+const smpVersion = 1 |
|
| 29 |
+ |
|
| 30 |
+const ( |
|
| 31 |
+ smpState1 = iota |
|
| 32 |
+ smpState2 |
|
| 33 |
+ smpState3 |
|
| 34 |
+ smpState4 |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+type smpState struct {
|
|
| 38 |
+ state int |
|
| 39 |
+ a2, a3, b2, b3, pb, qb *big.Int |
|
| 40 |
+ g2a, g3a *big.Int |
|
| 41 |
+ g2, g3 *big.Int |
|
| 42 |
+ g3b, papb, qaqb, ra *big.Int |
|
| 43 |
+ saved *tlv |
|
| 44 |
+ secret *big.Int |
|
| 45 |
+ question string |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func (c *Conversation) startSMP(question string) (tlvs []tlv) {
|
|
| 49 |
+ if c.smp.state != smpState1 {
|
|
| 50 |
+ tlvs = append(tlvs, c.generateSMPAbort()) |
|
| 51 |
+ } |
|
| 52 |
+ tlvs = append(tlvs, c.generateSMP1(question)) |
|
| 53 |
+ c.smp.question = "" |
|
| 54 |
+ c.smp.state = smpState2 |
|
| 55 |
+ return |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (c *Conversation) resetSMP() {
|
|
| 59 |
+ c.smp.state = smpState1 |
|
| 60 |
+ c.smp.secret = nil |
|
| 61 |
+ c.smp.question = "" |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func (c *Conversation) processSMP(in tlv) (out tlv, complete bool, err error) {
|
|
| 65 |
+ data := in.data |
|
| 66 |
+ |
|
| 67 |
+ switch in.typ {
|
|
| 68 |
+ case tlvTypeSMPAbort: |
|
| 69 |
+ if c.smp.state != smpState1 {
|
|
| 70 |
+ err = smpFailureError |
|
| 71 |
+ } |
|
| 72 |
+ c.resetSMP() |
|
| 73 |
+ return |
|
| 74 |
+ case tlvTypeSMP1WithQuestion: |
|
| 75 |
+ // We preprocess this into a SMP1 message. |
|
| 76 |
+ nulPos := bytes.IndexByte(data, 0) |
|
| 77 |
+ if nulPos == -1 {
|
|
| 78 |
+ err = errors.New("otr: SMP message with question didn't contain a NUL byte")
|
|
| 79 |
+ return |
|
| 80 |
+ } |
|
| 81 |
+ c.smp.question = string(data[:nulPos]) |
|
| 82 |
+ data = data[nulPos+1:] |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ numMPIs, data, ok := getU32(data) |
|
| 86 |
+ if !ok || numMPIs > 20 {
|
|
| 87 |
+ err = errors.New("otr: corrupt SMP message")
|
|
| 88 |
+ return |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ mpis := make([]*big.Int, numMPIs) |
|
| 92 |
+ for i := range mpis {
|
|
| 93 |
+ var ok bool |
|
| 94 |
+ mpis[i], data, ok = getMPI(data) |
|
| 95 |
+ if !ok {
|
|
| 96 |
+ err = errors.New("otr: corrupt SMP message")
|
|
| 97 |
+ return |
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ |
|
| 101 |
+ switch in.typ {
|
|
| 102 |
+ case tlvTypeSMP1, tlvTypeSMP1WithQuestion: |
|
| 103 |
+ if c.smp.state != smpState1 {
|
|
| 104 |
+ c.resetSMP() |
|
| 105 |
+ out = c.generateSMPAbort() |
|
| 106 |
+ return |
|
| 107 |
+ } |
|
| 108 |
+ if c.smp.secret == nil {
|
|
| 109 |
+ err = smpSecretMissingError |
|
| 110 |
+ return |
|
| 111 |
+ } |
|
| 112 |
+ if err = c.processSMP1(mpis); err != nil {
|
|
| 113 |
+ return |
|
| 114 |
+ } |
|
| 115 |
+ c.smp.state = smpState3 |
|
| 116 |
+ out = c.generateSMP2() |
|
| 117 |
+ case tlvTypeSMP2: |
|
| 118 |
+ if c.smp.state != smpState2 {
|
|
| 119 |
+ c.resetSMP() |
|
| 120 |
+ out = c.generateSMPAbort() |
|
| 121 |
+ return |
|
| 122 |
+ } |
|
| 123 |
+ if out, err = c.processSMP2(mpis); err != nil {
|
|
| 124 |
+ out = c.generateSMPAbort() |
|
| 125 |
+ return |
|
| 126 |
+ } |
|
| 127 |
+ c.smp.state = smpState4 |
|
| 128 |
+ case tlvTypeSMP3: |
|
| 129 |
+ if c.smp.state != smpState3 {
|
|
| 130 |
+ c.resetSMP() |
|
| 131 |
+ out = c.generateSMPAbort() |
|
| 132 |
+ return |
|
| 133 |
+ } |
|
| 134 |
+ if out, err = c.processSMP3(mpis); err != nil {
|
|
| 135 |
+ return |
|
| 136 |
+ } |
|
| 137 |
+ c.smp.state = smpState1 |
|
| 138 |
+ c.smp.secret = nil |
|
| 139 |
+ complete = true |
|
| 140 |
+ case tlvTypeSMP4: |
|
| 141 |
+ if c.smp.state != smpState4 {
|
|
| 142 |
+ c.resetSMP() |
|
| 143 |
+ out = c.generateSMPAbort() |
|
| 144 |
+ return |
|
| 145 |
+ } |
|
| 146 |
+ if err = c.processSMP4(mpis); err != nil {
|
|
| 147 |
+ out = c.generateSMPAbort() |
|
| 148 |
+ return |
|
| 149 |
+ } |
|
| 150 |
+ c.smp.state = smpState1 |
|
| 151 |
+ c.smp.secret = nil |
|
| 152 |
+ complete = true |
|
| 153 |
+ default: |
|
| 154 |
+ panic("unknown SMP message")
|
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ return |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func (c *Conversation) calcSMPSecret(mutualSecret []byte, weStarted bool) {
|
|
| 161 |
+ h := sha256.New() |
|
| 162 |
+ h.Write([]byte{smpVersion})
|
|
| 163 |
+ if weStarted {
|
|
| 164 |
+ h.Write(c.PrivateKey.PublicKey.Fingerprint()) |
|
| 165 |
+ h.Write(c.TheirPublicKey.Fingerprint()) |
|
| 166 |
+ } else {
|
|
| 167 |
+ h.Write(c.TheirPublicKey.Fingerprint()) |
|
| 168 |
+ h.Write(c.PrivateKey.PublicKey.Fingerprint()) |
|
| 169 |
+ } |
|
| 170 |
+ h.Write(c.SSID[:]) |
|
| 171 |
+ h.Write(mutualSecret) |
|
| 172 |
+ c.smp.secret = new(big.Int).SetBytes(h.Sum(nil)) |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+func (c *Conversation) generateSMP1(question string) tlv {
|
|
| 176 |
+ var randBuf [16]byte |
|
| 177 |
+ c.smp.a2 = c.randMPI(randBuf[:]) |
|
| 178 |
+ c.smp.a3 = c.randMPI(randBuf[:]) |
|
| 179 |
+ g2a := new(big.Int).Exp(g, c.smp.a2, p) |
|
| 180 |
+ g3a := new(big.Int).Exp(g, c.smp.a3, p) |
|
| 181 |
+ h := sha256.New() |
|
| 182 |
+ |
|
| 183 |
+ r2 := c.randMPI(randBuf[:]) |
|
| 184 |
+ r := new(big.Int).Exp(g, r2, p) |
|
| 185 |
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 1, r)) |
|
| 186 |
+ d2 := new(big.Int).Mul(c.smp.a2, c2) |
|
| 187 |
+ d2.Sub(r2, d2) |
|
| 188 |
+ d2.Mod(d2, q) |
|
| 189 |
+ if d2.Sign() < 0 {
|
|
| 190 |
+ d2.Add(d2, q) |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ r3 := c.randMPI(randBuf[:]) |
|
| 194 |
+ r.Exp(g, r3, p) |
|
| 195 |
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 2, r)) |
|
| 196 |
+ d3 := new(big.Int).Mul(c.smp.a3, c3) |
|
| 197 |
+ d3.Sub(r3, d3) |
|
| 198 |
+ d3.Mod(d3, q) |
|
| 199 |
+ if d3.Sign() < 0 {
|
|
| 200 |
+ d3.Add(d3, q) |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ var ret tlv |
|
| 204 |
+ if len(question) > 0 {
|
|
| 205 |
+ ret.typ = tlvTypeSMP1WithQuestion |
|
| 206 |
+ ret.data = append(ret.data, question...) |
|
| 207 |
+ ret.data = append(ret.data, 0) |
|
| 208 |
+ } else {
|
|
| 209 |
+ ret.typ = tlvTypeSMP1 |
|
| 210 |
+ } |
|
| 211 |
+ ret.data = appendU32(ret.data, 6) |
|
| 212 |
+ ret.data = appendMPIs(ret.data, g2a, c2, d2, g3a, c3, d3) |
|
| 213 |
+ return ret |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func (c *Conversation) processSMP1(mpis []*big.Int) error {
|
|
| 217 |
+ if len(mpis) != 6 {
|
|
| 218 |
+ return errors.New("otr: incorrect number of arguments in SMP1 message")
|
|
| 219 |
+ } |
|
| 220 |
+ g2a := mpis[0] |
|
| 221 |
+ c2 := mpis[1] |
|
| 222 |
+ d2 := mpis[2] |
|
| 223 |
+ g3a := mpis[3] |
|
| 224 |
+ c3 := mpis[4] |
|
| 225 |
+ d3 := mpis[5] |
|
| 226 |
+ h := sha256.New() |
|
| 227 |
+ |
|
| 228 |
+ r := new(big.Int).Exp(g, d2, p) |
|
| 229 |
+ s := new(big.Int).Exp(g2a, c2, p) |
|
| 230 |
+ r.Mul(r, s) |
|
| 231 |
+ r.Mod(r, p) |
|
| 232 |
+ t := new(big.Int).SetBytes(hashMPIs(h, 1, r)) |
|
| 233 |
+ if c2.Cmp(t) != 0 {
|
|
| 234 |
+ return errors.New("otr: ZKP c2 incorrect in SMP1 message")
|
|
| 235 |
+ } |
|
| 236 |
+ r.Exp(g, d3, p) |
|
| 237 |
+ s.Exp(g3a, c3, p) |
|
| 238 |
+ r.Mul(r, s) |
|
| 239 |
+ r.Mod(r, p) |
|
| 240 |
+ t.SetBytes(hashMPIs(h, 2, r)) |
|
| 241 |
+ if c3.Cmp(t) != 0 {
|
|
| 242 |
+ return errors.New("otr: ZKP c3 incorrect in SMP1 message")
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ c.smp.g2a = g2a |
|
| 246 |
+ c.smp.g3a = g3a |
|
| 247 |
+ return nil |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+func (c *Conversation) generateSMP2() tlv {
|
|
| 251 |
+ var randBuf [16]byte |
|
| 252 |
+ b2 := c.randMPI(randBuf[:]) |
|
| 253 |
+ c.smp.b3 = c.randMPI(randBuf[:]) |
|
| 254 |
+ r2 := c.randMPI(randBuf[:]) |
|
| 255 |
+ r3 := c.randMPI(randBuf[:]) |
|
| 256 |
+ r4 := c.randMPI(randBuf[:]) |
|
| 257 |
+ r5 := c.randMPI(randBuf[:]) |
|
| 258 |
+ r6 := c.randMPI(randBuf[:]) |
|
| 259 |
+ |
|
| 260 |
+ g2b := new(big.Int).Exp(g, b2, p) |
|
| 261 |
+ g3b := new(big.Int).Exp(g, c.smp.b3, p) |
|
| 262 |
+ |
|
| 263 |
+ r := new(big.Int).Exp(g, r2, p) |
|
| 264 |
+ h := sha256.New() |
|
| 265 |
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 3, r)) |
|
| 266 |
+ d2 := new(big.Int).Mul(b2, c2) |
|
| 267 |
+ d2.Sub(r2, d2) |
|
| 268 |
+ d2.Mod(d2, q) |
|
| 269 |
+ if d2.Sign() < 0 {
|
|
| 270 |
+ d2.Add(d2, q) |
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ r.Exp(g, r3, p) |
|
| 274 |
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 4, r)) |
|
| 275 |
+ d3 := new(big.Int).Mul(c.smp.b3, c3) |
|
| 276 |
+ d3.Sub(r3, d3) |
|
| 277 |
+ d3.Mod(d3, q) |
|
| 278 |
+ if d3.Sign() < 0 {
|
|
| 279 |
+ d3.Add(d3, q) |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ c.smp.g2 = new(big.Int).Exp(c.smp.g2a, b2, p) |
|
| 283 |
+ c.smp.g3 = new(big.Int).Exp(c.smp.g3a, c.smp.b3, p) |
|
| 284 |
+ c.smp.pb = new(big.Int).Exp(c.smp.g3, r4, p) |
|
| 285 |
+ c.smp.qb = new(big.Int).Exp(g, r4, p) |
|
| 286 |
+ r.Exp(c.smp.g2, c.smp.secret, p) |
|
| 287 |
+ c.smp.qb.Mul(c.smp.qb, r) |
|
| 288 |
+ c.smp.qb.Mod(c.smp.qb, p) |
|
| 289 |
+ |
|
| 290 |
+ s := new(big.Int) |
|
| 291 |
+ s.Exp(c.smp.g2, r6, p) |
|
| 292 |
+ r.Exp(g, r5, p) |
|
| 293 |
+ s.Mul(r, s) |
|
| 294 |
+ s.Mod(s, p) |
|
| 295 |
+ r.Exp(c.smp.g3, r5, p) |
|
| 296 |
+ cp := new(big.Int).SetBytes(hashMPIs(h, 5, r, s)) |
|
| 297 |
+ |
|
| 298 |
+ // D5 = r5 - r4 cP mod q and D6 = r6 - y cP mod q |
|
| 299 |
+ |
|
| 300 |
+ s.Mul(r4, cp) |
|
| 301 |
+ r.Sub(r5, s) |
|
| 302 |
+ d5 := new(big.Int).Mod(r, q) |
|
| 303 |
+ if d5.Sign() < 0 {
|
|
| 304 |
+ d5.Add(d5, q) |
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ s.Mul(c.smp.secret, cp) |
|
| 308 |
+ r.Sub(r6, s) |
|
| 309 |
+ d6 := new(big.Int).Mod(r, q) |
|
| 310 |
+ if d6.Sign() < 0 {
|
|
| 311 |
+ d6.Add(d6, q) |
|
| 312 |
+ } |
|
| 313 |
+ |
|
| 314 |
+ var ret tlv |
|
| 315 |
+ ret.typ = tlvTypeSMP2 |
|
| 316 |
+ ret.data = appendU32(ret.data, 11) |
|
| 317 |
+ ret.data = appendMPIs(ret.data, g2b, c2, d2, g3b, c3, d3, c.smp.pb, c.smp.qb, cp, d5, d6) |
|
| 318 |
+ return ret |
|
| 319 |
+} |
|
| 320 |
+ |
|
| 321 |
+func (c *Conversation) processSMP2(mpis []*big.Int) (out tlv, err error) {
|
|
| 322 |
+ if len(mpis) != 11 {
|
|
| 323 |
+ err = errors.New("otr: incorrect number of arguments in SMP2 message")
|
|
| 324 |
+ return |
|
| 325 |
+ } |
|
| 326 |
+ g2b := mpis[0] |
|
| 327 |
+ c2 := mpis[1] |
|
| 328 |
+ d2 := mpis[2] |
|
| 329 |
+ g3b := mpis[3] |
|
| 330 |
+ c3 := mpis[4] |
|
| 331 |
+ d3 := mpis[5] |
|
| 332 |
+ pb := mpis[6] |
|
| 333 |
+ qb := mpis[7] |
|
| 334 |
+ cp := mpis[8] |
|
| 335 |
+ d5 := mpis[9] |
|
| 336 |
+ d6 := mpis[10] |
|
| 337 |
+ h := sha256.New() |
|
| 338 |
+ |
|
| 339 |
+ r := new(big.Int).Exp(g, d2, p) |
|
| 340 |
+ s := new(big.Int).Exp(g2b, c2, p) |
|
| 341 |
+ r.Mul(r, s) |
|
| 342 |
+ r.Mod(r, p) |
|
| 343 |
+ s.SetBytes(hashMPIs(h, 3, r)) |
|
| 344 |
+ if c2.Cmp(s) != 0 {
|
|
| 345 |
+ err = errors.New("otr: ZKP c2 failed in SMP2 message")
|
|
| 346 |
+ return |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ r.Exp(g, d3, p) |
|
| 350 |
+ s.Exp(g3b, c3, p) |
|
| 351 |
+ r.Mul(r, s) |
|
| 352 |
+ r.Mod(r, p) |
|
| 353 |
+ s.SetBytes(hashMPIs(h, 4, r)) |
|
| 354 |
+ if c3.Cmp(s) != 0 {
|
|
| 355 |
+ err = errors.New("otr: ZKP c3 failed in SMP2 message")
|
|
| 356 |
+ return |
|
| 357 |
+ } |
|
| 358 |
+ |
|
| 359 |
+ c.smp.g2 = new(big.Int).Exp(g2b, c.smp.a2, p) |
|
| 360 |
+ c.smp.g3 = new(big.Int).Exp(g3b, c.smp.a3, p) |
|
| 361 |
+ |
|
| 362 |
+ r.Exp(g, d5, p) |
|
| 363 |
+ s.Exp(c.smp.g2, d6, p) |
|
| 364 |
+ r.Mul(r, s) |
|
| 365 |
+ s.Exp(qb, cp, p) |
|
| 366 |
+ r.Mul(r, s) |
|
| 367 |
+ r.Mod(r, p) |
|
| 368 |
+ |
|
| 369 |
+ s.Exp(c.smp.g3, d5, p) |
|
| 370 |
+ t := new(big.Int).Exp(pb, cp, p) |
|
| 371 |
+ s.Mul(s, t) |
|
| 372 |
+ s.Mod(s, p) |
|
| 373 |
+ t.SetBytes(hashMPIs(h, 5, s, r)) |
|
| 374 |
+ if cp.Cmp(t) != 0 {
|
|
| 375 |
+ err = errors.New("otr: ZKP cP failed in SMP2 message")
|
|
| 376 |
+ return |
|
| 377 |
+ } |
|
| 378 |
+ |
|
| 379 |
+ var randBuf [16]byte |
|
| 380 |
+ r4 := c.randMPI(randBuf[:]) |
|
| 381 |
+ r5 := c.randMPI(randBuf[:]) |
|
| 382 |
+ r6 := c.randMPI(randBuf[:]) |
|
| 383 |
+ r7 := c.randMPI(randBuf[:]) |
|
| 384 |
+ |
|
| 385 |
+ pa := new(big.Int).Exp(c.smp.g3, r4, p) |
|
| 386 |
+ r.Exp(c.smp.g2, c.smp.secret, p) |
|
| 387 |
+ qa := new(big.Int).Exp(g, r4, p) |
|
| 388 |
+ qa.Mul(qa, r) |
|
| 389 |
+ qa.Mod(qa, p) |
|
| 390 |
+ |
|
| 391 |
+ r.Exp(g, r5, p) |
|
| 392 |
+ s.Exp(c.smp.g2, r6, p) |
|
| 393 |
+ r.Mul(r, s) |
|
| 394 |
+ r.Mod(r, p) |
|
| 395 |
+ |
|
| 396 |
+ s.Exp(c.smp.g3, r5, p) |
|
| 397 |
+ cp.SetBytes(hashMPIs(h, 6, s, r)) |
|
| 398 |
+ |
|
| 399 |
+ r.Mul(r4, cp) |
|
| 400 |
+ d5 = new(big.Int).Sub(r5, r) |
|
| 401 |
+ d5.Mod(d5, q) |
|
| 402 |
+ if d5.Sign() < 0 {
|
|
| 403 |
+ d5.Add(d5, q) |
|
| 404 |
+ } |
|
| 405 |
+ |
|
| 406 |
+ r.Mul(c.smp.secret, cp) |
|
| 407 |
+ d6 = new(big.Int).Sub(r6, r) |
|
| 408 |
+ d6.Mod(d6, q) |
|
| 409 |
+ if d6.Sign() < 0 {
|
|
| 410 |
+ d6.Add(d6, q) |
|
| 411 |
+ } |
|
| 412 |
+ |
|
| 413 |
+ r.ModInverse(qb, p) |
|
| 414 |
+ qaqb := new(big.Int).Mul(qa, r) |
|
| 415 |
+ qaqb.Mod(qaqb, p) |
|
| 416 |
+ |
|
| 417 |
+ ra := new(big.Int).Exp(qaqb, c.smp.a3, p) |
|
| 418 |
+ r.Exp(qaqb, r7, p) |
|
| 419 |
+ s.Exp(g, r7, p) |
|
| 420 |
+ cr := new(big.Int).SetBytes(hashMPIs(h, 7, s, r)) |
|
| 421 |
+ |
|
| 422 |
+ r.Mul(c.smp.a3, cr) |
|
| 423 |
+ d7 := new(big.Int).Sub(r7, r) |
|
| 424 |
+ d7.Mod(d7, q) |
|
| 425 |
+ if d7.Sign() < 0 {
|
|
| 426 |
+ d7.Add(d7, q) |
|
| 427 |
+ } |
|
| 428 |
+ |
|
| 429 |
+ c.smp.g3b = g3b |
|
| 430 |
+ c.smp.qaqb = qaqb |
|
| 431 |
+ |
|
| 432 |
+ r.ModInverse(pb, p) |
|
| 433 |
+ c.smp.papb = new(big.Int).Mul(pa, r) |
|
| 434 |
+ c.smp.papb.Mod(c.smp.papb, p) |
|
| 435 |
+ c.smp.ra = ra |
|
| 436 |
+ |
|
| 437 |
+ out.typ = tlvTypeSMP3 |
|
| 438 |
+ out.data = appendU32(out.data, 8) |
|
| 439 |
+ out.data = appendMPIs(out.data, pa, qa, cp, d5, d6, ra, cr, d7) |
|
| 440 |
+ return |
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func (c *Conversation) processSMP3(mpis []*big.Int) (out tlv, err error) {
|
|
| 444 |
+ if len(mpis) != 8 {
|
|
| 445 |
+ err = errors.New("otr: incorrect number of arguments in SMP3 message")
|
|
| 446 |
+ return |
|
| 447 |
+ } |
|
| 448 |
+ pa := mpis[0] |
|
| 449 |
+ qa := mpis[1] |
|
| 450 |
+ cp := mpis[2] |
|
| 451 |
+ d5 := mpis[3] |
|
| 452 |
+ d6 := mpis[4] |
|
| 453 |
+ ra := mpis[5] |
|
| 454 |
+ cr := mpis[6] |
|
| 455 |
+ d7 := mpis[7] |
|
| 456 |
+ h := sha256.New() |
|
| 457 |
+ |
|
| 458 |
+ r := new(big.Int).Exp(g, d5, p) |
|
| 459 |
+ s := new(big.Int).Exp(c.smp.g2, d6, p) |
|
| 460 |
+ r.Mul(r, s) |
|
| 461 |
+ s.Exp(qa, cp, p) |
|
| 462 |
+ r.Mul(r, s) |
|
| 463 |
+ r.Mod(r, p) |
|
| 464 |
+ |
|
| 465 |
+ s.Exp(c.smp.g3, d5, p) |
|
| 466 |
+ t := new(big.Int).Exp(pa, cp, p) |
|
| 467 |
+ s.Mul(s, t) |
|
| 468 |
+ s.Mod(s, p) |
|
| 469 |
+ t.SetBytes(hashMPIs(h, 6, s, r)) |
|
| 470 |
+ if t.Cmp(cp) != 0 {
|
|
| 471 |
+ err = errors.New("otr: ZKP cP failed in SMP3 message")
|
|
| 472 |
+ return |
|
| 473 |
+ } |
|
| 474 |
+ |
|
| 475 |
+ r.ModInverse(c.smp.qb, p) |
|
| 476 |
+ qaqb := new(big.Int).Mul(qa, r) |
|
| 477 |
+ qaqb.Mod(qaqb, p) |
|
| 478 |
+ |
|
| 479 |
+ r.Exp(qaqb, d7, p) |
|
| 480 |
+ s.Exp(ra, cr, p) |
|
| 481 |
+ r.Mul(r, s) |
|
| 482 |
+ r.Mod(r, p) |
|
| 483 |
+ |
|
| 484 |
+ s.Exp(g, d7, p) |
|
| 485 |
+ t.Exp(c.smp.g3a, cr, p) |
|
| 486 |
+ s.Mul(s, t) |
|
| 487 |
+ s.Mod(s, p) |
|
| 488 |
+ t.SetBytes(hashMPIs(h, 7, s, r)) |
|
| 489 |
+ if t.Cmp(cr) != 0 {
|
|
| 490 |
+ err = errors.New("otr: ZKP cR failed in SMP3 message")
|
|
| 491 |
+ return |
|
| 492 |
+ } |
|
| 493 |
+ |
|
| 494 |
+ var randBuf [16]byte |
|
| 495 |
+ r7 := c.randMPI(randBuf[:]) |
|
| 496 |
+ rb := new(big.Int).Exp(qaqb, c.smp.b3, p) |
|
| 497 |
+ |
|
| 498 |
+ r.Exp(qaqb, r7, p) |
|
| 499 |
+ s.Exp(g, r7, p) |
|
| 500 |
+ cr = new(big.Int).SetBytes(hashMPIs(h, 8, s, r)) |
|
| 501 |
+ |
|
| 502 |
+ r.Mul(c.smp.b3, cr) |
|
| 503 |
+ d7 = new(big.Int).Sub(r7, r) |
|
| 504 |
+ d7.Mod(d7, q) |
|
| 505 |
+ if d7.Sign() < 0 {
|
|
| 506 |
+ d7.Add(d7, q) |
|
| 507 |
+ } |
|
| 508 |
+ |
|
| 509 |
+ out.typ = tlvTypeSMP4 |
|
| 510 |
+ out.data = appendU32(out.data, 3) |
|
| 511 |
+ out.data = appendMPIs(out.data, rb, cr, d7) |
|
| 512 |
+ |
|
| 513 |
+ r.ModInverse(c.smp.pb, p) |
|
| 514 |
+ r.Mul(pa, r) |
|
| 515 |
+ r.Mod(r, p) |
|
| 516 |
+ s.Exp(ra, c.smp.b3, p) |
|
| 517 |
+ if r.Cmp(s) != 0 {
|
|
| 518 |
+ err = smpFailureError |
|
| 519 |
+ } |
|
| 520 |
+ |
|
| 521 |
+ return |
|
| 522 |
+} |
|
| 523 |
+ |
|
| 524 |
+func (c *Conversation) processSMP4(mpis []*big.Int) error {
|
|
| 525 |
+ if len(mpis) != 3 {
|
|
| 526 |
+ return errors.New("otr: incorrect number of arguments in SMP4 message")
|
|
| 527 |
+ } |
|
| 528 |
+ rb := mpis[0] |
|
| 529 |
+ cr := mpis[1] |
|
| 530 |
+ d7 := mpis[2] |
|
| 531 |
+ h := sha256.New() |
|
| 532 |
+ |
|
| 533 |
+ r := new(big.Int).Exp(c.smp.qaqb, d7, p) |
|
| 534 |
+ s := new(big.Int).Exp(rb, cr, p) |
|
| 535 |
+ r.Mul(r, s) |
|
| 536 |
+ r.Mod(r, p) |
|
| 537 |
+ |
|
| 538 |
+ s.Exp(g, d7, p) |
|
| 539 |
+ t := new(big.Int).Exp(c.smp.g3b, cr, p) |
|
| 540 |
+ s.Mul(s, t) |
|
| 541 |
+ s.Mod(s, p) |
|
| 542 |
+ t.SetBytes(hashMPIs(h, 8, s, r)) |
|
| 543 |
+ if t.Cmp(cr) != 0 {
|
|
| 544 |
+ return errors.New("otr: ZKP cR failed in SMP4 message")
|
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ r.Exp(rb, c.smp.a3, p) |
|
| 548 |
+ if r.Cmp(c.smp.papb) != 0 {
|
|
| 549 |
+ return smpFailureError |
|
| 550 |
+ } |
|
| 551 |
+ |
|
| 552 |
+ return nil |
|
| 553 |
+} |
|
| 554 |
+ |
|
| 555 |
+func (c *Conversation) generateSMPAbort() tlv {
|
|
| 556 |
+ return tlv{typ: tlvTypeSMPAbort}
|
|
| 557 |
+} |
|
| 558 |
+ |
|
| 559 |
+func hashMPIs(h hash.Hash, magic byte, mpis ...*big.Int) []byte {
|
|
| 560 |
+ if h != nil {
|
|
| 561 |
+ h.Reset() |
|
| 562 |
+ } else {
|
|
| 563 |
+ h = sha256.New() |
|
| 564 |
+ } |
|
| 565 |
+ |
|
| 566 |
+ h.Write([]byte{magic})
|
|
| 567 |
+ for _, mpi := range mpis {
|
|
| 568 |
+ h.Write(appendMPI(nil, mpi)) |
|
| 569 |
+ } |
|
| 570 |
+ return h.Sum(nil) |
|
| 571 |
+} |
| 0 | 572 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+// Copyright 2012 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package test contains integration tests for the |
|
| 5 |
+// golang.org/x/crypto/ssh package. |
|
| 6 |
+package test // import "golang.org/x/crypto/ssh/test" |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,173 @@ |
| 0 |
+// Copyright 2017 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// sshd_test_pw.c |
|
| 5 |
+// Wrapper to inject test password data for sshd PAM authentication |
|
| 6 |
+// |
|
| 7 |
+// This wrapper implements custom versions of getpwnam, getpwnam_r, |
|
| 8 |
+// getspnam and getspnam_r. These functions first call their real |
|
| 9 |
+// libc versions, then check if the requested user matches test user |
|
| 10 |
+// specified in env variable TEST_USER and if so replace the password |
|
| 11 |
+// with crypted() value of TEST_PASSWD env variable. |
|
| 12 |
+// |
|
| 13 |
+// Compile: |
|
| 14 |
+// gcc -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c |
|
| 15 |
+// |
|
| 16 |
+// Compile with debug: |
|
| 17 |
+// gcc -DVERBOSE -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c |
|
| 18 |
+// |
|
| 19 |
+// Run sshd: |
|
| 20 |
+// LD_PRELOAD="sshd_test_pw.so" TEST_USER="..." TEST_PASSWD="..." sshd ... |
|
| 21 |
+ |
|
| 22 |
+// +build ignore |
|
| 23 |
+ |
|
| 24 |
+#define _GNU_SOURCE |
|
| 25 |
+#include <string.h> |
|
| 26 |
+#include <pwd.h> |
|
| 27 |
+#include <shadow.h> |
|
| 28 |
+#include <dlfcn.h> |
|
| 29 |
+#include <stdlib.h> |
|
| 30 |
+#include <unistd.h> |
|
| 31 |
+#include <stdio.h> |
|
| 32 |
+ |
|
| 33 |
+#ifdef VERBOSE |
|
| 34 |
+#define DEBUG(X...) fprintf(stderr, X) |
|
| 35 |
+#else |
|
| 36 |
+#define DEBUG(X...) while (0) { }
|
|
| 37 |
+#endif |
|
| 38 |
+ |
|
| 39 |
+/* crypt() password */ |
|
| 40 |
+static char * |
|
| 41 |
+pwhash(char *passwd) {
|
|
| 42 |
+ return strdup(crypt(passwd, "$6$")); |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+/* Pointers to real functions in libc */ |
|
| 46 |
+static struct passwd * (*real_getpwnam)(const char *) = NULL; |
|
| 47 |
+static int (*real_getpwnam_r)(const char *, struct passwd *, char *, size_t, struct passwd **) = NULL; |
|
| 48 |
+static struct spwd * (*real_getspnam)(const char *) = NULL; |
|
| 49 |
+static int (*real_getspnam_r)(const char *, struct spwd *, char *, size_t, struct spwd **) = NULL; |
|
| 50 |
+ |
|
| 51 |
+/* Cached test user and test password */ |
|
| 52 |
+static char *test_user = NULL; |
|
| 53 |
+static char *test_passwd_hash = NULL; |
|
| 54 |
+ |
|
| 55 |
+static void |
|
| 56 |
+init(void) {
|
|
| 57 |
+ /* Fetch real libc function pointers */ |
|
| 58 |
+ real_getpwnam = dlsym(RTLD_NEXT, "getpwnam"); |
|
| 59 |
+ real_getpwnam_r = dlsym(RTLD_NEXT, "getpwnam_r"); |
|
| 60 |
+ real_getspnam = dlsym(RTLD_NEXT, "getspnam"); |
|
| 61 |
+ real_getspnam_r = dlsym(RTLD_NEXT, "getspnam_r"); |
|
| 62 |
+ |
|
| 63 |
+ /* abort if env variables are not defined */ |
|
| 64 |
+ if (getenv("TEST_USER") == NULL || getenv("TEST_PASSWD") == NULL) {
|
|
| 65 |
+ fprintf(stderr, "env variables TEST_USER and TEST_PASSWD are missing\n"); |
|
| 66 |
+ abort(); |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ /* Fetch test user and test password from env */ |
|
| 70 |
+ test_user = strdup(getenv("TEST_USER"));
|
|
| 71 |
+ test_passwd_hash = pwhash(getenv("TEST_PASSWD"));
|
|
| 72 |
+ |
|
| 73 |
+ DEBUG("sshd_test_pw init():\n");
|
|
| 74 |
+ DEBUG("\treal_getpwnam: %p\n", real_getpwnam);
|
|
| 75 |
+ DEBUG("\treal_getpwnam_r: %p\n", real_getpwnam_r);
|
|
| 76 |
+ DEBUG("\treal_getspnam: %p\n", real_getspnam);
|
|
| 77 |
+ DEBUG("\treal_getspnam_r: %p\n", real_getspnam_r);
|
|
| 78 |
+ DEBUG("\tTEST_USER: '%s'\n", test_user);
|
|
| 79 |
+ DEBUG("\tTEST_PASSWD: '%s'\n", getenv("TEST_PASSWD"));
|
|
| 80 |
+ DEBUG("\tTEST_PASSWD_HASH: '%s'\n", test_passwd_hash);
|
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+static int |
|
| 84 |
+is_test_user(const char *name) {
|
|
| 85 |
+ if (test_user != NULL && strcmp(test_user, name) == 0) |
|
| 86 |
+ return 1; |
|
| 87 |
+ return 0; |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+/* getpwnam */ |
|
| 91 |
+ |
|
| 92 |
+struct passwd * |
|
| 93 |
+getpwnam(const char *name) {
|
|
| 94 |
+ struct passwd *pw; |
|
| 95 |
+ |
|
| 96 |
+ DEBUG("sshd_test_pw getpwnam(%s)\n", name);
|
|
| 97 |
+ |
|
| 98 |
+ if (real_getpwnam == NULL) |
|
| 99 |
+ init(); |
|
| 100 |
+ if ((pw = real_getpwnam(name)) == NULL) |
|
| 101 |
+ return NULL; |
|
| 102 |
+ |
|
| 103 |
+ if (is_test_user(name)) |
|
| 104 |
+ pw->pw_passwd = strdup(test_passwd_hash); |
|
| 105 |
+ |
|
| 106 |
+ return pw; |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+/* getpwnam_r */ |
|
| 110 |
+ |
|
| 111 |
+int |
|
| 112 |
+getpwnam_r(const char *name, |
|
| 113 |
+ struct passwd *pwd, |
|
| 114 |
+ char *buf, |
|
| 115 |
+ size_t buflen, |
|
| 116 |
+ struct passwd **result) {
|
|
| 117 |
+ int r; |
|
| 118 |
+ |
|
| 119 |
+ DEBUG("sshd_test_pw getpwnam_r(%s)\n", name);
|
|
| 120 |
+ |
|
| 121 |
+ if (real_getpwnam_r == NULL) |
|
| 122 |
+ init(); |
|
| 123 |
+ if ((r = real_getpwnam_r(name, pwd, buf, buflen, result)) != 0 || *result == NULL) |
|
| 124 |
+ return r; |
|
| 125 |
+ |
|
| 126 |
+ if (is_test_user(name)) |
|
| 127 |
+ pwd->pw_passwd = strdup(test_passwd_hash); |
|
| 128 |
+ |
|
| 129 |
+ return 0; |
|
| 130 |
+} |
|
| 131 |
+ |
|
| 132 |
+/* getspnam */ |
|
| 133 |
+ |
|
| 134 |
+struct spwd * |
|
| 135 |
+getspnam(const char *name) {
|
|
| 136 |
+ struct spwd *sp; |
|
| 137 |
+ |
|
| 138 |
+ DEBUG("sshd_test_pw getspnam(%s)\n", name);
|
|
| 139 |
+ |
|
| 140 |
+ if (real_getspnam == NULL) |
|
| 141 |
+ init(); |
|
| 142 |
+ if ((sp = real_getspnam(name)) == NULL) |
|
| 143 |
+ return NULL; |
|
| 144 |
+ |
|
| 145 |
+ if (is_test_user(name)) |
|
| 146 |
+ sp->sp_pwdp = strdup(test_passwd_hash); |
|
| 147 |
+ |
|
| 148 |
+ return sp; |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+/* getspnam_r */ |
|
| 152 |
+ |
|
| 153 |
+int |
|
| 154 |
+getspnam_r(const char *name, |
|
| 155 |
+ struct spwd *spbuf, |
|
| 156 |
+ char *buf, |
|
| 157 |
+ size_t buflen, |
|
| 158 |
+ struct spwd **spbufp) {
|
|
| 159 |
+ int r; |
|
| 160 |
+ |
|
| 161 |
+ DEBUG("sshd_test_pw getspnam_r(%s)\n", name);
|
|
| 162 |
+ |
|
| 163 |
+ if (real_getspnam_r == NULL) |
|
| 164 |
+ init(); |
|
| 165 |
+ if ((r = real_getspnam_r(name, spbuf, buf, buflen, spbufp)) != 0) |
|
| 166 |
+ return r; |
|
| 167 |
+ |
|
| 168 |
+ if (is_test_user(name)) |
|
| 169 |
+ spbuf->sp_pwdp = strdup(test_passwd_hash); |
|
| 170 |
+ |
|
| 171 |
+ return r; |
|
| 172 |
+} |
| 0 | 173 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// Package cpu implements processor feature detection for |
|
| 5 |
+// various CPU architectures. |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+// CacheLinePad is used to pad structs to avoid false sharing. |
|
| 9 |
+type CacheLinePad struct{ _ [cacheLineSize]byte }
|
|
| 10 |
+ |
|
| 11 |
+// X86 contains the supported CPU features of the |
|
| 12 |
+// current X86/AMD64 platform. If the current platform |
|
| 13 |
+// is not X86/AMD64 then all feature flags are false. |
|
| 14 |
+// |
|
| 15 |
+// X86 is padded to avoid false sharing. Further the HasAVX |
|
| 16 |
+// and HasAVX2 are only set if the OS supports XMM and YMM |
|
| 17 |
+// registers in addition to the CPUID feature bit being set. |
|
| 18 |
+var X86 struct {
|
|
| 19 |
+ _ CacheLinePad |
|
| 20 |
+ HasAES bool // AES hardware implementation (AES NI) |
|
| 21 |
+ HasADX bool // Multi-precision add-carry instruction extensions |
|
| 22 |
+ HasAVX bool // Advanced vector extension |
|
| 23 |
+ HasAVX2 bool // Advanced vector extension 2 |
|
| 24 |
+ HasBMI1 bool // Bit manipulation instruction set 1 |
|
| 25 |
+ HasBMI2 bool // Bit manipulation instruction set 2 |
|
| 26 |
+ HasERMS bool // Enhanced REP for MOVSB and STOSB |
|
| 27 |
+ HasFMA bool // Fused-multiply-add instructions |
|
| 28 |
+ HasOSXSAVE bool // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. |
|
| 29 |
+ HasPCLMULQDQ bool // PCLMULQDQ instruction - most often used for AES-GCM |
|
| 30 |
+ HasPOPCNT bool // Hamming weight instruction POPCNT. |
|
| 31 |
+ HasSSE2 bool // Streaming SIMD extension 2 (always available on amd64) |
|
| 32 |
+ HasSSE3 bool // Streaming SIMD extension 3 |
|
| 33 |
+ HasSSSE3 bool // Supplemental streaming SIMD extension 3 |
|
| 34 |
+ HasSSE41 bool // Streaming SIMD extension 4 and 4.1 |
|
| 35 |
+ HasSSE42 bool // Streaming SIMD extension 4 and 4.2 |
|
| 36 |
+ _ CacheLinePad |
|
| 37 |
+} |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build !gccgo |
|
| 6 |
+ |
|
| 7 |
+package cpu |
|
| 8 |
+ |
|
| 9 |
+// cpuid is implemented in cpu_x86.s for gc compiler |
|
| 10 |
+// and in cpu_gccgo.c for gccgo. |
|
| 11 |
+func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) |
|
| 12 |
+ |
|
| 13 |
+// xgetbv with ecx = 0 is implemented in cpu_x86.s for gc compiler |
|
| 14 |
+// and in cpu_gccgo.c for gccgo. |
|
| 15 |
+func xgetbv() (eax, edx uint32) |
| 0 | 16 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build gccgo |
|
| 6 |
+ |
|
| 7 |
+#include <cpuid.h> |
|
| 8 |
+#include <stdint.h> |
|
| 9 |
+ |
|
| 10 |
+// Need to wrap __get_cpuid_count because it's declared as static. |
|
| 11 |
+int |
|
| 12 |
+gccgoGetCpuidCount(uint32_t leaf, uint32_t subleaf, |
|
| 13 |
+ uint32_t *eax, uint32_t *ebx, |
|
| 14 |
+ uint32_t *ecx, uint32_t *edx) |
|
| 15 |
+{
|
|
| 16 |
+ return __get_cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// xgetbv reads the contents of an XCR (Extended Control Register) |
|
| 20 |
+// specified in the ECX register into registers EDX:EAX. |
|
| 21 |
+// Currently, the only supported value for XCR is 0. |
|
| 22 |
+// |
|
| 23 |
+// TODO: Replace with a better alternative: |
|
| 24 |
+// |
|
| 25 |
+// #include <xsaveintrin.h> |
|
| 26 |
+// |
|
| 27 |
+// #pragma GCC target("xsave")
|
|
| 28 |
+// |
|
| 29 |
+// void gccgoXgetbv(uint32_t *eax, uint32_t *edx) {
|
|
| 30 |
+// unsigned long long x = _xgetbv(0); |
|
| 31 |
+// *eax = x & 0xffffffff; |
|
| 32 |
+// *edx = (x >> 32) & 0xffffffff; |
|
| 33 |
+// } |
|
| 34 |
+// |
|
| 35 |
+// Note that _xgetbv is defined starting with GCC 8. |
|
| 36 |
+void |
|
| 37 |
+gccgoXgetbv(uint32_t *eax, uint32_t *edx) |
|
| 38 |
+{
|
|
| 39 |
+ __asm(" xorl %%ecx, %%ecx\n"
|
|
| 40 |
+ " xgetbv" |
|
| 41 |
+ : "=a"(*eax), "=d"(*edx)); |
|
| 42 |
+} |
| 0 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,26 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build gccgo |
|
| 6 |
+ |
|
| 7 |
+package cpu |
|
| 8 |
+ |
|
| 9 |
+//extern gccgoGetCpuidCount |
|
| 10 |
+func gccgoGetCpuidCount(eaxArg, ecxArg uint32, eax, ebx, ecx, edx *uint32) |
|
| 11 |
+ |
|
| 12 |
+func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) {
|
|
| 13 |
+ var a, b, c, d uint32 |
|
| 14 |
+ gccgoGetCpuidCount(eaxArg, ecxArg, &a, &b, &c, &d) |
|
| 15 |
+ return a, b, c, d |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+//extern gccgoXgetbv |
|
| 19 |
+func gccgoXgetbv(eax, edx *uint32) |
|
| 20 |
+ |
|
| 21 |
+func xgetbv() (eax, edx uint32) {
|
|
| 22 |
+ var a, d uint32 |
|
| 23 |
+ gccgoXgetbv(&a, &d) |
|
| 24 |
+ return a, d |
|
| 25 |
+} |
| 0 | 26 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build mips64 mips64le |
|
| 5 |
+ |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+const cacheLineSize = 32 |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,55 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+ |
|
| 6 |
+package cpu |
|
| 7 |
+ |
|
| 8 |
+const cacheLineSize = 64 |
|
| 9 |
+ |
|
| 10 |
+func init() {
|
|
| 11 |
+ maxID, _, _, _ := cpuid(0, 0) |
|
| 12 |
+ |
|
| 13 |
+ if maxID < 1 {
|
|
| 14 |
+ return |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ _, _, ecx1, edx1 := cpuid(1, 0) |
|
| 18 |
+ X86.HasSSE2 = isSet(26, edx1) |
|
| 19 |
+ |
|
| 20 |
+ X86.HasSSE3 = isSet(0, ecx1) |
|
| 21 |
+ X86.HasPCLMULQDQ = isSet(1, ecx1) |
|
| 22 |
+ X86.HasSSSE3 = isSet(9, ecx1) |
|
| 23 |
+ X86.HasFMA = isSet(12, ecx1) |
|
| 24 |
+ X86.HasSSE41 = isSet(19, ecx1) |
|
| 25 |
+ X86.HasSSE42 = isSet(20, ecx1) |
|
| 26 |
+ X86.HasPOPCNT = isSet(23, ecx1) |
|
| 27 |
+ X86.HasAES = isSet(25, ecx1) |
|
| 28 |
+ X86.HasOSXSAVE = isSet(27, ecx1) |
|
| 29 |
+ |
|
| 30 |
+ osSupportsAVX := false |
|
| 31 |
+ // For XGETBV, OSXSAVE bit is required and sufficient. |
|
| 32 |
+ if X86.HasOSXSAVE {
|
|
| 33 |
+ eax, _ := xgetbv() |
|
| 34 |
+ // Check if XMM and YMM registers have OS support. |
|
| 35 |
+ osSupportsAVX = isSet(1, eax) && isSet(2, eax) |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ X86.HasAVX = isSet(28, ecx1) && osSupportsAVX |
|
| 39 |
+ |
|
| 40 |
+ if maxID < 7 {
|
|
| 41 |
+ return |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ _, ebx7, _, _ := cpuid(7, 0) |
|
| 45 |
+ X86.HasBMI1 = isSet(3, ebx7) |
|
| 46 |
+ X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX |
|
| 47 |
+ X86.HasBMI2 = isSet(8, ebx7) |
|
| 48 |
+ X86.HasERMS = isSet(9, ebx7) |
|
| 49 |
+ X86.HasADX = isSet(19, ebx7) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func isSet(bitpos uint, value uint32) bool {
|
|
| 53 |
+ return value&(1<<bitpos) != 0 |
|
| 54 |
+} |
| 0 | 55 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+// Copyright 2018 The Go Authors. All rights reserved. |
|
| 1 |
+// Use of this source code is governed by a BSD-style |
|
| 2 |
+// license that can be found in the LICENSE file. |
|
| 3 |
+ |
|
| 4 |
+// +build 386 amd64 amd64p32 |
|
| 5 |
+// +build !gccgo |
|
| 6 |
+ |
|
| 7 |
+#include "textflag.h" |
|
| 8 |
+ |
|
| 9 |
+// func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) |
|
| 10 |
+TEXT ·cpuid(SB), NOSPLIT, $0-24 |
|
| 11 |
+ MOVL eaxArg+0(FP), AX |
|
| 12 |
+ MOVL ecxArg+4(FP), CX |
|
| 13 |
+ CPUID |
|
| 14 |
+ MOVL AX, eax+8(FP) |
|
| 15 |
+ MOVL BX, ebx+12(FP) |
|
| 16 |
+ MOVL CX, ecx+16(FP) |
|
| 17 |
+ MOVL DX, edx+20(FP) |
|
| 18 |
+ RET |
|
| 19 |
+ |
|
| 20 |
+// func xgetbv() (eax, edx uint32) |
|
| 21 |
+TEXT ·xgetbv(SB),NOSPLIT,$0-8 |
|
| 22 |
+ MOVL $0, CX |
|
| 23 |
+ XGETBV |
|
| 24 |
+ MOVL AX, eax+0(FP) |
|
| 25 |
+ MOVL DX, edx+4(FP) |
|
| 26 |
+ RET |