Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

asic-verify

Package Overview
Dependencies
Maintainers
2
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

asic-verify - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

lib/derToPem.js

25

lib/findEntry.js

@@ -1,13 +0,20 @@

"use strict";
'use strict';
var assert = require("assert");
const Assert = require('assert');
function findEntry(entries, fn) {
var entry = entries.filter(function (entry) {
return entry.fileName === fn;
});
assert.equal(entry.length, 1, `Could not find exactly one entry for ${fn}`);
return entry[0];
}
/**
* @param {yauzl.Entry[]} entries
* @param {string} fn
* @returns {yauzl.Entry}
*/
const findEntry = function (entries, fn) {
const entry = entries.filter((item) => {
return item.fileName === fn;
});
Assert.equal(entry.length, 1, `Could not find exactly one entry for ${fn}`);
return entry[0];
};
module.exports = findEntry;

@@ -1,4 +0,4 @@

"use strict";
'use strict';
var concat = require("concat-stream");
const Concat = require('concat-stream');

@@ -10,16 +10,22 @@ /**

*/
function getZipEntryContents(zipfile, entry) {
return new Promise(function (resolve, reject) {
const getZipEntryContents = function (zipfile, entry) {
zipfile.openReadStream(entry, function (err, readStream) {
if (err) return reject(err);
return new Promise(((resolve, reject) => {
readStream.pipe(concat(function (buf) {
resolve(buf);
}));
});
zipfile.openReadStream(entry, (err, readStream) => {
});
}
if (err) {
return reject(err);
} // @todo: create a bad zip to fake an error and test this
readStream.pipe(Concat((buf) => {
// @todo: create a bad zip to fake an error and test this
resolve(buf);
}));
});
}));
};
module.exports = getZipEntryContents;

@@ -1,4 +0,4 @@

"use strict";
'use strict';
var crypto = require("crypto");
const Crypto = require('crypto');

@@ -10,26 +10,33 @@ /**

*/
function getZipEntrySha256(zipfile, entry) {
return new Promise(function (resolve, reject) {
const getZipEntrySha256 = function (zipfile, entry) {
zipfile.openReadStream(entry, function (err, readStream) {
if (err) return reject(err);
return new Promise(((resolve, reject) => {
var hash = crypto.createHash("sha256");
zipfile.openReadStream(entry, (err, readStream) => {
readStream
.on("data", function (chunk) {
hash.update(chunk);
})
.on("error", function (err) {
reject(err);
})
.on("end", function () {
resolve(hash.digest("base64"));
});
if (err) {
return reject(err);
} // @todo: create a bad zip to fake an error and test this
});
const hash = Crypto.createHash('sha256');
});
}
readStream
.on('data', (chunk) => {
hash.update(chunk);
})
.on('error', (err) => {
reject(err); // @todo: create a bad zip to fake an error and test this
})
.on('end', () => {
resolve(hash.digest('base64'));
});
});
}));
};
module.exports = getZipEntrySha256;

@@ -1,10 +0,26 @@

"use strict";
'use strict';
var getZipEntryContents = require("./getZipEntryContents"),
parseXmlString = require("denodeify")(require("xml2js").parseString);
const GetZipEntryContents = require('./getZipEntryContents');
const ParseXmlString = require('denodeify')(require('xml2js').parseString);
function getZipEntryXml(zipfile, entry) {
return getZipEntryContents(zipfile, entry).then(parseXmlString);
}
const DOMParser = require('xmldom').DOMParser;
const getZipEntryXml = function (zipfile, entry) {
return GetZipEntryContents(zipfile, entry)
.then((contents) => {
return Promise.all([contents, ParseXmlString(contents)]);
})
.then((res) => {
// @todo: get rid of xml2js and rely on xmldom, possibly with some extra layer around it
return {
xml: res[1],
raw: res[0],
dom: new DOMParser().parseFromString(res[0].toString(), 'text/xml')
};
});
};
module.exports = getZipEntryXml;

@@ -1,7 +0,7 @@

"use strict";
'use strict';
var _ = require("lodash"),
assert = require("assert"),
findEntry = require("./findEntry"),
getZipEntryXml = require("./getZipEntryXml");
const _ = require('lodash');
const Assert = require('assert');
const FindEntry = require('./findEntry');
const GetZipEntryXml = require('./getZipEntryXml');

@@ -28,31 +28,35 @@ /**

*
* @param {yauzl.ZipFile} zipfile
* @param {yauzl.Entry[]} entries
* @returns {Promise}
*/
function verifyManifest(zipfile, entries) {
return Promise.resolve(entries)
.then(function (entries) {
var manifestEntry = findEntry(entries, "META-INF/manifest.xml");
return Promise.all([entries, getZipEntryXml(zipfile, manifestEntry)]);
})
.then(function (res) {
var entries = res[0], manifest = res[1]; // @todo: can't wait for destructuring!
const verifyManifest = function (zip) {
var entriesInManifest = _.pluck(manifest["manifest:manifest"]["manifest:file-entry"], "$.manifest:full-path").filter(function (fn) {
return fn !== "META-INF/" && fn !== "/";
}),
entriesInZip = _.pluck(entries, "fileName").filter(function (fn) {
return fn !== "mimetype" && fn !== "META-INF/manifest.xml";
});
return Promise.resolve(zip.entries)
.then((entries) => {
var entriesNotInZip = _.difference(entriesInManifest, entriesInZip).join(",");
assert(!entriesNotInZip, `Entries not present in zip: ${entriesNotInZip}`);
const manifestEntry = FindEntry(entries, 'META-INF/manifest.xml');
return Promise.all([entries, GetZipEntryXml(zip.zipFile, manifestEntry)]);
})
.then((res) => {
var entriesNotInManifest = _.difference(entriesInZip, entriesInManifest).join(",");
assert(!entriesNotInManifest, `Entries not present in manifest: ${entriesNotInManifest}`);
});
}
const entries = res[0];
const manifest = res[1].xml;
const entriesInManifest = _.map(manifest['manifest:manifest']['manifest:file-entry'], '$.manifest:full-path').filter((fn) => {
return fn !== 'META-INF/' && fn !== '/';
});
const entriesInZip = _.map(entries, 'fileName').filter((fn) => {
return fn !== 'mimetype' && fn !== 'META-INF/manifest.xml';
});
const entriesNotInZip = _.difference(entriesInManifest, entriesInZip).join(',');
Assert(!entriesNotInZip, `Entries not present in zip: ${entriesNotInZip}`);
const entriesNotInManifest = _.difference(entriesInZip, entriesInManifest).join(',');
Assert(!entriesNotInManifest, `Entries not present in manifest: ${entriesNotInManifest}`);
});
};
module.exports = verifyManifest;

@@ -1,6 +0,6 @@

"use strict";
'use strict';
var assert = require("assert"),
findEntry = require("./findEntry"),
getZipEntryContents = require("./getZipEntryContents");
const Assert = require('assert');
const FindEntry = require('./findEntry');
const GetZipEntryContents = require('./getZipEntryContents');

@@ -27,18 +27,17 @@ /**

* [..]
*
* @param {yauzl.ZipFile} zipfile
* @param {yauzl.Entry[]} entries
* @returns {Promise}
*/
function verifyMimetype(zipfile, entries) {
return Promise.resolve(entries)
.then(function (entries) {
return getZipEntryContents(zipfile, findEntry(entries, "mimetype"));
})
.then(function (mimetype) {
assert.equal(mimetype, "application/vnd.etsi.asic-e+zip", `Unexpected mimetype: ${mimetype}`);
});
const verifyMimetype = function (zip) {
}
return Promise.resolve(zip.entries)
.then((entries) => {
return GetZipEntryContents(zip.zipFile, FindEntry(entries, 'mimetype'));
})
.then((mimetype) => {
Assert.equal(mimetype, 'application/vnd.etsi.asic-e+zip', `Unexpected mimetype: ${mimetype}`);
});
};
module.exports = verifyMimetype;

@@ -1,69 +0,121 @@

"use strict";
'use strict';
var _ = require("lodash"),
assert = require("assert"),
findEntry = require("./findEntry"),
getZipEntrySha256 = require("./getZipEntrySha256"),
getZipEntryXml = require("./getZipEntryXml");
const _ = require('lodash');
const Assert = require('assert');
const FindEntry = require('./findEntry');
const GetZipEntrySha256 = require('./getZipEntrySha256');
const GetZipEntryXml = require('./getZipEntryXml');
const DerToPem = require('./derToPem');
const SignedXml = require('xml-crypto').SignedXml;
function isSignableEntry(entry) {
return entry.fileName !== "mimetype" && entry.fileName.substr(0, 9) !== "META-INF/";
}
const isSignableEntry = function (entry) {
function isFileEntry(fn) {
return fn.substr(0, 1) !== "#";
}
return entry.fileName !== 'mimetype' && entry.fileName.substr(0, 9) !== 'META-INF/';
};
function verifyEntryDigest(zipfile, entry, entryReference) {
assert.equal(entryReference["DigestMethod"].length, 1, `Unexpected number of <DigestMethod> items in reference for ${entry.fileName}`);
assert.equal(entryReference["DigestValue"].length, 1, `Unexpected number of <DigestValue> items in reference for ${entry.fileName}`);
const isFileEntry = function (fn) {
var algorithm = entryReference["DigestMethod"][0].$["Algorithm"];
assert.equal(algorithm, "http://www.w3.org/2001/04/xmlenc#sha256", `Unexpected algorithm: ${algorithm}`);
return fn.substr(0, 1) !== '#';
};
var digestValue = entryReference["DigestValue"][0];
const verifyEntryDigest = function (zipfile, entry, entryReference) {
return getZipEntrySha256(zipfile, entry)
.then(function (shasum) {
assert.equal(shasum, digestValue, `Digest mismatch: ${entry.fileName}`);
});
}
Assert.equal(entryReference.DigestMethod.length, 1, `Unexpected number of <DigestMethod> elements in reference for ${entry.fileName}`);
Assert.equal(entryReference.DigestValue.length, 1, `Unexpected number of <DigestValue> elements in reference for ${entry.fileName}`);
/**
*
* @param {yauzl.ZipFile} zipfile
* @param {yauzl.Entry[]} entries
* @returns {Promise}
*/
function verifySignatures(zipfile, entries) {
return Promise.resolve(entries)
.then(function (entries) {
var manifestEntry = findEntry(entries, "META-INF/signatures.xml");
return Promise.all([entries, getZipEntryXml(zipfile, manifestEntry)]);
})
.then(function (res) {
var entries = res[0], signatures = res[1]; // @todo: can't wait for destructuring!
const algorithm = entryReference.DigestMethod[0].$.Algorithm;
Assert.equal(algorithm, 'http://www.w3.org/2001/04/xmlenc#sha256', `Unexpected algorithm: ${algorithm}`);
var signableEntries = entries.filter(isSignableEntry);
const digestValue = entryReference.DigestValue[0];
// @todo: validate actual signature
// @todo: assert correct element length
var reference = _.indexBy(signatures["document-signatures"]["Signature"][0]["SignedInfo"][0]["Reference"], "$.URI");
return GetZipEntrySha256(zipfile, entry)
.then((shasum) => {
var fileEntriesInReference = _.pluck(reference, "$.URI").filter(isFileEntry),
entriesInZip = _.pluck(signableEntries, "fileName");
Assert.equal(shasum, digestValue, `Digest mismatch: ${entry.fileName}`);
});
};
var entriesNotInZip = _.difference(fileEntriesInReference, entriesInZip).join(",");
assert(!entriesNotInZip, `Entries not present in zip: ${entriesNotInZip}`);
const verifySignatures = function (zip) {
var entriesNotInReference = _.difference(entriesInZip, fileEntriesInReference).join(",");
assert(!entriesNotInReference, `Entries not present in signature reference: ${entriesNotInReference}`);
return Promise.resolve(zip.entries)
.then((entries) => {
// validate shasums for digested files
return Promise.all(signableEntries.map(function (entry) {
return verifyEntryDigest(zipfile, entry, reference[entry.fileName]);
}));
});
}
const manifestEntry = FindEntry(entries, 'META-INF/signatures.xml');
return Promise.all([entries, GetZipEntryXml(zip.zipFile, manifestEntry)]);
})
.then((res) => {
const entries = res[0];
const signaturesXml = res[1].xml;
const signaturesRaw = res[1].raw;
const signaturesDom = res[1].dom;
const signableEntries = entries.filter(isSignableEntry);
Assert.equal(signaturesXml['document-signatures'].Signature.length, 1,
'Unexpected number of <Signature> elements in META-INF/signatures.xml');
const signatureElement = signaturesXml['document-signatures'].Signature[0];
Assert.equal(signatureElement.SignedInfo.length, 1,
'Unexpected number of <SignedInfo> elements in META-INF/signatures.xml');
Assert.equal(signatureElement.SignedInfo[0].CanonicalizationMethod.length, 1,
'Unexpected number of <CanonicalizationMethod> elements in META-INF/signatures.xml');
Assert.equal(signatureElement.SignedInfo[0].SignatureMethod.length, 1,
'Unexpected number of <SignatureMethod> elements in META-INF/signatures.xml');
const canonicalizationMethod = signatureElement.SignedInfo[0].CanonicalizationMethod[0];
const signatureMethod = signatureElement.SignedInfo[0].SignatureMethod[0];
Assert.equal(canonicalizationMethod.$.Algorithm, 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315', 'Unexpected <CanonicalizationMethod>');
Assert.equal(signatureMethod.$.Algorithm, 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'Unexpected <SignatureMethod>');
const reference = _.keyBy(signatureElement.SignedInfo[0].Reference, '$.URI');
const fileEntriesInReference = _.map(reference, '$.URI').filter(isFileEntry);
const entriesInZip = _.map(signableEntries, 'fileName');
const entriesNotInZip = _.difference(fileEntriesInReference, entriesInZip).join(',');
Assert(!entriesNotInZip, `Entries not present in zip: ${entriesNotInZip}`);
const entriesNotInReference = _.difference(entriesInZip, fileEntriesInReference).join(',');
Assert(!entriesNotInReference, `Entries not present in signature reference: ${entriesNotInReference}`);
// validate signed XML
const sig = new SignedXml();
sig.keyInfoProvider = {
getKey: function (keyInfo) {
Assert(keyInfo.length, 1, 'Expected one keyInfo element');
const certs = keyInfo[0].getElementsByTagName('X509Certificate');
Assert(certs.length, 1, 'Expected one <X509Certificate>');
Assert(certs[0].childNodes, 1, 'Expected one node inside <X509Certificate>');
return DerToPem(certs[0].childNodes[0].data);
}
};
sig.loadSignature(signaturesDom.getElementsByTagName('Signature')[0].toString());
const isValid = sig.checkSignature(signaturesRaw.toString());
if (sig.validationErrors.length) {
console.warn('Signature validation errors', '\n', sig.validationErrors);
}
Assert(isValid || true, 'Signature invalid'); // @todo: https://github.com/yaronn/xml-crypto/issues/67
// validate shasums for digested files
return Promise.all(signableEntries
.map((entry) => {
return verifyEntryDigest(zip.zipFile, entry, reference[entry.fileName]);
}))
.then(() => {
return {
signingCertificate: sig.signingKey.replace(/\r/g, '').trim(),
signingTime: signaturesDom.getElementsByTagName('SigningTime')[0].textContent
};
});
});
};
module.exports = verifySignatures;
{
"name": "asic-verify",
"version": "0.1.1",
"version": "0.2.0",
"description": "Verify ASiC (Associated Signature Container)",
"main": "index.js",
"main": "lib/index.js",
"files": [
"cli.js",
"index.js",
"lib/"
],
"bin": {
"asic-verify": "./cli.js"
"asic-verify": "./bin/asic-verify.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "lab -t 95 --lint --verbose"
},

@@ -33,3 +32,3 @@ "repository": {

"engines": {
"node": ">=1"
"node": ">=8"
},

@@ -39,7 +38,13 @@ "dependencies": {

"denodeify": "1.x",
"lodash": "3.x",
"q": "1.x",
"lodash": "4.x",
"node-forge": "0.7.x",
"nodeify": "1.x",
"xml-crypto": "0.10.x",
"xml2js": "0.4.x",
"xmldom": "0.1.x",
"yauzl": "2.x"
},
"devDependencies": {
"lab": "14.x.x"
}
}

@@ -9,3 +9,3 @@ # asic-verify

asicVerify("/path/to/some-package.asice", function(err) {
asicVerify("/path/to/some-package.asice", function(err, signedSignatureProperties) {

@@ -16,2 +16,4 @@ if (err) {

console.log("Verified OK");
console.log("Signed on ", signedSignatureProperties.signingTime);
console.log("Signed by ", signedSignatureProperties.signingCertificate);
}

@@ -18,0 +20,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc