Add support for numeric fingerprints
This commit is contained in:
parent
dd4e8375cd
commit
8c5c6b6bb0
@ -75,7 +75,8 @@ module.exports = function(grunt) {
|
||||
'src/SignalProtocolAddress.js',
|
||||
'src/SessionBuilder.js',
|
||||
'src/SessionCipher.js',
|
||||
'src/SessionLock.js'
|
||||
'src/SessionLock.js',
|
||||
'src/NumericFingerprint.js'
|
||||
],
|
||||
dest: 'dist/libsignal-protocol.js',
|
||||
options: {
|
||||
|
||||
76
dist/libsignal-protocol.js
vendored
76
dist/libsignal-protocol.js
vendored
@ -35245,6 +35245,10 @@ var Internal = Internal || {};
|
||||
});
|
||||
},
|
||||
|
||||
hash: function(data) {
|
||||
return crypto.subtle.digest({name: 'SHA-512'}, data);
|
||||
},
|
||||
|
||||
HKDF: function(input, salt, info) {
|
||||
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
|
||||
// TODO: We dont always need the third chunk, we might skip it
|
||||
@ -36431,4 +36435,76 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
|
||||
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var VERSION = 0;
|
||||
|
||||
function iterateHash(data, key, count) {
|
||||
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
|
||||
return Internal.crypto.hash(data).then(function(result) {
|
||||
if (--count === 0) {
|
||||
return result;
|
||||
} else {
|
||||
return iterateHash(result, key, count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shortToArrayBuffer(number) {
|
||||
return new Uint16Array([number]).buffer;
|
||||
}
|
||||
|
||||
function getEncodedChunk(hash, offset) {
|
||||
var chunk = ( hash[offset] * Math.pow(2,32) +
|
||||
hash[offset+1] * Math.pow(2,24) +
|
||||
hash[offset+2] * Math.pow(2,16) +
|
||||
hash[offset+3] * Math.pow(2,8) +
|
||||
hash[offset+4] ) % 100000;
|
||||
var s = chunk.toString();
|
||||
while (s.length < 5) {
|
||||
s = '0' + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function getDisplayStringFor(identifier, key, iterations) {
|
||||
var bytes = dcodeIO.ByteBuffer.concat([
|
||||
shortToArrayBuffer(VERSION), key, identifier
|
||||
]).toArrayBuffer();
|
||||
return iterateHash(bytes, key, iterations).then(function(output) {
|
||||
output = new Uint8Array(output);
|
||||
return getEncodedChunk(output, 0) +
|
||||
getEncodedChunk(output, 5) +
|
||||
getEncodedChunk(output, 10) +
|
||||
getEncodedChunk(output, 15) +
|
||||
getEncodedChunk(output, 20) +
|
||||
getEncodedChunk(output, 25);
|
||||
});
|
||||
}
|
||||
|
||||
libsignal.FingerprintGenerator = function(iterations) {
|
||||
this.iterations = iterations;
|
||||
};
|
||||
libsignal.FingerprintGenerator.prototype = {
|
||||
createFor: function(localIdentifier, localIdentityKey,
|
||||
remoteIdentifier, remoteIdentityKey) {
|
||||
if (typeof localIdentifier !== 'string' ||
|
||||
typeof remoteIdentifier !== 'string' ||
|
||||
!(localIdentityKey instanceof ArrayBuffer) ||
|
||||
!(remoteIdentityKey instanceof ArrayBuffer)) {
|
||||
|
||||
throw new Error('Invalid arguments');
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
|
||||
getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
|
||||
]).then(function(fingerprints) {
|
||||
return fingerprints.sort().join('');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
||||
})();
|
||||
71
src/NumericFingerprint.js
Normal file
71
src/NumericFingerprint.js
Normal file
@ -0,0 +1,71 @@
|
||||
(function() {
|
||||
var VERSION = 0;
|
||||
|
||||
function iterateHash(data, key, count) {
|
||||
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
|
||||
return Internal.crypto.hash(data).then(function(result) {
|
||||
if (--count === 0) {
|
||||
return result;
|
||||
} else {
|
||||
return iterateHash(result, key, count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shortToArrayBuffer(number) {
|
||||
return new Uint16Array([number]).buffer;
|
||||
}
|
||||
|
||||
function getEncodedChunk(hash, offset) {
|
||||
var chunk = ( hash[offset] * Math.pow(2,32) +
|
||||
hash[offset+1] * Math.pow(2,24) +
|
||||
hash[offset+2] * Math.pow(2,16) +
|
||||
hash[offset+3] * Math.pow(2,8) +
|
||||
hash[offset+4] ) % 100000;
|
||||
var s = chunk.toString();
|
||||
while (s.length < 5) {
|
||||
s = '0' + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function getDisplayStringFor(identifier, key, iterations) {
|
||||
var bytes = dcodeIO.ByteBuffer.concat([
|
||||
shortToArrayBuffer(VERSION), key, identifier
|
||||
]).toArrayBuffer();
|
||||
return iterateHash(bytes, key, iterations).then(function(output) {
|
||||
output = new Uint8Array(output);
|
||||
return getEncodedChunk(output, 0) +
|
||||
getEncodedChunk(output, 5) +
|
||||
getEncodedChunk(output, 10) +
|
||||
getEncodedChunk(output, 15) +
|
||||
getEncodedChunk(output, 20) +
|
||||
getEncodedChunk(output, 25);
|
||||
});
|
||||
}
|
||||
|
||||
libsignal.FingerprintGenerator = function(iterations) {
|
||||
this.iterations = iterations;
|
||||
};
|
||||
libsignal.FingerprintGenerator.prototype = {
|
||||
createFor: function(localIdentifier, localIdentityKey,
|
||||
remoteIdentifier, remoteIdentityKey) {
|
||||
if (typeof localIdentifier !== 'string' ||
|
||||
typeof remoteIdentifier !== 'string' ||
|
||||
!(localIdentityKey instanceof ArrayBuffer) ||
|
||||
!(remoteIdentityKey instanceof ArrayBuffer)) {
|
||||
|
||||
throw new Error('Invalid arguments');
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
getDisplayStringFor(localIdentifier, localIdentityKey, this.iterations),
|
||||
getDisplayStringFor(remoteIdentifier, remoteIdentityKey, this.iterations)
|
||||
]).then(function(fingerprints) {
|
||||
return fingerprints.sort().join('');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@ -35,6 +35,10 @@ var Internal = Internal || {};
|
||||
});
|
||||
},
|
||||
|
||||
hash: function(data) {
|
||||
return crypto.subtle.digest({name: 'SHA-512'}, data);
|
||||
},
|
||||
|
||||
HKDF: function(input, salt, info) {
|
||||
// Specific implementation of RFC 5869 that only returns the first 3 32-byte chunks
|
||||
// TODO: We dont always need the third chunk, we might skip it
|
||||
|
||||
80
test/NumericFingerprintTest.js
Normal file
80
test/NumericFingerprintTest.js
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* vim: ts=4:sw=4
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
describe('NumericFingerprint', function() {
|
||||
this.timeout(5000);
|
||||
var ALICE_IDENTITY = [
|
||||
0x05, 0x06, 0x86, 0x3b, 0xc6, 0x6d, 0x02, 0xb4, 0x0d, 0x27, 0xb8, 0xd4,
|
||||
0x9c, 0xa7, 0xc0, 0x9e, 0x92, 0x39, 0x23, 0x6f, 0x9d, 0x7d, 0x25, 0xd6,
|
||||
0xfc, 0xca, 0x5c, 0xe1, 0x3c, 0x70, 0x64, 0xd8, 0x68
|
||||
];
|
||||
var BOB_IDENTITY = [
|
||||
0x05, 0xf7, 0x81, 0xb6, 0xfb, 0x32, 0xfe, 0xd9, 0xba, 0x1c, 0xf2, 0xde,
|
||||
0x97, 0x8d, 0x4d, 0x5d, 0xa2, 0x8d, 0xc3, 0x40, 0x46, 0xae, 0x81, 0x44,
|
||||
0x02, 0xb5, 0xc0, 0xdb, 0xd9, 0x6f, 0xda, 0x90, 0x7b
|
||||
];
|
||||
var FINGERPRINT = "300354477692869396892869876765458257569162576843440918079131";
|
||||
|
||||
var alice = {
|
||||
identifier: '+14152222222',
|
||||
key: new Uint8Array(ALICE_IDENTITY).buffer
|
||||
};
|
||||
var bob = {
|
||||
identifier: '+14153333333',
|
||||
key: new Uint8Array(BOB_IDENTITY).buffer
|
||||
};
|
||||
|
||||
it('returns the correct fingerprint', function(done) {
|
||||
var generator = new libsignal.FingerprintGenerator(5200);
|
||||
generator.createFor(
|
||||
alice.identifier, alice.key, bob.identifier, bob.key
|
||||
).then(function(fingerprint) {
|
||||
assert.strictEqual(fingerprint, FINGERPRINT);
|
||||
}).then(done,done);
|
||||
});
|
||||
|
||||
it ('alice and bob results match', function(done) {
|
||||
var generator = new libsignal.FingerprintGenerator(1024);
|
||||
Promise.all([
|
||||
generator.createFor(
|
||||
alice.identifier, alice.key, bob.identifier, bob.key
|
||||
),
|
||||
generator.createFor(
|
||||
bob.identifier, bob.key, alice.identifier, alice.key
|
||||
)
|
||||
]).then(function(fingerprints) {
|
||||
assert.strictEqual(fingerprints[0], fingerprints[1]);
|
||||
}).then(done,done);
|
||||
});
|
||||
|
||||
it ('alice and !bob results mismatch', function(done) {
|
||||
var generator = new libsignal.FingerprintGenerator(1024);
|
||||
Promise.all([
|
||||
generator.createFor(
|
||||
alice.identifier, alice.key, '+15558675309', bob.key
|
||||
),
|
||||
generator.createFor(
|
||||
bob.identifier, bob.key, alice.identifier, alice.key
|
||||
)
|
||||
]).then(function(fingerprints) {
|
||||
assert.notStrictEqual(fingerprints[0], fingerprints[1]);
|
||||
}).then(done,done);
|
||||
});
|
||||
|
||||
it ('alice and mitm results mismatch', function(done) {
|
||||
var mitm = libsignal.crypto.getRandomBytes(33);
|
||||
var generator = new libsignal.FingerprintGenerator(1024);
|
||||
Promise.all([
|
||||
generator.createFor(
|
||||
alice.identifier, alice.key, bob.identifier, mitm
|
||||
),
|
||||
generator.createFor(
|
||||
bob.identifier, bob.key, alice.identifier, alice.key
|
||||
)
|
||||
]).then(function(fingerprints) {
|
||||
assert.notStrictEqual(fingerprints[0], fingerprints[1]);
|
||||
}).then(done,done);
|
||||
});
|
||||
});
|
||||
@ -28,12 +28,14 @@
|
||||
<script type="text/javascript" src="../build/protobufs_concat.js" data-cover></script>
|
||||
<script type="text/javascript" src="../src/SessionRecord.js" data-cover></script>
|
||||
<script type="text/javascript" src="../src/SessionLock.js" data-cover></script>
|
||||
<script type="text/javascript" src="../src/NumericFingerprint.js" data-cover></script>
|
||||
|
||||
<script type="text/javascript" src="InMemorySignalProtocolStore.js"></script>
|
||||
<script type="text/javascript" src="crypto_test.js"></script>
|
||||
<script type="text/javascript" src="testvectors.js"></script>
|
||||
<script type="text/javascript" src="SessionCipherTest.js"></script>
|
||||
<script type="text/javascript" src="KeyHelperTest.js"></script>
|
||||
<script type="text/javascript" src="NumericFingerprintTest.js"></script>
|
||||
|
||||
<script type="text/javascript" src="SessionBuilderTest.js"></script>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user