This repository has been archived by the owner on Jun 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 44
/
check-assertion
executable file
·242 lines (202 loc) · 8.26 KB
/
check-assertion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/usr/bin/env node
/* this should be invoked as follows
*
* check-assertion [root_pk] <full_assertion>
*/
var jwcrypto = require("../index"),
path = require('path'),
fs = require('fs'),
https = require('https');
require("../lib/algs/rs");
require("../lib/algs/ds");
function fail(why) {
process.stderr.write(why + "\n");
process.exit(1);
}
if (process.argv.length < 3 || process.argv.length > 4) {
fail("Usage: " + path.basename(process.argv[1]) +
" [public key file or contents or url] " + " <assertion file or contents>\n");
}
// extractPublicKey, given a "thing" (something provided on the command line) and
// an "issuer" (a hostname extracted from the provided assertion), let's try
// our darndest to find the public key that this is associated with.
// The public key may be:
// * null - in which case the user didn't provide one, and we'll try to fetch it
// from the net, using the issuer.
// * a file name - in which case we'll read from that file
// * the "public key" itself!
//
// Once we handle those cases, there are various formats that a public key may be
// provided in:
// * base64 encoded ("serialized" form)
// * represented as a JSON object
//
// Let's find a public key and make the human happy!
function extractPublicKey(thing, issuer, cb) {
function dealWithTheThing() {
// if thing is null, let's fall through to our error case
if (thing === null) thing = "";
// is it a path?
fs.stat(thing, function(err, stats) {
if (!err && stats.isFile()) {
thing = fs.readFileSync(thing);
}
// now we've got a blob of text. what could it be?
thing = thing.toString();
// is it a base64 encoded public key?
try { return cb(null, jwcrypto.loadPublicKey(thing)); } catch(e) { console.log(e); }
// nooo.... Well is it encoded as an object?
try { return cb(null, jwcrypto.loadPublicKeyFromObject(JSON.parse(thing))); } catch(e) { }
try { return cb(null, jwcrypto.loadPublicKeyFromObject(JSON.parse(thing)['public-key'])); } catch(e) { }
cb('Cannot parse your public key. What is that thing?');
});
}
// if no public key was provided, let's try to fetch one!
if (thing == null) {
function tryToFetch(issuer) {
console.log("(seeking public key! trying to fetch .well-known from " + issuer + ")");
var req = https.request({
host: issuer,
path: '/.well-known/browserid',
method: 'GET'
}, function(res) {
var data = "";
res.on('data', function(x) { data += x });
res.on('end', function() {
thing = data;
try {
thing = JSON.stringify(JSON.parse(data)['public-key']);
return dealWithTheThing();
} catch(e) { }
// uh oh, couldn't parse. maybe this is delegated?
try {
issuer = JSON.parse(data)['authority'];
return tryToFetch(issuer);
} catch(e) { }
// nothing is working. let's fall through to parsing.
dealWithTheThing()
});
});
req.on('error', function() {
dealWithTheThing();
});
req.end();
}
tryToFetch(issuer);
} else {
dealWithTheThing();
}
}
// Here we'll try our best to parse an assertion. two common forms that assertions
// may be provided in include embedded in a json blob under the 'assertion' key
// (possible if the human pulls the payload out of an HTTP transaction from their
// browser) or in serialized form (possible if the copy/paste is more granular, or
// the human better understands the boundaries of what we call an "assertion").
function extractAssertion(thing, cb) {
// is it a path?
fs.stat(thing, function(err, stats) {
if (!err && stats.isFile()) {
thing = fs.readFileSync(thing);
}
// is it embedded in an object?
try { thing = JSON.parse(thing).assertion; } catch(e) {};
try {
var raw_assertion = thing.toString().trim();
return cb(null, raw_assertion, jwcrypto.cert.unbundle(raw_assertion)); } catch(e) { }
cb("Cannot parse your assertion. What is that thing!?");
});
}
// support two invocation forms:
// check_assertion <public key> <assertion>
// check_assertion <assertion>
var root_key = null;
var full_assertion = null;
if (process.argv.length === 4) {
root_key = process.argv[2];
full_assertion = process.argv[3];
} else {
root_key = null;
full_assertion = process.argv[2];
}
extractAssertion(full_assertion, function (err, raw_assertion, full_assertion) {
if (err) fail(err);
console.log("==== cert ====");
var components = jwcrypto.extractComponents(full_assertion.certs[0]);
console.log("issuer: " + components.payload.iss);
console.log("principal: " + JSON.stringify(components.payload.principal));
console.log("iat: " + new Date(components.payload.iat) + " (" + components.payload.iat + ")");
console.log("exp: " + new Date(components.payload.exp) + " (" + components.payload.exp + ")");
// now, let's try to parse this key thing
extractPublicKey(root_key, components.payload.iss, function(err, root_key) {
if (err) fail(err);
console.log("(using a median timestamp to ensure no timestamp failure)");
var median = new Date(components.payload.iat + (components.payload.exp - components.payload.iat)/2);
jwcrypto.cert.verify(
full_assertion.certs[0], root_key, median,
function(err, payload, assertionParams, certParams) {
var pk;
if (err) {
console.log("FATAL: cert is NOT properly signed: " + err);
console.log(components.payload['public-key']);
pk = jwcrypto.loadPublicKey(JSON.stringify(components.payload['public-key']));
} else {
console.log("cert is properly signed");
pk = certParams['publicKey'];
}
if (typeof components.payload.iat !== 'number')
console.log("FATAL: cert lacks an 'issued at' (.iat) field");
if (typeof components.payload.exp !== 'number')
console.log("FATAL: cert lacks an 'expires' (.exp) field");
doAssertion(pk, components.payload.iat, components.payload.exp);
});
function doAssertion(publicKey, iat, exp) {
console.log("\n==== assertion ====");
var components = jwcrypto.extractComponents(full_assertion.signedAssertion);
console.log(components);
console.log("audience: " + components.payload.aud);
console.log("expires: " + new Date(components.payload.exp));
if (components.payload.exp < iat)
console.log("FATAL: assertion expires before cert is valid");
if (components.payload.exp > (exp + 5000))
console.log("FATAL: assertion was likely issued after cert expired");
console.log("(verifying with an expiration date that should be valid for this assertion.)");
var checkDate = new Date(components.payload.exp - 1);
jwcrypto.assertion.verify(
full_assertion.signedAssertion, publicKey, checkDate,
function(err, payload, assertionParams) {
if (err) {
console.log("FATAL: assertion is NOT properly signed: " + err);
} else {
console.log("assertion is properly signed");
}
doBundle();
}
);
}
function doBundle() {
console.log("\n==== bundle ====");
var components = jwcrypto.extractComponents(full_assertion.signedAssertion);
var checkDate = new Date(components.payload.exp - 1);
var ultimateIssuer;
jwcrypto.cert.verifyBundle(
raw_assertion,
checkDate, function(issuer, next) {
console.log("assuming public key is the key for", issuer);
ultimateIssuer = issuer;
next(null, root_key);
},
function(err, certParamsArray, payload, assertionParams) {
if (err) {
console.log("FATAL: assertion is invalid:", err);
} else {
console.log("bundle *seems* to verify ok!");
console.log("cert chain length: ", certParamsArray.length);
console.log("audience: ", assertionParams.audience);
var principal = certParamsArray[certParamsArray.length - 1].certParams.principal;
console.log("domain from email: ", principal.email.replace(/^.*@/, ''));
console.log("certificate issuer:", ultimateIssuer);
}
});
}
});
});