IETFUtils.java revision e1142c149e244797ce73b0e7fad40816e447a817
1package org.bouncycastle.asn1.x500.style;
2
3import java.io.IOException;
4import java.util.Hashtable;
5import java.util.Vector;
6
7import org.bouncycastle.asn1.ASN1Encodable;
8import org.bouncycastle.asn1.ASN1Encoding;
9import org.bouncycastle.asn1.ASN1ObjectIdentifier;
10import org.bouncycastle.asn1.ASN1Primitive;
11import org.bouncycastle.asn1.ASN1String;
12import org.bouncycastle.asn1.DERUniversalString;
13import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
14import org.bouncycastle.asn1.x500.RDN;
15import org.bouncycastle.asn1.x500.X500NameBuilder;
16import org.bouncycastle.asn1.x500.X500NameStyle;
17import org.bouncycastle.util.Strings;
18import org.bouncycastle.util.encoders.Hex;
19
20public class IETFUtils
21{
22    public static RDN[] rDNsFromString(String name, X500NameStyle x500Style)
23    {
24        X500NameTokenizer nTok = new X500NameTokenizer(name);
25        X500NameBuilder builder = new X500NameBuilder(x500Style);
26
27        while (nTok.hasMoreTokens())
28        {
29            String  token = nTok.nextToken();
30            int     index = token.indexOf('=');
31
32            if (index == -1)
33            {
34                throw new IllegalArgumentException("badly formated directory string");
35            }
36
37            String               attr = token.substring(0, index);
38            String               value = token.substring(index + 1);
39            ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr);
40
41            if (value.indexOf('+') > 0)
42            {
43                X500NameTokenizer   vTok = new X500NameTokenizer(value, '+');
44                String  v = vTok.nextToken();
45
46                Vector oids = new Vector();
47                Vector values = new Vector();
48
49                oids.addElement(oid);
50                values.addElement(v);
51
52                while (vTok.hasMoreTokens())
53                {
54                    String  sv = vTok.nextToken();
55                    int     ndx = sv.indexOf('=');
56
57                    String  nm = sv.substring(0, ndx);
58                    String  vl = sv.substring(ndx + 1);
59
60                    oids.addElement(x500Style.attrNameToOID(nm));
61                    values.addElement(vl);
62                }
63
64                builder.addMultiValuedRDN(toOIDArray(oids), toValueArray(values));
65            }
66            else
67            {
68                builder.addRDN(oid, value);
69            }
70        }
71
72        return builder.build().getRDNs();
73    }
74
75    private static String[] toValueArray(Vector values)
76    {
77        String[] tmp = new String[values.size()];
78
79        for (int i = 0; i != tmp.length; i++)
80        {
81            tmp[i] = (String)values.elementAt(i);
82        }
83
84        return tmp;
85    }
86
87    private static ASN1ObjectIdentifier[] toOIDArray(Vector oids)
88    {
89        ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()];
90
91        for (int i = 0; i != tmp.length; i++)
92        {
93            tmp[i] = (ASN1ObjectIdentifier)oids.elementAt(i);
94        }
95
96        return tmp;
97    }
98
99    public static ASN1ObjectIdentifier decodeAttrName(
100        String      name,
101        Hashtable   lookUp)
102    {
103        if (Strings.toUpperCase(name).startsWith("OID."))
104        {
105            return new ASN1ObjectIdentifier(name.substring(4));
106        }
107        else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
108        {
109            return new ASN1ObjectIdentifier(name);
110        }
111
112        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name));
113        if (oid == null)
114        {
115            throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
116        }
117
118        return oid;
119    }
120
121    public static ASN1Encodable valueFromHexString(
122        String  str,
123        int     off)
124        throws IOException
125    {
126        str = Strings.toLowerCase(str);
127        byte[] data = new byte[(str.length() - off) / 2];
128        for (int index = 0; index != data.length; index++)
129        {
130            char left = str.charAt((index * 2) + off);
131            char right = str.charAt((index * 2) + off + 1);
132
133            if (left < 'a')
134            {
135                data[index] = (byte)((left - '0') << 4);
136            }
137            else
138            {
139                data[index] = (byte)((left - 'a' + 10) << 4);
140            }
141            if (right < 'a')
142            {
143                data[index] |= (byte)(right - '0');
144            }
145            else
146            {
147                data[index] |= (byte)(right - 'a' + 10);
148            }
149        }
150
151        return ASN1Primitive.fromByteArray(data);
152    }
153
154    public static void appendRDN(
155        StringBuffer          buf,
156        RDN                   rdn,
157        Hashtable             oidSymbols)
158    {
159        if (rdn.isMultiValued())
160        {
161            AttributeTypeAndValue[] atv = rdn.getTypesAndValues();
162            boolean firstAtv = true;
163
164            for (int j = 0; j != atv.length; j++)
165            {
166                if (firstAtv)
167                {
168                    firstAtv = false;
169                }
170                else
171                {
172                    buf.append('+');
173                }
174
175                IETFUtils.appendTypeAndValue(buf, atv[j], oidSymbols);
176            }
177        }
178        else
179        {
180            IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols);
181        }
182    }
183
184    public static void appendTypeAndValue(
185        StringBuffer          buf,
186        AttributeTypeAndValue typeAndValue,
187        Hashtable             oidSymbols)
188    {
189        String  sym = (String)oidSymbols.get(typeAndValue.getType());
190
191        if (sym != null)
192        {
193            buf.append(sym);
194        }
195        else
196        {
197            buf.append(typeAndValue.getType().getId());
198        }
199
200        buf.append('=');
201
202        buf.append(valueToString(typeAndValue.getValue()));
203    }
204
205    public static String valueToString(ASN1Encodable value)
206    {
207        StringBuffer vBuf = new StringBuffer();
208
209        if (value instanceof ASN1String && !(value instanceof DERUniversalString))
210        {
211            String v = ((ASN1String)value).getString();
212            if (v.length() > 0 && v.charAt(0) == '#')
213            {
214                vBuf.append("\\" + v);
215            }
216            else
217            {
218                vBuf.append(v);
219            }
220        }
221        else
222        {
223            try
224            {
225                vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER))));
226            }
227            catch (IOException e)
228            {
229                throw new IllegalArgumentException("Other value has no encoded form");
230            }
231        }
232
233        int     end = vBuf.length();
234        int     index = 0;
235
236        if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#')
237        {
238            index += 2;
239        }
240
241        while (index != end)
242        {
243            if ((vBuf.charAt(index) == ',')
244               || (vBuf.charAt(index) == '"')
245               || (vBuf.charAt(index) == '\\')
246               || (vBuf.charAt(index) == '+')
247               || (vBuf.charAt(index) == '=')
248               || (vBuf.charAt(index) == '<')
249               || (vBuf.charAt(index) == '>')
250               || (vBuf.charAt(index) == ';'))
251            {
252                vBuf.insert(index, "\\");
253                index++;
254                end++;
255            }
256
257            index++;
258        }
259
260        return vBuf.toString();
261    }
262
263    private static String bytesToString(
264        byte[] data)
265    {
266        char[]  cs = new char[data.length];
267
268        for (int i = 0; i != cs.length; i++)
269        {
270            cs[i] = (char)(data[i] & 0xff);
271        }
272
273        return new String(cs);
274    }
275
276    public static String canonicalize(String s)
277    {
278        String value = Strings.toLowerCase(s.trim());
279
280        if (value.length() > 0 && value.charAt(0) == '#')
281        {
282            ASN1Primitive obj = decodeObject(value);
283
284            if (obj instanceof ASN1String)
285            {
286                value = Strings.toLowerCase(((ASN1String)obj).getString().trim());
287            }
288        }
289
290        value = stripInternalSpaces(value);
291
292        return value;
293    }
294
295    private static ASN1Primitive decodeObject(String oValue)
296    {
297        try
298        {
299            return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1)));
300        }
301        catch (IOException e)
302        {
303            throw new IllegalStateException("unknown encoding in name: " + e);
304        }
305    }
306
307    public static String stripInternalSpaces(
308        String str)
309    {
310        StringBuffer res = new StringBuffer();
311
312        if (str.length() != 0)
313        {
314            char c1 = str.charAt(0);
315
316            res.append(c1);
317
318            for (int k = 1; k < str.length(); k++)
319            {
320                char c2 = str.charAt(k);
321                if (!(c1 == ' ' && c2 == ' '))
322                {
323                    res.append(c2);
324                }
325                c1 = c2;
326            }
327        }
328
329        return res.toString();
330    }
331
332    public static boolean rDNAreEqual(RDN rdn1, RDN rdn2)
333    {
334        if (rdn1.isMultiValued())
335        {
336            if (rdn2.isMultiValued())
337            {
338                AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues();
339                AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues();
340
341                if (atvs1.length != atvs2.length)
342                {
343                    return false;
344                }
345
346                for (int i = 0; i != atvs1.length; i++)
347                {
348                    if (!atvAreEqual(atvs1[i], atvs2[i]))
349                    {
350                        return false;
351                    }
352                }
353            }
354            else
355            {
356                return false;
357            }
358        }
359        else
360        {
361            if (!rdn2.isMultiValued())
362            {
363                return atvAreEqual(rdn1.getFirst(), rdn2.getFirst());
364            }
365            else
366            {
367                return false;
368            }
369        }
370
371        return true;
372    }
373
374    private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2)
375    {
376        if (atv1 == atv2)
377        {
378            return true;
379        }
380
381        if (atv1 == null)
382        {
383            return false;
384        }
385
386        if (atv2 == null)
387        {
388            return false;
389        }
390
391        ASN1ObjectIdentifier o1 = atv1.getType();
392        ASN1ObjectIdentifier o2 = atv2.getType();
393
394        if (!o1.equals(o2))
395        {
396            return false;
397        }
398
399        String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue()));
400        String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue()));
401
402        if (!v1.equals(v2))
403        {
404            return false;
405        }
406
407        return true;
408    }
409}
410