You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			580 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			580 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
| /* global libsignal, textsecure */
 | |
| 
 | |
| /* eslint-disable no-bitwise */
 | |
| 
 | |
| const CiphertextMessage = require('./CiphertextMessage');
 | |
| const {
 | |
|   bytesFromString,
 | |
|   concatenateBytes,
 | |
|   constantTimeEqual,
 | |
|   decryptAesCtr,
 | |
|   encryptAesCtr,
 | |
|   fromEncodedBinaryToArrayBuffer,
 | |
|   getViewOfArrayBuffer,
 | |
|   getZeroes,
 | |
|   highBitsToInt,
 | |
|   hmacSha256,
 | |
|   intsToByteHighAndLow,
 | |
|   splitBytes,
 | |
|   trimBytes,
 | |
| } = require('../crypto');
 | |
| 
 | |
| const REVOKED_CERTIFICATES = [];
 | |
| 
 | |
| function SecretSessionCipher(storage) {
 | |
|   this.storage = storage;
 | |
| 
 | |
|   // We do this on construction because libsignal won't be available when this file loads
 | |
|   const { SessionCipher } = libsignal;
 | |
|   this.SessionCipher = SessionCipher;
 | |
| }
 | |
| 
 | |
| const CIPHERTEXT_VERSION = 1;
 | |
| const UNIDENTIFIED_DELIVERY_PREFIX = 'UnidentifiedDelivery';
 | |
| 
 | |
| // public CertificateValidator(ECPublicKey trustRoot)
 | |
| function createCertificateValidator(trustRoot) {
 | |
|   return {
 | |
|     // public void validate(SenderCertificate certificate, long validationTime)
 | |
|     async validate(certificate, validationTime) {
 | |
|       const serverCertificate = certificate.signer;
 | |
| 
 | |
|       await libsignal.Curve.async.verifySignature(
 | |
|         trustRoot,
 | |
|         serverCertificate.certificate,
 | |
|         serverCertificate.signature
 | |
|       );
 | |
| 
 | |
|       const serverCertId = serverCertificate.certificate.id;
 | |
|       if (REVOKED_CERTIFICATES.includes(serverCertId)) {
 | |
|         throw new Error(
 | |
|           `Server certificate id ${serverCertId} has been revoked`
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       await libsignal.Curve.async.verifySignature(
 | |
|         serverCertificate.key,
 | |
|         certificate.certificate,
 | |
|         certificate.signature
 | |
|       );
 | |
| 
 | |
|       if (validationTime > certificate.expires) {
 | |
|         throw new Error('Certificate is expired');
 | |
|       }
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| function _decodePoint(serialized, offset = 0) {
 | |
|   const view =
 | |
|     offset > 0
 | |
|       ? getViewOfArrayBuffer(serialized, offset, serialized.byteLength)
 | |
|       : serialized;
 | |
| 
 | |
|   return libsignal.Curve.validatePubKeyFormat(view);
 | |
| }
 | |
| 
 | |
| // public ServerCertificate(byte[] serialized)
 | |
| function _createServerCertificateFromBuffer(serialized) {
 | |
|   const wrapper = textsecure.protobuf.ServerCertificate.decode(serialized);
 | |
| 
 | |
|   if (!wrapper.certificate || !wrapper.signature) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   const certificate = textsecure.protobuf.ServerCertificate.Certificate.decode(
 | |
|     wrapper.certificate.toArrayBuffer()
 | |
|   );
 | |
| 
 | |
|   if (!certificate.id || !certificate.key) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     id: certificate.id,
 | |
|     key: certificate.key.toArrayBuffer(),
 | |
|     serialized,
 | |
|     certificate: wrapper.certificate.toArrayBuffer(),
 | |
| 
 | |
|     signature: wrapper.signature.toArrayBuffer(),
 | |
|   };
 | |
| }
 | |
| 
 | |
| // public SenderCertificate(byte[] serialized)
 | |
| function _createSenderCertificateFromBuffer(serialized) {
 | |
|   const wrapper = textsecure.protobuf.SenderCertificate.decode(serialized);
 | |
| 
 | |
|   if (!wrapper.signature || !wrapper.certificate) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   const certificate = textsecure.protobuf.SenderCertificate.Certificate.decode(
 | |
|     wrapper.certificate.toArrayBuffer()
 | |
|   );
 | |
| 
 | |
|   if (
 | |
|     !certificate.signer ||
 | |
|     !certificate.identityKey ||
 | |
|     !certificate.senderDevice ||
 | |
|     !certificate.expires ||
 | |
|     !certificate.sender
 | |
|   ) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     sender: certificate.sender,
 | |
|     senderDevice: certificate.senderDevice,
 | |
|     expires: certificate.expires.toNumber(),
 | |
|     identityKey: certificate.identityKey.toArrayBuffer(),
 | |
|     signer: _createServerCertificateFromBuffer(
 | |
|       certificate.signer.toArrayBuffer()
 | |
|     ),
 | |
| 
 | |
|     certificate: wrapper.certificate.toArrayBuffer(),
 | |
|     signature: wrapper.signature.toArrayBuffer(),
 | |
| 
 | |
|     serialized,
 | |
|   };
 | |
| }
 | |
| 
 | |
| // public UnidentifiedSenderMessage(byte[] serialized)
 | |
| function _createUnidentifiedSenderMessageFromBuffer(serialized) {
 | |
|   const version = highBitsToInt(serialized[0]);
 | |
| 
 | |
|   if (version > CIPHERTEXT_VERSION) {
 | |
|     throw new Error(`Unknown version: ${this.version}`);
 | |
|   }
 | |
| 
 | |
|   const view = getViewOfArrayBuffer(serialized, 1, serialized.byteLength);
 | |
|   const unidentifiedSenderMessage = textsecure.protobuf.UnidentifiedSenderMessage.decode(
 | |
|     view
 | |
|   );
 | |
| 
 | |
|   if (
 | |
|     !unidentifiedSenderMessage.ephemeralPublic ||
 | |
|     !unidentifiedSenderMessage.encryptedStatic ||
 | |
|     !unidentifiedSenderMessage.encryptedMessage
 | |
|   ) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     version,
 | |
| 
 | |
|     ephemeralPublic: unidentifiedSenderMessage.ephemeralPublic.toArrayBuffer(),
 | |
|     encryptedStatic: unidentifiedSenderMessage.encryptedStatic.toArrayBuffer(),
 | |
|     encryptedMessage: unidentifiedSenderMessage.encryptedMessage.toArrayBuffer(),
 | |
| 
 | |
|     serialized,
 | |
|   };
 | |
| }
 | |
| 
 | |
| // public UnidentifiedSenderMessage(
 | |
| //   ECPublicKey ephemeral, byte[] encryptedStatic, byte[] encryptedMessage) {
 | |
| function _createUnidentifiedSenderMessage(
 | |
|   ephemeralPublic,
 | |
|   encryptedStatic,
 | |
|   encryptedMessage
 | |
| ) {
 | |
|   const versionBytes = new Uint8Array([
 | |
|     intsToByteHighAndLow(CIPHERTEXT_VERSION, CIPHERTEXT_VERSION),
 | |
|   ]);
 | |
|   const unidentifiedSenderMessage = new textsecure.protobuf.UnidentifiedSenderMessage();
 | |
| 
 | |
|   unidentifiedSenderMessage.encryptedMessage = encryptedMessage;
 | |
|   unidentifiedSenderMessage.encryptedStatic = encryptedStatic;
 | |
|   unidentifiedSenderMessage.ephemeralPublic = ephemeralPublic;
 | |
| 
 | |
|   const messageBytes = unidentifiedSenderMessage.encode().toArrayBuffer();
 | |
| 
 | |
|   return {
 | |
|     version: CIPHERTEXT_VERSION,
 | |
| 
 | |
|     ephemeralPublic,
 | |
|     encryptedStatic,
 | |
|     encryptedMessage,
 | |
| 
 | |
|     serialized: concatenateBytes(versionBytes, messageBytes),
 | |
|   };
 | |
| }
 | |
| 
 | |
| // public UnidentifiedSenderMessageContent(byte[] serialized)
 | |
| function _createUnidentifiedSenderMessageContentFromBuffer(serialized) {
 | |
|   const TypeEnum = textsecure.protobuf.UnidentifiedSenderMessage.Message.Type;
 | |
| 
 | |
|   const message = textsecure.protobuf.UnidentifiedSenderMessage.Message.decode(
 | |
|     serialized
 | |
|   );
 | |
| 
 | |
|   if (!message.type || !message.senderCertificate || !message.content) {
 | |
|     throw new Error('Missing fields');
 | |
|   }
 | |
| 
 | |
|   let type;
 | |
|   switch (message.type) {
 | |
|     case TypeEnum.MESSAGE:
 | |
|       type = CiphertextMessage.WHISPER_TYPE;
 | |
|       break;
 | |
|     case TypeEnum.PREKEY_MESSAGE:
 | |
|       type = CiphertextMessage.PREKEY_TYPE;
 | |
|       break;
 | |
|     default:
 | |
|       throw new Error(`Unknown type: ${message.type}`);
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     type,
 | |
|     senderCertificate: _createSenderCertificateFromBuffer(
 | |
|       message.senderCertificate.toArrayBuffer()
 | |
|     ),
 | |
|     content: message.content.toArrayBuffer(),
 | |
| 
 | |
|     serialized,
 | |
|   };
 | |
| }
 | |
| 
 | |
| // private int getProtoType(int type)
 | |
| function _getProtoMessageType(type) {
 | |
|   const TypeEnum = textsecure.protobuf.UnidentifiedSenderMessage.Message.Type;
 | |
| 
 | |
|   switch (type) {
 | |
|     case CiphertextMessage.WHISPER_TYPE:
 | |
|       return TypeEnum.MESSAGE;
 | |
|     case CiphertextMessage.PREKEY_TYPE:
 | |
|       return TypeEnum.PREKEY_MESSAGE;
 | |
|     default:
 | |
|       throw new Error(`_getProtoMessageType: type '${type}' does not exist`);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // public UnidentifiedSenderMessageContent(
 | |
| //   int type, SenderCertificate senderCertificate, byte[] content)
 | |
| function _createUnidentifiedSenderMessageContent(
 | |
|   type,
 | |
|   senderCertificate,
 | |
|   content
 | |
| ) {
 | |
|   const innerMessage = new textsecure.protobuf.UnidentifiedSenderMessage.Message();
 | |
|   innerMessage.type = _getProtoMessageType(type);
 | |
|   innerMessage.senderCertificate = textsecure.protobuf.SenderCertificate.decode(
 | |
|     senderCertificate.serialized
 | |
|   );
 | |
|   innerMessage.content = content;
 | |
| 
 | |
|   return {
 | |
|     type,
 | |
|     senderCertificate,
 | |
|     content,
 | |
| 
 | |
|     serialized: innerMessage.encode().toArrayBuffer(),
 | |
|   };
 | |
| }
 | |
| 
 | |
| SecretSessionCipher.prototype = {
 | |
|   // public byte[] encrypt(
 | |
|   //   SignalProtocolAddress destinationAddress,
 | |
|   //   SenderCertificate senderCertificate,
 | |
|   //   byte[] paddedPlaintext
 | |
|   // )
 | |
|   async encrypt(destinationAddress, senderCertificate, paddedPlaintext) {
 | |
|     // Capture this.xxx variables to replicate Java's implicit this syntax
 | |
|     const { SessionCipher } = this;
 | |
|     const signalProtocolStore = this.storage;
 | |
|     const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this);
 | |
|     const _encryptWithSecretKeys = this._encryptWithSecretKeys.bind(this);
 | |
|     const _calculateStaticKeys = this._calculateStaticKeys.bind(this);
 | |
| 
 | |
|     const sessionCipher = new SessionCipher(
 | |
|       signalProtocolStore,
 | |
|       destinationAddress
 | |
|     );
 | |
| 
 | |
|     const message = await sessionCipher.encrypt(paddedPlaintext);
 | |
|     const ourIdentity = await signalProtocolStore.getIdentityKeyPair();
 | |
|     const theirIdentity = fromEncodedBinaryToArrayBuffer(
 | |
|       await signalProtocolStore.loadIdentityKey(destinationAddress.getName())
 | |
|     );
 | |
| 
 | |
|     const ephemeral = await libsignal.Curve.async.generateKeyPair();
 | |
|     const ephemeralSalt = concatenateBytes(
 | |
|       bytesFromString(UNIDENTIFIED_DELIVERY_PREFIX),
 | |
|       theirIdentity,
 | |
|       ephemeral.pubKey
 | |
|     );
 | |
|     const ephemeralKeys = await _calculateEphemeralKeys(
 | |
|       theirIdentity,
 | |
|       ephemeral.privKey,
 | |
|       ephemeralSalt
 | |
|     );
 | |
|     const staticKeyCiphertext = await _encryptWithSecretKeys(
 | |
|       ephemeralKeys.cipherKey,
 | |
|       ephemeralKeys.macKey,
 | |
|       ourIdentity.pubKey
 | |
|     );
 | |
| 
 | |
|     const staticSalt = concatenateBytes(
 | |
|       ephemeralKeys.chainKey,
 | |
|       staticKeyCiphertext
 | |
|     );
 | |
|     const staticKeys = await _calculateStaticKeys(
 | |
|       theirIdentity,
 | |
|       ourIdentity.privKey,
 | |
|       staticSalt
 | |
|     );
 | |
|     const content = _createUnidentifiedSenderMessageContent(
 | |
|       message.type,
 | |
|       senderCertificate,
 | |
|       fromEncodedBinaryToArrayBuffer(message.body)
 | |
|     );
 | |
|     const messageBytes = await _encryptWithSecretKeys(
 | |
|       staticKeys.cipherKey,
 | |
|       staticKeys.macKey,
 | |
|       content.serialized
 | |
|     );
 | |
| 
 | |
|     const unidentifiedSenderMessage = _createUnidentifiedSenderMessage(
 | |
|       ephemeral.pubKey,
 | |
|       staticKeyCiphertext,
 | |
|       messageBytes
 | |
|     );
 | |
| 
 | |
|     return unidentifiedSenderMessage.serialized;
 | |
|   },
 | |
| 
 | |
|   // public Pair<SignalProtocolAddress, byte[]> decrypt(
 | |
|   //   CertificateValidator validator, byte[] ciphertext, long timestamp)
 | |
|   async decrypt(validator, ciphertext, timestamp, me) {
 | |
|     // Capture this.xxx variables to replicate Java's implicit this syntax
 | |
|     const signalProtocolStore = this.storage;
 | |
|     const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this);
 | |
|     const _calculateStaticKeys = this._calculateStaticKeys.bind(this);
 | |
|     const _decryptWithUnidentifiedSenderMessage = this._decryptWithUnidentifiedSenderMessage.bind(
 | |
|       this
 | |
|     );
 | |
|     const _decryptWithSecretKeys = this._decryptWithSecretKeys.bind(this);
 | |
| 
 | |
|     const ourIdentity = await signalProtocolStore.getIdentityKeyPair();
 | |
|     const wrapper = _createUnidentifiedSenderMessageFromBuffer(ciphertext);
 | |
|     const ephemeralSalt = concatenateBytes(
 | |
|       bytesFromString(UNIDENTIFIED_DELIVERY_PREFIX),
 | |
|       ourIdentity.pubKey,
 | |
|       wrapper.ephemeralPublic
 | |
|     );
 | |
|     const ephemeralKeys = await _calculateEphemeralKeys(
 | |
|       wrapper.ephemeralPublic,
 | |
|       ourIdentity.privKey,
 | |
|       ephemeralSalt
 | |
|     );
 | |
|     const staticKeyBytes = await _decryptWithSecretKeys(
 | |
|       ephemeralKeys.cipherKey,
 | |
|       ephemeralKeys.macKey,
 | |
|       wrapper.encryptedStatic
 | |
|     );
 | |
| 
 | |
|     const staticKey = _decodePoint(staticKeyBytes, 0);
 | |
|     const staticSalt = concatenateBytes(
 | |
|       ephemeralKeys.chainKey,
 | |
|       wrapper.encryptedStatic
 | |
|     );
 | |
|     const staticKeys = await _calculateStaticKeys(
 | |
|       staticKey,
 | |
|       ourIdentity.privKey,
 | |
|       staticSalt
 | |
|     );
 | |
|     const messageBytes = await _decryptWithSecretKeys(
 | |
|       staticKeys.cipherKey,
 | |
|       staticKeys.macKey,
 | |
|       wrapper.encryptedMessage
 | |
|     );
 | |
| 
 | |
|     const content = _createUnidentifiedSenderMessageContentFromBuffer(
 | |
|       messageBytes
 | |
|     );
 | |
| 
 | |
|     await validator.validate(content.senderCertificate, timestamp);
 | |
|     if (
 | |
|       !constantTimeEqual(content.senderCertificate.identityKey, staticKeyBytes)
 | |
|     ) {
 | |
|       throw new Error(
 | |
|         "Sender's certificate key does not match key used in message"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const { sender, senderDevice } = content.senderCertificate;
 | |
|     const { number, deviceId } = me || {};
 | |
|     if (sender === number && senderDevice === deviceId) {
 | |
|       return {
 | |
|         isMe: true,
 | |
|       };
 | |
|     }
 | |
|     const address = new libsignal.SignalProtocolAddress(sender, senderDevice);
 | |
| 
 | |
|     try {
 | |
|       return {
 | |
|         sender: address,
 | |
|         content: await _decryptWithUnidentifiedSenderMessage(content),
 | |
|       };
 | |
|     } catch (error) {
 | |
|       error.sender = address;
 | |
| 
 | |
|       throw error;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // public int getSessionVersion(SignalProtocolAddress remoteAddress) {
 | |
|   getSessionVersion(remoteAddress) {
 | |
|     const { SessionCipher } = this;
 | |
|     const signalProtocolStore = this.storage;
 | |
| 
 | |
|     const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | |
| 
 | |
|     return cipher.getSessionVersion();
 | |
|   },
 | |
| 
 | |
|   // public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) {
 | |
|   getRemoteRegistrationId(remoteAddress) {
 | |
|     const { SessionCipher } = this;
 | |
|     const signalProtocolStore = this.storage;
 | |
| 
 | |
|     const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | |
| 
 | |
|     return cipher.getRemoteRegistrationId();
 | |
|   },
 | |
| 
 | |
|   // Used by outgoing_message.js
 | |
|   closeOpenSessionForDevice(remoteAddress) {
 | |
|     const { SessionCipher } = this;
 | |
|     const signalProtocolStore = this.storage;
 | |
| 
 | |
|     const cipher = new SessionCipher(signalProtocolStore, remoteAddress);
 | |
| 
 | |
|     return cipher.closeOpenSessionForDevice();
 | |
|   },
 | |
| 
 | |
|   // private EphemeralKeys calculateEphemeralKeys(
 | |
|   //   ECPublicKey ephemeralPublic, ECPrivateKey ephemeralPrivate, byte[] salt)
 | |
|   async _calculateEphemeralKeys(ephemeralPublic, ephemeralPrivate, salt) {
 | |
|     const ephemeralSecret = await libsignal.Curve.async.calculateAgreement(
 | |
|       ephemeralPublic,
 | |
|       ephemeralPrivate
 | |
|     );
 | |
|     const ephemeralDerivedParts = await libsignal.HKDF.deriveSecrets(
 | |
|       ephemeralSecret,
 | |
|       salt,
 | |
|       new ArrayBuffer()
 | |
|     );
 | |
| 
 | |
|     // private EphemeralKeys(byte[] chainKey, byte[] cipherKey, byte[] macKey)
 | |
|     return {
 | |
|       chainKey: ephemeralDerivedParts[0],
 | |
|       cipherKey: ephemeralDerivedParts[1],
 | |
|       macKey: ephemeralDerivedParts[2],
 | |
|     };
 | |
|   },
 | |
| 
 | |
|   // private StaticKeys calculateStaticKeys(
 | |
|   //   ECPublicKey staticPublic, ECPrivateKey staticPrivate, byte[] salt)
 | |
|   async _calculateStaticKeys(staticPublic, staticPrivate, salt) {
 | |
|     const staticSecret = await libsignal.Curve.async.calculateAgreement(
 | |
|       staticPublic,
 | |
|       staticPrivate
 | |
|     );
 | |
|     const staticDerivedParts = await libsignal.HKDF.deriveSecrets(
 | |
|       staticSecret,
 | |
|       salt,
 | |
|       new ArrayBuffer()
 | |
|     );
 | |
| 
 | |
|     // private StaticKeys(byte[] cipherKey, byte[] macKey)
 | |
|     return {
 | |
|       cipherKey: staticDerivedParts[1],
 | |
|       macKey: staticDerivedParts[2],
 | |
|     };
 | |
|   },
 | |
| 
 | |
|   // private byte[] decrypt(UnidentifiedSenderMessageContent message)
 | |
|   _decryptWithUnidentifiedSenderMessage(message) {
 | |
|     const { SessionCipher } = this;
 | |
|     const signalProtocolStore = this.storage;
 | |
| 
 | |
|     const sender = new libsignal.SignalProtocolAddress(
 | |
|       message.senderCertificate.sender,
 | |
|       message.senderCertificate.senderDevice
 | |
|     );
 | |
| 
 | |
|     switch (message.type) {
 | |
|       case CiphertextMessage.WHISPER_TYPE:
 | |
|         return new SessionCipher(
 | |
|           signalProtocolStore,
 | |
|           sender
 | |
|         ).decryptWhisperMessage(message.content);
 | |
|       case CiphertextMessage.PREKEY_TYPE:
 | |
|         return new SessionCipher(
 | |
|           signalProtocolStore,
 | |
|           sender
 | |
|         ).decryptPreKeyWhisperMessage(message.content);
 | |
|       default:
 | |
|         throw new Error(`Unknown type: ${message.type}`);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // private byte[] encrypt(
 | |
|   //   SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] plaintext)
 | |
|   async _encryptWithSecretKeys(cipherKey, macKey, plaintext) {
 | |
|     // Cipher const cipher = Cipher.getInstance('AES/CTR/NoPadding');
 | |
|     // cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
 | |
| 
 | |
|     // Mac const mac = Mac.getInstance('HmacSHA256');
 | |
|     // mac.init(macKey);
 | |
| 
 | |
|     // byte[] const ciphertext = cipher.doFinal(plaintext);
 | |
|     const ciphertext = await encryptAesCtr(cipherKey, plaintext, getZeroes(16));
 | |
| 
 | |
|     // byte[] const ourFullMac = mac.doFinal(ciphertext);
 | |
|     const ourFullMac = await hmacSha256(macKey, ciphertext);
 | |
|     const ourMac = trimBytes(ourFullMac, 10);
 | |
| 
 | |
|     return concatenateBytes(ciphertext, ourMac);
 | |
|   },
 | |
| 
 | |
|   // private byte[] decrypt(
 | |
|   //   SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] ciphertext)
 | |
|   async _decryptWithSecretKeys(cipherKey, macKey, ciphertext) {
 | |
|     if (ciphertext.byteLength < 10) {
 | |
|       throw new Error('Ciphertext not long enough for MAC!');
 | |
|     }
 | |
| 
 | |
|     const ciphertextParts = splitBytes(
 | |
|       ciphertext,
 | |
|       ciphertext.byteLength - 10,
 | |
|       10
 | |
|     );
 | |
| 
 | |
|     // Mac const mac = Mac.getInstance('HmacSHA256');
 | |
|     // mac.init(macKey);
 | |
| 
 | |
|     // byte[] const digest   = mac.doFinal(ciphertextParts[0]);
 | |
|     const digest = await hmacSha256(macKey, ciphertextParts[0]);
 | |
|     const ourMac = trimBytes(digest, 10);
 | |
|     const theirMac = ciphertextParts[1];
 | |
| 
 | |
|     if (!constantTimeEqual(ourMac, theirMac)) {
 | |
|       throw new Error('Bad mac!');
 | |
|     }
 | |
| 
 | |
|     // Cipher const cipher = Cipher.getInstance('AES/CTR/NoPadding');
 | |
|     // cipher.init(Cipher.DECRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
 | |
| 
 | |
|     // return cipher.doFinal(ciphertextParts[0]);
 | |
|     return decryptAesCtr(cipherKey, ciphertextParts[0], getZeroes(16));
 | |
|   },
 | |
| };
 | |
| 
 | |
| module.exports = {
 | |
|   SecretSessionCipher,
 | |
|   createCertificateValidator,
 | |
|   _createServerCertificateFromBuffer,
 | |
|   _createSenderCertificateFromBuffer,
 | |
| };
 |