thali/NextGeneration/notification/thaliNotificationBeacons.js
function generatePreambleAndBeacons (publicKeysToNotify,
                                     ecdhForLocalDevice,
                                     millisecondsUntilExpiration) {
  if (publicKeysToNotify == null) {
    throw new Error('publicKeysToNotify cannot be null');
  }

  if (ecdhForLocalDevice == null) {
    throw new Error('ecdhForLocalDevice cannot be null');
  }

  if (millisecondsUntilExpiration <= 0 ||
      millisecondsUntilExpiration > module.exports.ONE_DAY) {
    throw new Error('millisecondsUntilExpiration must be > 0 & < ' +
      module.exports.ONE_DAY);
  }

  if (publicKeysToNotify.length === 0) { return null; }

  var beacons = [];

  var ke = crypto.createECDH(thaliConfig.BEACON_CURVE);

  // Generate preamble
  var pubKe = ke.generateKeys();
  // We fuzz the expiration by adding on a random number that fits in
  // an unsigned 8 bit integer.
  var expiration = Date.now() + millisecondsUntilExpiration +
    crypto.randomBytes(1).readUInt8(0);
  var expirationLong = Long.fromNumber(expiration);
  var expirationBuffer = new Buffer(module.exports.LONG_SIZE);
  expirationBuffer.writeInt32BE(expirationLong.high, 0);
  expirationBuffer.writeInt32BE(expirationLong.low, 4);

  beacons.push(Buffer.concat([pubKe, expirationBuffer]));

  // UnencryptedKeyId = SHA256(Kx.public().encode()).first(16)
  var unencryptedKeyId =
    createPublicKeyHash(ecdhForLocalDevice.getPublicKey());

  publicKeysToNotify.forEach(function (pubKy) {
    // Sxy = ECDH(Kx.private(), PubKy)
    var sxy = ecdhForLocalDevice.computeSecret(pubKy);

    // HKxy = HKDF(SHA256, Sxy, Expiration, 32)
    var hkxy = HKDF(module.exports.SHA256, sxy, expirationBuffer)
      .derive('', module.exports.SHA256_HMAC_KEY_SIZE);

    // BeaconHmac = HMAC(SHA256, HKxy, Expiration).first(16)
    var beaconHmac = crypto.createHmac(module.exports.SHA256, hkxy)
      .update(expirationBuffer)
      .digest()
      .slice(0, module.exports.TRUNCATED_HASH_SIZE);

    // Sey = ECDH(Ke.private(), PubKy)
    var sey = ke.computeSecret(pubKy);

    // hkey = HKDF(SHA256, Sey, Expiration, 16)
    var hkey = HKDF(module.exports.SHA256, sey, expirationBuffer)
      .derive('', module.exports.AES_128_KEY_SIZE);

    // beacons.append(AESEncrypt(GCM, HKey, 0, 128, UnencryptedKeyId) +
    // BeaconHmac)
    var aes = crypto.createCipher(module.exports.GCM, hkey);

    beacons.push(Buffer.concat([
      Buffer.concat([aes.update(unencryptedKeyId), aes.final()]),
      beaconHmac
    ]));
  });

  return Buffer.concat(beacons);
}
function generatePreambleAndBeacons (publicKeysToNotify,
                                     ecdhForLocalDevice,
                                     millisecondsUntilExpiration) {
  if (publicKeysToNotify == null) {
    throw new Error('publicKeysToNotify cannot be null');
  }

  if (ecdhForLocalDevice == null) {
    throw new Error('ecdhForLocalDevice cannot be null');
  }

  if (millisecondsUntilExpiration <= 0 ||
      millisecondsUntilExpiration > module.exports.ONE_DAY) {
    throw new Error('millisecondsUntilExpiration must be > 0 & < ' +
      module.exports.ONE_DAY);
  }

  if (publicKeysToNotify.length === 0) { return null; }

  var beacons = [];

  var ke = crypto.createECDH(thaliConfig.BEACON_CURVE);

  // Generate preamble
  var pubKe = ke.generateKeys();
  // We fuzz the expiration by adding on a random number that fits in
  // an unsigned 8 bit integer.
  var expiration = Date.now() + millisecondsUntilExpiration +
    crypto.randomBytes(1).readUInt8(0);
  var expirationLong = Long.fromNumber(expiration);
  var expirationBuffer = new Buffer(module.exports.LONG_SIZE);
  expirationBuffer.writeInt32BE(expirationLong.high, 0);
  expirationBuffer.writeInt32BE(expirationLong.low, 4);

  beacons.push(Buffer.concat([pubKe, expirationBuffer]));

  // UnencryptedKeyId = SHA256(Kx.public().encode()).first(16)
  var unencryptedKeyId =
    createPublicKeyHash(ecdhForLocalDevice.getPublicKey());

  publicKeysToNotify.forEach(function (pubKy) {
    // Sxy = ECDH(Kx.private(), PubKy)
    var sxy = ecdhForLocalDevice.computeSecret(pubKy);

    // HKxy = HKDF(SHA256, Sxy, Expiration, 32)
    var hkxy = HKDF(module.exports.SHA256, sxy, expirationBuffer)
      .derive('', module.exports.SHA256_HMAC_KEY_SIZE);

    // BeaconHmac = HMAC(SHA256, HKxy, Expiration).first(16)
    var beaconHmac = crypto.createHmac(module.exports.SHA256, hkxy)
      .update(expirationBuffer)
      .digest()
      .slice(0, module.exports.TRUNCATED_HASH_SIZE);

    // Sey = ECDH(Ke.private(), PubKy)
    var sey = ke.computeSecret(pubKy);

    // hkey = HKDF(SHA256, Sey, Expiration, 16)
    var hkey = HKDF(module.exports.SHA256, sey, expirationBuffer)
      .derive('', module.exports.AES_128_KEY_SIZE);

    // beacons.append(AESEncrypt(GCM, HKey, 0, 128, UnencryptedKeyId) +
    // BeaconHmac)
    var aes = crypto.createCipher(module.exports.GCM, hkey);

    beacons.push(Buffer.concat([
      Buffer.concat([aes.update(unencryptedKeyId), aes.final()]),
      beaconHmac
    ]));
  });

  return Buffer.concat(beacons);
}
function generatePreambleAndBeacons ( publicKeysToNotify , ecdhForLocalDevice , millisecondsUntilExpiration ) { if ( publicKeysToNotify == null ) { throw new Error ( 'publicKeysToNotify cannot be null' ) ; } if ( ecdhForLocalDevice == null ) { throw new Error ( 'ecdhForLocalDevice cannot be null' ) ; } if ( millisecondsUntilExpiration <= 0 || millisecondsUntilExpiration > module . exports . ONE_DAY ) { throw new Error ( 'millisecondsUntilExpiration must be > 0 & < ' + module . exports . ONE_DAY ) ; } if ( publicKeysToNotify . length === 0 ) { return null ; } var beacons = [ ] ; var ke = crypto . createECDH ( thaliConfig . BEACON_CURVE ) ; // Generate preamble var pubKe = ke . generateKeys ( ) ; // We fuzz the expiration by adding on a random number that fits in // an unsigned 8 bit integer. var expiration = Date . now ( ) + millisecondsUntilExpiration + crypto . randomBytes ( 1 ) . readUInt8 ( 0 ) ; var expirationLong = Long . fromNumber ( expiration ) ; var expirationBuffer = new Buffer ( module . exports . LONG_SIZE ) ; expirationBuffer . writeInt32BE ( expirationLong . high , 0 ) ; expirationBuffer . writeInt32BE ( expirationLong . low , 4 ) ; beacons . push ( Buffer . concat ( [ pubKe , expirationBuffer ] ) ) ; // UnencryptedKeyId = SHA256(Kx.public().encode()).first(16) var unencryptedKeyId = createPublicKeyHash ( ecdhForLocalDevice . getPublicKey ( ) ) ; publicKeysToNotify . forEach ( function ( pubKy ) { // Sxy = ECDH(Kx.private(), PubKy) var sxy = ecdhForLocalDevice . computeSecret ( pubKy ) ; // HKxy = HKDF(SHA256, Sxy, Expiration, 32) var hkxy = HKDF ( module . exports . SHA256 , sxy , expirationBuffer ) . derive ( '' , module . exports . SHA256_HMAC_KEY_SIZE ) ; // BeaconHmac = HMAC(SHA256, HKxy, Expiration).first(16) var beaconHmac = crypto . createHmac ( module . exports . SHA256 , hkxy ) . update ( expirationBuffer ) . digest ( ) . slice ( 0 , module . exports . TRUNCATED_HASH_SIZE ) ; // Sey = ECDH(Ke.private(), PubKy) var sey = ke . computeSecret ( pubKy ) ; // hkey = HKDF(SHA256, Sey, Expiration, 16) var hkey = HKDF ( module . exports . SHA256 , sey , expirationBuffer ) . derive ( '' , module . exports . AES_128_KEY_SIZE ) ; // beacons.append(AESEncrypt(GCM, HKey, 0, 128, UnencryptedKeyId) + // BeaconHmac) var aes = crypto . createCipher ( module . exports . GCM , hkey ) ; beacons . push ( Buffer . concat ( [ Buffer . concat ( [ aes . update ( unencryptedKeyId ) , aes . final ( ) ] ) , beaconHmac ] ) ) ; } ) ; return Buffer . concat ( beacons ) ; }
This function will generate a buffer containing the notification preamble and
beacons for the given set of public keys using the supplied private key and
set to the specified seconds until expiration.
@param {Buffer[]} publicKeysToNotify - An array of buffers holding ECDH
public keys.
@param {ECDH} ecdhForLocalDevice - A Crypto.ECDH object initialized with the
local device's public and private keys
@param {number} millisecondsUntilExpiration - The number of milliseconds into
the future after which the beacons should expire. Note that this value will
be fuzzed as previously described.
@returns {?Buffer} - A buffer containing the serialized preamble and beacons
or null if there are no beacons to generate
This function will generate a buffer containing the notification preamble and beacons for the given set of public keys using the supplied private key and set to the specified seconds until expiration .