cvcertstore.js
Summary
Support for a card verifiable certificates store according to EAC 1.1/2.0
Class Summary
|
CVCertificateStore |
Class that abstracts a certificate and key store for a EAC PKI. |
if (typeof(__ScriptingServer) == "undefined") {
load("tools/pkcs8.js");
load("cvc.js");
}
function CVCertificateStore(path) {
this.path = path;
}
CVCertificateStore.loadBinaryFile = function(filename) {
var f = new java.io.FileInputStream(filename);
var flen = f.available();
var bs = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, flen);
var len = f.read(bs);
f.close();
var bb = new ByteBuffer(bs);
var data = bb.toByteString();
return data;
}
CVCertificateStore.saveBinaryFile = function(filename, data) {
var f = new java.io.FileOutputStream(filename);
f.write(data);
f.close();
}
CVCertificateStore.loadXMLFile = function(filename) {
var f = new java.io.FileReader(filename);
var bfr = new java.io.BufferedReader(f);
var result;
do {
result = bfr.readLine();
} while ((result != null) && (result.substr(0, 2) == "<?"));
if (result == null) {
bfr.close();
f.close();
return null;
}
var line;
while ((line = bfr.readLine()) != null) {
result += line + "\n";
}
bfr.close();
f.close();
return new XML(result);
}
CVCertificateStore.saveXMLFile = function(filename, xml) {
var fw = new java.io.FileWriter(filename);
fw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
fw.write(xml.toXMLString());
fw.close();
}
CVCertificateStore.parentPathOf = function(path) {
var ofs = path.lastIndexOf("/");
if (ofs <= 0) {
return null;
}
return path.substr(0, ofs);
}
CVCertificateStore.nthElementOf = function(path, n) {
var pe = path.substr(1).split("/");
if (typeof(n) == "undefined") {
return pe[pe.length - 1];
}
return pe[n];
}
CVCertificateStore.checkPath = function(path) {
if ((path.indexOf("/..") >= 0) ||
(path.indexOf("../") >= 0) ||
(path.indexOf("\\..") >= 0) ||
(path.indexOf("..\\") >= 0) ||
(path.indexOf("\0") >= 0) ||
(path.indexOf("~") >= 0)) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Path \"" + path + "\" contains illegal characters");
}
}
CVCertificateStore.prototype.getCrypto = function() {
if (this.crypto == undefined) {
this.crypto = new Crypto();
}
return this.crypto;
}
CVCertificateStore.prototype.mapPath = function(path) {
CVCertificateStore.checkPath(path);
return this.path + path;
}
CVCertificateStore.prototype.getTerminalCertificateFor = function(cvcaref) {
var fn = this.mapPath("/" + cvcaref.getHolder() + "/terminal/current.cvcert");
var bin = CVCertificateStore.loadBinaryFile(fn);
var cvc = new CVC(bin);
return cvc;
}
CVCertificateStore.prototype.getDVCACertificateFor = function(cvcaref, dvcaref) {
var fn = this.mapPath("/" + cvcaref.getHolder() + "/" + dvcaref.getHolder() + "/" + dvcaref.toString() + ".cvcert");
var bin = CVCertificateStore.loadBinaryFile(fn);
var cvc = new CVC(bin);
return cvc;
}
CVCertificateStore.prototype.getCVCACertificateFor = function(cvcaref) {
var fn = this.mapPath("/" + cvcaref.getHolder() + "/" + cvcaref.toString() + ".cvcert");
var f = new java.io.File(fn);
if (!f.exists()) {
fn = this.path + "/" + cvcaref.getHolder() + "/" + cvcaref.toString() + ".selfsigned.cvcert";
}
var bin = CVCertificateStore.loadBinaryFile(fn);
var cvc = new CVC(bin);
return cvc;
}
CVCertificateStore.prototype.getCertificateChainFor = function(cvcaref) {
var chain = new Array();
var termcert = this.getTerminalCertificateFor(cvcaref);
chain.push(termcert);
var dvcaref = termcert.getCAR();
var dvcacert = this.getDVCACertificateFor(cvcaref, dvcaref);
chain.push(dvcacert);
var ref = dvcacert.getCAR();
while (ref.toString() != cvcaref.toString()) {
var cvcacert = this.getCVCACertificateFor(cvcaref);
chain.push(cvcacert);
var ref = cvcacert.getCAR();
}
return(chain.reverse());
}
CVCertificateStore.prototype.getTerminalKeyFor = function(cvcaref) {
var fn = this.mapPath("/" + cvcaref.getHolder() + "/terminal/current.pkcs8");
var bin = CVCertificateStore.loadBinaryFile(fn);
return PKCS8.decodeKeyFromPKCS8Format(bin);
}
CVCertificateStore.prototype.storePrivateKey = function(path, chr, prk) {
var cfg = this.loadConfig(path);
if (cfg == null) {
cfg = this.getDefaultConfig(path);
this.saveConfig(path, cfg);
}
var p8 = PKCS8.encodeKeyUsingPKCS8Format(prk);
var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
GPSystem.trace("Saving private key to " + fn);
CVCertificateStore.saveBinaryFile(fn, p8);
}
CVCertificateStore.prototype.getPrivateKey = function(path, chr) {
var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
try {
var bin = CVCertificateStore.loadBinaryFile(fn);
}
catch(e) {
return null;
}
return PKCS8.decodeKeyFromPKCS8Format(bin);
}
CVCertificateStore.prototype.deletePrivateKey = function(path, chr) {
var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
var f = new java.io.File(fn);
return f["delete"]();
}
CVCertificateStore.prototype.storeRequest = function(path, req) {
var chr = req.getCHR();
var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
GPSystem.trace("Saving request to " + fn);
CVCertificateStore.saveBinaryFile(fn, req.getBytes());
}
CVCertificateStore.prototype.getRequest = function(path, chr) {
var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
var bin = null;
try {
bin = CVCertificateStore.loadBinaryFile(fn);
}
catch (e) {
return null;
}
return new CVC(bin);
}
CVCertificateStore.prototype.deleteRequest = function(path, chr) {
var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
var f = new java.io.File(fn);
return f["delete"]();
}
CVCertificateStore.prototype.storeCertificate = function(path, cert, makeCurrent) {
var car = cert.getCAR();
var chr = cert.getCHR();
if (car.equals(chr)) {
var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
} else {
var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
}
var f = new java.io.File(fn);
if (f.exists()) {
return;
}
var cfg = this.loadConfig(path);
if (cfg == null) {
cfg = this.getDefaultConfig(path);
this.saveConfig(path, cfg);
}
GPSystem.trace("Saving certificate to " + fn);
CVCertificateStore.saveBinaryFile(fn, cert.getBytes());
if (makeCurrent) {
var cfg = this.loadConfig(path);
cfg.sequence.currentCHR = chr.toString();
this.saveConfig(path, cfg);
}
}
CVCertificateStore.prototype.deleteCertificate = function(path, chr, selfsigned) {
if (selfsigned) {
var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
} else {
var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
}
var f = new java.io.File(fn);
return f["delete"]();
}
CVCertificateStore.prototype.getCertificateBinary = function(path, chr, selfsigned) {
if (selfsigned) {
var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
} else {
var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
var f = new java.io.File(fn);
if (!f.exists()) {
var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
}
}
var bin = null;
try {
bin = CVCertificateStore.loadBinaryFile(fn);
}
catch (e) {
}
return bin;
}
CVCertificateStore.prototype.getCertificate = function(path, chr, selfsigned) {
var bin = this.getCertificateBinary(path, chr, selfsigned);
if (bin == null) {
return null;
}
var cvc = null;
try {
cvc = new CVC(bin);
}
catch (e) {
GPSystem.trace(e);
}
return cvc;
}
CVCertificateStore.prototype.getCertificateChain = function(path, tochr, fromcar) {
var chain = [];
var chr = tochr;
while (true) {
var cvc = this.getCertificate(path, chr, false);
if (cvc == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr);
}
chain.push(cvc);
if (typeof(fromcar) == "undefined") {
if (cvc.getCAR().equals(cvc.getCHR())) {
break;
}
} else {
if (cvc.getCAR().equals(fromcar)) {
break;
}
if (cvc.getCAR().equals(cvc.getCHR())) {
return null;
}
}
var ofs = path.lastIndexOf("/");
if (ofs > 0) {
path = path.substr(0, ofs);
}
chr = cvc.getCAR();
}
return chain.reverse();
}
CVCertificateStore.prototype.listCertificates = function(path) {
var result = [];
var fn = this.mapPath(path);
var f = new java.io.File(fn);
if (!f.exists()) {
return result;
}
var files = f.list();
for (var i = 0; i < files.length; i++) {
var s = new String(files[i]);
var n = s.match(/\.(cvcert|CVCERT)$/);
if (n) {
var bin = CVCertificateStore.loadBinaryFile(fn + "/" + s);
var cvc = new CVC(bin);
result.push(cvc);
}
}
result.sort(function(a,b) { return a.getCHR().toString() < b.getCHR().toString() ? -1 : (a.getCHR().toString() > b.getCHR().toString() ? 1 : 0) } );
return result;
}
CVCertificateStore.prototype.listHolders = function(path) {
var result = [];
var fn = this.mapPath(path);
var f = new java.io.File(fn);
if (!f.exists()) {
return result;
}
var files = f.list();
for (var i = 0; i < files.length; i++) {
var s = new String(files[i]);
var fd = new java.io.File(f, s);
if (fd.isDirectory()) {
result.push(s);
}
}
return result;
}
CVCertificateStore.prototype.getDomainParameter = function(path, chr) {
if (typeof(chr) == "undefined") {
chr = path;
var path = "/" + chr.getHolder();
}
do {
var ofs = path.lastIndexOf("/");
if (ofs > 0) {
var cvc = this.getCertificate(path, chr);
if (cvc == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr);
}
chr = cvc.getCAR();
path = path.substr(0, ofs);
}
} while (ofs > 0);
do {
var cvc = this.getCertificate(path, chr);
if (cvc == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr + " in " + path);
}
var p = cvc.getPublicKey();
if (typeof(p.getComponent(Key.ECC_P)) != "undefined") {
return p;
}
chr = cvc.getCAR();
} while (!chr.equals(cvc.getCHR()));
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate CVCA certificate with domain parameter");
}
CVCertificateStore.prototype.getDefaultDomainParameter = function(path) {
var pe = path.substr(1).split("/");
chr = this.getCurrentCHR("/" + pe[0]);
if (chr == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
}
return this.getDomainParameter(chr);
}
CVCertificateStore.prototype.getDefaultPublicKeyOID = function(path) {
var pe = path.substr(1).split("/");
chr = this.getCurrentCHR("/" + pe[0]);
if (chr == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
}
var cvc = this.getCertificate("/" + pe[0], chr);
if (cvc == null) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
}
return cvc.getPublicKeyOID();
}
CVCertificateStore.prototype.getCurrentCHR = function(path) {
var cfg = this.loadConfig(path);
if (cfg == null) {
return null;
}
if (cfg.sequence.currentCHR.toString()) {
return new PublicKeyReference(cfg.sequence.currentCHR.toString());
}
return null;
}
CVCertificateStore.prototype.getNextCHR = function(path, countryseq) {
var cfg = this.loadConfig(path);
if (cfg == null) {
cfg = this.getDefaultConfig();
}
var seq = parseInt(cfg.sequence.current);
seq += 1;
cfg.sequence.current = seq;
this.saveConfig(path, cfg);
return this.getCHRForSequenceNumber(path, seq, countryseq);
}
CVCertificateStore.encodeBase36 = function(value) {
value = value % (1000 + 26 * 36 * 36);
var seq;
if (value < 1000) {
seq = "" + value;
} else {
value += 11960; 10 * 36 * 36 - 1000
seq = "";
while(value > 0) {
var c = value % 36;
if (c >= 10) {
c += 55;
} else {
c += 48;
}
seq = String.fromCharCode(c) + seq;
value = Math.floor(value / 36);
}
}
seq = "000".substr(0, 3 - seq.length) + seq;
return seq;
}
CVCertificateStore.prototype.getCHRForSequenceNumber = function(path, sequence, countryseq) {
var pe = path.substr(1).split("/");
var l = pe[pe.length - 1];
var str;
if (countryseq) {
str = countryseq + CVCertificateStore.encodeBase36(sequence);
} else {
str = "" + sequence;
str = "0000".substr(4 - (5 - str.length)).concat(str);
}
return new PublicKeyReference(l + str);
}
CVCertificateStore.prototype.insertCertificates = function(crypto, certlist, insertSelfSigned) {
var chrmap = [];
var unprocessed = [];
for (var i = 0; i < certlist.length; i++) {
var cvc = certlist[i];
var chr = cvc.getCHR().toString();
if (chr == cvc.getCAR().toString()) {
var result = cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
if (result) {
var path = "/" + cvc.getCHR().getHolder();
if (insertSelfSigned) {
this.storeCertificate(path, cvc, true);
}
} else {
GPSystem.trace("Self-signed certificate failed signature verification. " + cvc);
}
} else {
unprocessed.push(cvc);
chrmap[chr] = cvc;
}
}
certlist = unprocessed;
var unprocessed = [];
var capath = [];
for (var i = 0; i < certlist.length; i++) {
var cvc = certlist[i];
var car = cvc.getCAR();
var cacert = this.getCertificate("/" + car.getHolder(), car);
if (cacert != null) {
var dp = this.getDomainParameter("/" + car.getHolder(), car);
var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
if (result) {
var chr = cvc.getCHR();
var holder = chr.getHolder();
if (holder == car.getHolder()) {
this.storeCertificate("/" + holder, cvc, true);
} else {
var path = "/" + car.getHolder() + "/" + holder;
this.storeCertificate(path, cvc, true);
capath[holder] = path;
}
} else {
GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacert);
}
} else {
unprocessed.push(cvc);
}
}
certlist = unprocessed;
var unprocessed = [];
for (var i = 0; i < certlist.length; i++) {
var cvc = certlist[i];
var car = cvc.getCAR();
var path = capath[car.getHolder()];
if (path) {
var cacert = this.getCertificate(path, car);
var cacertcar = cacert.getCAR();
if (cacert != null) {
var dp = this.getDomainParameter(path, cacertcar);
var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
if (result) {
var chr = cvc.getCHR();
var holder = chr.getHolder();
this.storeCertificate(path + "/" + holder, cvc, true);
} else {
GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacvc);
}
} else {
GPSystem.trace("Could not find certificate " + car.toString());
unprocessed.push(cvc);
}
} else {
GPSystem.trace("Could not locate CA " + car.toString());
unprocessed.push(cvc);
}
}
return unprocessed;
}
CVCertificateStore.prototype.insertCertificate = function(crypto, cvc, cvcahint) {
var car = cvc.getCAR();
var path = "/" + car.getHolder();
var cacert = this.getCertificate(path, car);
if (cacert == null) {
var path = "/" + CVCertificateStore.nthElementOf(cvcahint, 0) + "/" + car.getHolder();
var cacert = this.getCertificate(path, car);
if (cacert == null) {
return false;
}
}
if (CVC.isECDSA(cacert.getPublicKeyOID())) {
var dp = this.getDomainParameter(path, car);
} else {
var dp = null;
}
var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
if (!result) {
GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacert);
return false;
}
var chr = cvc.getCHR();
var holder = chr.getHolder();
if (holder == car.getHolder()) {
this.storeCertificate("/" + holder, cvc, true);
} else {
this.storeCertificate(path + "/" + holder, cvc, true);
}
return true;
}
CVCertificateStore.prototype.insertCertificates2 = function(crypto, certlist, insertSelfSigned, cvcahint) {
var chrmap = [];
var unprocessed = [];
for (var i = 0; i < certlist.length; i++) {
var cvc = certlist[i];
var chr = cvc.getCHR().toString();
if (chr == cvc.getCAR().toString()) {
var result = cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
if (result) {
var path = "/" + cvc.getCHR().getHolder();
if (insertSelfSigned) {
this.storeCertificate(path, cvc, true);
}
} else {
GPSystem.trace("Self-signed certificate failed signature verification. " + cvc);
}
} else {
var state = { cvc: cvc, end: true, stored: false };
unprocessed.push(state);
if (typeof(chrmap[chr]) == "undefined") {
chrmap[chr] = state;
} else {
chrmap[cvc.getCAR().toString() + "/" + chr] = state;
}
}
}
certlist = unprocessed;
for (var i = 0; i < certlist.length; i++) {
var cvc = certlist[i].cvc;
var state = chrmap[cvc.getCAR().toString()];
if (typeof(state) != "undefined") {
state.end = false;
}
}
var unprocessed = [];
for (var i = 0; i < certlist.length; i++) {
var state = certlist[i];
if (state.end) {
var list = [];
var lastpathelement = state.cvc.getCHR().getHolder();
var path = "/" + lastpathelement;
var singlecert = true;
while(true) {
var pathelement = state.cvc.getCAR().getHolder();
if (pathelement != lastpathelement) {
path = "/" + pathelement + path;
}
lastpathelement = pathelement;
if (!state.stored) {
list.push(state);
state.stored = true;
}
state = chrmap[state.cvc.getCAR().toString()];
if (typeof(state) == "undefined") {
break;
}
singlecert = false;
}
if (singlecert && cvcahint) {
path = cvcahint;
} else {
}
for (var j = list.length - 1; j >= 0; j--) {
var cvc = list[j].cvc;
if (!this.insertCertificate(crypto, cvc, path)) {
unprocessed.push(cvc);
}
}
}
}
return unprocessed;
}
CVCertificateStore.prototype.loadConfig = function(path) {
var fn = this.mapPath(path + "/config.xml");
var cfgxml = null;
try {
var cfgxml = CVCertificateStore.loadXMLFile(fn);
}
catch(e) {
}
return cfgxml;
}
CVCertificateStore.prototype.saveConfig = function(path, cfg) {
if (arguments.length != 2) {
throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "path and cfg argument required");
}
var fn = this.mapPath(path);
var f = new java.io.File(fn);
if (!f.exists()) {
f.mkdirs();
}
var fn = this.mapPath(path + "/config.xml");
CVCertificateStore.saveXMLFile(fn, cfg);
}
CVCertificateStore.prototype.getDefaultConfig = function() {
var defaultCfg =
<CAConfig>
<sequence>
<current>0</current>
</sequence>
</CAConfig>;
return defaultCfg;
}
Documentation generated by
JSDoc on Tue Sep 3 22:29:38 2013