summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/matrix-sdk/crypto/aes.js
blob: e48c59446c399f9d85f819195470dcbee5f04374 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.calculateKeyCheck = calculateKeyCheck;
exports.decryptAES = decryptAES;
exports.encryptAES = encryptAES;
var _olmlib = require("./olmlib");
var _crypto = require("./crypto");
/*
Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// salt for HKDF, with 8 bytes of zeros
const zeroSalt = new Uint8Array(8);
/**
 * encrypt a string
 *
 * @param data - the plaintext to encrypt
 * @param key - the encryption key to use
 * @param name - the name of the secret
 * @param ivStr - the initialization vector to use
 */
async function encryptAES(data, key, name, ivStr) {
  let iv;
  if (ivStr) {
    iv = (0, _olmlib.decodeBase64)(ivStr);
  } else {
    iv = new Uint8Array(16);
    _crypto.crypto.getRandomValues(iv);

    // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
    // (which would mean we wouldn't be able to decrypt on Android). The loss
    // of a single bit of iv is a price we have to pay.
    iv[8] &= 0x7f;
  }
  const [aesKey, hmacKey] = await deriveKeys(key, name);
  const encodedData = new _crypto.TextEncoder().encode(data);
  const ciphertext = await _crypto.subtleCrypto.encrypt({
    name: "AES-CTR",
    counter: iv,
    length: 64
  }, aesKey, encodedData);
  const hmac = await _crypto.subtleCrypto.sign({
    name: "HMAC"
  }, hmacKey, ciphertext);
  return {
    iv: (0, _olmlib.encodeBase64)(iv),
    ciphertext: (0, _olmlib.encodeBase64)(ciphertext),
    mac: (0, _olmlib.encodeBase64)(hmac)
  };
}

/**
 * decrypt a string
 *
 * @param data - the encrypted data
 * @param key - the encryption key to use
 * @param name - the name of the secret
 */
async function decryptAES(data, key, name) {
  const [aesKey, hmacKey] = await deriveKeys(key, name);
  const ciphertext = (0, _olmlib.decodeBase64)(data.ciphertext);
  if (!(await _crypto.subtleCrypto.verify({
    name: "HMAC"
  }, hmacKey, (0, _olmlib.decodeBase64)(data.mac), ciphertext))) {
    throw new Error(`Error decrypting secret ${name}: bad MAC`);
  }
  const plaintext = await _crypto.subtleCrypto.decrypt({
    name: "AES-CTR",
    counter: (0, _olmlib.decodeBase64)(data.iv),
    length: 64
  }, aesKey, ciphertext);
  return new TextDecoder().decode(new Uint8Array(plaintext));
}
async function deriveKeys(key, name) {
  const hkdfkey = await _crypto.subtleCrypto.importKey("raw", key, {
    name: "HKDF"
  }, false, ["deriveBits"]);
  const keybits = await _crypto.subtleCrypto.deriveBits({
    name: "HKDF",
    salt: zeroSalt,
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
    info: new _crypto.TextEncoder().encode(name),
    hash: "SHA-256"
  }, hkdfkey, 512);
  const aesKey = keybits.slice(0, 32);
  const hmacKey = keybits.slice(32);
  const aesProm = _crypto.subtleCrypto.importKey("raw", aesKey, {
    name: "AES-CTR"
  }, false, ["encrypt", "decrypt"]);
  const hmacProm = _crypto.subtleCrypto.importKey("raw", hmacKey, {
    name: "HMAC",
    hash: {
      name: "SHA-256"
    }
  }, false, ["sign", "verify"]);
  return Promise.all([aesProm, hmacProm]);
}

// string of zeroes, for calculating the key check
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

/** Calculate the MAC for checking the key.
 *
 * @param key - the key to use
 * @param iv - The initialization vector as a base64-encoded string.
 *     If omitted, a random initialization vector will be created.
 * @returns An object that contains, `mac` and `iv` properties.
 */
function calculateKeyCheck(key, iv) {
  return encryptAES(ZERO_STR, key, "", iv);
}