@fairblock/stabletrust 2.0.6 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -74
- package/examples/anonymous-flow.js +42 -31
- package/package.json +10 -3
- package/src/anonymous-client.js +565 -105
- package/src/backend.js +19 -0
- package/src/confidential-client.js +497 -82
- package/src/constants.js +36 -9
- package/src/ibe.js +59 -0
- package/src/index.d.ts +85 -18
- package/src/index.js +6 -0
- package/src/utils.js +169 -62
- package/src/vendor/ts-ibe/age/age-encrypt-decrypt.js +127 -0
- package/src/vendor/ts-ibe/age/age-reader-writer.js +94 -0
- package/src/vendor/ts-ibe/age/armor.js +52 -0
- package/src/vendor/ts-ibe/age/no-op-encdec.js +72 -0
- package/src/vendor/ts-ibe/age/stream-cipher.js +99 -0
- package/src/vendor/ts-ibe/age/utils-crypto.js +60 -0
- package/src/vendor/ts-ibe/age/utils.js +59 -0
- package/src/vendor/ts-ibe/crypto/ibe.js +198 -0
- package/src/vendor/ts-ibe/crypto/utils.js +50 -0
- package/src/vendor/ts-ibe/index.js +87 -0
- package/src/vendor/ts-ibe/types/index.js +2 -0
- package/src/vendor/ts-ibe/version.js +2 -0
- package/src/wasm-loader.js +3 -3
- package/pkg/README.md +0 -310
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { Buffer } from "buffer";
|
|
38
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
39
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
40
|
+
import { STREAM } from "./stream-cipher.js";
|
|
41
|
+
import { NoOpEncDec } from "./no-op-encdec.js";
|
|
42
|
+
import { readAge, writeAge } from "./age-reader-writer.js";
|
|
43
|
+
import { sliceUntil, unpaddedBase64Buffer } from "./utils.js";
|
|
44
|
+
import { createMacKey, random } from "./utils-crypto.js";
|
|
45
|
+
var ageVersion = "age-encryption.org/v1";
|
|
46
|
+
var headerMacMessage = "header"; // some plaintext used to generate the mac
|
|
47
|
+
var hkdfBodyMessage = "payload"; // some plaintext used for generating the key for encrypting the body
|
|
48
|
+
var fileKeyLengthBytes = 32;
|
|
49
|
+
var bodyHkdfNonceLengthBytes = 16;
|
|
50
|
+
var hkdfKeyLengthBytes = 32;
|
|
51
|
+
// encrypts a plaintext payload using AGE by generating a fileKey
|
|
52
|
+
// and passing the fileKey to another `EncryptionWrapper` for handling
|
|
53
|
+
export function encryptAge(plaintext, wrapFileKey) {
|
|
54
|
+
if (wrapFileKey === void 0) { wrapFileKey = NoOpEncDec.wrap; }
|
|
55
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
56
|
+
var fileKey, recipients, body;
|
|
57
|
+
return __generator(this, function (_a) {
|
|
58
|
+
switch (_a.label) {
|
|
59
|
+
case 0: return [4 /*yield*/, random(fileKeyLengthBytes)];
|
|
60
|
+
case 1:
|
|
61
|
+
fileKey = _a.sent();
|
|
62
|
+
return [4 /*yield*/, wrapFileKey(fileKey)];
|
|
63
|
+
case 2:
|
|
64
|
+
recipients = _a.sent();
|
|
65
|
+
return [4 /*yield*/, encryptedPayload(fileKey, plaintext)];
|
|
66
|
+
case 3:
|
|
67
|
+
body = _a.sent();
|
|
68
|
+
return [2 /*return*/, writeAge({
|
|
69
|
+
fileKey: fileKey,
|
|
70
|
+
version: ageVersion,
|
|
71
|
+
recipients: recipients,
|
|
72
|
+
headerMacMessage: headerMacMessage,
|
|
73
|
+
body: body,
|
|
74
|
+
})];
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function encryptedPayload(fileKey, payload) {
|
|
80
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
81
|
+
var nonce, hkdfKey, ciphertext;
|
|
82
|
+
return __generator(this, function (_a) {
|
|
83
|
+
switch (_a.label) {
|
|
84
|
+
case 0: return [4 /*yield*/, random(bodyHkdfNonceLengthBytes)];
|
|
85
|
+
case 1:
|
|
86
|
+
nonce = _a.sent();
|
|
87
|
+
hkdfKey = hkdf(sha256, fileKey, nonce, Buffer.from(hkdfBodyMessage, "utf8"), hkdfKeyLengthBytes);
|
|
88
|
+
ciphertext = STREAM.seal(payload, hkdfKey);
|
|
89
|
+
return [2 /*return*/, Buffer.concat([nonce, ciphertext])];
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// decrypts a payload that has been encrypted using AGE can unwrap
|
|
95
|
+
// any internal encryption by passing a `DecryptionWrapper` that can
|
|
96
|
+
// provide the `fileKey` created during encryption
|
|
97
|
+
export function decryptAge(payload, unwrapFileKey) {
|
|
98
|
+
if (unwrapFileKey === void 0) { unwrapFileKey = NoOpEncDec.unwrap; }
|
|
99
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
100
|
+
var encryptedPayload, version, fileKey, header, expectedMac, actualMac, nonce, cipherText, hkdfKey, plaintext;
|
|
101
|
+
return __generator(this, function (_a) {
|
|
102
|
+
switch (_a.label) {
|
|
103
|
+
case 0:
|
|
104
|
+
encryptedPayload = readAge(payload);
|
|
105
|
+
version = encryptedPayload.header.version;
|
|
106
|
+
if (version !== ageVersion) {
|
|
107
|
+
throw Error("The payload version ".concat(version, " is not supported, only ").concat(ageVersion));
|
|
108
|
+
}
|
|
109
|
+
return [4 /*yield*/, unwrapFileKey(encryptedPayload.header.recipients)];
|
|
110
|
+
case 1:
|
|
111
|
+
fileKey = _a.sent();
|
|
112
|
+
header = sliceUntil(payload, "---");
|
|
113
|
+
expectedMac = unpaddedBase64Buffer(createMacKey(fileKey, headerMacMessage, header));
|
|
114
|
+
actualMac = encryptedPayload.header.mac;
|
|
115
|
+
if (Buffer.compare(actualMac, expectedMac) !== 0) {
|
|
116
|
+
throw Error("The MAC did not validate for the fileKey and payload!");
|
|
117
|
+
}
|
|
118
|
+
nonce = Buffer.from(encryptedPayload.body.slice(0, bodyHkdfNonceLengthBytes));
|
|
119
|
+
cipherText = encryptedPayload.body.slice(bodyHkdfNonceLengthBytes);
|
|
120
|
+
hkdfKey = hkdf(sha256, fileKey, nonce, Buffer.from(hkdfBodyMessage, "utf8"), hkdfKeyLengthBytes);
|
|
121
|
+
plaintext = STREAM.open(cipherText, hkdfKey);
|
|
122
|
+
return [2 /*return*/, Buffer.from(plaintext)];
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=age-encrypt-decrypt.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
import { chunked, unpaddedBase64 } from "./utils.js";
|
|
3
|
+
import { createMacKey } from "./utils-crypto.js";
|
|
4
|
+
// takes the model to be encrypted and encodes everything to a string
|
|
5
|
+
// inserting newlines, other tags and the hmac as per the spec
|
|
6
|
+
export function writeAge(input) {
|
|
7
|
+
var headerStr = header(input);
|
|
8
|
+
var macKey = mac(createMacKey(input.fileKey, input.headerMacMessage, headerStr));
|
|
9
|
+
var payload = Buffer.from(input.body).toString("binary");
|
|
10
|
+
return "".concat(headerStr, " ").concat(macKey, "\n").concat(payload);
|
|
11
|
+
}
|
|
12
|
+
// ends with a `---`, as this is included in the header when
|
|
13
|
+
// calculating the MAC
|
|
14
|
+
export function header(input) {
|
|
15
|
+
return "".concat(input.version, "\n").concat(recipients(input.recipients), "---");
|
|
16
|
+
}
|
|
17
|
+
var recipients = function (stanzas) {
|
|
18
|
+
return stanzas.map(function (it) { return recipient(it) + "\n"; });
|
|
19
|
+
};
|
|
20
|
+
var recipient = function (stanza) {
|
|
21
|
+
var type = stanza.type;
|
|
22
|
+
var encodedBody = unpaddedBase64(stanza.body);
|
|
23
|
+
var chunkedEncodedBody = chunked(encodedBody, 64).join("\n");
|
|
24
|
+
return "-> ".concat(type, "\n") + chunkedEncodedBody;
|
|
25
|
+
};
|
|
26
|
+
// The `---` preceding the MAC is technically part of the MAC-able text
|
|
27
|
+
// so it's included in the header instead
|
|
28
|
+
var mac = function (macStr) { return unpaddedBase64(macStr); };
|
|
29
|
+
// parses an AGE encrypted string into a model object with all the
|
|
30
|
+
// relevant parts encoded correctly
|
|
31
|
+
// throws an error if things are missing, in the wrong place or cannot
|
|
32
|
+
// be parsed
|
|
33
|
+
export function readAge(input) {
|
|
34
|
+
var _a;
|
|
35
|
+
var _b = input.split("\n"), version = _b[0], lines = _b.slice(1);
|
|
36
|
+
var recipients = parseRecipients(lines);
|
|
37
|
+
var macStartingTag = "--- ";
|
|
38
|
+
var macLine = lines.shift();
|
|
39
|
+
if (!macLine || !macLine.startsWith(macStartingTag)) {
|
|
40
|
+
throw Error("Expected mac, but there were no more lines left!");
|
|
41
|
+
}
|
|
42
|
+
var mac = Buffer.from(macLine.slice(macStartingTag.length, macLine.length), "base64");
|
|
43
|
+
// any remaining newlines are actually part of the payload
|
|
44
|
+
var ciphertext = Buffer.from((_a = lines.join("\n")) !== null && _a !== void 0 ? _a : "", "binary");
|
|
45
|
+
return {
|
|
46
|
+
header: { version: version, recipients: recipients, mac: mac },
|
|
47
|
+
body: ciphertext,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// validates the code points of the characters of the args in line with the go implementation
|
|
51
|
+
// see: https://github.com/FiloSottile/age/blob/8e3f74c283b2e9b3cd0ec661fa4008504e536d20/internal/format/format.go#L301
|
|
52
|
+
function validateArguments(args) {
|
|
53
|
+
args.forEach(function (arg) {
|
|
54
|
+
for (var i = 0; i < arg.length; i++) {
|
|
55
|
+
var charCode = arg.charCodeAt(i);
|
|
56
|
+
if (charCode < 33 || charCode > 126) {
|
|
57
|
+
throw Error("Invalid character ".concat(arg[i], " in argument ").concat(arg));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// parses all the recipient stanzas from `lines`
|
|
63
|
+
// modifies `lines`!!
|
|
64
|
+
function parseRecipients(lines) {
|
|
65
|
+
var recipients = [];
|
|
66
|
+
for (var current = peek(lines); current != null && current.startsWith("->"); current = peek(lines)) {
|
|
67
|
+
var _a = current.slice(3, current.length).split(" "), type = _a[0], args = _a.slice(1);
|
|
68
|
+
lines.shift();
|
|
69
|
+
validateArguments(args);
|
|
70
|
+
var body = parseRecipientBody(lines);
|
|
71
|
+
if (!body) {
|
|
72
|
+
throw Error("expected stanza '".concat(type, " to have a body, but it didn't"));
|
|
73
|
+
}
|
|
74
|
+
recipients.push({ type: type, args: args, body: Buffer.from(body, "base64") });
|
|
75
|
+
}
|
|
76
|
+
if (recipients.length === 0) {
|
|
77
|
+
throw Error("Expected at least one stanza! (beginning with -->)");
|
|
78
|
+
}
|
|
79
|
+
return recipients;
|
|
80
|
+
}
|
|
81
|
+
function parseRecipientBody(lines) {
|
|
82
|
+
var body = "";
|
|
83
|
+
for (var next = peek(lines); next != null; next = peek(lines)) {
|
|
84
|
+
body += lines.shift();
|
|
85
|
+
if (next.length < 64) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
91
|
+
function peek(arr) {
|
|
92
|
+
return arr[0];
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=age-reader-writer.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
import { chunked } from "./utils.js";
|
|
3
|
+
var header = "-----BEGIN AGE ENCRYPTED FILE-----";
|
|
4
|
+
var footer = "-----END AGE ENCRYPTED FILE-----";
|
|
5
|
+
// takes some payload and encodes it as armor with the AGE armor headers in lines of size `chunkSize`
|
|
6
|
+
export function encodeArmor(input, chunkSize) {
|
|
7
|
+
if (chunkSize === void 0) { chunkSize = 64; }
|
|
8
|
+
var base64Input = Buffer.from(input, "binary").toString("base64");
|
|
9
|
+
var columnisedInput = chunked(base64Input, chunkSize).join("\n");
|
|
10
|
+
// if the last line is exactly 64 columns, add an extra newline
|
|
11
|
+
var paddedFooter = footer;
|
|
12
|
+
if (columnisedInput.length > 0 &&
|
|
13
|
+
columnisedInput[columnisedInput.length - 1].length === 64) {
|
|
14
|
+
paddedFooter = "\n" + footer;
|
|
15
|
+
}
|
|
16
|
+
return "".concat(header, "\n").concat(columnisedInput, "\n").concat(paddedFooter, "\n");
|
|
17
|
+
}
|
|
18
|
+
// takes an armored payload and decodes it if it is an AGE armor payload
|
|
19
|
+
// and it satisfies some security properties
|
|
20
|
+
export function decodeArmor(armor, chunkSize) {
|
|
21
|
+
if (chunkSize === void 0) { chunkSize = 64; }
|
|
22
|
+
// could start/end with space or newlines, let's strip them
|
|
23
|
+
armor = armor.trimStart();
|
|
24
|
+
var lengthBeforeEndTrim = armor.length;
|
|
25
|
+
armor = armor.trimEnd();
|
|
26
|
+
var lengthAfterTrim = armor.length;
|
|
27
|
+
// for compliance with the go age implementation, we deny more than 1024 whitespace chars:
|
|
28
|
+
// see: https://github.com/FiloSottile/age/blob/8e3f74c283b2e9b3cd0ec661fa4008504e536d20/armor/armor.go#L104
|
|
29
|
+
var trimmedWhitespace = lengthBeforeEndTrim - lengthAfterTrim;
|
|
30
|
+
if (trimmedWhitespace > 1024) {
|
|
31
|
+
throw Error("too much whitespace at the end of the armor payload");
|
|
32
|
+
}
|
|
33
|
+
if (!armor.startsWith(header)) {
|
|
34
|
+
throw Error("Armor cannot be decoded if it does not start with a header! i.e. ".concat(header));
|
|
35
|
+
}
|
|
36
|
+
if (!armor.endsWith(footer)) {
|
|
37
|
+
throw Error("Armor cannot be decoded if it does not end with a footer! i.e. ".concat(footer));
|
|
38
|
+
}
|
|
39
|
+
var base64Payload = armor.slice(header.length, armor.length - footer.length);
|
|
40
|
+
var lines = base64Payload.split("\n");
|
|
41
|
+
if (lines.some(function (line) { return line.length > chunkSize; })) {
|
|
42
|
+
throw Error("Armor to decode cannot have lines longer than ".concat(chunkSize, " (configurable) in order to stop padding attacks"));
|
|
43
|
+
}
|
|
44
|
+
if (lines[lines.length - 1].length >= chunkSize) {
|
|
45
|
+
throw Error("The last line of an armored payload must be less than ".concat(chunkSize, " (configurable) to stop padding attacks"));
|
|
46
|
+
}
|
|
47
|
+
return Buffer.from(base64Payload, "base64").toString("binary");
|
|
48
|
+
}
|
|
49
|
+
export function isProbablyArmored(input) {
|
|
50
|
+
return input.startsWith(header);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=armor.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var noOpType = "no-op";
|
|
38
|
+
// if you wish to encrypt with AGE but simply pass the filekey in the recipient stanza, then use this
|
|
39
|
+
// protip: you probably don't!
|
|
40
|
+
var NoOpEncDec = /** @class */ (function () {
|
|
41
|
+
function NoOpEncDec() {
|
|
42
|
+
}
|
|
43
|
+
NoOpEncDec.wrap = function (filekey) {
|
|
44
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
45
|
+
return __generator(this, function (_a) {
|
|
46
|
+
return [2 /*return*/, [
|
|
47
|
+
{
|
|
48
|
+
type: noOpType,
|
|
49
|
+
args: [],
|
|
50
|
+
body: filekey,
|
|
51
|
+
},
|
|
52
|
+
]];
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
NoOpEncDec.unwrap = function (recipients) {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
58
|
+
return __generator(this, function (_a) {
|
|
59
|
+
if (recipients.length !== 1) {
|
|
60
|
+
throw Error("NoOpEncDec only expects a single stanza!");
|
|
61
|
+
}
|
|
62
|
+
if (recipients[0].type !== noOpType) {
|
|
63
|
+
throw Error("NoOpEncDec expects the type of the stanza to be ".concat(noOpType));
|
|
64
|
+
}
|
|
65
|
+
return [2 /*return*/, recipients[0].body];
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
return NoOpEncDec;
|
|
70
|
+
}());
|
|
71
|
+
export { NoOpEncDec };
|
|
72
|
+
//# sourceMappingURL=no-op-encdec.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// largely taken from https://github.com/paulmillr/jage,
|
|
2
|
+
// but hooking in a browser compatible ChaCha20 implementation
|
|
3
|
+
// STREAM cipher
|
|
4
|
+
// https://eprint.iacr.org/2015/189.pdf
|
|
5
|
+
// age spec:
|
|
6
|
+
// After the header the binary payload is nonce || STREAM[HKDF[nonce, "payload"](file key)](plaintext) where nonce is random(16) and STREAM is from Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance with ChaCha20-Poly1305 in 64KiB chunks and a nonce structure of 11 bytes of big endian counter, and 1 byte of last block flag (0x00 / 0x01). (The STREAM scheme is similar to the one Tink and Miscreant use, but without nonce prefix as we use HKDF, and with ChaCha20-Poly1305 instead of AES-GCM because the latter is unreasonably hard to do well or fast without hardware support.)
|
|
7
|
+
import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305";
|
|
8
|
+
var CHUNK_SIZE = 64 * 1024; // 64 KiB
|
|
9
|
+
var TAG_SIZE = 16; // Poly1305 MAC size
|
|
10
|
+
var ENCRYPTED_CHUNK_SIZE = CHUNK_SIZE + TAG_SIZE;
|
|
11
|
+
var NONCE_SIZE = 12; // STREAM nonce size
|
|
12
|
+
// due to using a 32bit uint for the counter, this is the max
|
|
13
|
+
// value the counter can be without risking a nonce reuse
|
|
14
|
+
var COUNTER_MAX = Math.pow(2, 32) - 1;
|
|
15
|
+
var STREAM = /** @class */ (function () {
|
|
16
|
+
function STREAM(key) {
|
|
17
|
+
this.key = key.slice();
|
|
18
|
+
this.nonce = new Uint8Array(NONCE_SIZE);
|
|
19
|
+
this.nonceView = new DataView(this.nonce.buffer);
|
|
20
|
+
this.counter = 0;
|
|
21
|
+
}
|
|
22
|
+
STREAM.seal = function (plaintext, privateKey) {
|
|
23
|
+
var stream = new STREAM(privateKey);
|
|
24
|
+
var chunks = Math.ceil(plaintext.length / CHUNK_SIZE);
|
|
25
|
+
var ciphertext = new Uint8Array(plaintext.length + chunks * TAG_SIZE);
|
|
26
|
+
for (var chunk64kb = 1; chunk64kb <= chunks; chunk64kb++) {
|
|
27
|
+
var start = chunk64kb - 1;
|
|
28
|
+
var end = chunk64kb;
|
|
29
|
+
var isLast = chunk64kb === chunks;
|
|
30
|
+
var input = plaintext.slice(start * CHUNK_SIZE, end * CHUNK_SIZE);
|
|
31
|
+
var output = ciphertext.subarray(start * ENCRYPTED_CHUNK_SIZE, end * ENCRYPTED_CHUNK_SIZE);
|
|
32
|
+
stream.encryptChunk(input, isLast, output);
|
|
33
|
+
}
|
|
34
|
+
stream.clear();
|
|
35
|
+
return ciphertext;
|
|
36
|
+
};
|
|
37
|
+
STREAM.open = function (ciphertext, privateKey) {
|
|
38
|
+
var stream = new STREAM(privateKey);
|
|
39
|
+
var chunks = Math.ceil(ciphertext.length / ENCRYPTED_CHUNK_SIZE);
|
|
40
|
+
var plaintext = new Uint8Array(ciphertext.length - chunks * TAG_SIZE);
|
|
41
|
+
for (var chunk64kb = 1; chunk64kb <= chunks; chunk64kb++) {
|
|
42
|
+
var start = chunk64kb - 1;
|
|
43
|
+
var end = chunk64kb;
|
|
44
|
+
var isLast = chunk64kb === chunks;
|
|
45
|
+
var input = ciphertext.slice(start * ENCRYPTED_CHUNK_SIZE, end * ENCRYPTED_CHUNK_SIZE);
|
|
46
|
+
var output = plaintext.subarray(start * CHUNK_SIZE, end * CHUNK_SIZE);
|
|
47
|
+
stream.decryptChunk(input, isLast, output);
|
|
48
|
+
}
|
|
49
|
+
stream.clear();
|
|
50
|
+
return plaintext;
|
|
51
|
+
};
|
|
52
|
+
STREAM.prototype.encryptChunk = function (chunk, isLast, output) {
|
|
53
|
+
if (chunk.length > CHUNK_SIZE)
|
|
54
|
+
throw new Error("Chunk is too big");
|
|
55
|
+
if (this.nonce[11] === 1)
|
|
56
|
+
throw new Error("Last chunk has been processed");
|
|
57
|
+
if (isLast)
|
|
58
|
+
this.nonce[11] = 1;
|
|
59
|
+
var ciphertext = new ChaCha20Poly1305(this.key).seal(this.nonce, chunk);
|
|
60
|
+
output.set(ciphertext);
|
|
61
|
+
this.incrementCounter();
|
|
62
|
+
};
|
|
63
|
+
STREAM.prototype.decryptChunk = function (chunk, isLast, output) {
|
|
64
|
+
if (chunk.length > ENCRYPTED_CHUNK_SIZE)
|
|
65
|
+
throw new Error("Chunk is too big");
|
|
66
|
+
if (this.nonce[11] === 1)
|
|
67
|
+
throw new Error("Last chunk has been processed");
|
|
68
|
+
if (isLast)
|
|
69
|
+
this.nonce[11] = 1;
|
|
70
|
+
var plaintext = new ChaCha20Poly1305(this.key).open(this.nonce, chunk);
|
|
71
|
+
if (plaintext == null) {
|
|
72
|
+
throw Error("Error during decryption!");
|
|
73
|
+
}
|
|
74
|
+
output.set(plaintext);
|
|
75
|
+
this.incrementCounter();
|
|
76
|
+
};
|
|
77
|
+
// Increments Big Endian Uint8Array-based counter.
|
|
78
|
+
// [0, 0, 0] => [0, 0, 1] ... => [0, 0, 255] => [0, 1, 0]
|
|
79
|
+
STREAM.prototype.incrementCounter = function () {
|
|
80
|
+
if (this.counter == COUNTER_MAX) {
|
|
81
|
+
throw new Error("Stream cipher counter has already hit max value! Aborting to avoid nonce reuse - tlock only supports payloads up to 256TB");
|
|
82
|
+
}
|
|
83
|
+
this.counter += 1;
|
|
84
|
+
this.nonceView.setUint32(7, this.counter, false);
|
|
85
|
+
};
|
|
86
|
+
STREAM.prototype.clear = function () {
|
|
87
|
+
function clear(arr) {
|
|
88
|
+
for (var i = 0; i < arr.length; i++) {
|
|
89
|
+
arr[i] = 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
clear(this.key);
|
|
93
|
+
clear(this.nonce);
|
|
94
|
+
this.counter = 0;
|
|
95
|
+
};
|
|
96
|
+
return STREAM;
|
|
97
|
+
}());
|
|
98
|
+
export { STREAM };
|
|
99
|
+
//# sourceMappingURL=stream-cipher.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { Buffer } from "buffer";
|
|
38
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
39
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
40
|
+
import { hmac } from "@noble/hashes/hmac";
|
|
41
|
+
import { randomBytes } from "crypto";
|
|
42
|
+
export function createMacKey(fileKey, macMessage, headerText) {
|
|
43
|
+
// empty string salt as per the spec!
|
|
44
|
+
var hmacKey = hkdf(sha256, fileKey, "", Buffer.from(macMessage, "utf8"), 32);
|
|
45
|
+
return Buffer.from(hmac(sha256, hmacKey, Buffer.from(headerText, "utf8")));
|
|
46
|
+
}
|
|
47
|
+
// returns a string of n bytes read from a CSPRNG like /dev/urandom.
|
|
48
|
+
export function random(n) {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
50
|
+
var bytes;
|
|
51
|
+
return __generator(this, function (_a) {
|
|
52
|
+
if (typeof window === "object" && "crypto" in window) {
|
|
53
|
+
return [2 /*return*/, window.crypto.getRandomValues(new Uint8Array(n))];
|
|
54
|
+
}
|
|
55
|
+
bytes = randomBytes(n);
|
|
56
|
+
return [2 /*return*/, new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)];
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=utils-crypto.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
// as per the spec:
|
|
3
|
+
// RFC 4648, Section 4
|
|
4
|
+
// without = padding characters (sometimes referred to as "raw" or "unpadded" base64)
|
|
5
|
+
export function unpaddedBase64(buf) {
|
|
6
|
+
var encodedBuf = Buffer.from(buf).toString("base64");
|
|
7
|
+
var lastIndex = encodedBuf.length - 1;
|
|
8
|
+
while (encodedBuf[lastIndex] === "=") {
|
|
9
|
+
lastIndex--;
|
|
10
|
+
}
|
|
11
|
+
return encodedBuf.slice(0, lastIndex + 1);
|
|
12
|
+
}
|
|
13
|
+
export function unpaddedBase64Buffer(buf) {
|
|
14
|
+
return Buffer.from(unpaddedBase64(buf), "base64");
|
|
15
|
+
}
|
|
16
|
+
/*
|
|
17
|
+
e.g. chunked("hello world", 2, ".") returns
|
|
18
|
+
["he.", "ll.", "o .", "wo.", "rl.", "d."]
|
|
19
|
+
*/
|
|
20
|
+
export function chunked(input, chunkSize, suffix) {
|
|
21
|
+
if (suffix === void 0) { suffix = ""; }
|
|
22
|
+
var output = [];
|
|
23
|
+
var currentChunk = "";
|
|
24
|
+
for (var i = 0, chunks = 0; i < input.length; i++) {
|
|
25
|
+
currentChunk += input[i];
|
|
26
|
+
var posInChunk = i - chunks * chunkSize;
|
|
27
|
+
if (posInChunk === chunkSize - 1) {
|
|
28
|
+
output.push(currentChunk + suffix);
|
|
29
|
+
currentChunk = "";
|
|
30
|
+
chunks++;
|
|
31
|
+
}
|
|
32
|
+
else if (i === input.length - 1) {
|
|
33
|
+
output.push(currentChunk + suffix);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
// slices the input string up to and including the first
|
|
39
|
+
// occurrence of the string provided in `searchTerm`
|
|
40
|
+
// returns the whole string if it's not found
|
|
41
|
+
// e.g. sliceUntil("hello world", "ll") will return "hell"
|
|
42
|
+
export function sliceUntil(input, searchTerm) {
|
|
43
|
+
var lettersMatched = 0;
|
|
44
|
+
var inputPointer = 0;
|
|
45
|
+
while (inputPointer < input.length && lettersMatched < searchTerm.length) {
|
|
46
|
+
if (input[inputPointer] === searchTerm[lettersMatched]) {
|
|
47
|
+
++lettersMatched;
|
|
48
|
+
}
|
|
49
|
+
else if (input[inputPointer] === searchTerm[0]) {
|
|
50
|
+
lettersMatched = 1;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
lettersMatched = 0;
|
|
54
|
+
}
|
|
55
|
+
++inputPointer;
|
|
56
|
+
}
|
|
57
|
+
return input.slice(0, inputPointer);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=utils.js.map
|