UNPKG

9.06 kBJavaScriptView Raw
1import { toHex } from "@aws-sdk/util-hex-encoding";
2import { normalizeProvider } from "@aws-sdk/util-middleware";
3import { toUint8Array } from "@aws-sdk/util-utf8";
4import { ALGORITHM_IDENTIFIER, ALGORITHM_QUERY_PARAM, AMZ_DATE_HEADER, AMZ_DATE_QUERY_PARAM, AUTH_HEADER, CREDENTIAL_QUERY_PARAM, EVENT_ALGORITHM_IDENTIFIER, EXPIRES_QUERY_PARAM, MAX_PRESIGNED_TTL, SHA256_HEADER, SIGNATURE_QUERY_PARAM, SIGNED_HEADERS_QUERY_PARAM, TOKEN_HEADER, TOKEN_QUERY_PARAM, } from "./constants";
5import { createScope, getSigningKey } from "./credentialDerivation";
6import { getCanonicalHeaders } from "./getCanonicalHeaders";
7import { getCanonicalQuery } from "./getCanonicalQuery";
8import { getPayloadHash } from "./getPayloadHash";
9import { hasHeader } from "./headerUtil";
10import { moveHeadersToQuery } from "./moveHeadersToQuery";
11import { prepareRequest } from "./prepareRequest";
12import { iso8601 } from "./utilDate";
13export class SignatureV4 {
14 constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath = true, }) {
15 this.service = service;
16 this.sha256 = sha256;
17 this.uriEscapePath = uriEscapePath;
18 this.applyChecksum = typeof applyChecksum === "boolean" ? applyChecksum : true;
19 this.regionProvider = normalizeProvider(region);
20 this.credentialProvider = normalizeProvider(credentials);
21 }
22 async presign(originalRequest, options = {}) {
23 const { signingDate = new Date(), expiresIn = 3600, unsignableHeaders, unhoistableHeaders, signableHeaders, signingRegion, signingService, } = options;
24 const credentials = await this.credentialProvider();
25 this.validateResolvedCredentials(credentials);
26 const region = signingRegion ?? (await this.regionProvider());
27 const { longDate, shortDate } = formatDate(signingDate);
28 if (expiresIn > MAX_PRESIGNED_TTL) {
29 return Promise.reject("Signature version 4 presigned URLs" + " must have an expiration date less than one week in" + " the future");
30 }
31 const scope = createScope(shortDate, region, signingService ?? this.service);
32 const request = moveHeadersToQuery(prepareRequest(originalRequest), { unhoistableHeaders });
33 if (credentials.sessionToken) {
34 request.query[TOKEN_QUERY_PARAM] = credentials.sessionToken;
35 }
36 request.query[ALGORITHM_QUERY_PARAM] = ALGORITHM_IDENTIFIER;
37 request.query[CREDENTIAL_QUERY_PARAM] = `${credentials.accessKeyId}/${scope}`;
38 request.query[AMZ_DATE_QUERY_PARAM] = longDate;
39 request.query[EXPIRES_QUERY_PARAM] = expiresIn.toString(10);
40 const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders);
41 request.query[SIGNED_HEADERS_QUERY_PARAM] = getCanonicalHeaderList(canonicalHeaders);
42 request.query[SIGNATURE_QUERY_PARAM] = await this.getSignature(longDate, scope, this.getSigningKey(credentials, region, shortDate, signingService), this.createCanonicalRequest(request, canonicalHeaders, await getPayloadHash(originalRequest, this.sha256)));
43 return request;
44 }
45 async sign(toSign, options) {
46 if (typeof toSign === "string") {
47 return this.signString(toSign, options);
48 }
49 else if (toSign.headers && toSign.payload) {
50 return this.signEvent(toSign, options);
51 }
52 else {
53 return this.signRequest(toSign, options);
54 }
55 }
56 async signEvent({ headers, payload }, { signingDate = new Date(), priorSignature, signingRegion, signingService }) {
57 const region = signingRegion ?? (await this.regionProvider());
58 const { shortDate, longDate } = formatDate(signingDate);
59 const scope = createScope(shortDate, region, signingService ?? this.service);
60 const hashedPayload = await getPayloadHash({ headers: {}, body: payload }, this.sha256);
61 const hash = new this.sha256();
62 hash.update(headers);
63 const hashedHeaders = toHex(await hash.digest());
64 const stringToSign = [
65 EVENT_ALGORITHM_IDENTIFIER,
66 longDate,
67 scope,
68 priorSignature,
69 hashedHeaders,
70 hashedPayload,
71 ].join("\n");
72 return this.signString(stringToSign, { signingDate, signingRegion: region, signingService });
73 }
74 async signString(stringToSign, { signingDate = new Date(), signingRegion, signingService } = {}) {
75 const credentials = await this.credentialProvider();
76 this.validateResolvedCredentials(credentials);
77 const region = signingRegion ?? (await this.regionProvider());
78 const { shortDate } = formatDate(signingDate);
79 const hash = new this.sha256(await this.getSigningKey(credentials, region, shortDate, signingService));
80 hash.update(toUint8Array(stringToSign));
81 return toHex(await hash.digest());
82 }
83 async signRequest(requestToSign, { signingDate = new Date(), signableHeaders, unsignableHeaders, signingRegion, signingService, } = {}) {
84 const credentials = await this.credentialProvider();
85 this.validateResolvedCredentials(credentials);
86 const region = signingRegion ?? (await this.regionProvider());
87 const request = prepareRequest(requestToSign);
88 const { longDate, shortDate } = formatDate(signingDate);
89 const scope = createScope(shortDate, region, signingService ?? this.service);
90 request.headers[AMZ_DATE_HEADER] = longDate;
91 if (credentials.sessionToken) {
92 request.headers[TOKEN_HEADER] = credentials.sessionToken;
93 }
94 const payloadHash = await getPayloadHash(request, this.sha256);
95 if (!hasHeader(SHA256_HEADER, request.headers) && this.applyChecksum) {
96 request.headers[SHA256_HEADER] = payloadHash;
97 }
98 const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders);
99 const signature = await this.getSignature(longDate, scope, this.getSigningKey(credentials, region, shortDate, signingService), this.createCanonicalRequest(request, canonicalHeaders, payloadHash));
100 request.headers[AUTH_HEADER] =
101 `${ALGORITHM_IDENTIFIER} ` +
102 `Credential=${credentials.accessKeyId}/${scope}, ` +
103 `SignedHeaders=${getCanonicalHeaderList(canonicalHeaders)}, ` +
104 `Signature=${signature}`;
105 return request;
106 }
107 createCanonicalRequest(request, canonicalHeaders, payloadHash) {
108 const sortedHeaders = Object.keys(canonicalHeaders).sort();
109 return `${request.method}
110${this.getCanonicalPath(request)}
111${getCanonicalQuery(request)}
112${sortedHeaders.map((name) => `${name}:${canonicalHeaders[name]}`).join("\n")}
113
114${sortedHeaders.join(";")}
115${payloadHash}`;
116 }
117 async createStringToSign(longDate, credentialScope, canonicalRequest) {
118 const hash = new this.sha256();
119 hash.update(toUint8Array(canonicalRequest));
120 const hashedRequest = await hash.digest();
121 return `${ALGORITHM_IDENTIFIER}
122${longDate}
123${credentialScope}
124${toHex(hashedRequest)}`;
125 }
126 getCanonicalPath({ path }) {
127 if (this.uriEscapePath) {
128 const normalizedPathSegments = [];
129 for (const pathSegment of path.split("/")) {
130 if (pathSegment?.length === 0)
131 continue;
132 if (pathSegment === ".")
133 continue;
134 if (pathSegment === "..") {
135 normalizedPathSegments.pop();
136 }
137 else {
138 normalizedPathSegments.push(pathSegment);
139 }
140 }
141 const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""}`;
142 const doubleEncoded = encodeURIComponent(normalizedPath);
143 return doubleEncoded.replace(/%2F/g, "/");
144 }
145 return path;
146 }
147 async getSignature(longDate, credentialScope, keyPromise, canonicalRequest) {
148 const stringToSign = await this.createStringToSign(longDate, credentialScope, canonicalRequest);
149 const hash = new this.sha256(await keyPromise);
150 hash.update(toUint8Array(stringToSign));
151 return toHex(await hash.digest());
152 }
153 getSigningKey(credentials, region, shortDate, service) {
154 return getSigningKey(this.sha256, credentials, shortDate, region, service || this.service);
155 }
156 validateResolvedCredentials(credentials) {
157 if (typeof credentials !== "object" ||
158 typeof credentials.accessKeyId !== "string" ||
159 typeof credentials.secretAccessKey !== "string") {
160 throw new Error("Resolved credential object is not valid");
161 }
162 }
163}
164const formatDate = (now) => {
165 const longDate = iso8601(now).replace(/[\-:]/g, "");
166 return {
167 longDate,
168 shortDate: longDate.slice(0, 8),
169 };
170};
171const getCanonicalHeaderList = (headers) => Object.keys(headers).sort().join(";");