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,
|
assert,
|
||||||
assertThrows,
|
assertThrows,
|
||||||
assertRejects,
|
assertRejects,
|
||||||
assertEquals
|
assertEquals,
|
||||||
|
assertStringIncludes
|
||||||
} from "https://deno.land/std@0.167.0/testing/asserts.ts";
|
} 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 () => {
|
Deno.test("plugin loads", async () => {
|
||||||
const bi = await import('../../plugins/signed-integrity/cli.js')
|
const bi = await import('../../plugins/signed-integrity/cli.js')
|
||||||
assert("name" in bi)
|
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")
|
}, 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 bi = await import('../../plugins/signed-integrity/cli.js')
|
||||||
const gi = bi.actions["gen-integrity"]
|
const gi = bi.actions["gen-integrity"]
|
||||||
assertRejects(async ()=>{
|
assertRejects(async ()=>{
|
||||||
await gi.run(['./'], 'non-existent')
|
await gi.run(['./'], 'non-existent')
|
||||||
}, Error, "Failed to load private key from '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(
|
assertEquals(
|
||||||
await gi.run(['./'], './__denotests__/mocks/keyfile.json'),
|
await gi.run(['./'], './__denotests__/mocks/keyfile.json'),
|
||||||
'{}'
|
'{}'
|
||||||
)
|
)
|
||||||
/*assertEquals(
|
assertStringIncludes(
|
||||||
)await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json'),
|
await gi.run(['./__denotests__/mocks/hello.txt'], './__denotests__/mocks/keyfile.json'),
|
||||||
'{}'
|
'"./__denotests__/mocks/hello.txt":"eyJhbGciOiAiRVMzODQifQ.eyJpbnRlZ3JpdHkiOiAic2hhMjU2LXVVMG51Wk5OUGdpbExsTFgybjJyK3NTRTcrTjZVNER1a0lqM3JPTHZ6ZWs9In0.'
|
||||||
)*/
|
)
|
||||||
//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="]}')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*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 bi = await import('../../plugins/signed-integrity/cli.js')
|
||||||
const gi = bi.actions["gen-integrity"]
|
const gi = bi.actions["gen-integrity"]
|
||||||
assertRejects(async ()=>{
|
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')
|
}, 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"]}')
|
// helper function
|
||||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-512']), '{"./__denotests__/mocks/hello.txt":["sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw=="]}')
|
let getGeneratedTestIntegrity = async (algos) => {
|
||||||
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=="]}')
|
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 bi = await import('../../plugins/signed-integrity/cli.js')
|
||||||
const gi = bi.actions["gen-integrity"]
|
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')
|
let getGeneratedTestIntegrity = async (algos) => {
|
||||||
assertEquals(await gi.run(['./__denotests__/mocks/hello.txt'], ['SHA-512'], 'text'), './__denotests__/mocks/hello.txt: sha512-MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==\n')
|
let result = await gi.run(
|
||||||
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')
|
['./__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?
|
// are we working with a file?
|
||||||
if (fileInfo.isFile) {
|
if (fileInfo.isFile) {
|
||||||
|
|
||||||
//console.log(`+-- reading: ${path}`)
|
|
||||||
|
|
||||||
// initialize
|
// initialize
|
||||||
content = new Uint8Array()
|
content = new Uint8Array()
|
||||||
var buf = new Uint8Array(1000);
|
var buf = new Uint8Array(1000);
|
||||||
|
|
|
@ -106,6 +106,7 @@ let getPubkey = async (keyfile) => {
|
||||||
let getFileIntegrity = async (path, algos) => {
|
let getFileIntegrity = async (path, algos) => {
|
||||||
|
|
||||||
var result = []
|
var result = []
|
||||||
|
var content = false
|
||||||
|
|
||||||
// open the file and get some info on it
|
// open the file and get some info on it
|
||||||
const file = await Deno.open(
|
const file = await Deno.open(
|
||||||
|
@ -117,10 +118,8 @@ let getFileIntegrity = async (path, algos) => {
|
||||||
// are we working with a file?
|
// are we working with a file?
|
||||||
if (fileInfo.isFile) {
|
if (fileInfo.isFile) {
|
||||||
|
|
||||||
//console.log(`+-- reading: ${path}`)
|
|
||||||
|
|
||||||
// initialize
|
// initialize
|
||||||
var content = new Uint8Array()
|
content = new Uint8Array()
|
||||||
var buf = new Uint8Array(1000);
|
var buf = new Uint8Array(1000);
|
||||||
|
|
||||||
// read the first batch
|
// read the first batch
|
||||||
|
@ -145,22 +144,8 @@ let getFileIntegrity = async (path, algos) => {
|
||||||
// read some more
|
// read some more
|
||||||
numread = file.readSync(buf);
|
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
|
// putting this in a try-catch block as the file
|
||||||
// is apparently being auto-closed?
|
// is apparently being auto-closed?
|
||||||
// https://issueantenna.com/repo/denoland/deno/issues/15442
|
// https://issueantenna.com/repo/denoland/deno/issues/15442
|
||||||
|
@ -168,6 +153,20 @@ let getFileIntegrity = async (path, algos) => {
|
||||||
await file.close();
|
await file.close();
|
||||||
} catch (BadResource) {}
|
} 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 the result
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue