1 /** 2 * X.509 Distinguished Name 3 * 4 * Copyright: 5 * (C) 1999-2010 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.asn1.x509_dn; 12 13 import botan.constants; 14 public import botan.asn1.asn1_obj; 15 public import botan.asn1.asn1_oid; 16 public import botan.asn1.asn1_str; 17 public import botan.asn1.x509_dn; 18 import botan.asn1.der_enc; 19 import botan.asn1.ber_dec; 20 import botan.utils.parsing; 21 import botan.utils.types; 22 import memutils.dictionarylist; 23 import botan.asn1.oids; 24 import memutils.hashmap; 25 import std.array : Appender; 26 27 version(X509): 28 29 alias X509DN = RefCounted!X509DNImpl; 30 31 /** 32 * Distinguished Name 33 */ 34 final class X509DNImpl : ASN1Object 35 { 36 public: 37 /* 38 * DER encode a DistinguishedName 39 */ 40 override void encodeInto(ref DEREncoder der) const 41 { 42 auto dn_info = getAttributes(); 43 44 der.startCons(ASN1Tag.SEQUENCE); 45 46 if (!m_dn_bits.empty) 47 der.rawBytes(m_dn_bits); 48 else 49 { 50 doAva(der, dn_info, ASN1Tag.PRINTABLE_STRING, "X520.Country"); 51 doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.State"); 52 doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.Locality"); 53 doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.Organization"); 54 doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.OrganizationalUnit"); 55 doAva(der, dn_info, ASN1Tag.DIRECTORY_STRING, "X520.CommonName"); 56 doAva(der, dn_info, ASN1Tag.PRINTABLE_STRING, "X520.SerialNumber"); 57 } 58 59 der.endCons(); 60 } 61 62 /* 63 * Decode a BER encoded DistinguishedName 64 */ 65 override void decodeFrom(ref BERDecoder source) 66 { 67 Vector!ubyte bits; 68 69 source.startCons(ASN1Tag.SEQUENCE) 70 .rawBytes(bits) 71 .endCons(); 72 73 BERDecoder sequence = BERDecoder(bits); 74 75 while (sequence.moreItems()) 76 { 77 BERDecoder rdn = sequence.startCons(ASN1Tag.SET); 78 79 while (rdn.moreItems()) 80 { 81 OID oid = OID(); 82 ASN1String str = ASN1String(""); 83 84 rdn.startCons(ASN1Tag.SEQUENCE) 85 .decode(oid) 86 .decode(str) 87 .verifyEnd() 88 .endCons(); 89 90 addAttribute(oid, str.value()); 91 } 92 } 93 94 m_dn_bits = bits.dup; 95 } 96 97 /* 98 * Get the attributes of this X509DN 99 */ 100 DictionaryListRef!(OID, string) getAttributes() const 101 { 102 DictionaryListRef!(OID, string) retval; 103 foreach (const ref OID oid, const ref ASN1String asn1_str; m_dn_info) 104 retval.insert(oid, asn1_str.value()); 105 return retval; 106 } 107 108 /* 109 * Get a single attribute type 110 */ 111 Vector!string getAttribute(in string attr) const 112 { 113 const OID oid = OIDS.lookup(derefInfoField(attr)); 114 return getAttribute(oid); 115 } 116 117 private Vector!string getAttribute(in OID oid) const 118 { 119 auto range = m_dn_info.getValuesAt(oid); 120 121 Vector!string values; 122 foreach (const ref ASN1String asn1_string; range[]) 123 values.pushBack(asn1_string.value()); 124 return values.move; 125 } 126 127 /* 128 * Get the contents of this X.500 Name 129 */ 130 DictionaryListRef!(string, string) contents() const 131 { 132 DictionaryListRef!(string, string) retval; 133 foreach (const ref OID key, const ref ASN1String value; m_dn_info) 134 retval.insert(OIDS.lookup(key), value.value()); 135 return retval; 136 } 137 138 139 /* 140 * Add an attribute to a X509DN 141 */ 142 void addAttribute(in string type, in string str) 143 { 144 logTrace("Add X509DN Attribute Type: ", type, ", Value: ", str); 145 OID oid = OIDS.lookup(type); 146 addAttribute(oid, str); 147 } 148 149 /* 150 * Add an attribute to a X509DN 151 */ 152 void addAttribute(const ref OID oid, in string str) 153 { 154 if (str == "") 155 return; 156 157 bool exists; 158 void search_func(in ASN1String name) { 159 //logTrace(name.value()); 160 if (name.value() == str) { 161 exists = true; 162 } 163 } 164 m_dn_info.getValuesAt(oid, &search_func); 165 if (!exists) { 166 auto asn1_str = ASN1String(str.idup); 167 m_dn_info.insert(oid, asn1_str); 168 m_dn_bits.clear(); 169 } 170 } 171 172 /* 173 * Deref aliases in a subject/issuer info request 174 */ 175 static string derefInfoField(in string info) 176 { 177 if (info == "Name" || info == "CommonName") return "X520.CommonName"; 178 if (info == "SerialNumber") return "X520.SerialNumber"; 179 if (info == "Country") return "X520.Country"; 180 if (info == "Organization") return "X520.Organization"; 181 if (info == "Organizational Unit" || info == "OrgUnit") 182 return "X520.OrganizationalUnit"; 183 if (info == "Locality") return "X520.Locality"; 184 if (info == "State" || info == "Province") return "X520.State"; 185 if (info == "Email") return "RFC822"; 186 return info; 187 } 188 189 /* 190 * Return the BER encoded data, if any 191 */ 192 ref const(Vector!ubyte) getBits() const 193 { 194 return m_dn_bits; 195 } 196 197 /* 198 * Create an empty X509DN 199 */ 200 this() 201 { 202 } 203 204 /* 205 * Create an X509DN 206 */ 207 this(in DictionaryListRef!(OID, string) args) 208 { 209 foreach (const ref OID oid, const ref string val; args) 210 addAttribute(oid, val); 211 } 212 213 /* 214 * Create an X509DN 215 */ 216 this(in DictionaryListRef!(string, string) args) 217 { 218 foreach (const ref string key, const ref string val; args) { 219 OID oid = OIDS.lookup(key); 220 addAttribute(oid, val); 221 } 222 } 223 224 /* 225 * Compare two X509DNs for equality 226 */ 227 bool opEquals(in X509DNImpl dn2) const 228 { 229 size_t i; 230 foreach (const ref OID oid, const ref ASN1String str; m_dn_info) { 231 i++; 232 bool found; 233 size_t j; 234 foreach (const ref OID oid2, const ref ASN1String val; dn2.m_dn_info) { 235 j++; 236 if (j != i) continue; 237 if (x500NameCmp(val.value(), str.value())) 238 found = true; 239 240 break; 241 } 242 if (!found) return false; 243 } 244 return true; 245 246 } 247 248 /* 249 * Compare two X509DNs for inequality 250 */ 251 int opCmp(const X509DN dn2) const 252 { 253 if (this == dn2) 254 return 0; 255 else if (this.isSmallerThan(dn2)) 256 return -1; 257 else 258 return 1; 259 } 260 261 /* 262 * Induce an arbitrary ordering on DNs 263 */ 264 bool isSmallerThan(const X509DN dn2) const 265 { 266 const auto attr1 = getAttributes(); 267 const auto attr2 = dn2.getAttributes(); 268 269 if (attr1.length < attr2.length) return true; 270 if (attr1.length > attr2.length) return false; 271 272 foreach (const ref OID key, const ref string value; attr1) { 273 const auto value2 = attr2.get(key); 274 if (value2 == null) return false; 275 if (value > value2) return false; 276 if (value < value2) return true; 277 } 278 return false; 279 } 280 281 override string toString() const 282 { 283 return toVector()[].idup; 284 } 285 286 Vector!char toVector() const 287 { 288 Vector!char output; 289 DictionaryListRef!(string, string) contents = contents(); 290 291 foreach(const ref string key, const ref string val; contents) 292 { 293 output ~= toShortForm(key); 294 output ~= "="; 295 output ~= val; 296 output ~= ' '; 297 } 298 return output.move(); 299 } 300 @property X509DN dup() const { 301 return X509DN(getAttributes()); 302 } 303 304 private: 305 DictionaryList!(OID, ASN1String) m_dn_info; 306 Vector!ubyte m_dn_bits; 307 } 308 309 /* 310 * DER encode a RelativeDistinguishedName 311 */ 312 void doAva(ref DEREncoder encoder, 313 in DictionaryListRef!(OID, string) dn_info, 314 ASN1Tag string_type, in string oid_str, 315 bool must_exist = false) 316 { 317 const OID oid = OIDS.lookup(oid_str); 318 const bool exists = (dn_info.get(oid) != null); 319 320 if (!exists && must_exist) 321 throw new EncodingError("X509DN: No entry for " ~ oid_str); 322 if (!exists) return; 323 324 dn_info.getValuesAt(oid, (in string val) { 325 encoder.startCons(ASN1Tag.SET) 326 .startCons(ASN1Tag.SEQUENCE) 327 .encode(oid) 328 .encode(ASN1String(val, string_type)) 329 .endCons() 330 .endCons(); 331 332 }); 333 } 334 335 string toShortForm(in string long_id) 336 { 337 if (long_id == "X520.CommonName") 338 return "CN"; 339 340 if (long_id == "X520.Organization") 341 return "O"; 342 343 if (long_id == "X520.OrganizationalUnit") 344 return "OU"; 345 346 return long_id; 347 }