blob: 5b4ed7ff6ceb8ffa92d25d5e1a1f90644f3f1588 [file] [log] [blame]
thaidn397d2a72019-03-06 09:40:31 -08001// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13////////////////////////////////////////////////////////////////////////////////
14
15goog.module('tink.subtle.EncryptThenAuthenticate');
16
17const Aead = goog.require('tink.Aead');
18const AesCtr = goog.require('tink.subtle.AesCtr');
19const Bytes = goog.require('tink.subtle.Bytes');
20const Hmac = goog.require('tink.subtle.Hmac');
21const IndCpaCipher = goog.require('tink.subtle.IndCpaCipher');
22const InvalidArgumentsException = goog.require('tink.exception.InvalidArgumentsException');
23const Mac = goog.require('tink.Mac');
24const SecurityException = goog.require('tink.exception.SecurityException');
25const Validators = goog.require('tink.subtle.Validators');
26
27/**
28 * This primitive performs an encrypt-then-Mac operation on plaintext and
29 * additional authenticated data (aad).
30 *
31 * The Mac is computed over `aad || ciphertext || size of aad`, thus it
32 * doesn't violate https://en.wikipedia.org/wiki/Horton_Principle.
33 *
34 * This implementation is based on
35 * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
36 *
37 * @implements {Aead}
38 * @public
39 * @final
40 */
41class EncryptThenAuthenticate {
42 /**
43 * @param {!IndCpaCipher} cipher
44 * @param {number} ivSize the IV size in bytes
45 * @param {!Mac} mac
46 * @param {number} tagSize the MAC tag size in bytes
47 * @throws {InvalidArgumentsException}
48 */
49 constructor(cipher, ivSize, mac, tagSize) {
Tink Teamd32f2232019-10-22 07:46:28 -070050 /** @const @private {!IndCpaCipher} */
thaidn397d2a72019-03-06 09:40:31 -080051 this.cipher_ = cipher;
52
53 /** @const @private {number} */
54 this.ivSize_ = ivSize;
55
Tink Teamd32f2232019-10-22 07:46:28 -070056 /** @const @private {!Mac} */
thaidn397d2a72019-03-06 09:40:31 -080057 this.mac_ = mac;
58
59 /** @const @private {number} */
60 this.tagSize_ = tagSize;
61 }
62
63 /**
64 * @param {!Uint8Array} aesKey
65 * @param {number} ivSize the size of the IV
66 * @param {string} hmacHashAlgo accepted names are SHA-1, SHA-256 and SHA-512
67 * @param {!Uint8Array} hmacKey
68 * @param {number} tagSize the size of the tag
69 * @return {!Promise.<!EncryptThenAuthenticate>}
70 * @throws {InvalidArgumentsException}
71 * @static
72 */
73 static async newAesCtrHmac(aesKey, ivSize, hmacHashAlgo, hmacKey, tagSize) {
74 Validators.requireUint8Array(aesKey);
75 Validators.requireUint8Array(hmacKey);
76
77 const cipher = await AesCtr.newInstance(aesKey, ivSize);
78 const mac = await Hmac.newInstance(hmacHashAlgo, hmacKey, tagSize);
79 return new EncryptThenAuthenticate(cipher, ivSize, mac, tagSize);
80 }
81
82 /**
83 * The plaintext is encrypted with an {@link IndCpaCipher}, then MAC
84 * is computed over `aad || ciphertext || t` where t is aad's length in bits
85 * represented as 64-bit bigendian unsigned integer. The final ciphertext
86 * format is `ind-cpa ciphertext || mac`.
87 *
88 * @override
89 */
90 async encrypt(plaintext, opt_associatedData) {
91 Validators.requireUint8Array(plaintext);
92 const payload = await this.cipher_.encrypt(plaintext);
93 let aad = new Uint8Array(0);
Tink Teambb796df2019-08-14 12:43:15 -070094 if (opt_associatedData != null) {
thaidn397d2a72019-03-06 09:40:31 -080095 aad = opt_associatedData;
96 Validators.requireUint8Array(opt_associatedData);
97 }
98 const aadLength = Bytes.fromNumber(aad.length * 8);
99 const mac =
100 await this.mac_.computeMac(Bytes.concat(aad, payload, aadLength));
101 if (this.tagSize_ != mac.length) {
102 throw new SecurityException(
103 'invalid tag size, expected ' + this.tagSize_ + ' but got ' +
104 mac.length);
105 }
106 return Bytes.concat(payload, mac);
107 }
108
109 /**
110 * @override
111 */
112 async decrypt(ciphertext, opt_associatedData) {
113 Validators.requireUint8Array(ciphertext);
114 if (ciphertext.length < this.ivSize_ + this.tagSize_) {
115 throw new SecurityException('ciphertext too short');
116 }
117 const payload = new Uint8Array(
118 ciphertext.subarray(0, ciphertext.length - this.tagSize_));
119 let aad = new Uint8Array(0);
Tink Teambb796df2019-08-14 12:43:15 -0700120 if (opt_associatedData != null) {
thaidn397d2a72019-03-06 09:40:31 -0800121 aad = opt_associatedData;
122 Validators.requireUint8Array(opt_associatedData);
123 }
124 const aadLength = Bytes.fromNumber(aad.length * 8);
125 const input = Bytes.concat(aad, payload, aadLength);
126 const tag = new Uint8Array(ciphertext.subarray(payload.length));
127 const isValidMac = await this.mac_.verifyMac(tag, input);
128 if (!isValidMac) {
129 throw new SecurityException('invalid MAC');
130 }
131 return await this.cipher_.decrypt(payload);
132 }
133}
134
135exports = EncryptThenAuthenticate;
OSZAR »