kopia lustrzana https://gitlab.com/rysiekpl/libresilient
cli: more tests for signed-integrity cli done (ref. #66)
rodzic
d6ac169816
commit
a6985e8521
|
@ -2,9 +2,85 @@ import {
|
|||
assert,
|
||||
assertThrows,
|
||||
assertRejects,
|
||||
assertEquals
|
||||
assertEquals,
|
||||
assertStringIncludes
|
||||
} from "https://deno.land/std@0.167.0/testing/asserts.ts";
|
||||
|
||||
// this needs to be the same as the pubkey in:
|
||||
// ./__denotests__/mocks/keyfile.json
|
||||
var pubkey = {
|
||||
"kty": "EC",
|
||||
"crv": "P-384",
|
||||
"alg": "ES384",
|
||||
"x": "rrFawYTuFo8ZjoDxaztUU-c_RAwjw1Y9Tp3j4nH4WsY2Zlizf40Mvz_0BUkVVZCw",
|
||||
"y": "HaFct6PVK2CQ7ZT2SHClnN-knmGfjY_DFwc6qrAu1s0DFZ8fEUuNdmkTlj9T4NQw",
|
||||
"key_ops": [
|
||||
"verify"
|
||||
],
|
||||
"ext": true
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function — decode a b64url-encoded string
|
||||
*/
|
||||
let b64urlDecode = (data) => {
|
||||
data = data.replace(/_/g, '/').replace(/-/g, '+')
|
||||
data += '='.repeat((4 - (data.length % 4)) % 4)
|
||||
return atob(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function — verify a signed JWT using a provided key
|
||||
*/
|
||||
let verifySignedJWT = async (jwt, key) => {
|
||||
|
||||
// working in sections
|
||||
jwt = jwt.split('.')
|
||||
|
||||
// sections cannot be empty
|
||||
if ( (jwt[0].length == 0) || (jwt[1].length == 0) || (jwt[2].length == 0) ) {
|
||||
throw new Error('JWT is invalid, one or more sections are empty.')
|
||||
}
|
||||
|
||||
// load the key
|
||||
key = await crypto.subtle.importKey(
|
||||
"jwk",
|
||||
key,
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-384'
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
)
|
||||
|
||||
// checking the header
|
||||
var header = JSON.parse(b64urlDecode(jwt[0]))
|
||||
if (!("alg" in header) || (header.alg != "ES384")) {
|
||||
throw new Error('Expected header to contain alg field set to "ES384"')
|
||||
}
|
||||
|
||||
// get the signature in a usable format
|
||||
var signature = Uint8Array
|
||||
.from(
|
||||
Array
|
||||
.from(b64urlDecode(jwt[2]))
|
||||
.map(e=>e.charCodeAt(0))
|
||||
).buffer
|
||||
|
||||
// return the result of signature verification
|
||||
return await crypto.subtle.verify(
|
||||
{
|
||||
name: "ECDSA",
|
||||
hash: {name: "SHA-384"}
|
||||
},
|
||||
key,
|
||||
signature,
|
||||
new TextEncoder("utf-8").encode(jwt[0] + '.' + jwt[1])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Deno.test("plugin loads", async () => {
|
||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||
assert("name" in bi)
|
||||
|
@ -94,42 +170,104 @@ Deno.test("gen-integrity verifies arguments are sane", async () => {
|
|||
}, Error, "Failed to load private key from 'irrelevant': No such file or directory")
|
||||
});
|
||||
|
||||
Deno.test("gen-integrity handles paths in a sane way", async () => {
|
||||
Deno.test("gen-integrity handles paths correctly", async () => {
|
||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||
const gi = bi.actions["gen-integrity"]
|
||||
assertRejects(async ()=>{
|
||||
await gi.run(['./'], 'non-existent')
|
||||
}, Error, "Failed to load private key from 'non-existent'")
|
||||
assertRejects(async ()=>{
|
||||
await gi.run(['./'], './')
|
||||
}, Error, "ailed to load private key from './': Is a directory")
|
||||
assertEquals(
|
||||
await gi.run(['./'], './__denotests__/mocks/keyfile.json'),
|
||||
'{}'
|
||||
)
|
||||
/*assertEquals(
|
||||
)await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json'),
|
||||
'{}'
|
||||
)*/
|
||||
//assertEquals(await gi.run(['./__denotests__/mocks/hello.txt']), '{"./__denotests__/mocks/hello.txt":["sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="]}')
|
||||
//assertEquals(await gi.run(['./', './__denotests__/mocks/hello.txt']), '{"./__denotests__/mocks/hello.txt":["sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="]}')
|
||||
assertStringIncludes(
|
||||
await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json'),
|
||||
'"./__denotests__/mocks/hello.txt":"eyJhbGciOiAiRVMzODQifQ.eyJpbnRlZ3JpdHkiOiAic2hhMjU2LXVVMG51Wk5OUGdpbExsTFgybjJyK3NTRTcrTjZVNER1a0lqM3JPTHZ6ZWs9In0.'
|
||||
)
|
||||
});
|
||||
|
||||
/*Deno.test("gen-integrity handles algos argument in a sane way", async () => {
|
||||
Deno.test("gen-integrity handles algos argument correctly", async () => {
|
||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||
const gi = bi.actions["gen-integrity"]
|
||||
assertRejects(async ()=>{
|
||||
await gi.run(['./__denotests__/mocks/hello.txt'], ['BAD-ALG'])
|
||||
await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json', ['BAD-ALG'])
|
||||
}, Error, 'Unrecognized algorithm name')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-256']), '{"./__denotests__/mocks/hello.txt":["sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="]}')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-384']), '{"./__denotests__/mocks/hello.txt":["sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9"]}')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-512']), '{"./__denotests__/mocks/hello.txt":["sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="]}')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-256', 'SHA-384', 'SHA-512']), '{"./__denotests__/mocks/hello.txt":["sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=","sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9","sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="]}')
|
||||
|
||||
// helper function
|
||||
let getGeneratedTestIntegrity = async (algos) => {
|
||||
let integrity = JSON.parse(await gi.run(
|
||||
['./__denotests__/mocks/hello.txt'],
|
||||
'./__denotests__/mocks/keyfile.json',
|
||||
algos)
|
||||
)
|
||||
integrity = b64urlDecode(integrity["./__denotests__/mocks/hello.txt"].split('.')[1])
|
||||
return integrity
|
||||
}
|
||||
|
||||
assertEquals(await getGeneratedTestIntegrity(['SHA-256']), '{"integrity": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="}')
|
||||
assertEquals(await getGeneratedTestIntegrity(['SHA-384']), '{"integrity": "sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9"}')
|
||||
assertEquals(await getGeneratedTestIntegrity(['SHA-512']), '{"integrity": "sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="}')
|
||||
assertEquals(await getGeneratedTestIntegrity(['SHA-256', 'SHA-384', 'SHA-512']), '{"integrity": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek= sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9 sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="}')
|
||||
});
|
||||
|
||||
Deno.test("gen-integrity handles output argument in a sane way", async () => {
|
||||
Deno.test("gen-integrity text output is correct", async () => {
|
||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||
const gi = bi.actions["gen-integrity"]
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-256'], 'text'), './__denotests__/mocks/hello.txt: sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=\n')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-384'], 'text'), './__denotests__/mocks/hello.txt: sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9\n')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-512'], 'text'), './__denotests__/mocks/hello.txt: sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==\n')
|
||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-256', 'SHA-384', 'SHA-512'], 'text'), './__denotests__/mocks/hello.txt: sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek= sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9 sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==\n')
|
||||
|
||||
let getGeneratedTestIntegrity = async (algos) => {
|
||||
let result = await gi.run(
|
||||
['./__denotests__/mocks/hello.txt'],
|
||||
'./__denotests__/mocks/keyfile.json',
|
||||
algos,
|
||||
'text'
|
||||
)
|
||||
result = result.split(' ')
|
||||
return [result[0], b64urlDecode(result[1].split('.')[1])]
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
await getGeneratedTestIntegrity(['SHA-256']),
|
||||
[
|
||||
"./__denotests__/mocks/hello.txt:",
|
||||
'{"integrity": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="}'
|
||||
]
|
||||
)
|
||||
assertEquals(
|
||||
await getGeneratedTestIntegrity(['SHA-384']),
|
||||
[
|
||||
"./__denotests__/mocks/hello.txt:",
|
||||
'{"integrity": "sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9"}'
|
||||
]
|
||||
)
|
||||
assertEquals(
|
||||
await getGeneratedTestIntegrity(['SHA-512']),
|
||||
[
|
||||
"./__denotests__/mocks/hello.txt:",
|
||||
'{"integrity": "sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="}'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
assertEquals(
|
||||
await getGeneratedTestIntegrity(['SHA-256', 'SHA-384', 'SHA-512']),
|
||||
[
|
||||
"./__denotests__/mocks/hello.txt:",
|
||||
'{"integrity": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek= sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9 sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="}'
|
||||
]
|
||||
)
|
||||
});
|
||||
*/
|
||||
|
||||
// TODO: "files" output mode, which will require mocking file writing routines
|
||||
|
||||
Deno.test("gen-integrity signs the data correctly", async () => {
|
||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||
const gi = bi.actions["gen-integrity"]
|
||||
let jwt = JSON.parse(await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json'))
|
||||
assert(
|
||||
await verifySignedJWT(
|
||||
jwt['./__denotests__/mocks/hello.txt'],
|
||||
pubkey))
|
||||
})
|
||||
|
|
|
@ -47,8 +47,6 @@ let getFileIntegrity = async (path, algos) => {
|
|||
// are we working with a file?
|
||||
if (fileInfo.isFile) {
|
||||
|
||||
//console.log(`+-- reading: ${path}`)
|
||||
|
||||
// initialize
|
||||
content = new Uint8Array()
|
||||
var buf = new Uint8Array(1000);
|
||||
|
|
|
@ -106,6 +106,7 @@ let getPubkey = async (keyfile) => {
|
|||
let getFileIntegrity = async (path, algos) => {
|
||||
|
||||
var result = []
|
||||
var content = false
|
||||
|
||||
// open the file and get some info on it
|
||||
const file = await Deno.open(
|
||||
|
@ -117,10 +118,8 @@ let getFileIntegrity = async (path, algos) => {
|
|||
// are we working with a file?
|
||||
if (fileInfo.isFile) {
|
||||
|
||||
//console.log(`+-- reading: ${path}`)
|
||||
|
||||
// initialize
|
||||
var content = new Uint8Array()
|
||||
content = new Uint8Array()
|
||||
var buf = new Uint8Array(1000);
|
||||
|
||||
// read the first batch
|
||||
|
@ -145,22 +144,8 @@ let getFileIntegrity = async (path, algos) => {
|
|||
// read some more
|
||||
numread = file.readSync(buf);
|
||||
}
|
||||
//console.log(' +-- done.')
|
||||
|
||||
//console.log('+-- calculating digests')
|
||||
for (const algo of algos) {
|
||||
//console.log(` +-- ${algo}`)
|
||||
var digest = algo.toLowerCase().replace('-', '') + '-' + binToBase64(await crypto.subtle.digest(algo, content))
|
||||
//console.log(digest)
|
||||
result.push(digest)
|
||||
}
|
||||
//console.log(`+-- file done: ${path}`)
|
||||
|
||||
// we are not working with a file
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
|
||||
// putting this in a try-catch block as the file
|
||||
// is apparently being auto-closed?
|
||||
// https://issueantenna.com/repo/denoland/deno/issues/15442
|
||||
|
@ -168,6 +153,20 @@ let getFileIntegrity = async (path, algos) => {
|
|||
await file.close();
|
||||
} catch (BadResource) {}
|
||||
|
||||
// did we get any content?
|
||||
if (typeof content != "boolean") {
|
||||
for (const algo of algos) {
|
||||
//console.log(` +-- ${algo}`)
|
||||
var digest = algo.toLowerCase().replace('-', '') + '-' + binToBase64(await crypto.subtle.digest(algo, content))
|
||||
//console.log(digest)
|
||||
result.push(digest)
|
||||
}
|
||||
// no content means not a file
|
||||
} else {
|
||||
// so no result
|
||||
result = false
|
||||
}
|
||||
|
||||
// return the result
|
||||
return result
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue