Skip to content

Commit 375c9e1

Browse files
committed
Fixes #272: we now support plain RSA public keys without the X.509
SubjectPublicKeyInfo header.
1 parent aae97aa commit 375c9e1

File tree

2 files changed

+68
-7
lines changed

2 files changed

+68
-7
lines changed

src/editor/jwt.js

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
KEYUTIL,
66
b64utoutf8,
77
b64utohex,
8-
utf8tohex
8+
utf8tohex,
9+
b64tohex,
10+
ASN1HEX
911
} from 'jsrsasign';
1012

1113
import log from 'loglevel';
@@ -28,6 +30,44 @@ export function sign(header,
2830
}
2931
}
3032

33+
/**
34+
* This function takes a PEM string with a public key and returns a
35+
* jsrsasign key object (RSAKey, KJUR.crypto.DSA, KJUR.crypto.ECDSA). It also
36+
* handles plain RSA keys not wrapped in a X.509 SubjectPublicKeyInfo
37+
* structure.
38+
* See: https://stackoverflow.com/questions/18039401/how-can-i-transform-between-the-two-styles-of-public-key-format-one-begin-rsa
39+
* @param {String} publicKey The public key as a PEM string.
40+
* @returns {Object} The public key as a jsrsasign key object.
41+
*/
42+
function getPublicKeyObject(publicKey) {
43+
try {
44+
const startTag = '-----BEGIN RSA PUBLIC KEY-----';
45+
const endTag = '-----END RSA PUBLIC KEY-----';
46+
const startTagPos = publicKey.indexOf(startTag);
47+
const endTagPos = publicKey.indexOf(endTag);
48+
49+
if(startTagPos !== -1 && endTagPos !== -1) {
50+
const plainDataBase64 =
51+
publicKey.substr(0, endTagPos)
52+
.substr(startTagPos + startTag.length);
53+
54+
const plainDataDER = b64tohex(plainDataBase64);
55+
56+
const barePublicKey = {
57+
n: ASN1HEX.getVbyList(plainDataDER, 0, [0], '02'),
58+
e: ASN1HEX.getVbyList(plainDataDER, 0, [1], '02')
59+
};
60+
61+
return KEYUTIL.getKey(barePublicKey);
62+
}
63+
} catch(e) {
64+
log.error('Failed to make public key into X.509 ' +
65+
'SubjectPublicKeyInfo key:', e);
66+
}
67+
68+
return KEYUTIL.getKey(publicKey);
69+
}
70+
3171
export function verify(jwt, secretOrPublicKeyString, base64Secret = false) {
3272
if(!isToken(jwt)) {
3373
return false;
@@ -46,11 +86,12 @@ export function verify(jwt, secretOrPublicKeyString, base64Secret = false) {
4686
b64utohex(secretOrPublicKeyString) :
4787
utf8tohex(secretOrPublicKeyString));
4888
} else {
49-
return jws.JWS.verify(jwt, secretOrPublicKeyString);
89+
const publicKeyObject = getPublicKeyObject(secretOrPublicKeyString);
90+
return jws.JWS.verify(jwt, publicKeyObject);
5091
}
5192
} catch(e) {
5293
log.warn('Could not verify token, ' +
53-
'probably due to bad data in it or the keys: ', e);
94+
'probably due to bad data in it or the keys: ', e);
5495
return false;
5596
}
5697
}

test/unit/editor/jwt.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import { should } from 'chai';
99

1010
should();
1111

12+
const publicKeyPlainRSA =
13+
`-----BEGIN RSA PUBLIC KEY-----
14+
MIGJAoGBAN2Vq1GNGOiCjdaiOAYcUdgu6B1RYBj2JHd/LhqtY0DUqhLyRXDfdwmJ
15+
tevxu/BQBSlqsLCW91sfp28Q5+i7T+AIVCwdR9CtIO/4y5JQwB7yPMoTipb6Mr7F
16+
BT1rTcZScoeSSV75DSlf+DqNdnuvX/EArkOjaRD5fnEr1yKlGAQrAgMBAAE=
17+
-----END RSA PUBLIC KEY-----`;
18+
1219
describe('JWT', function() {
1320
it('detects tokens', function() {
1421
jwt.isToken('skdjf9238ujdhkf.asdfasdf2.sdsdffsfsd').should.be.false;
@@ -97,7 +104,7 @@ describe('JWT', function() {
97104
}
98105
});
99106

100-
it('signs tokens (HS256)', function() {
107+
it('signs/verifies tokens (HS256)', function() {
101108
const header = {
102109
alg: 'HS256'
103110
};
@@ -118,7 +125,7 @@ describe('JWT', function() {
118125
decoded.payload.should.deep.equal(payload);
119126
});
120127

121-
it('signs tokens (RS256)', function() {
128+
it('signs/verifies tokens (RS256)', function() {
122129
const header = {
123130
alg: 'RS256'
124131
};
@@ -139,7 +146,7 @@ describe('JWT', function() {
139146
decoded.payload.should.deep.equal(payload);
140147
});
141148

142-
it('signs tokens (ES256)', function() {
149+
it('signs/verifies tokens (ES256)', function() {
143150
const header = {
144151
alg: 'ES256'
145152
};
@@ -160,7 +167,7 @@ describe('JWT', function() {
160167
decoded.payload.should.deep.equal(payload);
161168
});
162169

163-
it('signs tokens (PS256)', function() {
170+
it('signs/verifies tokens (PS256)', function() {
164171
const header = {
165172
alg: 'PS256'
166173
};
@@ -180,4 +187,17 @@ describe('JWT', function() {
180187
decoded.header.should.deep.equal(header);
181188
decoded.payload.should.deep.equal(payload);
182189
});
190+
191+
it('verifies tokens (RS256) using a plain RSA public key', function() {
192+
const header = {
193+
alg: 'RS256'
194+
};
195+
const payload = {
196+
sub: 'test'
197+
};
198+
199+
const token = jwt.sign(header, payload, tokens.rs256.privateKey);
200+
201+
jwt.verify(token, publicKeyPlainRSA).should.be.true;
202+
});
183203
});

0 commit comments

Comments
 (0)