Skip to content

Instantly share code, notes, and snippets.

@donpark
Created July 18, 2016 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donpark/e70c83faca65256fc36e9a37bf289ad5 to your computer and use it in GitHub Desktop.
Save donpark/e70c83faca65256fc36e9a37bf289ad5 to your computer and use it in GitHub Desktop.
jwt-tweetnacl
{"kty":"OKP","crv":"Ed25519","d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
{"crv":"Ed25519","kty":"OKP","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
// 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)
})
})
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