2022-11-26 18:40:21 +00:00
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * \
| * === basic - integrity : pre - configured subresource integrity for content === * |
\ * === === === === === === === === === === === === === === === === === === === === === === === === = * /
/ * *
* basic - integrity plugin ' s deploy / utility functions
*
* this code expects a Deno runtime :
* https : //deno.land/
* /
2022-12-08 13:03:06 +00:00
/ * *
* 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 )
} , '' )
)
}
2022-12-10 23:48:14 +00:00
/ * *
* 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
* /
2022-12-07 00:13:52 +00:00
let getFileIntegrity = async ( path , algos ) => {
2022-12-09 13:45:05 +00:00
var result = [ ]
2022-12-13 23:44:12 +00:00
var content = false
2022-12-09 13:45:05 +00:00
// open the file and get some info on it
2022-12-07 00:13:52 +00:00
const file = await Deno . open (
path ,
{ read : true }
) ;
const fileInfo = await file . stat ( ) ;
2022-12-08 13:03:06 +00:00
// are we working with a file?
2022-12-07 00:13:52 +00:00
if ( fileInfo . isFile ) {
2022-12-09 13:45:05 +00:00
2022-12-08 13:03:06 +00:00
// initialize
2022-12-13 23:44:12 +00:00
content = new Uint8Array ( )
2022-12-08 13:03:06 +00:00
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 ) {
2022-12-08 13:57:05 +00:00
//console.log(` +-- read: ${numread}`)
//console.log(` +-- length: ${content.length}`)
2022-12-08 13:03:06 +00:00
// there has to be a better way...
var new _content = new Uint8Array ( content . length + numread ) ;
2022-12-08 13:57:05 +00:00
//console.log(` +-- new length: ${new_content.length}`)
2022-12-08 13:03:06 +00:00
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 ) ;
2022-12-07 00:13:52 +00:00
}
}
2022-12-09 13:45:05 +00:00
2022-12-07 00:13:52 +00:00
// 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 ) { }
2022-12-08 13:57:05 +00:00
2022-12-13 23:44:12 +00:00
// 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
}
2022-12-08 13:57:05 +00:00
// return the result
return result
2022-12-07 00:13:52 +00:00
}
2022-11-26 18:40:21 +00:00
/ * *
* 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" )
* /
2022-12-08 13:57:05 +00:00
let getIntegrity = async ( paths , algos = [ "SHA-256" ] , output = "json" ) => {
2022-11-26 18:40:21 +00:00
// we need non-emtpy arrays of string in the arguments
if ( ! Array . isArray ( paths ) || ( paths . length == 0 ) ) {
2022-12-09 18:22:24 +00:00
throw new Error ( "Expected non-empty list of files to generate digests of." )
2022-11-26 18:40:21 +00:00
}
if ( ! Array . isArray ( algos ) || ( algos . length == 0 ) ) {
2022-12-09 18:22:24 +00:00
throw new Error ( "Expected non-empty list of algorithms to use." )
2022-11-26 18:40:21 +00:00
}
2022-12-08 13:57:05 +00:00
if ( ! [ 'json' , 'text' ] . includes ( output ) ) {
2022-12-09 18:22:24 +00:00
throw new Error ( "Expected either 'json' or 'text' as output type to generate." )
2022-12-08 13:57:05 +00:00
}
var result = { }
for ( const p of paths ) {
2022-12-09 13:45:05 +00:00
// filter-out stuff we are not interested in
// like directories etc
var r = await getFileIntegrity ( p , algos )
if ( r !== false ) {
result [ p ] = r
}
2022-12-08 13:57:05 +00:00
}
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
}
2022-11-26 18:40:21 +00:00
}
// this never changes
const pluginName = "basic-integrity"
2022-12-09 18:22:24 +00:00
const pluginDescription = "Verifying subresource integrity for resources fetched by other plugins.\nCLI used to generate subresource integrity hashes for provided files."
2022-11-26 18:40:21 +00:00
const pluginVersion = 'COMMIT_UNKNOWN'
const pluginActions = {
"get-integrity" : {
run : getIntegrity ,
description : "calculate subresource integrity hashes for provided files" ,
arguments : {
2022-12-09 13:45:05 +00:00
_ : {
2022-12-10 18:40:30 +00:00
name : "file" ,
description : "paths of files to be processed"
2022-12-07 00:13:52 +00:00
} ,
algorithm : {
2022-12-11 14:15:02 +00:00
description : "SubtleCrypto.digest-compatible algorithm names to use when calculating digests (default: \"SHA-256\")" ,
2022-12-07 00:13:52 +00:00
collect : true ,
string : true ,
default : "SHA-256"
2022-12-08 13:57:05 +00:00
} ,
output : {
description : "a string, defining output mode ('json' or 'text'; 'json' is default)" ,
collect : false ,
string : true ,
default : "json"
2022-12-07 00:13:52 +00:00
}
2022-11-26 18:40:21 +00:00
}
}
}
export {
pluginName as name ,
pluginDescription as description ,
pluginVersion as version ,
pluginActions as actions
}