// Copyright 2018 JDCLOUD.COM // // 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 // This signer is modified from AWS V4 signer algorithm. var util = require('../util') var RequestSigner = require('./request_signer') var v2Credentials = require('./v2_credentials') var uuid = require('uuid') var JDCloud = require('../core') module.exports = class SignerV2 extends RequestSigner { constructor (request, serviceName, options = {}) { super(request) this.signatureCache = true this.algorithm = 'JDCLOUD2-HMAC-SHA256' this.unsignableHeaders = ['authorization', 'user-agent'] this.serviceName = serviceName // this.signatureCache = typeof options.signatureCache === 'boolean' ? options.signatureCache : true; } // 签名流程见 https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html addAuthorization (credentials, date) { // var datetime = '20180119T070300Z'; var datetime = util.date.iso8601(date).replace(/[:-]|\.\d{3}/g, '') this.addHeaders(credentials, datetime) this.request.request.headers.set( 'Authorization', this.authorization(credentials, datetime) ) } addHeaders (credentials, datetime) { this.request.request.headers.set('x-jdcloud-date', datetime) this.request.request.headers.set('x-jdcloud-nonce', uuid.v4()) this.request.request.headers.set( 'host', this.request.service.config.endpoint.host ) } signedHeaders () { var keys = [] this.request.request.headers.forEach((value, key) => { key = key.toLowerCase() if (this.isSignableHeader(key)) { keys.push(key) } }) /* util.each.call(this, this.request.headers, function (key) { }); */ return keys.sort().join(';') } credentialString (datetime) { return v2Credentials.createScope( datetime.substr(0, 8), this.request.regionId, this.serviceName ) } signature (credentials, datetime) { var signingKey = v2Credentials.getSigningKey( credentials, datetime.substr(0, 8), this.request.regionId, this.serviceName, this.signatureCache ) return util.crypto.hmac(signingKey, this.stringToSign(datetime), 'hex') } stringToSign (datetime) { var parts = [] parts.push(this.algorithm) parts.push(datetime) parts.push(this.credentialString(datetime)) parts.push(this.hexEncodedHash(this.canonicalString())) JDCloud.config.logger('StringToSign is \n' + JSON.stringify(parts), 'DEBUG') return parts.join('\n') } // 构建标准签名字符串 canonicalString () { var parts = [] var pathname = this.request.path // if (this.serviceName !== 'jfs') { // pathname = util.uriEscapePath(pathname) // } parts.push(this.request.request.method) parts.push(pathname) parts.push(this.request.search()) parts.push(this.canonicalHeaders() + '\n') parts.push(this.signedHeaders()) parts.push(this.hexEncodedBodyHash()) JDCloud.config.logger( 'canonicalString is \n' + JSON.stringify(parts), 'DEBUG' ) return parts.join('\n') } canonicalHeaders () { var headers = [] this.request.request.headers.forEach((value, key) => { headers.push([key, value]) }) headers.sort(function (a, b) { return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1 }) var parts = [] util.arrayEach.call(this, headers, function (item) { var key = item[0].toLowerCase() if (this.isSignableHeader(key)) { var value = item[1] if ( typeof value === 'undefined' || value === null || typeof value.toString !== 'function' ) { throw util.error( new Error('Header ' + key + ' contains invalid value'), { code: 'InvalidHeader' } ) } parts.push(key + ':' + this.canonicalHeaderValues(value.toString())) } }) return parts.join('\n') } canonicalHeaderValues (values) { return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '') } authorization (credentials, datetime) { var parts = [] var credString = this.credentialString(datetime) parts.push( this.algorithm + ' Credential=' + credentials.accessKeyId + '/' + credString ) parts.push('SignedHeaders=' + this.signedHeaders()) parts.push('Signature=' + this.signature(credentials, datetime)) JDCloud.config.logger('Signature is \n' + JSON.stringify(parts), 'DEBUG') return parts.join(', ') } hexEncodedHash (string) { return util.crypto.sha256(string, 'hex') } hexEncodedBodyHash () { return this.hexEncodedHash(this.request.request.body || '') /* var request = this.request; if (this.isPresigned() && this.serviceName === 's3' && !request.body) { return 'UNSIGNED-PAYLOAD'; } else if (request.headers['X-Amz-Content-Sha256']) { return request.headers['X-Amz-Content-Sha256']; } else { return this.hexEncodedHash(this.request.body || ''); } */ } isSignableHeader (key) { if (key.toLowerCase().includes('x-jdcloud-')) { return true } return !this.unsignableHeaders.includes(key.toLowerCase()) } }