Skip to content

Commit 445381d

Browse files
authored
Update: find keys embedded in JWS header(s) (#68)
Closes #65
1 parent 6c6028c commit 445381d

File tree

4 files changed

+198
-14
lines changed

4 files changed

+198
-14
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,20 @@ jose.JWS.createVerify(key).
398398
});
399399
```
400400

401+
To verify using a key embedded in the JWS:
402+
403+
```
404+
jose.JWS.createVerify().
405+
verify(input).
406+
then(function(result) {
407+
// ...
408+
});
409+
```
410+
411+
The key can be embedded using either 'jwk' or 'x5c', and can be located in either the JWS Unprotected Header or JWS Protected Header.
412+
413+
**NOTE:** `verify()` will use the embedded key (if found) instead of any other key.
414+
401415
#### Handling `crit` Header Members ####
402416

403417
To accept 'crit' field members, add the `handlers` member to the options Object. The `handlers` member is itself an Object, where its member names are the `crit` header member, and the value is one of:

lib/jws/verify.js

+36-13
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,25 @@ var JWSVerifier = function(ks, globalOpts) {
2323
keystore;
2424

2525
if (JWK.isKey(ks)) {
26-
assumedKey = ks;
27-
keystore = assumedKey.keystore;
26+
assumedKey = ks;
27+
keystore = assumedKey.keystore;
2828
} else if (JWK.isKeyStore(ks)) {
29-
keystore = ks;
29+
keystore = ks;
3030
} else {
31-
throw new TypeError("Keystore must be provided");
31+
keystore = JWK.createKeyStore();
3232
}
3333

3434
globalOpts = merge({}, globalOpts);
3535

36+
Object.defineProperty(this, "defaultKey", {
37+
value: assumedKey || undefined,
38+
enumerable: true
39+
});
40+
Object.defineProperty(this, "keystore", {
41+
value: keystore,
42+
enumerable: true
43+
});
44+
3645
Object.defineProperty(this, "verify", {
3746
value: function(input, opts) {
3847
opts = merge({}, globalOpts, opts || {});
@@ -113,17 +122,31 @@ var JWSVerifier = function(ks, globalOpts) {
113122
payload: input.payload
114123
});
115124
var p = Promise.resolve(sig);
125+
// find the key
116126
p = p.then(function(sig) {
117-
var algKey = assumedKey || keystore.get({
118-
use: "sig",
119-
alg: sig.header.alg,
120-
kid: sig.header.kid
121-
});
122-
if (!algKey) {
123-
return Promise.reject(new Error("key does not match"));
127+
var algKey;
128+
// TODO: resolve jku, x5c, x5u
129+
if (sig.header.jwk) {
130+
algKey = JWK.asKey(sig.header.jwk);
131+
} else if (sig.header.x5c) {
132+
algKey = sig.header.x5c[0];
133+
algKey = new Buffer(algKey, "base64");
134+
// TODO: callback to validate chain
135+
algKey = JWK.asKey(algKey, "pkix");
136+
} else {
137+
algKey = Promise.resolve(assumedKey || keystore.get({
138+
use: "sig",
139+
alg: sig.header.alg,
140+
kid: sig.header.kid
141+
}));
124142
}
125-
sig.key = algKey;
126-
return sig;
143+
return algKey.then(function(k) {
144+
if (!k) {
145+
return Promise.reject(new Error("key does not match"));
146+
}
147+
sig.key = k;
148+
return sig;
149+
});
127150
});
128151

129152
// process any prepare-verify handlers

test/jws/embed-test.js

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*!
2+
*
3+
* Copyright (c) 2016 Cisco Systems, Inc. See LICENSE file.
4+
*/
5+
"use strict";
6+
7+
var chai = require("chai");
8+
9+
var JWS = require("../../lib/jws");
10+
var JWK = require("../../lib/jwk");
11+
12+
var assert = chai.assert;
13+
14+
describe("jws/embedded", function() {
15+
var key = {
16+
"kty": "RSA",
17+
"kid": "2994e519-8d46-4b91-a33b-9979c8afa593",
18+
"e": "AQAB",
19+
"n": "lgCWZN9AU-GJJDO7uIaZP3X0LmhqjVvj-4KqRGh-BvbkLtLuJjrZ-TyitFRUw1jhE25vuhAi-tphyhR_dqHOd6f-X8DwCkM-esD8JDa-I1lYA1h9c-MUlsEGbVlbxwTbB1Nus47vd6lEx_03r5WQtJW9LyAgHiQBKoDDITGHYUOTd6tTRre00G4SCfvA2oAc2xl5RE-5S63yFGp48TKdudbLl6M3M3JHiUVrBY2qQKovJKm8NXPIJ5kaBo7lQrzN7o4nw89FvoLZZ22dK9sHP8Do8oHk7mF40Q5m4dVDWnvqxczldqtYNyEhr27ERTlaSeckUDTvq_3Gklq7RgPJnQ",
20+
"d": "EoVoDHR0YOcMI-gvWY1lBqztxX0nCuU5tShhFalBRmLdsdphhV7m4xtVi6aOAMDMqbWNHhA4AXlNccIuKtu3vpaDlhcgjGPZJxcFCwOnXn39nAwlEVYMiMC0pnPOHTjAQptOo-UWNFQ2JetiIM_62hFTFqqEzLPtYO4dKdAPwzZ_wyDMiT78RKMfXriExxusZ7KJVU-yJs6yKvO6XnaNRK8CT_JL5DQDBA2iaah_P3P3AWAr20QPXrY-jL3j0kSb9OBM_Z89-mUhoseBgiHYkux0ZdNS6Y5du3CquDIS041tfVWZYc7awM2SlHwEIGLXftvZs7L0J-ZxW-VZUAzPEQ",
21+
"p": "51_0X26mpJqTev8J0nTqnOz9v9eaKStx8dXuQz5ygTattcICwNiU1swGlU5R4YHmmq955WsK8rcQbq0H6Yx1eoLVzU8uBuzWj9z-M4qcIEKUeQHHeIbekV43UZs8KkD9yyaQTm_zAMHeeM_RtM4BySym4PPoSYRGhqMBKJXljXc",
22+
"q": "pfeN9m_PFeq0oFlHni3j2KhqZwyAry093kcoJGs5OSK5gS1t-eHkLAX89Bve_4pmPRl3zdLkAUNOazk-lPPkbI5in51esgyrx7VsnpmsZ4i4Smk1JgSDTmYcG7PH81Z6rIFmpLY66wUhpqg7ONVFvl0M3kP7aXMuSdumWPB2Vos",
23+
"dp": "I4X58QT-FNuetQ2fJm7I7pr8Qo4JnzSKZATidfSKhAgvF27YGV-nSms8v4Os0qCtFSbH4k9S-PzeSv_J7TOhfdPEm6cCfBG0x5W4eZVYbyOJxCJfy8N5PHxopeDdlecwkBY1pbVOa9lYHNhbbBUM9SQj4vnPuinS4iz4qpCJE_U",
24+
"dq": "F0nBVc8ik8S3S7i7X-q4ifI32_XZKLuEbug1LccN5IKG3SVuxR15UuQUNnyiseDNr80fDnaFH9g97LW_nk8KwmDIXfVLEFjO0dsXPrn5gx2gHnDc0FTZx-p0Dz8O04pS9FnD-WDIq6mwqx34EWV7v9Z2s8l-QbGz0RFNKjWzpTk",
25+
"qi": "uGslfmgxbrmeF8k4a2-sI4oMd0igZ_2kyAZXYsCXnYd6aO5iCEG9hQE9gOJ29qXReiec0F0ZZ873ROaFWZDaANbek6J7-5NJavTBEdyvT840m1siHIxX7j28-BVOcnI050cEUm4iM1y027Cll4Q4-qp54SmcO3hLhuXL3cT_apQ"
26+
};
27+
var cert = [
28+
"MIIEzjCCAragAwIBAgIBAjANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJVUzERMA8GA1UECBMIQ29sb3JhZG8xDzANBgNVBAcTBkRlbnZlcjESMBAGA1UEChMJbm9kZS1qb3NlMTgwNgYDVQQDEy9ub2RlLWpvc2UgdGVzdCBmaXh0dXJlcyBjZXJ0aWZpY2F0aW9uIGF1dGhvcml0eTAeFw0xNjA3MTYxMDE4MDBaFw0xNzA3MTYxMDE4MDBaMGUxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEPMA0GA1UEBxMGRGVudmVyMRIwEAYDVQQKEwlub2RlLWpvc2UxHjAcBgNVBAMTFXg1Yy5ub2RlLWpvc2UuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJYAlmTfQFPhiSQzu7iGmT919C5oao1b4/uCqkRofgb25C7S7iY62fk8orRUVMNY4RNub7oQIvraYcoUf3ahznen/l/A8ApDPnrA/CQ2viNZWANYfXPjFJbBBm1ZW8cE2wdTbrOO73epRMf9N6+VkLSVvS8gIB4kASqAwyExh2FDk3erU0a3tNBuEgn7wNqAHNsZeURPuUut8hRqePEynbnWy5ejNzNyR4lFawWNqkCqLySpvDVzyCeZGgaO5UK8ze6OJ8PPRb6C2WdtnSvbBz/A6PKB5O5heNEOZuHVQ1p76sXM5XarWDchIa9uxEU5WknnJFA076v9xpJau0YDyZ0CAwEAAaNvMG0wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUAcNVFCxTp2mB+usipAP1JxMljy4wCwYDVR0PBAQDAgSwMBEGCWCGSAGG+EIBAQQEAwIFoDAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQAra10+BhmZkcuZZG4RNHHg/PZU4mAbWKcCgSMsyblj6ahrh95admhgEiJkuEH+B4ol8TCwXRT2hsNt8HJznoOEPfWzh+yeqyIADVSkgN57AOcIViu1Eb5diBrzeWMUA1k1lzEJKAJFOCdLkIVFspzQDk2p1FUQ+LYepbcbk8dnCHlJjRmUPGRKhSyShQQPF6+F7E5xUd+nucCVnADSVW+qC1GGk3um3lhblEvpplQLXV+dJACwTPrJ+bj73OclGa6FH7k1WydLgpOYiW/MBCFUnlFCsSqXfoYZZ7yiN0XhmJGGn+Qt5i9IxkpogMPIvUL00aTmKf78+0pS5wCEtToewxV9m4ZGi7pkIpNpvgPa0SyWghfQpJRZ7bfWUJO4ZDUbKaAl2bWckoB6I6sPHZ7cOgRDZzsIPl9E3sWfyPha4gebP4wNFTYJZ4n/v06OoNQYRlz1dvCb+aLuc05u/fPiOf7gUYVCdIU+3fIE82DE/jv//KUwdAtqtgTsKju1GdTy9RHbuVnUi3T4srtLMwhQf60jRCzufFISMOwuUt1sV3jFOFi147JJi6bYHRaITSgCwapSn0gOlX4hhNmLmvoKS+AxSBm4mHE/K/h/mGi5RQAILofhUIylmNKv5SLWhS8OvGynZrcs0oB7v5DvehDzghVo83y9LvbMPc9gcTSE1A==",
29+
"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"
30+
];
31+
var payload = new Buffer("There and back again – A Hobbit's Tale, by Bilbo Baggins", "utf8");
32+
33+
before(function() {
34+
return JWK.asKey(key).
35+
then(function(jwk) {
36+
key = jwk;
37+
});
38+
})
39+
40+
describe("jwk", function() {
41+
var signature;
42+
it("creates a JWS with an embedded 'jwk'", function() {
43+
var opts = {
44+
format: "flattened",
45+
protect: false
46+
},
47+
jws,
48+
p;
49+
50+
jws = JWS.createSign(opts, {
51+
key: key,
52+
reference: "jwk"
53+
});
54+
jws.update(payload);
55+
p = jws.final();
56+
p = p.then(function(result) {
57+
var header = {
58+
alg: "RS256",
59+
jwk: key.toJSON()
60+
};
61+
assert.deepEqual(result.header, header);
62+
signature = result;
63+
});
64+
return p;
65+
});
66+
it("verifies a JWS using an embedded 'jwk'", function() {
67+
var p;
68+
var vfy = JWS.createVerify(JWK.createKeyStore());
69+
p = vfy.verify(signature);
70+
p = p.then(function(result) {
71+
assert.deepEqual(result.payload, payload);
72+
});
73+
return p;
74+
});
75+
});
76+
77+
//TODO: x5c
78+
describe("x5c", function() {
79+
var signature;
80+
it("creates a JWS with an embedded 'x5c'", function() {
81+
var opts = {
82+
format: "flattened",
83+
protect: false
84+
},
85+
jws,
86+
p;
87+
88+
jws = JWS.createSign(opts, {
89+
key: key,
90+
reference: "x5c",
91+
header: {
92+
x5c: cert
93+
}
94+
});
95+
jws.update(payload);
96+
p = jws.final();
97+
p = p.then(function(result) {
98+
var header = {
99+
alg: "RS256",
100+
x5c: cert
101+
};
102+
assert.deepEqual(result.header, header);
103+
signature = result;
104+
});
105+
return p;
106+
});
107+
it("verifies a JWS using an embedded 'x5c'", function() {
108+
var p;
109+
var vfy = JWS.createVerify(JWK.createKeyStore());
110+
p = vfy.verify(signature);
111+
p = p.then(function(result) {
112+
assert.deepEqual(result.payload, payload);
113+
});
114+
return p;
115+
});
116+
});
117+
});

test/jws/jws-test.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ var forEach = require("lodash.foreach");
99
var chai = require("chai");
1010

1111
var JWS = require("../../lib/jws");
12-
1312
var JWK = require("../../lib/jwk");
1413

1514
var assert = chai.assert;
@@ -22,6 +21,37 @@ var fixtures = {
2221
};
2322

2423
describe("jws", function() {
24+
describe("createVerify", function() {
25+
var key = {
26+
"kty": "oct",
27+
"kid": "xV-UT6IYtLwpff7SYQUH2PgbB_dKmndejyFpJc56-Ec",
28+
"k": "vFfSurgM7hZIkirsjn8IFhJ3optS_GCecC-_qGfhMRQ"
29+
};
30+
31+
before(function() {
32+
return JWK.asKey(key).
33+
then(function(result) {
34+
key = result;
35+
});
36+
});
37+
38+
it("creates a verify using a keystore", function() {
39+
var vfy = JWS.createVerify(key.keystore);
40+
assert.strictEqual(vfy.keystore, key.keystore);
41+
assert.isUndefined(vfy.defaultKey);
42+
});
43+
it("creates a verify using an assumed key", function() {
44+
var vfy = JWS.createVerify(key);
45+
assert.strictEqual(vfy.keystore, key.keystore);
46+
assert.strictEqual(vfy.defaultKey, key);
47+
});
48+
it("creates a verify with an empty keystore", function() {
49+
var vfy = JWS.createVerify();
50+
assert.ok(vfy.keystore);
51+
assert.isUndefined(vfy.defaultKey);
52+
});
53+
});
54+
2555
forEach(fixtures, function(fixture) {
2656
var input = fixture.input;
2757
var output = fixture.output;

0 commit comments

Comments
 (0)