001package org.apache.commons.ssl.org.bouncycastle.asn1.x500.style;
002
003import java.io.IOException;
004import java.util.Enumeration;
005import java.util.Hashtable;
006import java.util.Vector;
007
008import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Encodable;
009import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Encoding;
010import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1ObjectIdentifier;
011import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Primitive;
012import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1String;
013import org.apache.commons.ssl.org.bouncycastle.asn1.DERUniversalString;
014import org.apache.commons.ssl.org.bouncycastle.asn1.x500.AttributeTypeAndValue;
015import org.apache.commons.ssl.org.bouncycastle.asn1.x500.RDN;
016import org.apache.commons.ssl.org.bouncycastle.asn1.x500.X500NameBuilder;
017import org.apache.commons.ssl.org.bouncycastle.asn1.x500.X500NameStyle;
018import org.bouncycastle.util.Strings;
019import org.bouncycastle.util.encoders.Hex;
020
021public class IETFUtils
022{
023    private static String unescape(String elt)
024    {
025        if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0))
026        {
027            return elt.trim();
028        }
029
030        char[] elts = elt.toCharArray();
031        boolean escaped = false;
032        boolean quoted = false;
033        StringBuffer buf = new StringBuffer(elt.length());
034        int start = 0;
035
036        // if it's an escaped hash string and not an actual encoding in string form
037        // we need to leave it escaped.
038        if (elts[0] == '\\')
039        {
040            if (elts[1] == '#')
041            {
042                start = 2;
043                buf.append("\\#");
044            }
045        }
046
047        boolean nonWhiteSpaceEncountered = false;
048        int     lastEscaped = 0;
049        char    hex1 = 0;
050
051        for (int i = start; i != elts.length; i++)
052        {
053            char c = elts[i];
054
055            if (c != ' ')
056            {
057                nonWhiteSpaceEncountered = true;
058            }
059
060            if (c == '"')
061            {
062                if (!escaped)
063                {
064                    quoted = !quoted;
065                }
066                else
067                {
068                    buf.append(c);
069                }
070                escaped = false;
071            }
072            else if (c == '\\' && !(escaped || quoted))
073            {
074                escaped = true;
075                lastEscaped = buf.length();
076            }
077            else
078            {
079                if (c == ' ' && !escaped && !nonWhiteSpaceEncountered)
080                {
081                    continue;
082                }
083                if (escaped && isHexDigit(c))
084                {
085                    if (hex1 != 0)
086                    {
087                        buf.append((char)(convertHex(hex1) * 16 + convertHex(c)));
088                        escaped = false;
089                        hex1 = 0;
090                        continue;
091                    }
092                    hex1 = c;
093                    continue;
094                }
095                buf.append(c);
096                escaped = false;
097            }
098        }
099
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
111    private static boolean isHexDigit(char c)
112    {
113        return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
114    }
115
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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}