@opengovsg/formsg-sdk 0.10.0 → 0.12.0-alpha.1
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/.github/workflows/ci.yml +6 -4
- package/LICENSE.md +21 -0
- package/README.md +8 -1
- package/dist/crypto-base.d.ts +25 -0
- package/dist/crypto-base.js +93 -0
- package/dist/crypto-v3.d.ts +37 -0
- package/dist/crypto-v3.js +134 -0
- package/dist/crypto.d.ts +4 -25
- package/dist/crypto.js +43 -64
- package/dist/errors.js +4 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -2
- package/dist/resource/signing-keys.js +1 -0
- package/dist/resource/verification-keys.js +1 -0
- package/dist/types.d.ts +37 -18
- package/dist/util/crypto.d.ts +3 -3
- package/dist/util/crypto.js +22 -15
- package/dist/util/parser.d.ts +2 -2
- package/dist/util/parser.js +5 -2
- package/dist/util/publicKey.js +1 -0
- package/dist/util/signature.js +21 -4
- package/dist/util/validate.d.ts +3 -2
- package/dist/util/validate.js +13 -0
- package/dist/util/webhooks.js +21 -4
- package/dist/verification/index.js +9 -9
- package/dist/verification/utils.js +6 -3
- package/dist/webhooks.js +27 -11
- package/package.json +9 -9
package/.github/workflows/ci.yml
CHANGED
|
@@ -6,13 +6,13 @@ on:
|
|
|
6
6
|
jobs:
|
|
7
7
|
build:
|
|
8
8
|
name: build
|
|
9
|
-
runs-on: ubuntu-
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
10
|
steps:
|
|
11
11
|
- uses: actions/checkout@v2
|
|
12
12
|
- name: Use Node.js
|
|
13
13
|
uses: actions/setup-node@v1
|
|
14
14
|
with:
|
|
15
|
-
node-version:
|
|
15
|
+
node-version: 18
|
|
16
16
|
- name: Cache Node.js modules
|
|
17
17
|
uses: actions/cache@v2
|
|
18
18
|
with:
|
|
@@ -27,13 +27,13 @@ jobs:
|
|
|
27
27
|
- run: npm run build
|
|
28
28
|
test:
|
|
29
29
|
name: test
|
|
30
|
-
runs-on: ubuntu-
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
31
|
steps:
|
|
32
32
|
- uses: actions/checkout@v2
|
|
33
33
|
- name: Use Node.js
|
|
34
34
|
uses: actions/setup-node@v1
|
|
35
35
|
with:
|
|
36
|
-
node-version:
|
|
36
|
+
node-version: 18
|
|
37
37
|
- name: Cache Node.js modules
|
|
38
38
|
uses: actions/cache@v2
|
|
39
39
|
with:
|
|
@@ -45,6 +45,8 @@ jobs:
|
|
|
45
45
|
${{ runner.OS }}-
|
|
46
46
|
- run: npm ci
|
|
47
47
|
- run: npm run test-ci
|
|
48
|
+
env:
|
|
49
|
+
NODE_OPTIONS: '--max-old-space-size=8192'
|
|
48
50
|
- name: Submit test coverage to Coveralls
|
|
49
51
|
uses: coverallsapp/github-action@v1.1.2
|
|
50
52
|
with:
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Open Government Products
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -7,6 +7,10 @@ _Please note that this is an SDK for webhooks integration, and_ **_not_** _the F
|
|
|
7
7
|
|
|
8
8
|
This SDK provides convenient utilities for verifying FormSG webhooks and decrypting submissions in JavaScript and Node.js.
|
|
9
9
|
|
|
10
|
+
Not using Javascript? Check out our sister SDKs:
|
|
11
|
+
- [formsg-python-sdk](https://github.com/opengovsg/formsg-python-sdk)
|
|
12
|
+
- [formsg-ruby-sdk](https://github.com/opengovsg/formsg-ruby-sdk)
|
|
13
|
+
|
|
10
14
|
## Installation
|
|
11
15
|
|
|
12
16
|
Install the package with
|
|
@@ -132,7 +136,7 @@ of the returned object.
|
|
|
132
136
|
Note that due to end-to-end encryption, FormSG servers are unable to verify the data format.
|
|
133
137
|
|
|
134
138
|
However, the `decrypt` function exposed by this library [validates](https://github.com/opengovsg/formsg-javascript-sdk/blob/master/src/util/validate.ts) the decrypted content and will **return `null` if the
|
|
135
|
-
decrypted content does not
|
|
139
|
+
decrypted content does not contain all of the fields displayed in the schema below.**
|
|
136
140
|
|
|
137
141
|
| Key | Type | Description |
|
|
138
142
|
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
|
@@ -142,6 +146,9 @@ decrypted content does not fit the schema displayed below.**
|
|
|
142
146
|
| fieldType | string | The type of field for the question. |
|
|
143
147
|
| \_id | string | A unique identifier of the form field. WARNING: Changes when new fields are created/removed in the form. |
|
|
144
148
|
|
|
149
|
+
**Important Note: **
|
|
150
|
+
Additional internal fields may be included in webhooks from time to time, which will then be published as part of our official schema once it is stable for public consumption. If you are applying your own validation, you should account for this e.g. by not rejecting the webhook if there are additional fields included.
|
|
151
|
+
|
|
145
152
|
The full schema can be viewed in
|
|
146
153
|
[`validate.ts`](https://github.com/opengovsg/formsg-javascript-sdk/tree/master/src/util/validate.ts).
|
|
147
154
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EncryptedFileContent } from './types';
|
|
2
|
+
export default class CryptoBase {
|
|
3
|
+
/**
|
|
4
|
+
* Generates a new keypair for encryption.
|
|
5
|
+
* @returns The generated keypair.
|
|
6
|
+
*/
|
|
7
|
+
generate: () => import("./types").Keypair;
|
|
8
|
+
/**
|
|
9
|
+
* Encrypt given binary file with a unique keypair for each submission.
|
|
10
|
+
* @param binary The file to encrypt, should be a blob that is converted to Uint8Array binary
|
|
11
|
+
* @param publicKey The base-64 encoded public key
|
|
12
|
+
* @returns Promise holding the encrypted file
|
|
13
|
+
* @throws error if any of the encrypt methods fail
|
|
14
|
+
*/
|
|
15
|
+
encryptFile: (binary: Uint8Array, publicKey: string) => Promise<EncryptedFileContent>;
|
|
16
|
+
/**
|
|
17
|
+
* Decrypt the given encrypted file content.
|
|
18
|
+
* @param secretKey Secret key as a base-64 string
|
|
19
|
+
* @param encrypted Object returned from encryptFile function
|
|
20
|
+
* @param encrypted.submissionPublicKey The file's public key as a base-64 string
|
|
21
|
+
* @param encrypted.nonce The nonce as a base-64 string
|
|
22
|
+
* @param encrypted.blob The encrypted file as a Blob object
|
|
23
|
+
*/
|
|
24
|
+
decryptFile: (secretKey: string, { submissionPublicKey: filePublicKey, nonce, binary: encryptedBinary, }: EncryptedFileContent) => Promise<Uint8Array | null>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
43
|
+
var tweetnacl_util_1 = require("tweetnacl-util");
|
|
44
|
+
var crypto_1 = require("./util/crypto");
|
|
45
|
+
var CryptoBase = /** @class */ (function () {
|
|
46
|
+
function CryptoBase() {
|
|
47
|
+
var _this = this;
|
|
48
|
+
/**
|
|
49
|
+
* Generates a new keypair for encryption.
|
|
50
|
+
* @returns The generated keypair.
|
|
51
|
+
*/
|
|
52
|
+
this.generate = crypto_1.generateKeypair;
|
|
53
|
+
/**
|
|
54
|
+
* Encrypt given binary file with a unique keypair for each submission.
|
|
55
|
+
* @param binary The file to encrypt, should be a blob that is converted to Uint8Array binary
|
|
56
|
+
* @param publicKey The base-64 encoded public key
|
|
57
|
+
* @returns Promise holding the encrypted file
|
|
58
|
+
* @throws error if any of the encrypt methods fail
|
|
59
|
+
*/
|
|
60
|
+
this.encryptFile = function (binary, publicKey) { return __awaiter(_this, void 0, void 0, function () {
|
|
61
|
+
var fileKeypair, nonce;
|
|
62
|
+
return __generator(this, function (_a) {
|
|
63
|
+
fileKeypair = this.generate();
|
|
64
|
+
nonce = tweetnacl_1.default.randomBytes(24);
|
|
65
|
+
return [2 /*return*/, {
|
|
66
|
+
//! NOTE: submissionPublicKey here is a misnomer as a new keypair is generated per file.
|
|
67
|
+
// The naming is only retained for backward-compatibility purposes.
|
|
68
|
+
submissionPublicKey: fileKeypair.publicKey,
|
|
69
|
+
nonce: (0, tweetnacl_util_1.encodeBase64)(nonce),
|
|
70
|
+
binary: tweetnacl_1.default.box(binary, nonce, (0, tweetnacl_util_1.decodeBase64)(publicKey), (0, tweetnacl_util_1.decodeBase64)(fileKeypair.secretKey)),
|
|
71
|
+
}];
|
|
72
|
+
});
|
|
73
|
+
}); };
|
|
74
|
+
/**
|
|
75
|
+
* Decrypt the given encrypted file content.
|
|
76
|
+
* @param secretKey Secret key as a base-64 string
|
|
77
|
+
* @param encrypted Object returned from encryptFile function
|
|
78
|
+
* @param encrypted.submissionPublicKey The file's public key as a base-64 string
|
|
79
|
+
* @param encrypted.nonce The nonce as a base-64 string
|
|
80
|
+
* @param encrypted.blob The encrypted file as a Blob object
|
|
81
|
+
*/
|
|
82
|
+
this.decryptFile = function (secretKey, _a) {
|
|
83
|
+
var filePublicKey = _a.submissionPublicKey, nonce = _a.nonce, encryptedBinary = _a.binary;
|
|
84
|
+
return __awaiter(_this, void 0, void 0, function () {
|
|
85
|
+
return __generator(this, function (_b) {
|
|
86
|
+
return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, (0, tweetnacl_util_1.decodeBase64)(nonce), (0, tweetnacl_util_1.decodeBase64)(filePublicKey), (0, tweetnacl_util_1.decodeBase64)(secretKey))];
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return CryptoBase;
|
|
92
|
+
}());
|
|
93
|
+
exports.default = CryptoBase;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import CryptoBase from './crypto-base';
|
|
2
|
+
import { DecryptedContentV3, DecryptParams, DecryptParamsV3, EncryptedContentV3 } from './types';
|
|
3
|
+
export default class CryptoV3 extends CryptoBase {
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Encrypt input with a unique keypair for each submission.
|
|
7
|
+
* @param msg The message to encrypt, will be stringified.
|
|
8
|
+
* @param form The base-64 encoded form public key for encrypting.
|
|
9
|
+
* @returns The encrypted basestring.
|
|
10
|
+
*/
|
|
11
|
+
encrypt: (msg: any, formPublicKey: string) => EncryptedContentV3;
|
|
12
|
+
/**
|
|
13
|
+
* Decrypts an encrypted submission and returns it.
|
|
14
|
+
* @param submissionSecretKey The base-64 encoded secret key for decrypting.
|
|
15
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
16
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
17
|
+
* @param decryptParams.version The version of the payload.
|
|
18
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
19
|
+
*/
|
|
20
|
+
decryptFromSubmissionKey: (submissionSecretKey: string, decryptParams: DecryptParams) => DecryptedContentV3 | null;
|
|
21
|
+
/**
|
|
22
|
+
* Decrypts an encrypted submission and returns it.
|
|
23
|
+
* @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
|
|
24
|
+
* @param decryptParams The params containing encrypted content, encrypted submission key and information.
|
|
25
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
26
|
+
* @param decryptParams.encryptedSubmissionSecretKey The encrypted submission secret key encoded with base-64.
|
|
27
|
+
* @param decryptParams.version The version of the payload. Used to determine the decryption process to decrypt the content with.
|
|
28
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
29
|
+
*/
|
|
30
|
+
decrypt: (formSecretKey: string, decryptParams: DecryptParamsV3) => DecryptedContentV3 | null;
|
|
31
|
+
/**
|
|
32
|
+
* Returns true if a pair of public & secret keys are associated with each other
|
|
33
|
+
* @param publicKey The public key to verify against.
|
|
34
|
+
* @param secretKey The private key to verify against.
|
|
35
|
+
*/
|
|
36
|
+
valid: (publicKey: string, secretKey: string) => boolean;
|
|
37
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __assign = (this && this.__assign) || function () {
|
|
18
|
+
__assign = Object.assign || function(t) {
|
|
19
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
20
|
+
s = arguments[i];
|
|
21
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
22
|
+
t[p] = s[p];
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
};
|
|
26
|
+
return __assign.apply(this, arguments);
|
|
27
|
+
};
|
|
28
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
29
|
+
var t = {};
|
|
30
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
31
|
+
t[p] = s[p];
|
|
32
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
33
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
34
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
35
|
+
t[p[i]] = s[p[i]];
|
|
36
|
+
}
|
|
37
|
+
return t;
|
|
38
|
+
};
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
var tweetnacl_util_1 = require("tweetnacl-util");
|
|
44
|
+
var crypto_1 = require("./util/crypto");
|
|
45
|
+
var validate_1 = require("./util/validate");
|
|
46
|
+
var crypto_base_1 = __importDefault(require("./crypto-base"));
|
|
47
|
+
var CryptoV3 = /** @class */ (function (_super) {
|
|
48
|
+
__extends(CryptoV3, _super);
|
|
49
|
+
function CryptoV3() {
|
|
50
|
+
var _this = _super.call(this) || this;
|
|
51
|
+
/**
|
|
52
|
+
* Encrypt input with a unique keypair for each submission.
|
|
53
|
+
* @param msg The message to encrypt, will be stringified.
|
|
54
|
+
* @param form The base-64 encoded form public key for encrypting.
|
|
55
|
+
* @returns The encrypted basestring.
|
|
56
|
+
*/
|
|
57
|
+
_this.encrypt = function (msg, formPublicKey) {
|
|
58
|
+
var submissionKeypair = (0, crypto_1.generateKeypair)();
|
|
59
|
+
var encryptedSubmissionSecretKey = (0, crypto_1.encryptMessage)((0, tweetnacl_util_1.decodeBase64)(submissionKeypair.secretKey), formPublicKey);
|
|
60
|
+
var processedMsg = (0, tweetnacl_util_1.decodeUTF8)(JSON.stringify(msg));
|
|
61
|
+
var encryptedContent = (0, crypto_1.encryptMessage)(processedMsg, submissionKeypair.publicKey);
|
|
62
|
+
return {
|
|
63
|
+
submissionPublicKey: submissionKeypair.publicKey,
|
|
64
|
+
submissionSecretKey: submissionKeypair.secretKey,
|
|
65
|
+
encryptedContent: encryptedContent,
|
|
66
|
+
encryptedSubmissionSecretKey: encryptedSubmissionSecretKey,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Decrypts an encrypted submission and returns it.
|
|
71
|
+
* @param submissionSecretKey The base-64 encoded secret key for decrypting.
|
|
72
|
+
* @param decryptParams The params containing encrypted content and information.
|
|
73
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
74
|
+
* @param decryptParams.version The version of the payload.
|
|
75
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
76
|
+
*/
|
|
77
|
+
_this.decryptFromSubmissionKey = function (submissionSecretKey, decryptParams) {
|
|
78
|
+
try {
|
|
79
|
+
var encryptedContent = decryptParams.encryptedContent;
|
|
80
|
+
// Do not return the transformed object in `_decrypt` function as a signed
|
|
81
|
+
// object is not encoded in UTF8 and is encoded in Base-64 instead.
|
|
82
|
+
var decryptedContent = (0, crypto_1.decryptContent)(submissionSecretKey, encryptedContent);
|
|
83
|
+
if (!decryptedContent) {
|
|
84
|
+
throw new Error('Failed to decrypt content');
|
|
85
|
+
}
|
|
86
|
+
var decryptedObject = JSON.parse((0, tweetnacl_util_1.encodeUTF8)(decryptedContent));
|
|
87
|
+
if (!(0, validate_1.determineIsFormFieldsV3)(decryptedObject)) {
|
|
88
|
+
throw new Error('Decrypted object does not fit expected shape');
|
|
89
|
+
}
|
|
90
|
+
var returnedObject = {
|
|
91
|
+
submissionSecretKey: submissionSecretKey,
|
|
92
|
+
responses: decryptedObject,
|
|
93
|
+
};
|
|
94
|
+
return returnedObject;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Decrypts an encrypted submission and returns it.
|
|
102
|
+
* @param formSecretKey The base-64 encoded form secret key for decrypting the submission.
|
|
103
|
+
* @param decryptParams The params containing encrypted content, encrypted submission key and information.
|
|
104
|
+
* @param decryptParams.encryptedContent The encrypted content encoded with base-64.
|
|
105
|
+
* @param decryptParams.encryptedSubmissionSecretKey The encrypted submission secret key encoded with base-64.
|
|
106
|
+
* @param decryptParams.version The version of the payload. Used to determine the decryption process to decrypt the content with.
|
|
107
|
+
* @returns The decrypted content if successful. Else, null will be returned.
|
|
108
|
+
*/
|
|
109
|
+
_this.decrypt = function (formSecretKey, decryptParams) {
|
|
110
|
+
var encryptedSubmissionSecretKey = decryptParams.encryptedSubmissionSecretKey, rest = __rest(decryptParams, ["encryptedSubmissionSecretKey"]);
|
|
111
|
+
var submissionSecretKey = (0, crypto_1.decryptContent)(formSecretKey, encryptedSubmissionSecretKey);
|
|
112
|
+
if (submissionSecretKey === null)
|
|
113
|
+
return null;
|
|
114
|
+
return _this.decryptFromSubmissionKey((0, tweetnacl_util_1.encodeBase64)(submissionSecretKey), rest);
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Returns true if a pair of public & secret keys are associated with each other
|
|
118
|
+
* @param publicKey The public key to verify against.
|
|
119
|
+
* @param secretKey The private key to verify against.
|
|
120
|
+
*/
|
|
121
|
+
_this.valid = function (publicKey, secretKey) {
|
|
122
|
+
var _a;
|
|
123
|
+
var testResponse = {};
|
|
124
|
+
var internalValidationVersion = 3;
|
|
125
|
+
var cipherResponse = _this.encrypt(testResponse, publicKey);
|
|
126
|
+
// Use toString here since the return should be an empty array.
|
|
127
|
+
return (testResponse.toString() ===
|
|
128
|
+
((_a = _this.decrypt(secretKey, __assign(__assign({}, cipherResponse), { version: internalValidationVersion }))) === null || _a === void 0 ? void 0 : _a.responses.toString()));
|
|
129
|
+
};
|
|
130
|
+
return _this;
|
|
131
|
+
}
|
|
132
|
+
return CryptoV3;
|
|
133
|
+
}(crypto_base_1.default));
|
|
134
|
+
exports.default = CryptoV3;
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import CryptoBase from './crypto-base';
|
|
2
|
+
import { DecryptedContent, DecryptedContentAndAttachments, DecryptParams, EncryptedContent } from './types';
|
|
3
|
+
export default class Crypto extends CryptoBase {
|
|
3
4
|
signingPublicKey?: string;
|
|
4
5
|
constructor({ signingPublicKey }?: {
|
|
5
6
|
signingPublicKey?: string;
|
|
@@ -11,7 +12,7 @@ export default class Crypto {
|
|
|
11
12
|
* @param signingPrivateKey Optional. Must be a base-64 encoded private key. If given, will be used to signing the given msg param prior to encrypting.
|
|
12
13
|
* @returns The encrypted basestring.
|
|
13
14
|
*/
|
|
14
|
-
encrypt: (msg: any, encryptionPublicKey: string, signingPrivateKey?: string
|
|
15
|
+
encrypt: (msg: any, encryptionPublicKey: string, signingPrivateKey?: string) => EncryptedContent;
|
|
15
16
|
/**
|
|
16
17
|
* Decrypts an encrypted submission and returns it.
|
|
17
18
|
* @param formSecretKey The base-64 secret key of the form to decrypt with.
|
|
@@ -23,34 +24,12 @@ export default class Crypto {
|
|
|
23
24
|
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
24
25
|
*/
|
|
25
26
|
decrypt: (formSecretKey: string, decryptParams: DecryptParams) => DecryptedContent | null;
|
|
26
|
-
/**
|
|
27
|
-
* Generates a new keypair for encryption.
|
|
28
|
-
* @returns The generated keypair.
|
|
29
|
-
*/
|
|
30
|
-
generate: () => import("./types").Keypair;
|
|
31
27
|
/**
|
|
32
28
|
* Returns true if a pair of public & secret keys are associated with each other
|
|
33
29
|
* @param publicKey The public key to verify against.
|
|
34
30
|
* @param secretKey The private key to verify against.
|
|
35
31
|
*/
|
|
36
32
|
valid: (publicKey: string, secretKey: string) => boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Encrypt given binary file with a unique keypair for each submission.
|
|
39
|
-
* @param binary The file to encrypt, should be a blob that is converted to Uint8Array binary
|
|
40
|
-
* @param formPublicKey The base-64 encoded public key
|
|
41
|
-
* @returns Promise holding the encrypted file
|
|
42
|
-
* @throws error if any of the encrypt methods fail
|
|
43
|
-
*/
|
|
44
|
-
encryptFile: (binary: Uint8Array, formPublicKey: string) => Promise<EncryptedFileContent>;
|
|
45
|
-
/**
|
|
46
|
-
* Decrypt the given encrypted file content.
|
|
47
|
-
* @param formSecretKey Secret key as a base-64 string
|
|
48
|
-
* @param encrypted Object returned from encryptFile function
|
|
49
|
-
* @param encrypted.submissionPublicKey The submission public key as a base-64 string
|
|
50
|
-
* @param encrypted.nonce The nonce as a base-64 string
|
|
51
|
-
* @param encrypted.blob The encrypted file as a Blob object
|
|
52
|
-
*/
|
|
53
|
-
decryptFile: (formSecretKey: string, { submissionPublicKey, nonce, binary: encryptedBinary, }: EncryptedFileContent) => Promise<Uint8Array | null>;
|
|
54
33
|
/**
|
|
55
34
|
* Decrypts an encrypted submission, and also download and decrypt any attachments alongside it.
|
|
56
35
|
* @param formSecretKey Secret key as a base-64 string
|
package/dist/crypto.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
2
17
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
18
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
19
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -14,7 +29,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
14
29
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
30
|
function step(op) {
|
|
16
31
|
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (_) try {
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
33
|
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;
|
|
19
34
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
35
|
switch (op[0]) {
|
|
@@ -44,11 +59,13 @@ var tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
|
44
59
|
var tweetnacl_util_1 = require("tweetnacl-util");
|
|
45
60
|
var crypto_1 = require("./util/crypto");
|
|
46
61
|
var validate_1 = require("./util/validate");
|
|
62
|
+
var crypto_base_1 = __importDefault(require("./crypto-base"));
|
|
47
63
|
var errors_1 = require("./errors");
|
|
48
|
-
var Crypto = /** @class */ (function () {
|
|
64
|
+
var Crypto = /** @class */ (function (_super) {
|
|
65
|
+
__extends(Crypto, _super);
|
|
49
66
|
function Crypto(_a) {
|
|
50
|
-
var
|
|
51
|
-
var
|
|
67
|
+
var _b = _a === void 0 ? {} : _a, signingPublicKey = _b.signingPublicKey;
|
|
68
|
+
var _this = _super.call(this) || this;
|
|
52
69
|
/**
|
|
53
70
|
* Encrypt input with a unique keypair for each submission
|
|
54
71
|
* @param encryptionPublicKey The base-64 encoded public key for encrypting.
|
|
@@ -56,12 +73,12 @@ var Crypto = /** @class */ (function () {
|
|
|
56
73
|
* @param signingPrivateKey Optional. Must be a base-64 encoded private key. If given, will be used to signing the given msg param prior to encrypting.
|
|
57
74
|
* @returns The encrypted basestring.
|
|
58
75
|
*/
|
|
59
|
-
|
|
60
|
-
var processedMsg = tweetnacl_util_1.decodeUTF8(JSON.stringify(msg));
|
|
76
|
+
_this.encrypt = function (msg, encryptionPublicKey, signingPrivateKey) {
|
|
77
|
+
var processedMsg = (0, tweetnacl_util_1.decodeUTF8)(JSON.stringify(msg));
|
|
61
78
|
if (signingPrivateKey) {
|
|
62
|
-
processedMsg = tweetnacl_1.default.sign(processedMsg, tweetnacl_util_1.decodeBase64(signingPrivateKey));
|
|
79
|
+
processedMsg = tweetnacl_1.default.sign(processedMsg, (0, tweetnacl_util_1.decodeBase64)(signingPrivateKey));
|
|
63
80
|
}
|
|
64
|
-
return crypto_1.encryptMessage(processedMsg, encryptionPublicKey);
|
|
81
|
+
return (0, crypto_1.encryptMessage)(processedMsg, encryptionPublicKey);
|
|
65
82
|
};
|
|
66
83
|
/**
|
|
67
84
|
* Decrypts an encrypted submission and returns it.
|
|
@@ -73,17 +90,17 @@ var Crypto = /** @class */ (function () {
|
|
|
73
90
|
* @returns The decrypted content if successful. Else, null will be returned.
|
|
74
91
|
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
75
92
|
*/
|
|
76
|
-
|
|
93
|
+
_this.decrypt = function (formSecretKey, decryptParams) {
|
|
77
94
|
try {
|
|
78
95
|
var encryptedContent = decryptParams.encryptedContent, verifiedContent = decryptParams.verifiedContent;
|
|
79
96
|
// Do not return the transformed object in `_decrypt` function as a signed
|
|
80
97
|
// object is not encoded in UTF8 and is encoded in Base-64 instead.
|
|
81
|
-
var decryptedContent = crypto_1.decryptContent(formSecretKey, encryptedContent);
|
|
98
|
+
var decryptedContent = (0, crypto_1.decryptContent)(formSecretKey, encryptedContent);
|
|
82
99
|
if (!decryptedContent) {
|
|
83
100
|
throw new Error('Failed to decrypt content');
|
|
84
101
|
}
|
|
85
|
-
var decryptedObject = JSON.parse(tweetnacl_util_1.encodeUTF8(decryptedContent));
|
|
86
|
-
if (!validate_1.determineIsFormFields(decryptedObject)) {
|
|
102
|
+
var decryptedObject = JSON.parse((0, tweetnacl_util_1.encodeUTF8)(decryptedContent));
|
|
103
|
+
if (!(0, validate_1.determineIsFormFields)(decryptedObject)) {
|
|
87
104
|
throw new Error('Decrypted object does not fit expected shape');
|
|
88
105
|
}
|
|
89
106
|
var returnedObject = {
|
|
@@ -96,12 +113,12 @@ var Crypto = /** @class */ (function () {
|
|
|
96
113
|
// Only care if it is the correct shape if verifiedContent exists, since
|
|
97
114
|
// we need to append it to the end.
|
|
98
115
|
// Decrypted message must be able to be authenticated by the public key.
|
|
99
|
-
var decryptedVerifiedContent = crypto_1.decryptContent(formSecretKey, verifiedContent);
|
|
116
|
+
var decryptedVerifiedContent = (0, crypto_1.decryptContent)(formSecretKey, verifiedContent);
|
|
100
117
|
if (!decryptedVerifiedContent) {
|
|
101
118
|
// Returns null if decrypting verified content failed.
|
|
102
119
|
throw new Error('Failed to decrypt verified content');
|
|
103
120
|
}
|
|
104
|
-
var decryptedVerifiedObject = crypto_1.verifySignedMessage(decryptedVerifiedContent, _this.signingPublicKey);
|
|
121
|
+
var decryptedVerifiedObject = (0, crypto_1.verifySignedMessage)(decryptedVerifiedContent, _this.signingPublicKey);
|
|
105
122
|
returnedObject.verified = decryptedVerifiedObject;
|
|
106
123
|
}
|
|
107
124
|
return returnedObject;
|
|
@@ -116,61 +133,22 @@ var Crypto = /** @class */ (function () {
|
|
|
116
133
|
return null;
|
|
117
134
|
}
|
|
118
135
|
};
|
|
119
|
-
/**
|
|
120
|
-
* Generates a new keypair for encryption.
|
|
121
|
-
* @returns The generated keypair.
|
|
122
|
-
*/
|
|
123
|
-
this.generate = crypto_1.generateKeypair;
|
|
124
136
|
/**
|
|
125
137
|
* Returns true if a pair of public & secret keys are associated with each other
|
|
126
138
|
* @param publicKey The public key to verify against.
|
|
127
139
|
* @param secretKey The private key to verify against.
|
|
128
140
|
*/
|
|
129
|
-
|
|
141
|
+
_this.valid = function (publicKey, secretKey) {
|
|
130
142
|
var _a;
|
|
131
143
|
var testResponse = [];
|
|
132
144
|
var internalValidationVersion = 1;
|
|
133
145
|
var cipherResponse = _this.encrypt(testResponse, publicKey);
|
|
134
146
|
// Use toString here since the return should be an empty array.
|
|
135
|
-
return (testResponse.toString() ===
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Encrypt given binary file with a unique keypair for each submission.
|
|
142
|
-
* @param binary The file to encrypt, should be a blob that is converted to Uint8Array binary
|
|
143
|
-
* @param formPublicKey The base-64 encoded public key
|
|
144
|
-
* @returns Promise holding the encrypted file
|
|
145
|
-
* @throws error if any of the encrypt methods fail
|
|
146
|
-
*/
|
|
147
|
-
this.encryptFile = function (binary, formPublicKey) { return __awaiter(_this, void 0, void 0, function () {
|
|
148
|
-
var submissionKeypair, nonce;
|
|
149
|
-
return __generator(this, function (_a) {
|
|
150
|
-
submissionKeypair = this.generate();
|
|
151
|
-
nonce = tweetnacl_1.default.randomBytes(24);
|
|
152
|
-
return [2 /*return*/, {
|
|
153
|
-
submissionPublicKey: submissionKeypair.publicKey,
|
|
154
|
-
nonce: tweetnacl_util_1.encodeBase64(nonce),
|
|
155
|
-
binary: tweetnacl_1.default.box(binary, nonce, tweetnacl_util_1.decodeBase64(formPublicKey), tweetnacl_util_1.decodeBase64(submissionKeypair.secretKey)),
|
|
156
|
-
}];
|
|
157
|
-
});
|
|
158
|
-
}); };
|
|
159
|
-
/**
|
|
160
|
-
* Decrypt the given encrypted file content.
|
|
161
|
-
* @param formSecretKey Secret key as a base-64 string
|
|
162
|
-
* @param encrypted Object returned from encryptFile function
|
|
163
|
-
* @param encrypted.submissionPublicKey The submission public key as a base-64 string
|
|
164
|
-
* @param encrypted.nonce The nonce as a base-64 string
|
|
165
|
-
* @param encrypted.blob The encrypted file as a Blob object
|
|
166
|
-
*/
|
|
167
|
-
this.decryptFile = function (formSecretKey, _a) {
|
|
168
|
-
var submissionPublicKey = _a.submissionPublicKey, nonce = _a.nonce, encryptedBinary = _a.binary;
|
|
169
|
-
return __awaiter(_this, void 0, void 0, function () {
|
|
170
|
-
return __generator(this, function (_b) {
|
|
171
|
-
return [2 /*return*/, tweetnacl_1.default.box.open(encryptedBinary, tweetnacl_util_1.decodeBase64(nonce), tweetnacl_util_1.decodeBase64(submissionPublicKey), tweetnacl_util_1.decodeBase64(formSecretKey))];
|
|
172
|
-
});
|
|
173
|
-
});
|
|
147
|
+
return (testResponse.toString() ===
|
|
148
|
+
((_a = _this.decrypt(secretKey, {
|
|
149
|
+
encryptedContent: cipherResponse,
|
|
150
|
+
version: internalValidationVersion,
|
|
151
|
+
})) === null || _a === void 0 ? void 0 : _a.responses.toString()));
|
|
174
152
|
};
|
|
175
153
|
/**
|
|
176
154
|
* Decrypts an encrypted submission, and also download and decrypt any attachments alongside it.
|
|
@@ -179,7 +157,7 @@ var Crypto = /** @class */ (function () {
|
|
|
179
157
|
* @returns A promise of the decrypted submission, including attachments (if any). Or else returns null if a decryption error decrypting any part of the submission.
|
|
180
158
|
* @throws {MissingPublicKeyError} if a public key is not provided when instantiating this class and is needed for verifying signed content.
|
|
181
159
|
*/
|
|
182
|
-
|
|
160
|
+
_this.decryptWithAttachments = function (formSecretKey, decryptParams) { return __awaiter(_this, void 0, void 0, function () {
|
|
183
161
|
var decryptedRecords, filenames, attachmentRecords, decryptedContent, fieldIds, downloadPromises, _a;
|
|
184
162
|
var _this = this;
|
|
185
163
|
var _b;
|
|
@@ -202,7 +180,7 @@ var Crypto = /** @class */ (function () {
|
|
|
202
180
|
});
|
|
203
181
|
fieldIds = Object.keys(attachmentRecords);
|
|
204
182
|
// Check if all fieldIds are within filenames
|
|
205
|
-
if (!crypto_1.areAttachmentFieldIdsValid(fieldIds, filenames)) {
|
|
183
|
+
if (!(0, crypto_1.areAttachmentFieldIdsValid)(fieldIds, filenames)) {
|
|
206
184
|
return [2 /*return*/, null];
|
|
207
185
|
}
|
|
208
186
|
downloadPromises = fieldIds.map(function (fieldId) {
|
|
@@ -214,7 +192,7 @@ var Crypto = /** @class */ (function () {
|
|
|
214
192
|
// Decrypt all the attachments
|
|
215
193
|
.then(function (_a) {
|
|
216
194
|
var downloadResponse = _a.data;
|
|
217
|
-
var encryptedFile = crypto_1.convertEncryptedAttachmentToFileContent(downloadResponse);
|
|
195
|
+
var encryptedFile = (0, crypto_1.convertEncryptedAttachmentToFileContent)(downloadResponse);
|
|
218
196
|
return _this.decryptFile(formSecretKey, encryptedFile);
|
|
219
197
|
})
|
|
220
198
|
.then(function (decryptedFile) {
|
|
@@ -247,8 +225,9 @@ var Crypto = /** @class */ (function () {
|
|
|
247
225
|
}
|
|
248
226
|
});
|
|
249
227
|
}); };
|
|
250
|
-
|
|
228
|
+
_this.signingPublicKey = signingPublicKey;
|
|
229
|
+
return _this;
|
|
251
230
|
}
|
|
252
231
|
return Crypto;
|
|
253
|
-
}());
|
|
232
|
+
}(crypto_base_1.default));
|
|
254
233
|
exports.default = Crypto;
|