Skip to content
This repository was archived by the owner on Aug 7, 2021. It is now read-only.

Commit b208bc2

Browse files
committed
Merge pull request #34 from AzureAD/dev
release to master
2 parents 9320d55 + cd70a17 commit b208bc2

15 files changed

+640
-25
lines changed

changelog.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Version 0.1.11
2+
--------------
3+
Release Date: 16 December 2014
4+
5+
* Added support for certificate authentication for confidential clients.
6+
17
Version 0.1.10
28
--------------
39
Release Date: 24 November 2014

lib/authentication-context.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,30 @@ AuthenticationContext.prototype.acquireTokenWithRefreshToken = function(refreshT
335335
});
336336
};
337337

338-
util.adalInit();
338+
/**
339+
* Gets a new access token using via a certificate credential.
340+
* @param {string} resource A URI that identifies the resource for which the token is valid.
341+
* @param {string} clientId The OAuth client id of the calling application.
342+
* @param {string} certificate A PEM encoded certificate private key.
343+
* @param {string} thumbprint A hex encoded thumbprint of the certificate.
344+
* @param {AcquireTokenCallback} callback The callback function.
345+
*/
346+
AuthenticationContext.prototype.acquireTokenWithClientCertificate = function(resource, clientId, certificate, thumbprint, callback) {
347+
argument.validateCallbackType(callback);
348+
try {
349+
argument.validateStringParameter(resource, 'resource');
350+
argument.validateStringParameter(certificate, 'certificate');
351+
argument.validateStringParameter(thumbprint, 'thumbprint');
352+
} catch(err) {
353+
callback(err);
354+
return;
355+
}
356+
357+
this._acquireToken(callback, function() {
358+
var tokenRequest = new TokenRequest(this._callContext, this, clientId, resource);
359+
tokenRequest.getTokenWithCertificate(certificate, thumbprint, callback);
360+
});
361+
};
339362

340363
var exports = {
341364
AuthenticationContext : AuthenticationContext,
@@ -347,4 +370,5 @@ var exports = {
347370
}
348371
};
349372

350-
module.exports = exports;
373+
util.adalInit();
374+
module.exports = exports;

lib/authentication-parameters.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* @copyright
33
* Copyright © Microsoft Open Technologies, Inc.
44
*

lib/constants.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ var Constants = {
2424
OAuth2 : {
2525
Parameters : {
2626
GRANT_TYPE : 'grant_type',
27+
CLIENT_ASSERTION : 'client_assertion',
28+
CLIENT_ASSERTION_TYPE : 'client_assertion_type',
2729
CLIENT_ID : 'client_id',
2830
CLIENT_SECRET : 'client_secret',
2931
REDIRECT_URI : 'redirect_uri',
@@ -41,6 +43,7 @@ var Constants = {
4143
AUTHORIZATION_CODE : 'authorization_code',
4244
REFRESH_TOKEN : 'refresh_token',
4345
CLIENT_CREDENTIALS : 'client_credentials',
46+
JWT_BEARER : 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
4447
PASSWORD : 'password',
4548
SAML1 : 'urn:ietf:params:oauth:grant-type:saml1_1-bearer',
4649
SAML2 : 'urn:ietf:params:oauth:grant-type:saml2-bearer'
@@ -99,6 +102,16 @@ var Constants = {
99102
CLOCK_BUFFER : 5 // In minutes.
100103
},
101104

105+
Jwt : {
106+
SELF_SIGNED_JWT_LIFETIME : 10, // 10 mins in mins
107+
AUDIENCE : 'aud',
108+
ISSUER : 'iss',
109+
SUBJECT : 'sub',
110+
NOT_BEFORE : 'nbf',
111+
EXPIRES_ON : 'exp',
112+
JWT_ID : 'jti'
113+
},
114+
102115
AADConstants : {
103116
WORLD_WIDE_AUTHORITY : 'login.windows.net',
104117
WELL_KNOWN_AUTHORITY_HOSTS : ['login.windows.net', 'login.chinacloudapi.cn', 'login.cloudgovapi.us'],
@@ -156,4 +169,4 @@ var Constants = {
156169
}
157170
};
158171

159-
module.exports = Constants;
172+
module.exports = Constants;

lib/self-signed-jwt.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* @copyright
3+
* Copyright © Microsoft Open Technologies, Inc.
4+
*
5+
* All Rights Reserved
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http: *www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
15+
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
16+
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
17+
*
18+
* See the Apache License, Version 2.0 for the specific language
19+
* governing permissions and limitations under the License.
20+
*/
21+
'use strict';
22+
23+
var jwtConstants = require('./constants').Jwt;
24+
var Logger = require('./log').Logger;
25+
var util = require('./util');
26+
27+
require('date-utils');
28+
var jws = require('jws');
29+
var uuid = require('node-uuid');
30+
31+
/**
32+
* JavaScript dates are in milliseconds, but JWT dates are in seconds.
33+
* This function does the conversion.
34+
* @param {Date} date
35+
* @return {string}
36+
*/
37+
function dateGetTimeInSeconds(date) {
38+
return Math.floor(date.getTime()/1000);
39+
}
40+
41+
/**
42+
* Constructs a new SelfSignedJwt object.
43+
* @param {object} callContext Context specific to this token request.
44+
* @param {Authority} authority The authority to be used as the JWT audience.
45+
* @param {string} clientId The client id of the calling app.
46+
*/
47+
function SelfSignedJwt(callContext, authority, clientId) {
48+
this._log = new Logger('SelfSignedJwt', callContext._logContext);
49+
this._callContext = callContext;
50+
51+
this._authority = authority;
52+
this._tokenEndpoint = authority.tokenEndpoint;
53+
this._clientId = clientId;
54+
}
55+
56+
/**
57+
* This wraps date creation in order to make unit testing easier.
58+
* @return {Date}
59+
*/
60+
SelfSignedJwt.prototype._getDateNow = function() {
61+
return new Date();
62+
};
63+
64+
SelfSignedJwt.prototype._getNewJwtId = function() {
65+
return uuid.v4();
66+
};
67+
68+
/**
69+
* A regular certificate thumbprint is a hex encode string of the binary certificate
70+
* hash. For some reason teh x5t value in a JWT is a url save base64 encoded string
71+
* instead. This function does the conversion.
72+
* @param {string} thumbprint A hex encoded certificate thumbprint.
73+
* @return {string} A url safe base64 encoded certificate thumbprint.
74+
*/
75+
SelfSignedJwt.prototype._createx5tValue = function(thumbprint) {
76+
var hexString = thumbprint.replace(/:/g, '').replace(/ /g, '');
77+
var base64 = (new Buffer(hexString, 'hex')).toString('base64');
78+
return util.convertRegularToUrlSafeBase64EncodedString(base64);
79+
};
80+
81+
/**
82+
* Creates the JWT header.
83+
* @param {string} thumbprint A hex encoded certificate thumbprint.
84+
* @return {object}
85+
*/
86+
SelfSignedJwt.prototype._createHeader = function(thumbprint) {
87+
var x5t = this._createx5tValue(thumbprint);
88+
var header = { typ: 'JWT', alg: 'RS256', x5t : x5t };
89+
90+
this._log.verbose('Creating self signed JWT header. x5t: ' + x5t);
91+
92+
return header;
93+
};
94+
95+
/**
96+
* Creates the JWT payload.
97+
* @return {object}
98+
*/
99+
SelfSignedJwt.prototype._createPayload = function() {
100+
var now = this._getDateNow();
101+
var expires = (new Date(now.getTime())).addMinutes(jwtConstants.SELF_SIGNED_JWT_LIFETIME);
102+
103+
this._log.verbose('Creating self signed JWT payload. Expires: ' + expires + ' NotBefore: ' + now);
104+
105+
var jwtPayload = {};
106+
jwtPayload[jwtConstants.AUDIENCE] = this._tokenEndpoint;
107+
jwtPayload[jwtConstants.ISSUER] = this._clientId;
108+
jwtPayload[jwtConstants.SUBJECT] = this._clientId;
109+
jwtPayload[jwtConstants.NOT_BEFORE] = dateGetTimeInSeconds(now);
110+
jwtPayload[jwtConstants.EXPIRES_ON] = dateGetTimeInSeconds(expires);
111+
jwtPayload[jwtConstants.JWT_ID] = this._getNewJwtId();
112+
113+
return jwtPayload;
114+
};
115+
116+
SelfSignedJwt.prototype._throwOnInvalidJwtSignature = function(jwt) {
117+
var jwtSegments = jwt.split('.');
118+
119+
if (3 > jwtSegments.length || !jwtSegments[2]) {
120+
throw this._log.createError('Failed to sign JWT. This is most likely due to an invalid certificate.');
121+
}
122+
123+
return;
124+
};
125+
126+
SelfSignedJwt.prototype._signJwt = function(header, payload, certificate) {
127+
var jwt = jws.sign({ header : header, payload : payload, secret : certificate});
128+
this._throwOnInvalidJwtSignature(jwt);
129+
return jwt;
130+
};
131+
132+
SelfSignedJwt.prototype._reduceThumbprint = function(thumbprint) {
133+
var canonical = thumbprint.toLowerCase().replace(/ /g, '').replace(/:/g, '');
134+
this._throwOnInvalidThumbprint(canonical);
135+
return canonical;
136+
};
137+
138+
var numCharIn128BitHexString = 128/8*2;
139+
var numCharIn160BitHexString = 160/8*2;
140+
var thumbprintSizes = {};
141+
thumbprintSizes[numCharIn128BitHexString] = true;
142+
thumbprintSizes[numCharIn160BitHexString] = true;
143+
var thumbprintRegExp = /^[a-f\d]*$/;
144+
145+
SelfSignedJwt.prototype._throwOnInvalidThumbprint = function(thumbprint) {
146+
if (!thumbprintSizes[thumbprint.length] || !thumbprintRegExp.test(thumbprint)) {
147+
throw this._log.createError('The thumbprint does not match a known format');
148+
}
149+
};
150+
151+
/**
152+
* Creates a self signed JWT that can be used as a client_assertion.
153+
* @param {string} certificate A PEM encoded certificate private key.
154+
* @param {string} thumbprint A hex encoded thumbprint of the certificate.
155+
* @return {string} A self signed JWT token.
156+
*/
157+
SelfSignedJwt.prototype.create = function(certificate, thumbprint) {
158+
thumbprint = this._reduceThumbprint(thumbprint);
159+
var header = this._createHeader(thumbprint);
160+
161+
var payload = this._createPayload();
162+
163+
var jwt = this._signJwt(header, payload, certificate);
164+
return jwt;
165+
};
166+
167+
module.exports = SelfSignedJwt;

0 commit comments

Comments
 (0)