signed-integrity: we are now able to verify the JWT signature (ref. #28)

merge-requests/9/merge
Michał 'rysiek' Woźniak 2022-01-12 23:47:00 +00:00
rodzic 68ad9b1fa6
commit 8f2d095ff0
2 zmienionych plików z 71 dodań i 14 usunięć

Wyświetl plik

@ -30,6 +30,7 @@ describe("plugin: signed-integrity", () => {
global.Blob = require('buffer').Blob;
jest.resetModules();
self = global
global.subtle = subtle
global.btoa = (bin) => {
return Buffer.from(bin, 'binary').toString('base64')
}
@ -46,13 +47,13 @@ describe("plugin: signed-integrity", () => {
// debug
console.log(await getArmouredKey((await generateECDSAKeypair()).publicKey))
console.log(await getArmouredKey((await generateECDSAKeypair()).privateKey))
// ES384: ECDSA using P-384 and SHA-384
header = btoa('{"alg": "ES384"}').replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '')
payload = btoa('{"integrity": "sha256-eiMrFuthzteJuj8fPwUMyNQMb2SMW7VITmmt2oAxGj0="}').replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '')
var header = btoa('{"alg": "ES384"}').replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '')
var payload = btoa('{"integrity": "sha256-eiMrFuthzteJuj8fPwUMyNQMb2SMW7VITmmt2oAxGj0="}').replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '')
signature = await subtle.sign(
// get a signature
var signature = await subtle.sign(
{
name: "ECDSA",
hash: {name: "SHA-384"}
@ -60,6 +61,7 @@ describe("plugin: signed-integrity", () => {
(await generateECDSAKeypair()).privateKey,
(header + '.' + payload)
)
// prepare it for inclusion in the JWT
signature = btoa(signature).replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '')
global.resolvingFetch = jest.fn((url, init)=>{
@ -99,7 +101,8 @@ describe("plugin: signed-integrity", () => {
fetch: resolvingFetch
}
],
requireIntegrity: false
requireIntegrity: false,
publicKey: await subtle.exportKey('jwk', (await generateECDSAKeypair()).publicKey)
}
requestInit = {
integrity: "sha256-eiMrFuthzteJuj8fPwUMyNQMb2SMW7VITmmt2oAxGj0="

Wyświetl plik

@ -15,7 +15,7 @@
// sane defaults
let defaultConfig = {
// public key used for signature verification on integrity files
pubkey: null,
publicKey: null,
// suffix of integrity data files
integrityFileSuffix: '.integrity',
// is integrity data required for any fetched content?
@ -46,6 +46,30 @@
throw new Error(`Expected exactly one plugin to wrap, but ${config.uses.length} configured.`)
}
// getting the key from the config
let jwtPublicKey = null
let getJWTPublicKey = async () => {
if (jwtPublicKey == null) {
try {
jwtPublicKey = await subtle
.importKey(
"jwk",
config.publicKey,
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["verify"]
)
LR.log(pluginName, 'JWT signing key successfully loaded.')
} catch(e) {
throw new Error(`Unable to load the public key: ${e}`)
}
}
return jwtPublicKey;
}
/**
* utility function
* base64url decode
@ -62,12 +86,10 @@
pad = 0
}
// we're done, atob that thing
return atob(
data
return data
.replace(/_/g, '/')
.replace(/-/g, '+')
+ '='.repeat(pad)
)
}
@ -98,11 +120,43 @@
console.log('jwt: ' + jwt)
jwt = jwt.split('.')
// unpack it
var header = b64urlDecode(jwt[0])
var payload = b64urlDecode(jwt[1])
var signature = jwt[2]
LR.log(pluginName, `got a JWT:\n- header : ${header}\n- payload: ${payload}`)
// get the key
let k = await getJWTPublicKey()
console.log(`JWT b64urlDecoded:\n- ${b64urlDecode(jwt[0])}\n- ${b64urlDecode(jwt[1])}\n- ${b64urlDecode(jwt[2])}`)
// WARNING: this is in neither efficient or clear... but works, and this is a PoC
var signature = Uint8Array.from(
Array.from(
atob(
b64urlDecode(jwt[2])
)
)
.map(e=>e.charCodeAt(0))
).buffer
// verify the JWT
if (await subtle
.verify(
{
name: "ECDSA",
hash: {name: "SHA-384"}
},
k,
signature,
(jwt[0] + '.' + jwt[1])
)) {
// unpack it
var header = atob(b64urlDecode(jwt[0]))
var payload = atob(b64urlDecode(jwt[1]))
LR.log(pluginName, `got a valid, signed JWT with integrity data:\n- header : ${header}\n- payload: ${payload}`)
} else {
// we want to error out here, because we did get the integrity file,
// which means we should expect valid and signed integrity data!
throw new Error(`JWT signature validation failed! Somebody might be doing something nasty!`)
}
} else {
LR.log(pluginName, `fetching integrity data failed: ${integrityResponse.status} ${integrityResponse.statusText}`)
}