Skip to content

Commit

Permalink
[Auth](ft): Synchronious check
Browse files Browse the repository at this point in the history
- Split the check and the backend call for the
  authentification
  • Loading branch information
alexandre-merle committed Jun 24, 2016
1 parent f39cfb5 commit bf1b9b5
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 276 deletions.
69 changes: 45 additions & 24 deletions lib/auth/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@ const constructStringToSignV4 = require('./v4/constructStringToSign');
const convertUTCtoISO8601 = require('./v4/timeUtils').convertUTCtoISO8601;
const crypto = require('crypto');
const vaultUtilities = require('./in_memory/vaultUtilities');

let vault = require('./vault');
const auth = {};

auth.setAuthHandler = handler => {
authV2.headerAuthCheck.setAuthHandler(handler);
authV2.queryAuthCheck.setAuthHandler(handler);
authV4.headerAuthCheck.setAuthHandler(handler);
authV4.queryAuthCheck.setAuthHandler(handler);
vault = handler;
return auth;
};

auth.doAuth = (request, log, cb, awsService, data) => {
auth.check = (request, log, awsService, data) => {
log.debug('running auth checks', { method: 'auth' });
const authHeader = request.headers.authorization;
// Check whether signature is in header
Expand All @@ -31,41 +28,64 @@ auth.doAuth = (request, log, cb, awsService, data) => {
// handle temporary security credentials
if (authHeader.startsWith('AWS ')) {
log.trace('authenticating request with auth v2 using headers');
authV2.headerAuthCheck.check(request, log, cb);
return authV2.headerAuthCheck.check(request, log, data);
} else if (authHeader.startsWith('AWS4')) {
log.debug('authenticating request with Auth V4 using headers');
authV4.headerAuthCheck.check(request, log, cb, awsService);
} else {
log.warn('missing authorization security header');
return cb(errors.MissingSecurityHeader);
return authV4.headerAuthCheck.check(request, log, awsService);
}
log.warn('missing authorization security header');
return { err: errors.MissingSecurityHeader };
} else if (data.Signature) {
// Check whether signature is in query string
log.trace('authenticating request with auth v2 using query string');
authV2.queryAuthCheck.check(request, log, data, cb);
return authV2.queryAuthCheck.check(request, log, data);
} else if (data['X-Amz-Algorithm']) {
log.debug('authenticating request with Auth v4 using query string');
authV4.queryAuthCheck.check(request, log, data, cb);
} else {
// If no auth information is provided in request, then
// user is part of 'All Users Group' so send back this
// group as the canonicalID
log.trace('No authentication provided. User identified as public');
const authInfo = new AuthInfo({ canonicalID: constants.publicId });
return cb(null, authInfo);
return authV4.queryAuthCheck.check(request, log, data);
}
// If no auth information is provided in request, then
// user is part of 'All Users Group' so send back this
// group as the canonicalID
log.trace('No authentication provided. User identified as public');
const authInfo = new AuthInfo({ canonicalID: constants.publicId });
return { err: null, data: authInfo };
};

auth.doAuth = (request, log, cb, awsService, data) => {
const res = auth.check(request, log, awsService, data);
if (res.err) {
return cb(res.err);
} else if (res.version === 2) {
return vault.authenticateV2Request(res.data.accessKey,
res.data.signatureFromRequest,
res.data.stringToSign, res.data.algo, log,
(err, authInfo) => {
if (err) {
return cb(err);
}
return cb(null, authInfo);
});
} else if (res.version === 4) {
res.data.log = log;
return vault.authenticateV4Request(res.data, cb, awsService);
} else if (res.data instanceof AuthInfo) {
return cb(null, res.data);
}
return undefined;
log.error('Cannot found authentification method', {
method: 'Arsenal.auth.doAuth',
});
return cb(errors.InternalError);
};

auth.generateV4Headers =
(request, data, accessKey, secretKeyValue) => {
(request, data, accessKey, secretKeyValue, awsService) => {
Object.assign(request, { headers: {} });
const amzDate = convertUTCtoISO8601(Date.now());
// get date without time
const scopeDate = amzDate.slice(0, amzDate.indexOf('T'));
const signedHeaders = 'host;x-amz-date;x-amz-content-sha256';
const region = 'us-east-1';
const service = 'iam';
const service = awsService || 'iam';
const credentialScope =
`${scopeDate}/${region}/${service}/aws4_request`;
const timestamp = amzDate;
Expand All @@ -91,7 +111,8 @@ auth.generateV4Headers =
const stringToSign = constructStringToSignV4(params);
const signingKey = vaultUtilities.calculateSigningKey(secretKeyValue,
region,
scopeDate);
scopeDate,
service);
const signature = crypto.createHmac('sha256', signingKey)
.update(stringToSign).digest('hex');
const authorizationHeader = `${algorithm} Credential=${accessKey}` +
Expand Down
5 changes: 3 additions & 2 deletions lib/auth/in_memory/vaultUtilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ function hashSignature(stringToSign, secretKey, algorithm) {
* @param {string} secretKey - requester's secretKey
* @param {string} region - region included in request
* @param {string} scopeDate - scopeDate included in request
* @param {string} [service] - To specify another service than s3
* @return {string} signingKey - signingKey to calculate signature
*/
function calculateSigningKey(secretKey, region, scopeDate) {
function calculateSigningKey(secretKey, region, scopeDate, service) {
const dateKey = crypto.createHmac('sha256', `AWS4${secretKey}`)
.update(scopeDate).digest('binary');
const dateRegionKey = crypto.createHmac('sha256', dateKey)
.update(region).digest('binary');
const dateRegionServiceKey = crypto.createHmac('sha256', dateRegionKey)
.update('s3').digest('binary');
.update(service || 's3').digest('binary');
const signingKey = crypto.createHmac('sha256', dateRegionServiceKey)
.update('aws4_request').digest('binary');
return signingKey;
Expand Down
40 changes: 18 additions & 22 deletions lib/auth/v2/headerAuthCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@ const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry');
const algoCheck = require('./algoCheck');

let vault = require('../vault');

const headerAuthCheck = {};

headerAuthCheck.setAuthHandler = handler => {
vault = handler;
return headerAuthCheck;
};

headerAuthCheck.check = (request, log, callback) => {
headerAuthCheck.check = (request, log, data) => {
log.trace('running header auth check');
const headers = request.headers;

Expand All @@ -24,51 +17,54 @@ headerAuthCheck.check = (request, log, callback) => {
timestamp = Date.parse(timestamp);
if (!timestamp) {
log.warn('missing security header: invalid date/timestamp');
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}

const timeout = checkRequestExpiry(timestamp, log);
if (timeout) {
log.warn('request time too skewed', { timestamp });
return callback(errors.RequestTimeTooSkewed);
return { err: errors.RequestTimeTooSkewed };
}
// Authorization Header should be
// in the format of 'AWS AccessKey:Signature'
const authInfo = headers.authorization;

if (!authInfo) {
log.warn('missing authorization security header');
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}
const semicolonIndex = authInfo.indexOf(':');
if (semicolonIndex < 0) {
log.warn('invalid authorization header', { authInfo });
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}
const accessKey = semicolonIndex > 4 ?
authInfo.substring(4, semicolonIndex).trim() : undefined;
if (typeof accessKey !== 'string' || accessKey.length === 0) {
log.trace('invalid authorization header', { authInfo });
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}
log.addDefaultFields({ accessKey });

const signatureFromRequest = authInfo.substring(semicolonIndex + 1).trim();
log.trace('signature from request', { signatureFromRequest });
const stringToSign = constructStringToSign(request, log);
const stringToSign = constructStringToSign(request, data, log);
log.trace('constructed string to sign', { stringToSign });
const algo = algoCheck(signatureFromRequest.length);
log.trace('algo for calculating signature', { algo });
if (algo === undefined) {
return callback(errors.InvalidArgument);
return { err: errors.InvalidArgument };
}
return vault.authenticateV2Request(accessKey,
signatureFromRequest, stringToSign, algo, log, (err, authInfo) => {
if (err) {
return callback(err);
}
return callback(null, authInfo);
});
return {
err: null,
version: 2,
data: {
accessKey,
signatureFromRequest,
stringToSign,
algo,
},
};
};

module.exports = headerAuthCheck;
40 changes: 17 additions & 23 deletions lib/auth/v2/queryAuthCheck.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
'use strict'; // eslint-disable-line strict

const errors = require('../../../index').errors;
const errors = require('../../errors');

const algoCheck = require('./algoCheck');
const constructStringToSign = require('./constructStringToSign');
const checkRequestExpiry = require('./checkRequestExpiry');

let vault = require('../vault');

const queryAuthCheck = {};

queryAuthCheck.setAuthHandler = handler => {
vault = handler;
return queryAuthCheck;
};

queryAuthCheck.check = (request, log, data, callback) => {
queryAuthCheck.check = (request, log, data) => {
log.trace('running query auth check');
if (request.method === 'POST') {
log.warn('query string auth not supported for post requests');
return callback(errors.NotImplemented);
return { err: errors.NotImplemented };
}

/*
Check whether request has expired or if
expires parameter is more than 15 minutes in the future.
Expand All @@ -33,11 +25,11 @@ queryAuthCheck.check = (request, log, data, callback) => {
if (isNaN(expirationTime)) {
log.warn('invalid expires parameter',
{ expires: data.Expires });
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}
const timeout = checkRequestExpiry(expirationTime, log);
if (timeout) {
return callback(errors.RequestTimeTooSkewed);
return { err: errors.RequestTimeTooSkewed };
}
const accessKey = data.AWSAccessKeyId;
log.addDefaultFields({ accessKey });
Expand All @@ -46,23 +38,25 @@ queryAuthCheck.check = (request, log, data, callback) => {
log.trace('signature from request', { signatureFromRequest });
if (!accessKey || !signatureFromRequest) {
log.warn('invalid access key/signature parameters');
return callback(errors.MissingSecurityHeader);
return { err: errors.MissingSecurityHeader };
}
const stringToSign = constructStringToSign(request, data, log);
log.trace('constructed string to sign', { stringToSign });
const algo = algoCheck(signatureFromRequest.length);
log.trace('algo for calculating signature', { algo });
if (algo === undefined) {
return callback(errors.InvalidArgument);
return { err: errors.InvalidArgument };
}
return vault.authenticateV2Request(accessKey,
signatureFromRequest, stringToSign, algo, log, (err, authInfo) => {
if (err) {
return callback(err);
}
return callback(null, authInfo);
}
);
return {
err: null,
version: 2,
data: {
accessKey,
signatureFromRequest,
stringToSign,
algo,
},
};
};

module.exports = queryAuthCheck;
Loading

0 comments on commit bf1b9b5

Please sign in to comment.