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