Pairing authorisations: refactor proto, change sql table, add getters

pull/427/head
sachaaaaa 6 years ago
parent 33d789b688
commit a4411007b0

@ -73,6 +73,8 @@ module.exports = {
removeAllContactSignedPreKeys, removeAllContactSignedPreKeys,
createOrUpdatePairingAuthorisation, createOrUpdatePairingAuthorisation,
getAuthorisationForPubKey,
getSecondaryDevicesFor,
createOrUpdateItem, createOrUpdateItem,
getItemById, getItemById,
@ -787,13 +789,17 @@ async function updateToLokiSchemaVersion2(currentVersion, instance) {
await instance.run( await instance.run(
`CREATE TABLE pairingAuthorisations( `CREATE TABLE pairingAuthorisations(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
issuerPubKey VARCHAR(255), primaryDevicePubKey VARCHAR(255),
secondaryDevicePubKey VARCHAR(255), secondaryDevicePubKey VARCHAR(255),
signature VARCHAR(255), isGranted BOOLEAN,
json TEXT json TEXT
);` );`
); );
await instance.run(`CREATE UNIQUE INDEX pairing_authorisations_secondary_device_pubkey ON pairingAuthorisations (
secondaryDevicePubKey
);`);
await instance.run( await instance.run(
`INSERT INTO loki_schema ( `INSERT INTO loki_schema (
version version
@ -1223,14 +1229,16 @@ async function removeAllSignedPreKeys() {
} }
const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations'; const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations';
async function getPairingAuthorisation(issuerPubKey, secondaryDevicePubKey) { async function getAuthorisationForPubKey(pubKey, options) {
const granted = options && options.granted;
let filter = '';
if (granted) {
filter = 'AND isGranted = 1';
}
const row = await db.get( const row = await db.get(
`SELECT * FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey ${filter};`,
issuerPubKey = $issuerPubKey AND secondaryDevicePubKey = $secondaryDevicePubKey
LIMIT 1;`,
{ {
$issuerPubKey: issuerPubKey, $secondaryDevicePubKey: pubKey,
$secondaryDevicePubKey: secondaryDevicePubKey,
} }
); );
@ -1240,39 +1248,41 @@ async function getPairingAuthorisation(issuerPubKey, secondaryDevicePubKey) {
return jsonToObject(row.json); return jsonToObject(row.json);
} }
async function createOrUpdatePairingAuthorisation(data) {
const { issuerPubKey, secondaryDevicePubKey, signature } = data;
const existing = await getPairingAuthorisation( async function createOrUpdatePairingAuthorisation(data) {
issuerPubKey, const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data;
secondaryDevicePubKey
);
// prevent adding duplicate entries
if (existing) {
return;
}
await db.run( await db.run(
`INSERT INTO ${PAIRING_AUTHORISATIONS_TABLE} ( `INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} (
issuerPubKey, primaryDevicePubKey,
secondaryDevicePubKey, secondaryDevicePubKey,
signature, isGranted,
json json
) values ( ) values (
$issuerPubKey, $primaryDevicePubKey,
$secondaryDevicePubKey, $secondaryDevicePubKey,
$signature, $isGranted,
$json $json
)`, )`,
{ {
$issuerPubKey: issuerPubKey, $primaryDevicePubKey: primaryDevicePubKey,
$secondaryDevicePubKey: secondaryDevicePubKey, $secondaryDevicePubKey: secondaryDevicePubKey,
$signature: signature, $isGranted: Boolean(grantSignature),
$json: objectToJSON(data), $json: objectToJSON(data),
} }
); );
} }
async function getSecondaryDevicesFor(primaryDevicePubKey) {
const rows = await db.all(
`SELECT secondaryDevicePubKey FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey AND isGranted = 1 ORDER BY secondaryDevicePubKey ASC;`,
{
$primaryDevicePubKey: primaryDevicePubKey,
}
);
return map(rows, row => row.secondaryDevicePubKey);
}
const ITEMS_TABLE = 'items'; const ITEMS_TABLE = 'items';
async function createOrUpdateItem(data) { async function createOrUpdateItem(data) {
return createOrUpdate(ITEMS_TABLE, data); return createOrUpdate(ITEMS_TABLE, data);

@ -1,4 +1,4 @@
/* global window, setTimeout, IDBKeyRange */ /* global window, setTimeout, IDBKeyRange, dcodeIO */
const electron = require('electron'); const electron = require('electron');
@ -90,6 +90,9 @@ module.exports = {
removeAllContactSignedPreKeys, removeAllContactSignedPreKeys,
createOrUpdatePairingAuthorisation, createOrUpdatePairingAuthorisation,
getGrantAuthorisationForPubKey,
getAuthorisationForPubKey,
getSecondaryDevicesFor,
createOrUpdateItem, createOrUpdateItem,
getItemById, getItemById,
@ -573,21 +576,57 @@ async function removeAllContactSignedPreKeys() {
await channels.removeAllContactSignedPreKeys(); await channels.removeAllContactSignedPreKeys();
} }
async function createOrUpdatePairingAuthorisation(data) { function signatureToBase64(signature) {
let sig; if (signature.constructor === dcodeIO.ByteBuffer) {
if (isArrayBuffer(data.signature)) { return dcodeIO.ByteBuffer.wrap(signature).toString('base64');
sig = arrayBufferToBase64(data.signature); } else if (isArrayBuffer(signature)) {
} else if (typeof signature === 'string') { return arrayBufferToBase64(signature);
sig = data.signature;
} else {
throw new Error(
'Invalid signature provided in createOrUpdatePairingAuthorisation. Needs to be either ArrayBuffer or string.'
);
} }
throw new Error(
'Invalid signature provided in createOrUpdatePairingAuthorisation. Needs to be either ArrayBuffer or ByteBuffer.'
);
}
async function createOrUpdatePairingAuthorisation(data) {
const { requestSignature, grantSignature } = data;
return channels.createOrUpdatePairingAuthorisation({ return channels.createOrUpdatePairingAuthorisation({
...data, ...data,
signature: sig, requestSignature: signatureToBase64(requestSignature),
grantSignature: grantSignature ? signatureToBase64(grantSignature) : null,
});
}
async function getGrantAuthorisationForPubKey(pubKey) {
const authorisation = await channels.getAuthorisationForPubKey(pubKey, {
granted: true,
}); });
if (!authorisation) {
return null;
}
return {
...authorisation,
requestSignature: base64ToArrayBuffer(authorisation.requestSignature),
grantSignature: base64ToArrayBuffer(authorisation.grantSignature),
};
}
async function getAuthorisationForPubKey(pubKey) {
const authorisation = await channels.getAuthorisationForPubKey(pubKey);
if (!authorisation) {
return null;
}
return {
...authorisation,
requestSignature: base64ToArrayBuffer(authorisation.requestSignature),
grantSignature: authorisation.grantSignature
? base64ToArrayBuffer(authorisation.grantSignature)
: null,
};
}
function getSecondaryDevicesFor(primareyDevicePubKey) {
return channels.getSecondaryDevicesFor(primareyDevicePubKey);
} }
// Items // Items

@ -63,30 +63,70 @@
await outgoingMessage.sendToNumber(pubKey); await outgoingMessage.sendToNumber(pubKey);
} }
async function sendPairingAuthorisation(secondaryDevicePubKey, signature) { function createPairingAuthorisationProtoMessage({
const pairingAuthorisation = new textsecure.protobuf.PairingAuthorisationMessage( primaryDevicePubKey,
{ secondaryDevicePubKey,
signature, requestSignature,
primaryDevicePubKey: textsecure.storage.user.getNumber(), grantSignature,
secondaryDevicePubKey, type,
type: }) {
textsecure.protobuf.PairingAuthorisationMessage.Type.PAIRING_REQUEST, if (
} !primaryDevicePubKey ||
!secondaryDevicePubKey ||
type === undefined ||
type === null
) {
throw new Error(
'createPairingAuthorisationProtoMessage: pubkeys or type is not set'
);
}
if (requestSignature.constructor !== ArrayBuffer) {
throw new Error(
'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer'
);
}
if (grantSignature && grantSignature.constructor !== ArrayBuffer) {
throw new Error(
'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer'
);
}
return new textsecure.protobuf.PairingAuthorisationMessage({
requestSignature: new Uint8Array(requestSignature),
grantSignature: grantSignature ? new Uint8Array(grantSignature) : null,
primaryDevicePubKey,
secondaryDevicePubKey,
type,
});
}
async function sendPairingAuthorisation(authorisation, recipientPubKey) {
const pairingAuthorisation = createPairingAuthorisationProtoMessage(
authorisation
); );
const content = new textsecure.protobuf.Content({ const content = new textsecure.protobuf.Content({
pairingAuthorisation, pairingAuthorisation,
}); });
const options = {}; const options = {};
const outgoingMessage = new textsecure.OutgoingMessage( const p = new Promise((resolve, reject) => {
null, // server const outgoingMessage = new textsecure.OutgoingMessage(
Date.now(), // timestamp, null, // server
[secondaryDevicePubKey], // numbers Date.now(), // timestamp,
content, // message [recipientPubKey], // numbers
true, // silent content, // message
() => null, // callback true, // silent
options result => {
); // callback
await outgoingMessage.sendToNumber(secondaryDevicePubKey); if (result.errors.length > 0) {
reject(result.errors[0]);
} else {
resolve();
}
},
options
);
outgoingMessage.sendToNumber(recipientPubKey);
});
return p;
} }
window.libloki.api = { window.libloki.api = {
@ -94,5 +134,6 @@
sendOnlineBroadcastMessage, sendOnlineBroadcastMessage,
broadcastOnlineStatus, broadcastOnlineStatus,
sendPairingAuthorisation, sendPairingAuthorisation,
createPairingAuthorisationProtoMessage,
}; };
})(); })();

@ -171,32 +171,38 @@
myKeyPair.privKey, myKeyPair.privKey,
data.buffer data.buffer
); );
return new Uint8Array(signature); return signature;
} }
async function verifyPairingAuthorisation( async function verifyPairingAuthorisation(
issuerPubKey, primaryDevicePubKey,
secondaryPubKey, secondaryPubKey,
signature, signature,
type type
) { ) {
const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); const secondaryPubKeyArrayBuffer = StringView.hexToArrayBuffer(
if (StringView.arrayBufferToHex(myKeyPair.pubKey) !== secondaryPubKey) { secondaryPubKey
throw new Error( );
'Invalid pairing authorisation: we are not the recipient of the authorisation!' const primaryDevicePubKeyArrayBuffer = StringView.hexToArrayBuffer(
); primaryDevicePubKey
} );
const len = myKeyPair.pubKey.byteLength; const len = secondaryPubKeyArrayBuffer.byteLength;
const data = new Uint8Array(len + 1); const data = new Uint8Array(len + 1);
data.set(new Uint8Array(myKeyPair.pubKey), 0); // For REQUEST type message, the secondary device signs the primary device pubkey
// For GRANT type message, the primary device signs the secondary device pubkey
let issuer;
if (type === textsecure.protobuf.PairingAuthorisationMessage.Type.GRANT) {
data.set(new Uint8Array(secondaryPubKeyArrayBuffer));
issuer = primaryDevicePubKeyArrayBuffer;
} else if (
type === textsecure.protobuf.PairingAuthorisationMessage.Type.REQUEST
) {
data.set(new Uint8Array(primaryDevicePubKeyArrayBuffer));
issuer = secondaryPubKeyArrayBuffer;
}
data[len] = type; data[len] = type;
const issuerPubKeyArrayBuffer = StringView.hexToArrayBuffer(issuerPubKey);
// Throws for invalid signature // Throws for invalid signature
await libsignal.Curve.async.verifySignature( await libsignal.Curve.async.verifySignature(issuer, data.buffer, signature);
issuerPubKeyArrayBuffer,
data.buffer,
signature
);
} }
const snodeCipher = new LokiSnodeChannel(); const snodeCipher = new LokiSnodeChannel();

@ -113,16 +113,33 @@
} }
} }
async function savePairingAuthorisation( function savePairingAuthorisation({
issuerPubKey, primaryDevicePubKey,
secondaryDevicePubKey, secondaryDevicePubKey,
signature requestSignature,
) { grantSignature,
return textsecure.storage.protocol.storePairingAuthorisation( }) {
issuerPubKey, return window.Signal.Data.createOrUpdatePairingAuthorisation({
primaryDevicePubKey,
secondaryDevicePubKey, secondaryDevicePubKey,
signature requestSignature,
); grantSignature,
});
}
function getGrantAuthorisationForSecondaryPubKey(secondaryPubKey) {
return window.Signal.Data.getGrantAuthorisationForPubKey(secondaryPubKey);
}
function getAuthorisationForSecondaryPubKey(secondaryPubKey) {
return window.Signal.Data.getAuthorisationForPubKey(secondaryPubKey);
}
async function getAllDevicePubKeysForPrimaryPubKey(primaryDevicePubKey) {
const secondaryPubKeys =
(await window.Signal.Data.getSecondaryDevicesFor(primaryDevicePubKey)) ||
[];
return secondaryPubKeys.concat(primaryDevicePubKey);
} }
window.libloki.storage = { window.libloki.storage = {
@ -131,6 +148,9 @@
removeContactPreKeyBundle, removeContactPreKeyBundle,
verifyFriendRequestAcceptPreKey, verifyFriendRequestAcceptPreKey,
savePairingAuthorisation, savePairingAuthorisation,
getGrantAuthorisationForSecondaryPubKey,
getAuthorisationForSecondaryPubKey,
getAllDevicePubKeysForPrimaryPubKey,
}; };
// Libloki protocol store // Libloki protocol store
@ -256,15 +276,4 @@
store.clearContactSignedPreKeysStore = async () => { store.clearContactSignedPreKeysStore = async () => {
await window.Signal.Data.removeAllContactSignedPreKeys(); await window.Signal.Data.removeAllContactSignedPreKeys();
}; };
store.storePairingAuthorisation = (
issuerPubKey,
secondaryDevicePubKey,
signature
) =>
window.Signal.Data.createOrUpdatePairingAuthorisation({
issuerPubKey,
secondaryDevicePubKey,
signature,
});
})(); })();

@ -51,13 +51,15 @@ message LokiAddressMessage {
message PairingAuthorisationMessage { message PairingAuthorisationMessage {
enum Type { enum Type {
PAIRING_REQUEST = 1; REQUEST = 1;
UNPAIRING_REQUEST = 2; GRANT = 2;
REVOKE = 3;
} }
optional string primaryDevicePubKey = 1; optional string primaryDevicePubKey = 1;
optional string secondaryDevicePubKey = 2; optional string secondaryDevicePubKey = 2;
optional bytes signature = 3; optional bytes requestSignature = 3;
optional Type type = 4; optional bytes grantSignature = 4;
optional Type type = 5;
} }
message PreKeyBundleMessage { message PreKeyBundleMessage {

Loading…
Cancel
Save