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 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