@cubist-labs/cubesigner-sdk-fs-storage 0.3.23 → 0.3.25
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/dist/index.d.ts +2 -0
- package/dist/index.js +17 -1
- package/dist/org_event_processor.d.ts +57 -0
- package/dist/org_event_processor.js +137 -0
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/org_event_processor.ts +173 -0
package/dist/index.d.ts
CHANGED
|
@@ -23,3 +23,5 @@ export declare function defaultSignerSessionStorage(): SignerSessionStorage;
|
|
|
23
23
|
* @return {Promise<SignerSession>} Existing signer session from a default file on disk.
|
|
24
24
|
*/
|
|
25
25
|
export declare function loadSignerSession(): Promise<SignerSession>;
|
|
26
|
+
/** Utils for processing org events */
|
|
27
|
+
export * from "./org_event_processor";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
18
|
};
|
|
@@ -54,4 +68,6 @@ async function loadSignerSession() {
|
|
|
54
68
|
return await cubesigner_sdk_1.SignerSession.loadSignerSession(defaultSignerSessionStorage());
|
|
55
69
|
}
|
|
56
70
|
exports.loadSignerSession = loadSignerSession;
|
|
57
|
-
|
|
71
|
+
/** Utils for processing org events */
|
|
72
|
+
__exportStar(require("./org_event_processor"), exports);
|
|
73
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxnRUFBb0c7QUFDcEcsaURBQXdEO0FBQ3hELGdEQUF3QjtBQUV4QixzQkFBc0I7QUFDdEIsK0NBQXdEO0FBQS9DLHNIQUFBLHNCQUFzQixPQUFBO0FBRS9COzs7R0FHRztBQUNILFNBQVMsU0FBUztJQUNoQixNQUFNLFNBQVMsR0FDYixPQUFPLENBQUMsUUFBUSxLQUFLLFFBQVE7UUFDM0IsQ0FBQyxDQUFDLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLDhCQUE4QjtRQUNuRCxDQUFDLENBQUMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksVUFBVSxDQUFDO0lBQ3BDLE9BQU8sY0FBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDNUMsQ0FBQztBQUVELHNEQUFzRDtBQUN6QyxRQUFBLFVBQVUsR0FBRyxTQUFTLEVBQUUsQ0FBQztBQUV0QyxzRUFBc0U7QUFDekQsUUFBQSx1QkFBdUIsR0FBRyxjQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFVLEVBQUUseUJBQXlCLENBQUMsQ0FBQztBQUV4RixrRUFBa0U7QUFDckQsUUFBQSxtQkFBbUIsR0FBRyxjQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFVLEVBQUUscUJBQXFCLENBQUMsQ0FBQztBQUVoRjs7R0FFRztBQUNILFNBQWdCLCtCQUErQjtJQUM3QyxPQUFPLElBQUkscUNBQXNCLENBQUMsK0JBQXVCLENBQUMsQ0FBQztBQUM3RCxDQUFDO0FBRkQsMEVBRUM7QUFFRDs7R0FFRztBQUNJLEtBQUssVUFBVSxxQkFBcUI7SUFDekMsT0FBTyxNQUFNLGlDQUFnQixDQUFDLHFCQUFxQixDQUFDLCtCQUErQixFQUFFLENBQUMsQ0FBQztBQUN6RixDQUFDO0FBRkQsc0RBRUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLDJCQUEyQjtJQUN6QyxPQUFPLElBQUkscUNBQXNCLENBQUMsMkJBQW1CLENBQUMsQ0FBQztBQUN6RCxDQUFDO0FBRkQsa0VBRUM7QUFFRDs7R0FFRztBQUNJLEtBQUssVUFBVSxpQkFBaUI7SUFDckMsT0FBTyxNQUFNLDhCQUFhLENBQUMsaUJBQWlCLENBQUMsMkJBQTJCLEVBQUUsQ0FBQyxDQUFDO0FBQzlFLENBQUM7QUFGRCw4Q0FFQztBQUVELHNDQUFzQztBQUN0Qyx3REFBc0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDdWJlU2lnbmVyQ2xpZW50LCBTaWduZXJTZXNzaW9uLCBTaWduZXJTZXNzaW9uU3RvcmFnZSB9IGZyb20gXCJAY3ViaXN0LWxhYnMvY3ViZXNpZ25lci1zZGtcIjtcbmltcG9ydCB7IEpzb25GaWxlU2Vzc2lvblN0b3JhZ2UgfSBmcm9tIFwiLi9maWxlX3N0b3JhZ2VcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbi8qKiBTZXNzaW9uIHN0b3JhZ2UgKi9cbmV4cG9ydCB7IEpzb25GaWxlU2Vzc2lvblN0b3JhZ2UgfSBmcm9tIFwiLi9maWxlX3N0b3JhZ2VcIjtcblxuLyoqXG4gKiBEaXJlY3Rvcnkgd2hlcmUgQ3ViZVNpZ25lciBzdG9yZXMgY29uZmlnIGZpbGVzLlxuICogQHJldHVybiB7c3RyaW5nfSBDb25maWcgZGlyXG4gKi9cbmZ1bmN0aW9uIGNvbmZpZ0RpcigpOiBzdHJpbmcge1xuICBjb25zdCBjb25maWdEaXIgPVxuICAgIHByb2Nlc3MucGxhdGZvcm0gPT09IFwiZGFyd2luXCJcbiAgICAgID8gYCR7cHJvY2Vzcy5lbnYuSE9NRX0vTGlicmFyeS9BcHBsaWNhdGlvbiBTdXBwb3J0YFxuICAgICAgOiBgJHtwcm9jZXNzLmVudi5IT01FfS8uY29uZmlnYDtcbiAgcmV0dXJuIHBhdGguam9pbihjb25maWdEaXIsIFwiY3ViZXNpZ25lclwiKTtcbn1cblxuLyoqIERpcmVjdG9yeSB3aGVyZSBDdWJlU2lnbmVyIHN0b3JlcyBjb25maWcgZmlsZXMuICovXG5leHBvcnQgY29uc3QgQ09ORklHX0RJUiA9IGNvbmZpZ0RpcigpO1xuXG4vKiogRGVmYXVsdCBmaWxlIHBhdGggd2hlcmUgdGhlIG1hbmFnZW1lbnQgc2Vzc2lvbiB0b2tlbiBpcyBzdG9yZWQuICovXG5leHBvcnQgY29uc3QgTUFOQUdFTUVOVF9TRVNTSU9OX1BBVEggPSBwYXRoLmpvaW4oQ09ORklHX0RJUiwgXCJtYW5hZ2VtZW50LXNlc3Npb24uanNvblwiKTtcblxuLyoqIERlZmF1bHQgZmlsZSBwYXRoIHdoZXJlIHRoZSBzaWduZXIgc2Vzc2lvbiB0b2tlbiBpcyBzdG9yZWQuICovXG5leHBvcnQgY29uc3QgU0lHTkVSX1NFU1NJT05fUEFUSCA9IHBhdGguam9pbihDT05GSUdfRElSLCBcInNpZ25lci1zZXNzaW9uLmpzb25cIik7XG5cbi8qKlxuICogQHJldHVybiB7U2lnbmVyU2Vzc2lvblN0b3JhZ2V9IFN0b3JhZ2UgcG9pbnRpbmcgdG8gdGhlIGRlZmF1bHQgbWFuYWdlbWVudCBzZXNzaW9uIGZpbGUgb24gZGlzay5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlZmF1bHRNYW5hZ2VtZW50U2Vzc2lvblN0b3JhZ2UoKTogU2lnbmVyU2Vzc2lvblN0b3JhZ2Uge1xuICByZXR1cm4gbmV3IEpzb25GaWxlU2Vzc2lvblN0b3JhZ2UoTUFOQUdFTUVOVF9TRVNTSU9OX1BBVEgpO1xufVxuXG4vKipcbiAqIEByZXR1cm4ge1Byb21pc2U8Q3ViZVNpZ25lckNsaWVudD59IEV4aXN0aW5nIG1hbmFnZW1lbnQgc2Vzc2lvbiBmcm9tIHRoZSBkZWZhdWx0IGZpbGUgb24gZGlzay5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRNYW5hZ2VtZW50U2Vzc2lvbigpOiBQcm9taXNlPEN1YmVTaWduZXJDbGllbnQ+IHtcbiAgcmV0dXJuIGF3YWl0IEN1YmVTaWduZXJDbGllbnQubG9hZE1hbmFnZW1lbnRTZXNzaW9uKGRlZmF1bHRNYW5hZ2VtZW50U2Vzc2lvblN0b3JhZ2UoKSk7XG59XG5cbi8qKlxuICogQHJldHVybiB7U2lnbmVyU2Vzc2lvblN0b3JhZ2V9IFN0b3JhZ2UgcG9pbnRpbmcgdG8gdGhlIGRlZmF1bHQgc2lnbmVyIHNlc3Npb24gZmlsZSBvbiBkaXNrLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZGVmYXVsdFNpZ25lclNlc3Npb25TdG9yYWdlKCk6IFNpZ25lclNlc3Npb25TdG9yYWdlIHtcbiAgcmV0dXJuIG5ldyBKc29uRmlsZVNlc3Npb25TdG9yYWdlKFNJR05FUl9TRVNTSU9OX1BBVEgpO1xufVxuXG4vKipcbiAqIEByZXR1cm4ge1Byb21pc2U8U2lnbmVyU2Vzc2lvbj59IEV4aXN0aW5nIHNpZ25lciBzZXNzaW9uIGZyb20gYSBkZWZhdWx0IGZpbGUgb24gZGlzay5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRTaWduZXJTZXNzaW9uKCk6IFByb21pc2U8U2lnbmVyU2Vzc2lvbj4ge1xuICByZXR1cm4gYXdhaXQgU2lnbmVyU2Vzc2lvbi5sb2FkU2lnbmVyU2Vzc2lvbihkZWZhdWx0U2lnbmVyU2Vzc2lvblN0b3JhZ2UoKSk7XG59XG5cbi8qKiBVdGlscyBmb3IgcHJvY2Vzc2luZyBvcmcgZXZlbnRzICovXG5leHBvcnQgKiBmcm9tIFwiLi9vcmdfZXZlbnRfcHJvY2Vzc29yXCI7XG4iXX0=
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Environment } from "@cubist-labs/cubesigner-sdk";
|
|
2
|
+
/** The common fields of SNS messages */
|
|
3
|
+
export interface SnsMessage {
|
|
4
|
+
Type: string;
|
|
5
|
+
MessageId: string;
|
|
6
|
+
TopicArn: string;
|
|
7
|
+
Message: string;
|
|
8
|
+
Timestamp: string;
|
|
9
|
+
SignatureVersion: string;
|
|
10
|
+
Signature: string;
|
|
11
|
+
SigningCertURL: string;
|
|
12
|
+
}
|
|
13
|
+
/** The format of a subscription confirmation sent by SNS */
|
|
14
|
+
export interface SubscriptionConfirmationMessage extends SnsMessage {
|
|
15
|
+
Token: string;
|
|
16
|
+
SubscribeURL: string;
|
|
17
|
+
}
|
|
18
|
+
/** Common fields for an org event */
|
|
19
|
+
export interface OrgEventBase {
|
|
20
|
+
org: string;
|
|
21
|
+
utc_timestamp: number;
|
|
22
|
+
org_event: string;
|
|
23
|
+
}
|
|
24
|
+
/** The format of an event message sent by SNS */
|
|
25
|
+
export interface OrgEventMessage extends SnsMessage {
|
|
26
|
+
Subject?: string;
|
|
27
|
+
UnsubscribeURL: string;
|
|
28
|
+
}
|
|
29
|
+
/** Options for the processor */
|
|
30
|
+
export interface OrgEventProcessorOptions {
|
|
31
|
+
env: Environment;
|
|
32
|
+
}
|
|
33
|
+
/** A utility for processing org event messages */
|
|
34
|
+
export declare class OrgEventProcessor {
|
|
35
|
+
#private;
|
|
36
|
+
/**
|
|
37
|
+
* Constructor.
|
|
38
|
+
* @param {string} orgId The org id
|
|
39
|
+
* @param {OrgEventProcessorOptions} options Additional options for the processor
|
|
40
|
+
*/
|
|
41
|
+
constructor(orgId: string, options?: OrgEventProcessorOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Checks an SNS message and its signature. Throws an error if the message
|
|
44
|
+
* invalid or the signature is invalid.
|
|
45
|
+
*
|
|
46
|
+
* @param {SnsMessage} message The SNS message to check
|
|
47
|
+
*/
|
|
48
|
+
checkMessage(message: SnsMessage): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Parse an org event and check its signature. Throws an error if the
|
|
51
|
+
* message is not a valid org event or the signature is invalid.
|
|
52
|
+
*
|
|
53
|
+
* @param {OrgEventMessage} message The org event message to check
|
|
54
|
+
* @return {OrgEventBase} The org event
|
|
55
|
+
*/
|
|
56
|
+
parse(message: OrgEventMessage): Promise<OrgEventBase>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _OrgEventProcessor_instances, _OrgEventProcessor_topicArn, _OrgEventProcessor_orgId, _OrgEventProcessor_cachedCertificates, _OrgEventProcessor_fetchAndValidateCertificate;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OrgEventProcessor = void 0;
|
|
16
|
+
const crypto_1 = require("crypto");
|
|
17
|
+
const cubesigner_sdk_1 = require("@cubist-labs/cubesigner-sdk");
|
|
18
|
+
// URLs that are safe to retrieve certificates from
|
|
19
|
+
const SNS_CERTIFICATE_URL_HOSTS = ["sns.us-east-1.amazonaws.com"];
|
|
20
|
+
const SNS_CERTIFICATE_HOST = "sns.amazonaws.com";
|
|
21
|
+
/** A utility for processing org event messages */
|
|
22
|
+
class OrgEventProcessor {
|
|
23
|
+
/**
|
|
24
|
+
* Constructor.
|
|
25
|
+
* @param {string} orgId The org id
|
|
26
|
+
* @param {OrgEventProcessorOptions} options Additional options for the processor
|
|
27
|
+
*/
|
|
28
|
+
constructor(orgId, options) {
|
|
29
|
+
_OrgEventProcessor_instances.add(this);
|
|
30
|
+
_OrgEventProcessor_topicArn.set(this, void 0);
|
|
31
|
+
_OrgEventProcessor_orgId.set(this, void 0);
|
|
32
|
+
_OrgEventProcessor_cachedCertificates.set(this, void 0);
|
|
33
|
+
__classPrivateFieldSet(this, _OrgEventProcessor_topicArn, cubesigner_sdk_1.envs[options?.env ?? "prod"].OrgEventsTopicArn, "f");
|
|
34
|
+
__classPrivateFieldSet(this, _OrgEventProcessor_orgId, orgId, "f");
|
|
35
|
+
__classPrivateFieldSet(this, _OrgEventProcessor_cachedCertificates, new Map(), "f");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks an SNS message and its signature. Throws an error if the message
|
|
39
|
+
* invalid or the signature is invalid.
|
|
40
|
+
*
|
|
41
|
+
* @param {SnsMessage} message The SNS message to check
|
|
42
|
+
*/
|
|
43
|
+
async checkMessage(message) {
|
|
44
|
+
// Check the topic ARN
|
|
45
|
+
if (message.TopicArn !== __classPrivateFieldGet(this, _OrgEventProcessor_topicArn, "f")) {
|
|
46
|
+
throw new Error(`Expected topic ARN '${__classPrivateFieldGet(this, _OrgEventProcessor_topicArn, "f")}', found '${message.TopicArn}'`);
|
|
47
|
+
}
|
|
48
|
+
// Both subscription confirmations and org event messages should have no subject
|
|
49
|
+
if ("Subject" in message) {
|
|
50
|
+
throw new Error("Expected a message without a subject");
|
|
51
|
+
}
|
|
52
|
+
// The org events topic uses signature version 2 (SHA256)
|
|
53
|
+
if (message.SignatureVersion !== "2") {
|
|
54
|
+
throw new Error("Expected signature version 2");
|
|
55
|
+
}
|
|
56
|
+
// Retrieve the certificate and sanity check it
|
|
57
|
+
const certificate = await __classPrivateFieldGet(this, _OrgEventProcessor_instances, "m", _OrgEventProcessor_fetchAndValidateCertificate).call(this, new URL(message.SigningCertURL));
|
|
58
|
+
// Extract fields specific to subscription confirmations
|
|
59
|
+
const subscribeUrl = message.SubscribeURL;
|
|
60
|
+
const token = message.Token;
|
|
61
|
+
// Check the signature
|
|
62
|
+
const fields = ["Message", message.Message, "MessageId", message.MessageId]
|
|
63
|
+
.concat(subscribeUrl !== undefined ? ["SubscribeURL", subscribeUrl] : [])
|
|
64
|
+
.concat(["Timestamp", message.Timestamp])
|
|
65
|
+
.concat(token !== undefined ? ["Token", token] : [])
|
|
66
|
+
.concat(["TopicArn", message.TopicArn, "Type", message.Type]);
|
|
67
|
+
const verify = (0, crypto_1.createVerify)("RSA-SHA256");
|
|
68
|
+
verify.update(fields.join("\n") + "\n");
|
|
69
|
+
const isValid = verify.verify(certificate.publicKey, message.Signature, "base64");
|
|
70
|
+
if (!isValid) {
|
|
71
|
+
throw new Error("The org event has an invalid signature");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse an org event and check its signature. Throws an error if the
|
|
76
|
+
* message is not a valid org event or the signature is invalid.
|
|
77
|
+
*
|
|
78
|
+
* @param {OrgEventMessage} message The org event message to check
|
|
79
|
+
* @return {OrgEventBase} The org event
|
|
80
|
+
*/
|
|
81
|
+
async parse(message) {
|
|
82
|
+
await this.checkMessage(message);
|
|
83
|
+
// Check that the event is for the expected org
|
|
84
|
+
const orgEvent = JSON.parse(message.Message);
|
|
85
|
+
if (orgEvent.org !== __classPrivateFieldGet(this, _OrgEventProcessor_orgId, "f")) {
|
|
86
|
+
throw new Error(`Expected org to be '${__classPrivateFieldGet(this, _OrgEventProcessor_orgId, "f")}', found '${orgEvent.org}'`);
|
|
87
|
+
}
|
|
88
|
+
return orgEvent;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.OrgEventProcessor = OrgEventProcessor;
|
|
92
|
+
_OrgEventProcessor_topicArn = new WeakMap(), _OrgEventProcessor_orgId = new WeakMap(), _OrgEventProcessor_cachedCertificates = new WeakMap(), _OrgEventProcessor_instances = new WeakSet(), _OrgEventProcessor_fetchAndValidateCertificate =
|
|
93
|
+
/**
|
|
94
|
+
* Fetches a certificate from a given URL or from the certificate cache.
|
|
95
|
+
* Throws an error if the URL does not correspond to an SNS certificate URL.
|
|
96
|
+
*
|
|
97
|
+
* Note: Ideally, this method would verify the certificate chain, but there
|
|
98
|
+
* is no obvious chain. Instead, this method only fetches certificates from
|
|
99
|
+
* a small set of allowlisted URLs.
|
|
100
|
+
*
|
|
101
|
+
* @param {URL} url The URL of the certificate
|
|
102
|
+
* @return {X509Certificate} The certificate
|
|
103
|
+
*/
|
|
104
|
+
async function _OrgEventProcessor_fetchAndValidateCertificate(url) {
|
|
105
|
+
const currTime = new Date().getTime();
|
|
106
|
+
const cachedCertificate = __classPrivateFieldGet(this, _OrgEventProcessor_cachedCertificates, "f").get(url);
|
|
107
|
+
if (cachedCertificate && currTime < new Date(cachedCertificate.validTo).getTime()) {
|
|
108
|
+
return cachedCertificate;
|
|
109
|
+
}
|
|
110
|
+
// Only fetch certificates from HTTPS URLs
|
|
111
|
+
if (url.protocol !== "https:") {
|
|
112
|
+
throw new Error("Expected signing certificate URL to use HTTPS");
|
|
113
|
+
}
|
|
114
|
+
// Only fetch certificate URLs for SNS
|
|
115
|
+
if (SNS_CERTIFICATE_URL_HOSTS.indexOf(url.host) === -1) {
|
|
116
|
+
throw new Error("Expected signing certificate URL for SNS in us-east-1");
|
|
117
|
+
}
|
|
118
|
+
const response = await fetch(url);
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw new Error(`Unable to download certificate. Status: ${response.status}`);
|
|
121
|
+
}
|
|
122
|
+
const blob = await response.blob();
|
|
123
|
+
const certificate = new crypto_1.X509Certificate(await blob.text());
|
|
124
|
+
if (!certificate.checkHost(SNS_CERTIFICATE_HOST)) {
|
|
125
|
+
throw new Error(`Expected certificate to be for '${SNS_CERTIFICATE_HOST}'`);
|
|
126
|
+
}
|
|
127
|
+
// Check validity times
|
|
128
|
+
if (currTime < new Date(certificate.validFrom).getTime()) {
|
|
129
|
+
throw new Error("Certificate not valid yet");
|
|
130
|
+
}
|
|
131
|
+
if (new Date(certificate.validTo).getTime() < currTime) {
|
|
132
|
+
throw new Error("Certificate expired");
|
|
133
|
+
}
|
|
134
|
+
__classPrivateFieldGet(this, _OrgEventProcessor_cachedCertificates, "f").set(url, certificate);
|
|
135
|
+
return certificate;
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnX2V2ZW50X3Byb2Nlc3Nvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vcmdfZXZlbnRfcHJvY2Vzc29yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG1DQUF1RDtBQUN2RCxnRUFBZ0U7QUFFaEUsbURBQW1EO0FBQ25ELE1BQU0seUJBQXlCLEdBQUcsQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO0FBRWxFLE1BQU0sb0JBQW9CLEdBQUcsbUJBQW1CLENBQUM7QUFzQ2pELGtEQUFrRDtBQUNsRCxNQUFhLGlCQUFpQjtJQUs1Qjs7OztPQUlHO0lBQ0gsWUFBWSxLQUFhLEVBQUUsT0FBa0M7O1FBVHBELDhDQUFrQjtRQUNsQiwyQ0FBZTtRQUN4Qix3REFBK0M7UUFRN0MsdUJBQUEsSUFBSSwrQkFBYSxxQkFBSSxDQUFDLE9BQU8sRUFBRSxHQUFHLElBQUksTUFBTSxDQUFDLENBQUMsaUJBQWlCLE1BQUEsQ0FBQztRQUNoRSx1QkFBQSxJQUFJLDRCQUFVLEtBQUssTUFBQSxDQUFDO1FBQ3BCLHVCQUFBLElBQUkseUNBQXVCLElBQUksR0FBRyxFQUFFLE1BQUEsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxLQUFLLENBQUMsWUFBWSxDQUFDLE9BQW1CO1FBQ3BDLHNCQUFzQjtRQUN0QixJQUFJLE9BQU8sQ0FBQyxRQUFRLEtBQUssdUJBQUEsSUFBSSxtQ0FBVSxFQUFFLENBQUM7WUFDeEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsdUJBQUEsSUFBSSxtQ0FBVSxhQUFhLE9BQU8sQ0FBQyxRQUFRLEdBQUcsQ0FBQyxDQUFDO1FBQ3pGLENBQUM7UUFFRCxnRkFBZ0Y7UUFDaEYsSUFBSSxTQUFTLElBQUksT0FBTyxFQUFFLENBQUM7WUFDekIsTUFBTSxJQUFJLEtBQUssQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDO1FBQzFELENBQUM7UUFFRCx5REFBeUQ7UUFDekQsSUFBSSxPQUFPLENBQUMsZ0JBQWdCLEtBQUssR0FBRyxFQUFFLENBQUM7WUFDckMsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO1FBQ2xELENBQUM7UUFFRCwrQ0FBK0M7UUFDL0MsTUFBTSxXQUFXLEdBQUcsTUFBTSx1QkFBQSxJQUFJLG9GQUE2QixNQUFqQyxJQUFJLEVBQThCLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBRTdGLHdEQUF3RDtRQUN4RCxNQUFNLFlBQVksR0FBSSxPQUEyQyxDQUFDLFlBQVksQ0FBQztRQUMvRSxNQUFNLEtBQUssR0FBSSxPQUEyQyxDQUFDLEtBQUssQ0FBQztRQUVqRSxzQkFBc0I7UUFDdEIsTUFBTSxNQUFNLEdBQUcsQ0FBQyxTQUFTLEVBQUUsT0FBTyxDQUFDLE9BQU8sRUFBRSxXQUFXLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQzthQUN4RSxNQUFNLENBQUMsWUFBWSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxjQUFjLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQzthQUN4RSxNQUFNLENBQUMsQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO2FBQ3hDLE1BQU0sQ0FBQyxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO2FBQ25ELE1BQU0sQ0FBQyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUNoRSxNQUFNLE1BQU0sR0FBRyxJQUFBLHFCQUFZLEVBQUMsWUFBWSxDQUFDLENBQUM7UUFDMUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQ3hDLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ2xGLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE1BQU0sSUFBSSxLQUFLLENBQUMsd0NBQXdDLENBQUMsQ0FBQztRQUM1RCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBd0I7UUFDbEMsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRWpDLCtDQUErQztRQUMvQyxNQUFNLFFBQVEsR0FBaUIsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDM0QsSUFBSSxRQUFRLENBQUMsR0FBRyxLQUFLLHVCQUFBLElBQUksZ0NBQU8sRUFBRSxDQUFDO1lBQ2pDLE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLHVCQUFBLElBQUksZ0NBQU8sYUFBYSxRQUFRLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNsRixDQUFDO1FBRUQsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztDQW1ERjtBQS9IRCw4Q0ErSEM7O0FBakRDOzs7Ozs7Ozs7O0dBVUc7QUFDSCxLQUFLLHlEQUE4QixHQUFRO0lBQ3pDLE1BQU0sUUFBUSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDdEMsTUFBTSxpQkFBaUIsR0FBRyx1QkFBQSxJQUFJLDZDQUFvQixDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM1RCxJQUFJLGlCQUFpQixJQUFJLFFBQVEsR0FBRyxJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDO1FBQ2xGLE9BQU8saUJBQWlCLENBQUM7SUFDM0IsQ0FBQztJQUVELDBDQUEwQztJQUMxQyxJQUFJLEdBQUcsQ0FBQyxRQUFRLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDOUIsTUFBTSxJQUFJLEtBQUssQ0FBQywrQ0FBK0MsQ0FBQyxDQUFDO0lBQ25FLENBQUM7SUFFRCxzQ0FBc0M7SUFDdEMsSUFBSSx5QkFBeUIsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDdkQsTUFBTSxJQUFJLEtBQUssQ0FBQyx1REFBdUQsQ0FBQyxDQUFDO0lBQzNFLENBQUM7SUFFRCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNsQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsMkNBQTJDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7SUFDRCxNQUFNLElBQUksR0FBRyxNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNuQyxNQUFNLFdBQVcsR0FBRyxJQUFJLHdCQUFlLENBQUMsTUFBTSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUMzRCxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLENBQUM7UUFDakQsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQ0FBbUMsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO0lBQzlFLENBQUM7SUFFRCx1QkFBdUI7SUFDdkIsSUFBSSxRQUFRLEdBQUcsSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7UUFDekQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO0lBQy9DLENBQUM7SUFDRCxJQUFJLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxRQUFRLEVBQUUsQ0FBQztRQUN2RCxNQUFNLElBQUksS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVELHVCQUFBLElBQUksNkNBQW9CLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxXQUFXLENBQUMsQ0FBQztJQUMvQyxPQUFPLFdBQVcsQ0FBQztBQUNyQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgWDUwOUNlcnRpZmljYXRlLCBjcmVhdGVWZXJpZnkgfSBmcm9tIFwiY3J5cHRvXCI7XG5pbXBvcnQgeyBFbnZpcm9ubWVudCwgZW52cyB9IGZyb20gXCJAY3ViaXN0LWxhYnMvY3ViZXNpZ25lci1zZGtcIjtcblxuLy8gVVJMcyB0aGF0IGFyZSBzYWZlIHRvIHJldHJpZXZlIGNlcnRpZmljYXRlcyBmcm9tXG5jb25zdCBTTlNfQ0VSVElGSUNBVEVfVVJMX0hPU1RTID0gW1wic25zLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXCJdO1xuXG5jb25zdCBTTlNfQ0VSVElGSUNBVEVfSE9TVCA9IFwic25zLmFtYXpvbmF3cy5jb21cIjtcblxuLyoqIFRoZSBjb21tb24gZmllbGRzIG9mIFNOUyBtZXNzYWdlcyAqL1xuZXhwb3J0IGludGVyZmFjZSBTbnNNZXNzYWdlIHtcbiAgVHlwZTogc3RyaW5nO1xuICBNZXNzYWdlSWQ6IHN0cmluZztcbiAgVG9waWNBcm46IHN0cmluZztcbiAgTWVzc2FnZTogc3RyaW5nO1xuICBUaW1lc3RhbXA6IHN0cmluZztcbiAgU2lnbmF0dXJlVmVyc2lvbjogc3RyaW5nO1xuICBTaWduYXR1cmU6IHN0cmluZztcbiAgU2lnbmluZ0NlcnRVUkw6IHN0cmluZztcbn1cblxuLyoqIFRoZSBmb3JtYXQgb2YgYSBzdWJzY3JpcHRpb24gY29uZmlybWF0aW9uIHNlbnQgYnkgU05TICovXG5leHBvcnQgaW50ZXJmYWNlIFN1YnNjcmlwdGlvbkNvbmZpcm1hdGlvbk1lc3NhZ2UgZXh0ZW5kcyBTbnNNZXNzYWdlIHtcbiAgVG9rZW46IHN0cmluZztcbiAgU3Vic2NyaWJlVVJMOiBzdHJpbmc7XG59XG5cbi8qKiBDb21tb24gZmllbGRzIGZvciBhbiBvcmcgZXZlbnQgKi9cbmV4cG9ydCBpbnRlcmZhY2UgT3JnRXZlbnRCYXNlIHtcbiAgb3JnOiBzdHJpbmc7XG4gIHV0Y190aW1lc3RhbXA6IG51bWJlcjtcbiAgb3JnX2V2ZW50OiBzdHJpbmc7XG59XG5cbi8qKiBUaGUgZm9ybWF0IG9mIGFuIGV2ZW50IG1lc3NhZ2Ugc2VudCBieSBTTlMgKi9cbmV4cG9ydCBpbnRlcmZhY2UgT3JnRXZlbnRNZXNzYWdlIGV4dGVuZHMgU25zTWVzc2FnZSB7XG4gIFN1YmplY3Q/OiBzdHJpbmc7XG4gIFVuc3Vic2NyaWJlVVJMOiBzdHJpbmc7XG59XG5cbi8qKiBPcHRpb25zIGZvciB0aGUgcHJvY2Vzc29yICovXG5leHBvcnQgaW50ZXJmYWNlIE9yZ0V2ZW50UHJvY2Vzc29yT3B0aW9ucyB7XG4gIGVudjogRW52aXJvbm1lbnQ7XG59XG5cbi8qKiBBIHV0aWxpdHkgZm9yIHByb2Nlc3Npbmcgb3JnIGV2ZW50IG1lc3NhZ2VzICovXG5leHBvcnQgY2xhc3MgT3JnRXZlbnRQcm9jZXNzb3Ige1xuICByZWFkb25seSAjdG9waWNBcm46IHN0cmluZztcbiAgcmVhZG9ubHkgI29yZ0lkOiBzdHJpbmc7XG4gICNjYWNoZWRDZXJ0aWZpY2F0ZXM6IE1hcDxVUkwsIFg1MDlDZXJ0aWZpY2F0ZT47XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdG9yLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gb3JnSWQgVGhlIG9yZyBpZFxuICAgKiBAcGFyYW0ge09yZ0V2ZW50UHJvY2Vzc29yT3B0aW9uc30gb3B0aW9ucyBBZGRpdGlvbmFsIG9wdGlvbnMgZm9yIHRoZSBwcm9jZXNzb3JcbiAgICovXG4gIGNvbnN0cnVjdG9yKG9yZ0lkOiBzdHJpbmcsIG9wdGlvbnM/OiBPcmdFdmVudFByb2Nlc3Nvck9wdGlvbnMpIHtcbiAgICB0aGlzLiN0b3BpY0FybiA9IGVudnNbb3B0aW9ucz8uZW52ID8/IFwicHJvZFwiXS5PcmdFdmVudHNUb3BpY0FybjtcbiAgICB0aGlzLiNvcmdJZCA9IG9yZ0lkO1xuICAgIHRoaXMuI2NhY2hlZENlcnRpZmljYXRlcyA9IG5ldyBNYXAoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3MgYW4gU05TIG1lc3NhZ2UgYW5kIGl0cyBzaWduYXR1cmUuIFRocm93cyBhbiBlcnJvciBpZiB0aGUgbWVzc2FnZVxuICAgKiBpbnZhbGlkIG9yIHRoZSBzaWduYXR1cmUgaXMgaW52YWxpZC5cbiAgICpcbiAgICogQHBhcmFtIHtTbnNNZXNzYWdlfSBtZXNzYWdlIFRoZSBTTlMgbWVzc2FnZSB0byBjaGVja1xuICAgKi9cbiAgYXN5bmMgY2hlY2tNZXNzYWdlKG1lc3NhZ2U6IFNuc01lc3NhZ2UpIHtcbiAgICAvLyBDaGVjayB0aGUgdG9waWMgQVJOXG4gICAgaWYgKG1lc3NhZ2UuVG9waWNBcm4gIT09IHRoaXMuI3RvcGljQXJuKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIHRvcGljIEFSTiAnJHt0aGlzLiN0b3BpY0Fybn0nLCBmb3VuZCAnJHttZXNzYWdlLlRvcGljQXJufSdgKTtcbiAgICB9XG5cbiAgICAvLyBCb3RoIHN1YnNjcmlwdGlvbiBjb25maXJtYXRpb25zIGFuZCBvcmcgZXZlbnQgbWVzc2FnZXMgc2hvdWxkIGhhdmUgbm8gc3ViamVjdFxuICAgIGlmIChcIlN1YmplY3RcIiBpbiBtZXNzYWdlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJFeHBlY3RlZCBhIG1lc3NhZ2Ugd2l0aG91dCBhIHN1YmplY3RcIik7XG4gICAgfVxuXG4gICAgLy8gVGhlIG9yZyBldmVudHMgdG9waWMgdXNlcyBzaWduYXR1cmUgdmVyc2lvbiAyIChTSEEyNTYpXG4gICAgaWYgKG1lc3NhZ2UuU2lnbmF0dXJlVmVyc2lvbiAhPT0gXCIyXCIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkV4cGVjdGVkIHNpZ25hdHVyZSB2ZXJzaW9uIDJcIik7XG4gICAgfVxuXG4gICAgLy8gUmV0cmlldmUgdGhlIGNlcnRpZmljYXRlIGFuZCBzYW5pdHkgY2hlY2sgaXRcbiAgICBjb25zdCBjZXJ0aWZpY2F0ZSA9IGF3YWl0IHRoaXMuI2ZldGNoQW5kVmFsaWRhdGVDZXJ0aWZpY2F0ZShuZXcgVVJMKG1lc3NhZ2UuU2lnbmluZ0NlcnRVUkwpKTtcblxuICAgIC8vIEV4dHJhY3QgZmllbGRzIHNwZWNpZmljIHRvIHN1YnNjcmlwdGlvbiBjb25maXJtYXRpb25zXG4gICAgY29uc3Qgc3Vic2NyaWJlVXJsID0gKG1lc3NhZ2UgYXMgU3Vic2NyaXB0aW9uQ29uZmlybWF0aW9uTWVzc2FnZSkuU3Vic2NyaWJlVVJMO1xuICAgIGNvbnN0IHRva2VuID0gKG1lc3NhZ2UgYXMgU3Vic2NyaXB0aW9uQ29uZmlybWF0aW9uTWVzc2FnZSkuVG9rZW47XG5cbiAgICAvLyBDaGVjayB0aGUgc2lnbmF0dXJlXG4gICAgY29uc3QgZmllbGRzID0gW1wiTWVzc2FnZVwiLCBtZXNzYWdlLk1lc3NhZ2UsIFwiTWVzc2FnZUlkXCIsIG1lc3NhZ2UuTWVzc2FnZUlkXVxuICAgICAgLmNvbmNhdChzdWJzY3JpYmVVcmwgIT09IHVuZGVmaW5lZCA/IFtcIlN1YnNjcmliZVVSTFwiLCBzdWJzY3JpYmVVcmxdIDogW10pXG4gICAgICAuY29uY2F0KFtcIlRpbWVzdGFtcFwiLCBtZXNzYWdlLlRpbWVzdGFtcF0pXG4gICAgICAuY29uY2F0KHRva2VuICE9PSB1bmRlZmluZWQgPyBbXCJUb2tlblwiLCB0b2tlbl0gOiBbXSlcbiAgICAgIC5jb25jYXQoW1wiVG9waWNBcm5cIiwgbWVzc2FnZS5Ub3BpY0FybiwgXCJUeXBlXCIsIG1lc3NhZ2UuVHlwZV0pO1xuICAgIGNvbnN0IHZlcmlmeSA9IGNyZWF0ZVZlcmlmeShcIlJTQS1TSEEyNTZcIik7XG4gICAgdmVyaWZ5LnVwZGF0ZShmaWVsZHMuam9pbihcIlxcblwiKSArIFwiXFxuXCIpO1xuICAgIGNvbnN0IGlzVmFsaWQgPSB2ZXJpZnkudmVyaWZ5KGNlcnRpZmljYXRlLnB1YmxpY0tleSwgbWVzc2FnZS5TaWduYXR1cmUsIFwiYmFzZTY0XCIpO1xuICAgIGlmICghaXNWYWxpZCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVGhlIG9yZyBldmVudCBoYXMgYW4gaW52YWxpZCBzaWduYXR1cmVcIik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlIGFuIG9yZyBldmVudCBhbmQgY2hlY2sgaXRzIHNpZ25hdHVyZS4gVGhyb3dzIGFuIGVycm9yIGlmIHRoZVxuICAgKiBtZXNzYWdlIGlzIG5vdCBhIHZhbGlkIG9yZyBldmVudCBvciB0aGUgc2lnbmF0dXJlIGlzIGludmFsaWQuXG4gICAqXG4gICAqIEBwYXJhbSB7T3JnRXZlbnRNZXNzYWdlfSBtZXNzYWdlIFRoZSBvcmcgZXZlbnQgbWVzc2FnZSB0byBjaGVja1xuICAgKiBAcmV0dXJuIHtPcmdFdmVudEJhc2V9IFRoZSBvcmcgZXZlbnRcbiAgICovXG4gIGFzeW5jIHBhcnNlKG1lc3NhZ2U6IE9yZ0V2ZW50TWVzc2FnZSk6IFByb21pc2U8T3JnRXZlbnRCYXNlPiB7XG4gICAgYXdhaXQgdGhpcy5jaGVja01lc3NhZ2UobWVzc2FnZSk7XG5cbiAgICAvLyBDaGVjayB0aGF0IHRoZSBldmVudCBpcyBmb3IgdGhlIGV4cGVjdGVkIG9yZ1xuICAgIGNvbnN0IG9yZ0V2ZW50OiBPcmdFdmVudEJhc2UgPSBKU09OLnBhcnNlKG1lc3NhZ2UuTWVzc2FnZSk7XG4gICAgaWYgKG9yZ0V2ZW50Lm9yZyAhPT0gdGhpcy4jb3JnSWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgb3JnIHRvIGJlICcke3RoaXMuI29yZ0lkfScsIGZvdW5kICcke29yZ0V2ZW50Lm9yZ30nYCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIG9yZ0V2ZW50O1xuICB9XG5cbiAgLyoqXG4gICAqIEZldGNoZXMgYSBjZXJ0aWZpY2F0ZSBmcm9tIGEgZ2l2ZW4gVVJMIG9yIGZyb20gdGhlIGNlcnRpZmljYXRlIGNhY2hlLlxuICAgKiBUaHJvd3MgYW4gZXJyb3IgaWYgdGhlIFVSTCBkb2VzIG5vdCBjb3JyZXNwb25kIHRvIGFuIFNOUyBjZXJ0aWZpY2F0ZSBVUkwuXG4gICAqXG4gICAqIE5vdGU6IElkZWFsbHksIHRoaXMgbWV0aG9kIHdvdWxkIHZlcmlmeSB0aGUgY2VydGlmaWNhdGUgY2hhaW4sIGJ1dCB0aGVyZVxuICAgKiBpcyBubyBvYnZpb3VzIGNoYWluLiBJbnN0ZWFkLCB0aGlzIG1ldGhvZCBvbmx5IGZldGNoZXMgY2VydGlmaWNhdGVzIGZyb21cbiAgICogYSBzbWFsbCBzZXQgb2YgYWxsb3dsaXN0ZWQgVVJMcy5cbiAgICpcbiAgICogQHBhcmFtIHtVUkx9IHVybCBUaGUgVVJMIG9mIHRoZSBjZXJ0aWZpY2F0ZVxuICAgKiBAcmV0dXJuIHtYNTA5Q2VydGlmaWNhdGV9IFRoZSBjZXJ0aWZpY2F0ZVxuICAgKi9cbiAgYXN5bmMgI2ZldGNoQW5kVmFsaWRhdGVDZXJ0aWZpY2F0ZSh1cmw6IFVSTCk6IFByb21pc2U8WDUwOUNlcnRpZmljYXRlPiB7XG4gICAgY29uc3QgY3VyclRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICBjb25zdCBjYWNoZWRDZXJ0aWZpY2F0ZSA9IHRoaXMuI2NhY2hlZENlcnRpZmljYXRlcy5nZXQodXJsKTtcbiAgICBpZiAoY2FjaGVkQ2VydGlmaWNhdGUgJiYgY3VyclRpbWUgPCBuZXcgRGF0ZShjYWNoZWRDZXJ0aWZpY2F0ZS52YWxpZFRvKS5nZXRUaW1lKCkpIHtcbiAgICAgIHJldHVybiBjYWNoZWRDZXJ0aWZpY2F0ZTtcbiAgICB9XG5cbiAgICAvLyBPbmx5IGZldGNoIGNlcnRpZmljYXRlcyBmcm9tIEhUVFBTIFVSTHNcbiAgICBpZiAodXJsLnByb3RvY29sICE9PSBcImh0dHBzOlwiKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJFeHBlY3RlZCBzaWduaW5nIGNlcnRpZmljYXRlIFVSTCB0byB1c2UgSFRUUFNcIik7XG4gICAgfVxuXG4gICAgLy8gT25seSBmZXRjaCBjZXJ0aWZpY2F0ZSBVUkxzIGZvciBTTlNcbiAgICBpZiAoU05TX0NFUlRJRklDQVRFX1VSTF9IT1NUUy5pbmRleE9mKHVybC5ob3N0KSA9PT0gLTEpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkV4cGVjdGVkIHNpZ25pbmcgY2VydGlmaWNhdGUgVVJMIGZvciBTTlMgaW4gdXMtZWFzdC0xXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsKTtcbiAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFVuYWJsZSB0byBkb3dubG9hZCBjZXJ0aWZpY2F0ZS4gU3RhdHVzOiAke3Jlc3BvbnNlLnN0YXR1c31gKTtcbiAgICB9XG4gICAgY29uc3QgYmxvYiA9IGF3YWl0IHJlc3BvbnNlLmJsb2IoKTtcbiAgICBjb25zdCBjZXJ0aWZpY2F0ZSA9IG5ldyBYNTA5Q2VydGlmaWNhdGUoYXdhaXQgYmxvYi50ZXh0KCkpO1xuICAgIGlmICghY2VydGlmaWNhdGUuY2hlY2tIb3N0KFNOU19DRVJUSUZJQ0FURV9IT1NUKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBjZXJ0aWZpY2F0ZSB0byBiZSBmb3IgJyR7U05TX0NFUlRJRklDQVRFX0hPU1R9J2ApO1xuICAgIH1cblxuICAgIC8vIENoZWNrIHZhbGlkaXR5IHRpbWVzXG4gICAgaWYgKGN1cnJUaW1lIDwgbmV3IERhdGUoY2VydGlmaWNhdGUudmFsaWRGcm9tKS5nZXRUaW1lKCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkNlcnRpZmljYXRlIG5vdCB2YWxpZCB5ZXRcIik7XG4gICAgfVxuICAgIGlmIChuZXcgRGF0ZShjZXJ0aWZpY2F0ZS52YWxpZFRvKS5nZXRUaW1lKCkgPCBjdXJyVGltZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2VydGlmaWNhdGUgZXhwaXJlZFwiKTtcbiAgICB9XG5cbiAgICB0aGlzLiNjYWNoZWRDZXJ0aWZpY2F0ZXMuc2V0KHVybCwgY2VydGlmaWNhdGUpO1xuICAgIHJldHVybiBjZXJ0aWZpY2F0ZTtcbiAgfVxufVxuIl19
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubist-labs/cubesigner-sdk-fs-storage",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Filesystem storage implementation for CubeSigner SDK",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"author": "Cubist, Inc.",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"test": "jest --maxWorkers=1"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@cubist-labs/cubesigner-sdk": "^0.3.
|
|
23
|
+
"@cubist-labs/cubesigner-sdk": "^0.3.25"
|
|
24
24
|
},
|
|
25
25
|
"directories": {
|
|
26
26
|
"test": "test"
|
package/src/index.ts
CHANGED
|
@@ -53,3 +53,6 @@ export function defaultSignerSessionStorage(): SignerSessionStorage {
|
|
|
53
53
|
export async function loadSignerSession(): Promise<SignerSession> {
|
|
54
54
|
return await SignerSession.loadSignerSession(defaultSignerSessionStorage());
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/** Utils for processing org events */
|
|
58
|
+
export * from "./org_event_processor";
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { X509Certificate, createVerify } from "crypto";
|
|
2
|
+
import { Environment, envs } from "@cubist-labs/cubesigner-sdk";
|
|
3
|
+
|
|
4
|
+
// URLs that are safe to retrieve certificates from
|
|
5
|
+
const SNS_CERTIFICATE_URL_HOSTS = ["sns.us-east-1.amazonaws.com"];
|
|
6
|
+
|
|
7
|
+
const SNS_CERTIFICATE_HOST = "sns.amazonaws.com";
|
|
8
|
+
|
|
9
|
+
/** The common fields of SNS messages */
|
|
10
|
+
export interface SnsMessage {
|
|
11
|
+
Type: string;
|
|
12
|
+
MessageId: string;
|
|
13
|
+
TopicArn: string;
|
|
14
|
+
Message: string;
|
|
15
|
+
Timestamp: string;
|
|
16
|
+
SignatureVersion: string;
|
|
17
|
+
Signature: string;
|
|
18
|
+
SigningCertURL: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The format of a subscription confirmation sent by SNS */
|
|
22
|
+
export interface SubscriptionConfirmationMessage extends SnsMessage {
|
|
23
|
+
Token: string;
|
|
24
|
+
SubscribeURL: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Common fields for an org event */
|
|
28
|
+
export interface OrgEventBase {
|
|
29
|
+
org: string;
|
|
30
|
+
utc_timestamp: number;
|
|
31
|
+
org_event: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The format of an event message sent by SNS */
|
|
35
|
+
export interface OrgEventMessage extends SnsMessage {
|
|
36
|
+
Subject?: string;
|
|
37
|
+
UnsubscribeURL: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Options for the processor */
|
|
41
|
+
export interface OrgEventProcessorOptions {
|
|
42
|
+
env: Environment;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A utility for processing org event messages */
|
|
46
|
+
export class OrgEventProcessor {
|
|
47
|
+
readonly #topicArn: string;
|
|
48
|
+
readonly #orgId: string;
|
|
49
|
+
#cachedCertificates: Map<URL, X509Certificate>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Constructor.
|
|
53
|
+
* @param {string} orgId The org id
|
|
54
|
+
* @param {OrgEventProcessorOptions} options Additional options for the processor
|
|
55
|
+
*/
|
|
56
|
+
constructor(orgId: string, options?: OrgEventProcessorOptions) {
|
|
57
|
+
this.#topicArn = envs[options?.env ?? "prod"].OrgEventsTopicArn;
|
|
58
|
+
this.#orgId = orgId;
|
|
59
|
+
this.#cachedCertificates = new Map();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Checks an SNS message and its signature. Throws an error if the message
|
|
64
|
+
* invalid or the signature is invalid.
|
|
65
|
+
*
|
|
66
|
+
* @param {SnsMessage} message The SNS message to check
|
|
67
|
+
*/
|
|
68
|
+
async checkMessage(message: SnsMessage) {
|
|
69
|
+
// Check the topic ARN
|
|
70
|
+
if (message.TopicArn !== this.#topicArn) {
|
|
71
|
+
throw new Error(`Expected topic ARN '${this.#topicArn}', found '${message.TopicArn}'`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Both subscription confirmations and org event messages should have no subject
|
|
75
|
+
if ("Subject" in message) {
|
|
76
|
+
throw new Error("Expected a message without a subject");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The org events topic uses signature version 2 (SHA256)
|
|
80
|
+
if (message.SignatureVersion !== "2") {
|
|
81
|
+
throw new Error("Expected signature version 2");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Retrieve the certificate and sanity check it
|
|
85
|
+
const certificate = await this.#fetchAndValidateCertificate(new URL(message.SigningCertURL));
|
|
86
|
+
|
|
87
|
+
// Extract fields specific to subscription confirmations
|
|
88
|
+
const subscribeUrl = (message as SubscriptionConfirmationMessage).SubscribeURL;
|
|
89
|
+
const token = (message as SubscriptionConfirmationMessage).Token;
|
|
90
|
+
|
|
91
|
+
// Check the signature
|
|
92
|
+
const fields = ["Message", message.Message, "MessageId", message.MessageId]
|
|
93
|
+
.concat(subscribeUrl !== undefined ? ["SubscribeURL", subscribeUrl] : [])
|
|
94
|
+
.concat(["Timestamp", message.Timestamp])
|
|
95
|
+
.concat(token !== undefined ? ["Token", token] : [])
|
|
96
|
+
.concat(["TopicArn", message.TopicArn, "Type", message.Type]);
|
|
97
|
+
const verify = createVerify("RSA-SHA256");
|
|
98
|
+
verify.update(fields.join("\n") + "\n");
|
|
99
|
+
const isValid = verify.verify(certificate.publicKey, message.Signature, "base64");
|
|
100
|
+
if (!isValid) {
|
|
101
|
+
throw new Error("The org event has an invalid signature");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parse an org event and check its signature. Throws an error if the
|
|
107
|
+
* message is not a valid org event or the signature is invalid.
|
|
108
|
+
*
|
|
109
|
+
* @param {OrgEventMessage} message The org event message to check
|
|
110
|
+
* @return {OrgEventBase} The org event
|
|
111
|
+
*/
|
|
112
|
+
async parse(message: OrgEventMessage): Promise<OrgEventBase> {
|
|
113
|
+
await this.checkMessage(message);
|
|
114
|
+
|
|
115
|
+
// Check that the event is for the expected org
|
|
116
|
+
const orgEvent: OrgEventBase = JSON.parse(message.Message);
|
|
117
|
+
if (orgEvent.org !== this.#orgId) {
|
|
118
|
+
throw new Error(`Expected org to be '${this.#orgId}', found '${orgEvent.org}'`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return orgEvent;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fetches a certificate from a given URL or from the certificate cache.
|
|
126
|
+
* Throws an error if the URL does not correspond to an SNS certificate URL.
|
|
127
|
+
*
|
|
128
|
+
* Note: Ideally, this method would verify the certificate chain, but there
|
|
129
|
+
* is no obvious chain. Instead, this method only fetches certificates from
|
|
130
|
+
* a small set of allowlisted URLs.
|
|
131
|
+
*
|
|
132
|
+
* @param {URL} url The URL of the certificate
|
|
133
|
+
* @return {X509Certificate} The certificate
|
|
134
|
+
*/
|
|
135
|
+
async #fetchAndValidateCertificate(url: URL): Promise<X509Certificate> {
|
|
136
|
+
const currTime = new Date().getTime();
|
|
137
|
+
const cachedCertificate = this.#cachedCertificates.get(url);
|
|
138
|
+
if (cachedCertificate && currTime < new Date(cachedCertificate.validTo).getTime()) {
|
|
139
|
+
return cachedCertificate;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Only fetch certificates from HTTPS URLs
|
|
143
|
+
if (url.protocol !== "https:") {
|
|
144
|
+
throw new Error("Expected signing certificate URL to use HTTPS");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Only fetch certificate URLs for SNS
|
|
148
|
+
if (SNS_CERTIFICATE_URL_HOSTS.indexOf(url.host) === -1) {
|
|
149
|
+
throw new Error("Expected signing certificate URL for SNS in us-east-1");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const response = await fetch(url);
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(`Unable to download certificate. Status: ${response.status}`);
|
|
155
|
+
}
|
|
156
|
+
const blob = await response.blob();
|
|
157
|
+
const certificate = new X509Certificate(await blob.text());
|
|
158
|
+
if (!certificate.checkHost(SNS_CERTIFICATE_HOST)) {
|
|
159
|
+
throw new Error(`Expected certificate to be for '${SNS_CERTIFICATE_HOST}'`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check validity times
|
|
163
|
+
if (currTime < new Date(certificate.validFrom).getTime()) {
|
|
164
|
+
throw new Error("Certificate not valid yet");
|
|
165
|
+
}
|
|
166
|
+
if (new Date(certificate.validTo).getTime() < currTime) {
|
|
167
|
+
throw new Error("Certificate expired");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.#cachedCertificates.set(url, certificate);
|
|
171
|
+
return certificate;
|
|
172
|
+
}
|
|
173
|
+
}
|