minio
Version:
S3 Compatible Cloud Storage client
336 lines (262 loc) • 13.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.postPresignSignatureV4 = postPresignSignatureV4;
exports.presignSignatureV4 = presignSignatureV4;
exports.signV4 = signV4;
exports.signV4ByServiceName = signV4ByServiceName;
var _crypto = _interopRequireDefault(require("crypto"));
var _lodash = _interopRequireDefault(require("lodash"));
var _helpers = require("./helpers.js");
var errors = _interopRequireWildcard(require("./errors.js"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
* MinIO Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2016 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const signV4Algorithm = 'AWS4-HMAC-SHA256'; // getCanonicalRequest generate a canonical request of style.
//
// canonicalRequest =
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
//
function getCanonicalRequest(method, path, headers, signedHeaders, hashedPayload) {
if (!(0, _helpers.isString)(method)) {
throw new TypeError('method should be of type "string"');
}
if (!(0, _helpers.isString)(path)) {
throw new TypeError('path should be of type "string"');
}
if (!(0, _helpers.isObject)(headers)) {
throw new TypeError('headers should be of type "object"');
}
if (!(0, _helpers.isArray)(signedHeaders)) {
throw new TypeError('signedHeaders should be of type "array"');
}
if (!(0, _helpers.isString)(hashedPayload)) {
throw new TypeError('hashedPayload should be of type "string"');
}
const headersArray = signedHeaders.reduce((acc, i) => {
// Trim spaces from the value (required by V4 spec)
const val = `${headers[i]}`.replace(/ +/g, " ");
acc.push(`${i.toLowerCase()}:${val}`);
return acc;
}, []);
const requestResource = path.split('?')[0];
let requestQuery = path.split('?')[1];
if (!requestQuery) requestQuery = '';
if (requestQuery) {
requestQuery = requestQuery.split('&').sort().map(element => element.indexOf('=') === -1 ? element + '=' : element).join('&');
}
const canonical = [];
canonical.push(method.toUpperCase());
canonical.push(requestResource);
canonical.push(requestQuery);
canonical.push(headersArray.join('\n') + '\n');
canonical.push(signedHeaders.join(';').toLowerCase());
canonical.push(hashedPayload);
return canonical.join('\n');
} // generate a credential string
function getCredential(accessKey, region, requestDate, serviceName = "s3") {
if (!(0, _helpers.isString)(accessKey)) {
throw new TypeError('accessKey should be of type "string"');
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isObject)(requestDate)) {
throw new TypeError('requestDate should be of type "object"');
}
return `${accessKey}/${(0, _helpers.getScope)(region, requestDate, serviceName)}`;
} // Returns signed headers array - alphabetically sorted
function getSignedHeaders(headers) {
if (!(0, _helpers.isObject)(headers)) {
throw new TypeError('request should be of type "object"');
} // Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
//
// User-Agent:
//
// This is ignored from signing because signing this causes problems with generating pre-signed URLs
// (that are executed by other agents) or when customers pass requests through proxies, which may
// modify the user-agent.
//
// Content-Length:
//
// This is ignored from signing because generating a pre-signed URL should not provide a content-length
// constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to this is that when
// sending regular requests (non-pre-signed), the signature contains a checksum of the body, which
// implicitly validates the payload length (since changing the number of bytes would change the checksum)
// and therefore this header is not valuable in the signature.
//
// Content-Type:
//
// Signing this header causes quite a number of problems in browser environments, where browsers
// like to modify and normalize the content-type header in different ways. There is more information
// on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field simplifies logic
// and reduces the possibility of future bugs
//
// Authorization:
//
// Is skipped for obvious reasons
const ignoredHeaders = ['authorization', 'content-length', 'content-type', 'user-agent'];
return _lodash.default.map(headers, (v, header) => header).filter(header => ignoredHeaders.indexOf(header) === -1).sort();
} // returns the key used for calculating signature
function getSigningKey(date, region, secretKey, serviceName = "s3") {
if (!(0, _helpers.isObject)(date)) {
throw new TypeError('date should be of type "object"');
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isString)(secretKey)) {
throw new TypeError('secretKey should be of type "string"');
}
const dateLine = (0, _helpers.makeDateShort)(date);
let hmac1 = _crypto.default.createHmac('sha256', 'AWS4' + secretKey).update(dateLine).digest(),
hmac2 = _crypto.default.createHmac('sha256', hmac1).update(region).digest(),
hmac3 = _crypto.default.createHmac('sha256', hmac2).update(serviceName).digest();
return _crypto.default.createHmac('sha256', hmac3).update('aws4_request').digest();
} // returns the string that needs to be signed
function getStringToSign(canonicalRequest, requestDate, region, serviceName = "s3") {
if (!(0, _helpers.isString)(canonicalRequest)) {
throw new TypeError('canonicalRequest should be of type "string"');
}
if (!(0, _helpers.isObject)(requestDate)) {
throw new TypeError('requestDate should be of type "object"');
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
const hash = _crypto.default.createHash('sha256').update(canonicalRequest).digest('hex');
const scope = (0, _helpers.getScope)(region, requestDate, serviceName);
const stringToSign = [];
stringToSign.push(signV4Algorithm);
stringToSign.push((0, _helpers.makeDateLong)(requestDate));
stringToSign.push(scope);
stringToSign.push(hash);
const signString = stringToSign.join('\n');
return signString;
} // calculate the signature of the POST policy
function postPresignSignatureV4(region, date, secretKey, policyBase64) {
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isObject)(date)) {
throw new TypeError('date should be of type "object"');
}
if (!(0, _helpers.isString)(secretKey)) {
throw new TypeError('secretKey should be of type "string"');
}
if (!(0, _helpers.isString)(policyBase64)) {
throw new TypeError('policyBase64 should be of type "string"');
}
const signingKey = getSigningKey(date, region, secretKey);
return _crypto.default.createHmac('sha256', signingKey).update(policyBase64).digest('hex').toLowerCase();
} // Returns the authorization header
function signV4(request, accessKey, secretKey, region, requestDate, serviceName = "s3") {
if (!(0, _helpers.isObject)(request)) {
throw new TypeError('request should be of type "object"');
}
if (!(0, _helpers.isString)(accessKey)) {
throw new TypeError('accessKey should be of type "string"');
}
if (!(0, _helpers.isString)(secretKey)) {
throw new TypeError('secretKey should be of type "string"');
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!accessKey) {
throw new errors.AccessKeyRequiredError('accessKey is required for signing');
}
if (!secretKey) {
throw new errors.SecretKeyRequiredError('secretKey is required for signing');
}
const sha256sum = request.headers['x-amz-content-sha256'];
const signedHeaders = getSignedHeaders(request.headers);
const canonicalRequest = getCanonicalRequest(request.method, request.path, request.headers, signedHeaders, sha256sum);
const serviceIdentifier = serviceName || "s3";
const stringToSign = getStringToSign(canonicalRequest, requestDate, region, serviceIdentifier);
const signingKey = getSigningKey(requestDate, region, secretKey, serviceIdentifier);
const credential = getCredential(accessKey, region, requestDate, serviceIdentifier);
const signature = _crypto.default.createHmac('sha256', signingKey).update(stringToSign).digest('hex').toLowerCase();
return `${signV4Algorithm} Credential=${credential}, SignedHeaders=${signedHeaders.join(';').toLowerCase()}, Signature=${signature}`;
}
function signV4ByServiceName(request, accessKey, secretKey, region, requestDate, serviceName = "s3") {
return signV4(request, accessKey, secretKey, region, requestDate, serviceName);
} // returns a presigned URL string
function presignSignatureV4(request, accessKey, secretKey, sessionToken, region, requestDate, expires) {
if (!(0, _helpers.isObject)(request)) {
throw new TypeError('request should be of type "object"');
}
if (!(0, _helpers.isString)(accessKey)) {
throw new TypeError('accessKey should be of type "string"');
}
if (!(0, _helpers.isString)(secretKey)) {
throw new TypeError('secretKey should be of type "string"');
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!accessKey) {
throw new errors.AccessKeyRequiredError('accessKey is required for presigning');
}
if (!secretKey) {
throw new errors.SecretKeyRequiredError('secretKey is required for presigning');
}
if (!(0, _helpers.isNumber)(expires)) {
throw new TypeError('expires should be of type "number"');
}
if (expires < 1) {
throw new errors.ExpiresParamError('expires param cannot be less than 1 seconds');
}
if (expires > 604800) {
throw new errors.ExpiresParamError('expires param cannot be greater than 7 days');
}
const iso8601Date = (0, _helpers.makeDateLong)(requestDate);
const signedHeaders = getSignedHeaders(request.headers);
const credential = getCredential(accessKey, region, requestDate);
const hashedPayload = 'UNSIGNED-PAYLOAD';
const requestQuery = [];
requestQuery.push(`X-Amz-Algorithm=${signV4Algorithm}`);
requestQuery.push(`X-Amz-Credential=${(0, _helpers.uriEscape)(credential)}`);
requestQuery.push(`X-Amz-Date=${iso8601Date}`);
requestQuery.push(`X-Amz-Expires=${expires}`);
requestQuery.push(`X-Amz-SignedHeaders=${(0, _helpers.uriEscape)(signedHeaders.join(';').toLowerCase())}`);
if (sessionToken) {
requestQuery.push(`X-Amz-Security-Token=${(0, _helpers.uriEscape)(sessionToken)}`);
}
const resource = request.path.split('?')[0];
let query = request.path.split('?')[1];
if (query) {
query = query + '&' + requestQuery.join('&');
} else {
query = requestQuery.join('&');
}
const path = resource + '?' + query;
const canonicalRequest = getCanonicalRequest(request.method, path, request.headers, signedHeaders, hashedPayload);
const stringToSign = getStringToSign(canonicalRequest, requestDate, region);
const signingKey = getSigningKey(requestDate, region, secretKey);
const signature = _crypto.default.createHmac('sha256', signingKey).update(stringToSign).digest('hex').toLowerCase();
const presignedUrl = request.protocol + '//' + request.headers.host + path + `&X-Amz-Signature=${signature}`;
return presignedUrl;
}
//# sourceMappingURL=signing.js.map