• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.bouncycastle.asn1.x500.style;
2 
3 import java.io.IOException;
4 import java.util.Enumeration;
5 import java.util.Hashtable;
6 import java.util.Vector;
7 
8 import org.bouncycastle.asn1.ASN1Encodable;
9 import org.bouncycastle.asn1.ASN1Encoding;
10 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
11 import org.bouncycastle.asn1.ASN1Primitive;
12 import org.bouncycastle.asn1.ASN1String;
13 import org.bouncycastle.asn1.DERUniversalString;
14 import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
15 import org.bouncycastle.asn1.x500.RDN;
16 import org.bouncycastle.asn1.x500.X500NameBuilder;
17 import org.bouncycastle.asn1.x500.X500NameStyle;
18 import org.bouncycastle.util.Strings;
19 import org.bouncycastle.util.encoders.Hex;
20 
21 public class IETFUtils
22 {
unescape(String elt)23     private static String unescape(String elt)
24     {
25         if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0))
26         {
27             return elt.trim();
28         }
29 
30         char[] elts = elt.toCharArray();
31         boolean escaped = false;
32         boolean quoted = false;
33         StringBuffer buf = new StringBuffer(elt.length());
34         int start = 0;
35 
36         // if it's an escaped hash string and not an actual encoding in string form
37         // we need to leave it escaped.
38         if (elts[0] == '\\')
39         {
40             if (elts[1] == '#')
41             {
42                 start = 2;
43                 buf.append("\\#");
44             }
45         }
46 
47         boolean nonWhiteSpaceEncountered = false;
48         int     lastEscaped = 0;
49         char    hex1 = 0;
50 
51         for (int i = start; i != elts.length; i++)
52         {
53             char c = elts[i];
54 
55             if (c != ' ')
56             {
57                 nonWhiteSpaceEncountered = true;
58             }
59 
60             if (c == '"')
61             {
62                 if (!escaped)
63                 {
64                     quoted = !quoted;
65                 }
66                 else
67                 {
68                     buf.append(c);
69                 }
70                 escaped = false;
71             }
72             else if (c == '\\' && !(escaped || quoted))
73             {
74                 escaped = true;
75                 lastEscaped = buf.length();
76             }
77             else
78             {
79                 if (c == ' ' && !escaped && !nonWhiteSpaceEncountered)
80                 {
81                     continue;
82                 }
83                 if (escaped && isHexDigit(c))
84                 {
85                     if (hex1 != 0)
86                     {
87                         buf.append((char)(convertHex(hex1) * 16 + convertHex(c)));
88                         escaped = false;
89                         hex1 = 0;
90                         continue;
91                     }
92                     hex1 = c;
93                     continue;
94                 }
95                 buf.append(c);
96                 escaped = false;
97             }
98         }
99 
100         if (buf.length() > 0)
101         {
102             while (buf.charAt(buf.length() - 1) == ' ' && lastEscaped != (buf.length() - 1))
103             {
104                 buf.setLength(buf.length() - 1);
105             }
106         }
107 
108         return buf.toString();
109     }
110 
isHexDigit(char c)111     private static boolean isHexDigit(char c)
112     {
113         return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
114     }
115 
convertHex(char c)116     private static int convertHex(char c)
117     {
118         if ('0' <= c && c <= '9')
119         {
120             return c - '0';
121         }
122         if ('a' <= c && c <= 'f')
123         {
124             return c - 'a' + 10;
125         }
126         return c - 'A' + 10;
127     }
128 
rDNsFromString(String name, X500NameStyle x500Style)129     public static RDN[] rDNsFromString(String name, X500NameStyle x500Style)
130     {
131         X500NameTokenizer nTok = new X500NameTokenizer(name);
132         X500NameBuilder builder = new X500NameBuilder(x500Style);
133 
134         while (nTok.hasMoreTokens())
135         {
136             String  token = nTok.nextToken();
137 
138             if (token.indexOf('+') > 0)
139             {
140                 X500NameTokenizer   pTok = new X500NameTokenizer(token, '+');
141                 X500NameTokenizer   vTok = new X500NameTokenizer(pTok.nextToken(), '=');
142 
143                 String              attr = vTok.nextToken();
144 
145                 if (!vTok.hasMoreTokens())
146                 {
147                     throw new IllegalArgumentException("badly formatted directory string");
148                 }
149 
150                 String               value = vTok.nextToken();
151                 ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim());
152 
153                 if (pTok.hasMoreTokens())
154                 {
155                     Vector oids = new Vector();
156                     Vector values = new Vector();
157 
158                     oids.addElement(oid);
159                     values.addElement(unescape(value));
160 
161                     while (pTok.hasMoreTokens())
162                     {
163                         vTok = new X500NameTokenizer(pTok.nextToken(), '=');
164 
165                         attr = vTok.nextToken();
166 
167                         if (!vTok.hasMoreTokens())
168                         {
169                             throw new IllegalArgumentException("badly formatted directory string");
170                         }
171 
172                         value = vTok.nextToken();
173                         oid = x500Style.attrNameToOID(attr.trim());
174 
175 
176                         oids.addElement(oid);
177                         values.addElement(unescape(value));
178                     }
179 
180                     builder.addMultiValuedRDN(toOIDArray(oids), toValueArray(values));
181                 }
182                 else
183                 {
184                     builder.addRDN(oid, unescape(value));
185                 }
186             }
187             else
188             {
189                 X500NameTokenizer   vTok = new X500NameTokenizer(token, '=');
190 
191                 String              attr = vTok.nextToken();
192 
193                 if (!vTok.hasMoreTokens())
194                 {
195                     throw new IllegalArgumentException("badly formatted directory string");
196                 }
197 
198                 String               value = vTok.nextToken();
199                 ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim());
200 
201                 builder.addRDN(oid, unescape(value));
202             }
203         }
204 
205         return builder.build().getRDNs();
206     }
207 
toValueArray(Vector values)208     private static String[] toValueArray(Vector values)
209     {
210         String[] tmp = new String[values.size()];
211 
212         for (int i = 0; i != tmp.length; i++)
213         {
214             tmp[i] = (String)values.elementAt(i);
215         }
216 
217         return tmp;
218     }
219 
toOIDArray(Vector oids)220     private static ASN1ObjectIdentifier[] toOIDArray(Vector oids)
221     {
222         ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()];
223 
224         for (int i = 0; i != tmp.length; i++)
225         {
226             tmp[i] = (ASN1ObjectIdentifier)oids.elementAt(i);
227         }
228 
229         return tmp;
230     }
231 
findAttrNamesForOID( ASN1ObjectIdentifier oid, Hashtable lookup)232     public static String[] findAttrNamesForOID(
233         ASN1ObjectIdentifier oid,
234         Hashtable            lookup)
235     {
236         int count = 0;
237         for (Enumeration en = lookup.elements(); en.hasMoreElements();)
238         {
239             if (oid.equals(en.nextElement()))
240             {
241                 count++;
242             }
243         }
244 
245         String[] aliases = new String[count];
246         count = 0;
247 
248         for (Enumeration en = lookup.keys(); en.hasMoreElements();)
249         {
250             String key = (String)en.nextElement();
251             if (oid.equals(lookup.get(key)))
252             {
253                 aliases[count++] = key;
254             }
255         }
256 
257         return aliases;
258     }
259 
decodeAttrName( String name, Hashtable lookUp)260     public static ASN1ObjectIdentifier decodeAttrName(
261         String      name,
262         Hashtable   lookUp)
263     {
264         if (Strings.toUpperCase(name).startsWith("OID."))
265         {
266             return new ASN1ObjectIdentifier(name.substring(4));
267         }
268         else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
269         {
270             return new ASN1ObjectIdentifier(name);
271         }
272 
273         ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name));
274         if (oid == null)
275         {
276             throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
277         }
278 
279         return oid;
280     }
281 
valueFromHexString( String str, int off)282     public static ASN1Encodable valueFromHexString(
283         String  str,
284         int     off)
285         throws IOException
286     {
287         byte[] data = new byte[(str.length() - off) / 2];
288         for (int index = 0; index != data.length; index++)
289         {
290             char left = str.charAt((index * 2) + off);
291             char right = str.charAt((index * 2) + off + 1);
292 
293             data[index] = (byte)((convertHex(left) << 4) | convertHex(right));
294         }
295 
296         return ASN1Primitive.fromByteArray(data);
297     }
298 
appendRDN( StringBuffer buf, RDN rdn, Hashtable oidSymbols)299     public static void appendRDN(
300         StringBuffer          buf,
301         RDN                   rdn,
302         Hashtable             oidSymbols)
303     {
304         if (rdn.isMultiValued())
305         {
306             AttributeTypeAndValue[] atv = rdn.getTypesAndValues();
307             boolean firstAtv = true;
308 
309             for (int j = 0; j != atv.length; j++)
310             {
311                 if (firstAtv)
312                 {
313                     firstAtv = false;
314                 }
315                 else
316                 {
317                     buf.append('+');
318                 }
319 
320                 IETFUtils.appendTypeAndValue(buf, atv[j], oidSymbols);
321             }
322         }
323         else
324         {
325             if (rdn.getFirst() != null)
326             {
327                 IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols);
328             }
329         }
330     }
331 
appendTypeAndValue( StringBuffer buf, AttributeTypeAndValue typeAndValue, Hashtable oidSymbols)332     public static void appendTypeAndValue(
333         StringBuffer          buf,
334         AttributeTypeAndValue typeAndValue,
335         Hashtable             oidSymbols)
336     {
337         String  sym = (String)oidSymbols.get(typeAndValue.getType());
338 
339         if (sym != null)
340         {
341             buf.append(sym);
342         }
343         else
344         {
345             buf.append(typeAndValue.getType().getId());
346         }
347 
348         buf.append('=');
349 
350         buf.append(valueToString(typeAndValue.getValue()));
351     }
352 
valueToString(ASN1Encodable value)353     public static String valueToString(ASN1Encodable value)
354     {
355         StringBuffer vBuf = new StringBuffer();
356 
357         if (value instanceof ASN1String && !(value instanceof DERUniversalString))
358         {
359             String v = ((ASN1String)value).getString();
360             if (v.length() > 0 && v.charAt(0) == '#')
361             {
362                 vBuf.append("\\" + v);
363             }
364             else
365             {
366                 vBuf.append(v);
367             }
368         }
369         else
370         {
371             try
372             {
373                 vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER))));
374             }
375             catch (IOException e)
376             {
377                 throw new IllegalArgumentException("Other value has no encoded form");
378             }
379         }
380 
381         int     end = vBuf.length();
382         int     index = 0;
383 
384         if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#')
385         {
386             index += 2;
387         }
388 
389         while (index != end)
390         {
391             if ((vBuf.charAt(index) == ',')
392                || (vBuf.charAt(index) == '"')
393                || (vBuf.charAt(index) == '\\')
394                || (vBuf.charAt(index) == '+')
395                || (vBuf.charAt(index) == '=')
396                || (vBuf.charAt(index) == '<')
397                || (vBuf.charAt(index) == '>')
398                || (vBuf.charAt(index) == ';'))
399             {
400                 vBuf.insert(index, "\\");
401                 index++;
402                 end++;
403             }
404 
405             index++;
406         }
407 
408         int start = 0;
409         if (vBuf.length() > 0)
410         {
411             while (vBuf.length() > start && vBuf.charAt(start) == ' ')
412             {
413                 vBuf.insert(start, "\\");
414                 start += 2;
415             }
416         }
417 
418         int endBuf = vBuf.length() - 1;
419 
420         while (endBuf >= 0 && vBuf.charAt(endBuf) == ' ')
421         {
422             vBuf.insert(endBuf, '\\');
423             endBuf--;
424         }
425 
426         return vBuf.toString();
427     }
428 
bytesToString( byte[] data)429     private static String bytesToString(
430         byte[] data)
431     {
432         char[]  cs = new char[data.length];
433 
434         for (int i = 0; i != cs.length; i++)
435         {
436             cs[i] = (char)(data[i] & 0xff);
437         }
438 
439         return new String(cs);
440     }
441 
canonicalize(String s)442     public static String canonicalize(String s)
443     {
444         String value = Strings.toLowerCase(s);
445 
446         if (value.length() > 0 && value.charAt(0) == '#')
447         {
448             ASN1Primitive obj = decodeObject(value);
449 
450             if (obj instanceof ASN1String)
451             {
452                 value = Strings.toLowerCase(((ASN1String)obj).getString());
453             }
454         }
455 
456         if (value.length() > 1)
457         {
458             int start = 0;
459             while (start + 1 < value.length() && value.charAt(start) == '\\' && value.charAt(start + 1) == ' ')
460             {
461                 start += 2;
462             }
463 
464             int end = value.length() - 1;
465             while (end - 1 > 0 && value.charAt(end - 1) == '\\' && value.charAt(end) == ' ')
466             {
467                 end -= 2;
468             }
469 
470             if (start > 0 || end < value.length() - 1)
471             {
472                 value = value.substring(start, end + 1);
473             }
474         }
475 
476         value = stripInternalSpaces(value);
477 
478         return value;
479     }
480 
decodeObject(String oValue)481     private static ASN1Primitive decodeObject(String oValue)
482     {
483         try
484         {
485             return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1)));
486         }
487         catch (IOException e)
488         {
489             throw new IllegalStateException("unknown encoding in name: " + e);
490         }
491     }
492 
stripInternalSpaces( String str)493     public static String stripInternalSpaces(
494         String str)
495     {
496         StringBuffer res = new StringBuffer();
497 
498         if (str.length() != 0)
499         {
500             char c1 = str.charAt(0);
501 
502             res.append(c1);
503 
504             for (int k = 1; k < str.length(); k++)
505             {
506                 char c2 = str.charAt(k);
507                 if (!(c1 == ' ' && c2 == ' '))
508                 {
509                     res.append(c2);
510                 }
511                 c1 = c2;
512             }
513         }
514 
515         return res.toString();
516     }
517 
rDNAreEqual(RDN rdn1, RDN rdn2)518     public static boolean rDNAreEqual(RDN rdn1, RDN rdn2)
519     {
520         if (rdn1.isMultiValued())
521         {
522             if (rdn2.isMultiValued())
523             {
524                 AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues();
525                 AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues();
526 
527                 if (atvs1.length != atvs2.length)
528                 {
529                     return false;
530                 }
531 
532                 for (int i = 0; i != atvs1.length; i++)
533                 {
534                     if (!atvAreEqual(atvs1[i], atvs2[i]))
535                     {
536                         return false;
537                     }
538                 }
539             }
540             else
541             {
542                 return false;
543             }
544         }
545         else
546         {
547             if (!rdn2.isMultiValued())
548             {
549                 return atvAreEqual(rdn1.getFirst(), rdn2.getFirst());
550             }
551             else
552             {
553                 return false;
554             }
555         }
556 
557         return true;
558     }
559 
atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2)560     private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2)
561     {
562         if (atv1 == atv2)
563         {
564             return true;
565         }
566 
567         if (atv1 == null)
568         {
569             return false;
570         }
571 
572         if (atv2 == null)
573         {
574             return false;
575         }
576 
577         ASN1ObjectIdentifier o1 = atv1.getType();
578         ASN1ObjectIdentifier o2 = atv2.getType();
579 
580         if (!o1.equals(o2))
581         {
582             return false;
583         }
584 
585         String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue()));
586         String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue()));
587 
588         if (!v1.equals(v2))
589         {
590             return false;
591         }
592 
593         return true;
594     }
595 }
596