cli: more tests for signed-integrity cli done (ref. #66)

merge-requests/23/head
Michał 'rysiek' Woźniak 2022-12-15 16:56:50 +00:00
rodzic d6ac169816
commit a6985e8521
3 zmienionych plików z 175 dodań i 40 usunięć

Wyświetl plik

@ -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))
})

Wyświetl plik

@ -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);

Wyświetl plik

@ -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
}