Created
July 18, 2016 08:04
-
-
Save donpark/e70c83faca65256fc36e9a37bf289ad5 to your computer and use it in GitHub Desktop.
jwt-tweetnacl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"kty":"OKP","crv":"Ed25519","d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"crv":"Ed25519","kty":"OKP","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mocha unit tests replicating JOSE Ed25519 signing & validation example in | |
// https://tools.ietf.org/html/draft-ietf-jose-cfrg-curves-05 Appendix A | |
var assert = require('assert') | |
var path = require('path') | |
var jwt = require('./jwt-tweetnacl') | |
describe('jwt-tweetnacl', function () { | |
it('should encode/decode Base64 URL', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
jwt.decodeBase64URL(jwt.encodeBase64URL(key.sk)).equals(key.sk).should.equal(true) | |
jwt.decodeBase64URL(jwt.encodeBase64URL(key.pk)).equals(key.pk).should.equal(true) | |
jwt.decodeBase64URL(jwt.encodeBase64URL(key.skpk)).equals(key.skpk).should.equal(true) | |
}) | |
it('should read full key file', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
key.kty.should.equal('OKP') | |
key.crv.should.equal('Ed25519') | |
key.sk.toString('hex').should.equal('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60') | |
key.pk.toString('hex').should.equal('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a') | |
}) | |
it('should generate public key thumbprint', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'data', 'ed25519-public.json')) | |
key.hash.toString('hex').should.equal('90facafea9b1556698540f70c0117a22ea37bd5cf3ed3c47093c1707282b4b89') | |
key.thumbprint.should.equal('kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k') | |
}) | |
it('should create signature with secret key', function () { | |
let header = jwt.encodeBase64URL({ | |
alg: 'EdDSA', | |
}) | |
header.should.equal('eyJhbGciOiJFZERTQSJ9') | |
let payload = jwt.encodeBase64URL('Example of Ed25519 signing') | |
payload.should.equal('RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc') | |
let input = jwt.encodeInput('Example of Ed25519 signing', { | |
alg: 'EdDSA', | |
}) | |
input.should.equal('eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc') | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
let sig = jwt.signature(key.skpk, input) | |
sig.toString('hex').should.equal('860c98d2297f3060a33f42739672d61b53cf3adefed3d3c672f320dc021b411e9d59b8628dc351e248b88b29468e0e41855b0fb7d83bb15be902bfccb8cd0a02') | |
jwt.encodeBase64URL(sig).should.equal('hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg') | |
}) | |
it('should verify signature with public key', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
let input = jwt.encodeInput('Example of Ed25519 signing', { | |
alg: 'EdDSA', | |
}) | |
let sig = jwt.signature(key.skpk, input) | |
jwt.verifySignature(key.pk, input, sig).should.equal(true) | |
}) | |
it('should create signed input with secret key', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
let payload = 'Example of Ed25519 signing' | |
let signed = jwt.sign(key.skpk, payload, { | |
alg: 'EdDSA', | |
}) | |
signed.should.equal('eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg') | |
}) | |
it('should verify signed input with public key', function () { | |
let key = jwt.readKeyFile(path.join(__dirname, 'ed25519-full.json')) | |
let payload = 'Example of Ed25519 signing' | |
let signed = jwt.sign(key.skpk, payload, { | |
alg: 'EdDSA', | |
}) | |
let verified = jwt.verify(key.pk, signed) | |
// console.log('verified', verified) | |
verified.should.not.equal(null) | |
}) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var crypto = require('crypto') | |
var fs = require('fs') | |
var path = require('path') | |
var nacl = require('tweetnacl') | |
function encodeBase64URL(b) { | |
let data = b | |
if (!Buffer.isBuffer(data)) { | |
if (typeof data === 'string') { | |
data = new Buffer(data) | |
} else if (typeof data === 'number') { | |
data = new Buffer(data.toString()) | |
} else if (typeof data === 'object') { | |
if (data instanceof Uint8Array) { | |
throw new Error('Uint8Array not supported yet') | |
} else { | |
data = new Buffer(JSON.stringify(data)) | |
} | |
} | |
} | |
return data.toString('base64') | |
.replace(/\+/g, '-') // map + to - | |
.replace(/\//g, '_') // map / to _ | |
.replace(/=+$/, '') // truncate = | |
} | |
function decodeBase64URL(s) { | |
let base64 = s | |
.replace(/\-/g, '\+') // map - to + | |
.replace(/_/g, '/') // map _ to / | |
// HACK: Buffer ignores missing =. Other impls. may not. | |
return new Buffer(base64, 'base64') | |
} | |
function decodeBase64URLToString(s) { | |
let b = decodeBase64URL(s) | |
return b.toString('utf8') | |
} | |
function decodeBase64URLToJSON(s) { | |
let b = decodeBase64URL(s) | |
return JSON.parse(b.toString('utf8')) | |
} | |
function readKeyFile(file) { | |
let data = fs.readFileSync(file, 'utf8') | |
let key = JSON.parse(data) | |
if (key.d) { | |
key.sk = new Buffer(key.d, 'base64') | |
} | |
if (key.x) { | |
key.pk = new Buffer(key.x, 'base64') | |
} | |
if (key.sk && key.pk) { | |
key.skpk = Buffer.concat([key.sk, key.pk]) | |
} | |
key.data = data | |
key.hash = crypto.createHash('sha256').update(data, 'utf8').digest() | |
key.thumbprint = encodeBase64URL(key.hash) | |
return key | |
} | |
function encodeInput(payload, header) { | |
let headerUrl = encodeBase64URL(header) | |
let payloadUrl = encodeBase64URL(payload) | |
return `${headerUrl}.${payloadUrl}` | |
} | |
function signature(skpk, input) { | |
let m = new Buffer(input) | |
return new Buffer(nacl.sign.detached(m, skpk)) | |
} | |
function verifySignature(pk, input, sig) { | |
let m = new Buffer(input) | |
return nacl.sign.detached.verify(m, sig, pk) | |
} | |
function sign(skpk, payload, header) { | |
let input = encodeInput(payload, header) | |
let m = new Buffer(input) | |
let sig = new Buffer(nacl.sign.detached(m, skpk)) | |
return `${input}.${encodeBase64URL(sig)}` | |
} | |
function verify(pk, signed) { | |
let parts = signed.split('.') | |
let input = `${parts[0]}.${parts[1]}` | |
let sig = decodeBase64URL(parts[2]) | |
if (!verifySignature(pk, input, sig)) { | |
return null | |
} | |
return { | |
header: decodeBase64URLToJSON(parts[0]), | |
payload: decodeBase64URLToString(parts[1]), | |
sig: sig, | |
} | |
return true | |
} | |
module.exports = { | |
encodeBase64URL, | |
decodeBase64URL, | |
readKeyFile, | |
encodeInput, | |
signature, | |
verifySignature, | |
sign, | |
verify, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment