kopia lustrzana https://gitlab.com/rysiekpl/libresilient
183 wiersze
5.6 KiB
JavaScript
183 wiersze
5.6 KiB
JavaScript
/* ========================================================================= *\
|
|
|* === basic-integrity: pre-configured subresource integrity for content === *|
|
|
\* ========================================================================= */
|
|
|
|
/**
|
|
* basic-integrity plugin's deploy/utility functions
|
|
*
|
|
* this code expects a Deno runtime:
|
|
* https://deno.land/
|
|
*/
|
|
|
|
|
|
/**
|
|
* helper function, converting binary to base64
|
|
* this need not be extremely fast, since it will only be used on digests
|
|
*
|
|
* binary_data - data to convert to base64
|
|
*/
|
|
let binToBase64 = (binary_data) => {
|
|
return btoa(
|
|
(new Uint8Array(binary_data))
|
|
.reduce((bin, byte)=>{
|
|
return bin += String.fromCharCode(byte)
|
|
}, '')
|
|
)
|
|
}
|
|
|
|
|
|
/**
|
|
* get integrity digests for a given path
|
|
*
|
|
* path - path to a file whose digest is to be generated
|
|
* algos - array of SubtleCrypto.digest-compatible algorithm names
|
|
*/
|
|
let getFileIntegrity = async (path, algos) => {
|
|
|
|
var result = []
|
|
var content = false
|
|
|
|
// open the file and get some info on it
|
|
const file = await Deno.open(
|
|
path,
|
|
{ read: true }
|
|
);
|
|
const fileInfo = await file.stat();
|
|
|
|
// are we working with a file?
|
|
if (fileInfo.isFile) {
|
|
|
|
// initialize
|
|
content = new Uint8Array()
|
|
var buf = new Uint8Array(1000);
|
|
|
|
// read the first batch
|
|
var numread = file.readSync(buf);
|
|
|
|
// read the rest, if there is anything to read
|
|
while (numread !== null) {
|
|
//console.log(` +-- read: ${numread}`)
|
|
//console.log(` +-- length: ${content.length}`)
|
|
|
|
// there has to be a better way...
|
|
var new_content = new Uint8Array(content.length + numread);
|
|
//console.log(` +-- new length: ${new_content.length}`)
|
|
new_content.set(content)
|
|
if (buf.length === numread) {
|
|
new_content.set(buf, content.length)
|
|
} else {
|
|
new_content.set(buf.slice(0, numread), content.length)
|
|
}
|
|
content = new_content
|
|
|
|
// read some more
|
|
numread = file.readSync(buf);
|
|
}
|
|
}
|
|
|
|
// putting this in a try-catch block as the file
|
|
// is apparently being auto-closed?
|
|
// https://issueantenna.com/repo/denoland/deno/issues/15442
|
|
try {
|
|
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
|
|
}
|
|
|
|
|
|
/**
|
|
* get integrity data for specified urls, using specified algorithms
|
|
*
|
|
* naming of algorithms as accepted by SubtleCrypto.digest():
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
|
*
|
|
* paths - array of strings, paths to individual pieces of content
|
|
* algos - array of algorithms to use to calculate digests (default: "SHA-256")
|
|
*/
|
|
let getIntegrity = async (paths, algos=["SHA-256"], output="json") => {
|
|
|
|
// we need non-emtpy arrays of string in the arguments
|
|
if (!Array.isArray(paths) || (paths.length == 0)) {
|
|
throw new Error("Expected non-empty list of files to generate digests of.")
|
|
}
|
|
if (!Array.isArray(algos) || (algos.length == 0)) {
|
|
throw new Error("Expected non-empty list of algorithms to use.")
|
|
}
|
|
if (!['json', 'text'].includes(output)) {
|
|
throw new Error("Expected either 'json' or 'text' as output type to generate.")
|
|
}
|
|
|
|
var result = {}
|
|
for (const p of paths) {
|
|
// filter-out stuff we are not interested in
|
|
// like directories etc
|
|
var r = await getFileIntegrity(p, algos)
|
|
if (r !== false) {
|
|
result[p] = r
|
|
}
|
|
}
|
|
|
|
if (output == 'json') {
|
|
return JSON.stringify(result)
|
|
} else {
|
|
var text_result = ''
|
|
for (const p of paths) {
|
|
text_result += `${p}: ${result[p].join(' ')}\n`
|
|
}
|
|
return text_result
|
|
}
|
|
}
|
|
|
|
|
|
// this never changes
|
|
const pluginName = "basic-integrity"
|
|
const pluginDescription = "Verifying subresource integrity for resources fetched by other plugins.\nCLI used to generate subresource integrity hashes for provided files."
|
|
const pluginVersion = 'COMMIT_UNKNOWN'
|
|
const pluginActions = {
|
|
"get-integrity": {
|
|
run: getIntegrity,
|
|
description: "calculate subresource integrity hashes for provided files",
|
|
arguments: {
|
|
_: {
|
|
name: "file",
|
|
description: "paths of files to be processed"
|
|
},
|
|
algorithm: {
|
|
description: "SubtleCrypto.digest-compatible algorithm names to use when calculating digests (default: \"SHA-256\")",
|
|
collect: true,
|
|
string: true,
|
|
default: "SHA-256"
|
|
},
|
|
output: {
|
|
description: "a string, defining output mode ('json' or 'text'; 'json' is default)",
|
|
collect: false,
|
|
string: true,
|
|
default: "json"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export {
|
|
pluginName as name,
|
|
pluginDescription as description,
|
|
pluginVersion as version,
|
|
pluginActions as actions
|
|
}
|