      1 /**
      2  *  ---------
      3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
      4  * |#       #|
      5  * |#       #|  Copyright (c) 1999-2016 CardContact Software & System Consulting
      6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
      7  *  ---------
      8  *
      9  *  This file is part of OpenSCDP.
     10  *
     11  *  OpenSCDP is free software; you can redistribute it and/or modify
     12  *  it under the terms of the GNU General Public License version 2 as
     13  *  published by the Free Software Foundation.
     14  *
     15  *  OpenSCDP is distributed in the hope that it will be useful,
     16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  *  GNU General Public License for more details.
     19  *
     20  *  You should have received a copy of the GNU General Public License
     21  *  along with OpenSCDP; if not, write to the Free Software
     22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     23  *
     24  * @fileoverview Encoder for Distinguished Names using input data and a mask
     25  */
     26
     27 var PKIXCommon			= require("scsh/x509/PKIXCommon").PKIXCommon;
     28
     29
     30
     31 /**
     32  * An instance of this class allow generating a Distinguished Name from components
     33  * defined in the mask given as constructor. The mask may contain placeholders in the
     34  * form ${major.minor} that are resolved against other DNs and objects set with setDN() or
     35  * setMap().
     36  *
     37  * @constructor
     38  * @param {String} mask the mask for generating the DN
     39  */
     40 function DNEncoder(mask) {
     41 	this.dnmask = PKIXCommon.parseDN(mask);
     42 	this.dnmap = {};
     43 	this.objmap = {};
     44 }
     45
     46 exports.DNEncoder = DNEncoder;
     47
     48
     49
     50 /**
     51  * Register a DN under the given name. Elements in the DN can be referenced by ${major.minor}
     52  * with major set to the name given as argument and minor being the component in the given DN.
     53  *
     54  * @param {String} name the name for the major key
     55  * @param {String/Object} dn the Distinguished Name given as String or in the format returned by PKIXCommon.parseDN.
     56  */
     57 DNEncoder.prototype.setDN = function(name, dn) {
     58 	if (typeof(dn) == "string") {
     59 		dn = PKIXCommon.parseDN(dn);
     60 	}
     61 	this.dnmap[name] = dn;
     62 }
     63
     64
     65
     66 /**
     67  * Register an object and it properties the given name. Elements in the object can be referenced by ${major.minor}
     68  * with major set to the name given as argument and minor being the member of the given object.
     69  *
     70  * @param {String} name the name for the major key
     71  * @param {Object} map the map object
     72  */
     73 DNEncoder.prototype.setMap = function(name, map) {
     74 	this.objmap[name] = map;
     75 }
     76
     77
     78
     79 /**
     80  * Replace key with value from DN
     81  * @private
     82  * @param {Object} o the DN object
     83  * @param {String} major the major key value
     84  * @param {String} minor the minor key value
     85  * @type String
     86  * @return the replacement value
     87  */
     88 DNEncoder.prototype.replaceFromDN = function(o, major, minor) {
     89 	var v = PKIXCommon.findRDN(o, minor.toUpperCase());
     90 	if (!v) {
     91 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Element " + minor + " not found in " + major);
     92 	}
     93 	return v;
     94 }
     95
     96
     97
     98 /**
     99  * Replace key with value from object
    100  * @private
    101  * @param {Object} o the object
    102  * @param {String} major the major key value
    103  * @param {String} minor the minor key value
    104  * @type String
    105  * @return the replacement value
    106  */
    107 DNEncoder.prototype.replaceFromObject = function(o, major, minor) {
    108 	if (typeof(o[minor]) == "undefined") {
    109 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Element " + minor + " not found in " + major);
    110 	}
    111 	return o[minor];
    112 }
    113
    114
    115
    116 /**
    117  * Replace key
    118  * @private
    119  * @param {String} key the in the format ${major.minor}
    120  * @type String
    121  * @return the replacement value
    122  */
    123 DNEncoder.prototype.replace = function(key) {
    124 	var r = /\$\{(\w+)\.(\w+)\}/.exec(key);
    125 	if (r == null) {
    126 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "The expression " + key + " is not valid");
    127 	}
    128
    129 	var major = r[1];
    130 	var minor = r[2];
    131
    132 	var o = this.dnmap[major];
    133 	if (typeof(o) != "undefined") {
    134 		return this.replaceFromDN(o, major, minor);
    135 	} else {
    136 		var o = this.objmap[major];
    137
    138 		if (typeof(o) != "undefined") {
    139 			return this.replaceFromObject(o, major, minor);
    140 		} else {
    141 			throw new GPError(module.id, GPError.INVALID_DATA, 0, "Unknown major key " + major);
    142 		}
    143 	}
    144 }
    145
    146
    147
    148 /**
    149  * Check key for validity
    150  * @private
    151  * @param {String} key the in the format ${major.minor}
    152  */
    153 DNEncoder.prototype.check = function(key) {
    154 	var r = /\$\{(\w+)\.(\w+)\}/.exec(key);
    155 	if (r == null) {
    156 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "The expression " + key + " is not valid");
    157 	}
    158
    159 	var major = r[1];
    160 	var minor = r[2];
    161
    162 	var o = this.dnmap[major];
    163 	if (typeof(o) != "undefined") {
    164 		if (PKIXCommon.validRDN.indexOf(minor.toUpperCase()) == -1) {
    165 			throw new GPError(module.id, GPError.INVALID_DATA, 0, minor + " is not a supported RDN (Valid RDNs are " + PKIXCommon.validRDN + ")");
    166 		}
    167 	} else {
    168 		var o = this.objmap[major];
    169
    170 		if (typeof(o) != "undefined") {
    171 			if ((typeof(o.any) == "undefined") && (typeof(o[minor]) == "undefined")) {
    172 				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Unknown minor key " + minor);
    173 			}
    174 		} else {
    175 			throw new GPError(module.id, GPError.INVALID_DATA, 0, "Unknown major key " + major);
    176 		}
    177 	}
    178 	return "";
    179 }
    180
    181
    182
    183 /**
    184  * Iterate replacement strings
    185  * @private
    186  *
    187  * @param {String} func the name of the replacement function
    188  */
    189 DNEncoder.prototype.iterate = function(func) {
    190 	var dn = [];
    191
    192 	for (var i = 0; i < this.dnmask.length; i++) {
    193 		var rdn = this.dnmask[i];
    194 		var nrdn = {};
    195 		for (var k in rdn) {
    196 			var inp = rdn[k];
    197 //			print(k + "=" + inp);
    198 			var fixed = inp.split(/\$\{.+?\}/);
    199 //			print("Fixed[" + fixed.length + "]: " + fixed);
    200 			var keys = inp.match(/\$\{.+?\}/g);
    201 			if (keys == null) {
    202 				keys = [];
    203 			}
    204 //			print("Keys[" + keys.length + "] : " + keys);
    205
    206 			var res = "";
    207 			for (var c = 0; c < (fixed.length + keys.length); c++) {
    208 				if ((c & 1) == 0) {
    209 					res += fixed[c >> 1];
    210 				} else {
    211 					res += this[func](keys[c >> 1]);
    212 				}
    213 			}
    214 			nrdn[k] = res;
    215 		}
    216 		dn.push(nrdn);
    217 	}
    218 	return dn;
    219 }
    220
    221
    222
    223 /**
    224  * Encode DN based on mask and previously set objects
    225  *
    226  * @type Object
    227  * @return DN in the format as returned by PKIXCommon.parseDN()
    228  */
    229 DNEncoder.prototype.encode = function() {
    230 	return this.iterate("replace");
    231 }
    232
    233
    234
    235 /**
    236  * Validate DN mask against previously set reference objects
    237  *
    238  * DN objects can be set with setDN(name, {}). The minor key is validated against the list of
    239  * valid RDN as defined in PKIXCommon.validRDN.
    240  *
    241  * Other objects can be set with setMap(), but only the key names in the map are relevant. If the
    242  * object contains the property "any", then any minor keys are acceptable.
    243  *
    244  * The method will throw an exception if a key is invalid.
    245  */
    246 DNEncoder.prototype.validate = function() {
    247 	return this.iterate("check");
    248 }
    249
    250
    251
    252 DNEncoder.test = function() {
    253 	var d = new DNEncoder("c=${issuer.c},o=${issuer.o},cn=ABC ${issuer.o}${servicerequest.commonName} DEF");
    254
    255 	d.setDN("issuer", "c=UT,o=ACME,cn=ACME Root CA");
    256
    257 	var sr = {
    258 		commonName: "Hello"
    259 	};
    260
    261 	d.setMap("servicerequest", sr);
    262
    263 	d.validate();
    264 	print(PKIXCommon.dnToString(d.encode()));
    265 }
    266