Skip to content
This repository was archived by the owner on Aug 5, 2021. It is now read-only.

Commit 8c5c6b6

Browse files
committed
Add support for numeric fingerprints
1 parent dd4e837 commit 8c5c6b6

File tree

6 files changed

+235
-1
lines changed

6 files changed

+235
-1
lines changed

Gruntfile.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ module.exports = function(grunt) {
7575
'src/SignalProtocolAddress.js',
7676
'src/SessionBuilder.js',
7777
'src/SessionCipher.js',
78-
'src/SessionLock.js'
78+
'src/SessionLock.js',
79+
'src/NumericFingerprint.js'
7980
],
8081
dest: 'dist/libsignal-protocol.js',
8182
options: {

dist/libsignal-protocol.js

+76
Original file line numberDiff line numberDiff line change
@@ -35245,6 +35245,10 @@ var Internal = Internal || {};
3524535245
});
3524635246
},
3524735247

35248+
hash: function(data) {
35249+
return crypto.subtle.digest({name: 'SHA-512'}, data);
35250+
},
35251+
3524835252
HKDF: function(input, salt, info) {
3524935253
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
3525035254
// TODO: We dont always need the third chunk, we might skip it
@@ -36431,4 +36435,76 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
3643136435

3643236436
})();
3643336437

36438+
(function() {
36439+
var VERSION = 0;
36440+
36441+
function iterateHash(data, key, count) {
36442+
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
36443+
return Internal.crypto.hash(data).then(function(result) {
36444+
if (--count === 0) {
36445+
return result;
36446+
} else {
36447+
return iterateHash(result, key, count);
36448+
}
36449+
});
36450+
}
36451+
36452+
function shortToArrayBuffer(number) {
36453+
return new Uint16Array([number]).buffer;
36454+
}
36455+
36456+
function getEncodedChunk(hash, offset) {
36457+
var chunk = ( hash[offset] * Math.pow(2,32) +
36458+
hash[offset+1] * Math.pow(2,24) +
36459+
hash[offset+2] * Math.pow(2,16) +
36460+
hash[offset+3] * Math.pow(2,8) +
36461+
hash[offset+4] ) % 100000;
36462+
var s = chunk.toString();
36463+
while (s.length < 5) {
36464+
s = '0' + s;
36465+
}
36466+
return s;
36467+
}
36468+
36469+
function getDisplayStringFor(identifier, key, iterations) {
36470+
var bytes = dcodeIO.ByteBuffer.concat([
36471+
shortToArrayBuffer(VERSION), key, identifier
36472+
]).toArrayBuffer();
36473+
return iterateHash(bytes, key, iterations).then(function(output) {
36474+
output = new Uint8Array(output);
36475+
return getEncodedChunk(output, 0) +
36476+
getEncodedChunk(output, 5) +
36477+
getEncodedChunk(output, 10) +
36478+
getEncodedChunk(output, 15) +
36479+
getEncodedChunk(output, 20) +
36480+
getEncodedChunk(output, 25);
36481+
});
36482+
}
36483+
36484+
libsignal.FingerprintGenerator = function(iterations) {
36485+
this.iterations = iterations;
36486+
};
36487+
libsignal.FingerprintGenerator.prototype = {
36488+
createFor: function(localIdentifier, localIdentityKey,
36489+
remoteIdentifier, remoteIdentityKey) {
36490+
if (typeof localIdentifier !== 'string' ||
36491+
typeof remoteIdentifier !== 'string' ||
36492+
!(localIdentityKey instanceof ArrayBuffer) ||
36493+
!(remoteIdentityKey instanceof ArrayBuffer)) {
36494+
36495+
throw new Error('Invalid arguments');
36496+
}
36497+
36498+
return Promise.all([
36499+
getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
36500+
getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
36501+
]).then(function(fingerprints) {
36502+
return fingerprints.sort().join('');
36503+
});
36504+
}
36505+
};
36506+
36507+
})();
36508+
36509+
3643436510
})();

src/NumericFingerprint.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
(function() {
2+
var VERSION = 0;
3+
4+
function iterateHash(data, key, count) {
5+
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
6+
return Internal.crypto.hash(data).then(function(result) {
7+
if (--count === 0) {
8+
return result;
9+
} else {
10+
return iterateHash(result, key, count);
11+
}
12+
});
13+
}
14+
15+
function shortToArrayBuffer(number) {
16+
return new Uint16Array([number]).buffer;
17+
}
18+
19+
function getEncodedChunk(hash, offset) {
20+
var chunk = ( hash[offset] * Math.pow(2,32) +
21+
hash[offset+1] * Math.pow(2,24) +
22+
hash[offset+2] * Math.pow(2,16) +
23+
hash[offset+3] * Math.pow(2,8) +
24+
hash[offset+4] ) % 100000;
25+
var s = chunk.toString();
26+
while (s.length < 5) {
27+
s = '0' + s;
28+
}
29+
return s;
30+
}
31+
32+
function getDisplayStringFor(identifier, key, iterations) {
33+
var bytes = dcodeIO.ByteBuffer.concat([
34+
shortToArrayBuffer(VERSION), key, identifier
35+
]).toArrayBuffer();
36+
return iterateHash(bytes, key, iterations).then(function(output) {
37+
output = new Uint8Array(output);
38+
return getEncodedChunk(output, 0) +
39+
getEncodedChunk(output, 5) +
40+
getEncodedChunk(output, 10) +
41+
getEncodedChunk(output, 15) +
42+
getEncodedChunk(output, 20) +
43+
getEncodedChunk(output, 25);
44+
});
45+
}
46+
47+
libsignal.FingerprintGenerator = function(iterations) {
48+
this.iterations = iterations;
49+
};
50+
libsignal.FingerprintGenerator.prototype = {
51+
createFor: function(localIdentifier, localIdentityKey,
52+
remoteIdentifier, remoteIdentityKey) {
53+
if (typeof localIdentifier !== 'string' ||
54+
typeof remoteIdentifier !== 'string' ||
55+
!(localIdentityKey instanceof ArrayBuffer) ||
56+
!(remoteIdentityKey instanceof ArrayBuffer)) {
57+
58+
throw new Error('Invalid arguments');
59+
}
60+
61+
return Promise.all([
62+
getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
63+
getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
64+
]).then(function(fingerprints) {
65+
return fingerprints.sort().join('');
66+
});
67+
}
68+
};
69+
70+
})();
71+

src/crypto.js

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ var Internal = Internal || {};
3535
});
3636
},
3737

38+
hash: function(data) {
39+
return crypto.subtle.digest({name: 'SHA-512'}, data);
40+
},
41+
3842
HKDF: function(input, salt, info) {
3943
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
4044
// TODO: We dont always need the third chunk, we might skip it

test/NumericFingerprintTest.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* vim: ts=4:sw=4
3+
*/
4+
5+
'use strict';
6+
describe('NumericFingerprint', function() {
7+
this.timeout(5000);
8+
var ALICE_IDENTITY = [
9+
0x05, 0x06, 0x86, 0x3b, 0xc6, 0x6d, 0x02, 0xb4, 0x0d, 0x27, 0xb8, 0xd4,
10+
0x9c, 0xa7, 0xc0, 0x9e, 0x92, 0x39, 0x23, 0x6f, 0x9d, 0x7d, 0x25, 0xd6,
11+
0xfc, 0xca, 0x5c, 0xe1, 0x3c, 0x70, 0x64, 0xd8, 0x68
12+
];
13+
var BOB_IDENTITY = [
14+
0x05, 0xf7, 0x81, 0xb6, 0xfb, 0x32, 0xfe, 0xd9, 0xba, 0x1c, 0xf2, 0xde,
15+
0x97, 0x8d, 0x4d, 0x5d, 0xa2, 0x8d, 0xc3, 0x40, 0x46, 0xae, 0x81, 0x44,
16+
0x02, 0xb5, 0xc0, 0xdb, 0xd9, 0x6f, 0xda, 0x90, 0x7b
17+
];
18+
var FINGERPRINT = "300354477692869396892869876765458257569162576843440918079131";
19+
20+
var alice = {
21+
identifier: '+14152222222',
22+
key: new Uint8Array(ALICE_IDENTITY).buffer
23+
};
24+
var bob = {
25+
identifier: '+14153333333',
26+
key: new Uint8Array(BOB_IDENTITY).buffer
27+
};
28+
29+
it('returns the correct fingerprint', function(done) {
30+
var generator = new libsignal.FingerprintGenerator(5200);
31+
generator.createFor(
32+
alice.identifier, alice.key, bob.identifier, bob.key
33+
).then(function(fingerprint) {
34+
assert.strictEqual(fingerprint, FINGERPRINT);
35+
}).then(done,done);
36+
});
37+
38+
it ('alice and bob results match', function(done) {
39+
var generator = new libsignal.FingerprintGenerator(1024);
40+
Promise.all([
41+
generator.createFor(
42+
alice.identifier, alice.key, bob.identifier, bob.key
43+
),
44+
generator.createFor(
45+
bob.identifier, bob.key, alice.identifier, alice.key
46+
)
47+
]).then(function(fingerprints) {
48+
assert.strictEqual(fingerprints[0], fingerprints[1]);
49+
}).then(done,done);
50+
});
51+
52+
it ('alice and !bob results mismatch', function(done) {
53+
var generator = new libsignal.FingerprintGenerator(1024);
54+
Promise.all([
55+
generator.createFor(
56+
alice.identifier, alice.key, '+15558675309', bob.key
57+
),
58+
generator.createFor(
59+
bob.identifier, bob.key, alice.identifier, alice.key
60+
)
61+
]).then(function(fingerprints) {
62+
assert.notStrictEqual(fingerprints[0], fingerprints[1]);
63+
}).then(done,done);
64+
});
65+
66+
it ('alice and mitm results mismatch', function(done) {
67+
var mitm = libsignal.crypto.getRandomBytes(33);
68+
var generator = new libsignal.FingerprintGenerator(1024);
69+
Promise.all([
70+
generator.createFor(
71+
alice.identifier, alice.key, bob.identifier, mitm
72+
),
73+
generator.createFor(
74+
bob.identifier, bob.key, alice.identifier, alice.key
75+
)
76+
]).then(function(fingerprints) {
77+
assert.notStrictEqual(fingerprints[0], fingerprints[1]);
78+
}).then(done,done);
79+
});
80+
});

test/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
<script type="text/javascript" src="../build/protobufs_concat.js" data-cover></script>
2929
<script type="text/javascript" src="../src/SessionRecord.js" data-cover></script>
3030
<script type="text/javascript" src="../src/SessionLock.js" data-cover></script>
31+
<script type="text/javascript" src="../src/NumericFingerprint.js" data-cover></script>
3132

3233
<script type="text/javascript" src="InMemorySignalProtocolStore.js"></script>
3334
<script type="text/javascript" src="crypto_test.js"></script>
3435
<script type="text/javascript" src="testvectors.js"></script>
3536
<script type="text/javascript" src="SessionCipherTest.js"></script>
3637
<script type="text/javascript" src="KeyHelperTest.js"></script>
38+
<script type="text/javascript" src="NumericFingerprintTest.js"></script>
3739

3840
<script type="text/javascript" src="SessionBuilderTest.js"></script>
3941

0 commit comments

Comments
 (0)