1package org.bouncycastle.asn1.x509;
2
3import java.io.IOException;
4import java.util.StringTokenizer;
5
6import org.bouncycastle.asn1.ASN1Choice;
7import org.bouncycastle.asn1.ASN1Encodable;
8import org.bouncycastle.asn1.ASN1Object;
9import org.bouncycastle.asn1.ASN1ObjectIdentifier;
10import org.bouncycastle.asn1.ASN1OctetString;
11import org.bouncycastle.asn1.ASN1Primitive;
12import org.bouncycastle.asn1.ASN1Sequence;
13import org.bouncycastle.asn1.ASN1TaggedObject;
14import org.bouncycastle.asn1.DERIA5String;
15import org.bouncycastle.asn1.DEROctetString;
16import org.bouncycastle.asn1.DERTaggedObject;
17import org.bouncycastle.asn1.x500.X500Name;
18import org.bouncycastle.util.IPAddress;
19
20/**
21 * The GeneralName object.
22 * <pre>
23 * GeneralName ::= CHOICE {
24 *      otherName                       [0]     OtherName,
25 *      rfc822Name                      [1]     IA5String,
26 *      dNSName                         [2]     IA5String,
27 *      x400Address                     [3]     ORAddress,
28 *      directoryName                   [4]     Name,
29 *      ediPartyName                    [5]     EDIPartyName,
30 *      uniformResourceIdentifier       [6]     IA5String,
31 *      iPAddress                       [7]     OCTET STRING,
32 *      registeredID                    [8]     OBJECT IDENTIFIER}
33 *
34 * OtherName ::= SEQUENCE {
35 *      type-id    OBJECT IDENTIFIER,
36 *      value      [0] EXPLICIT ANY DEFINED BY type-id }
37 *
38 * EDIPartyName ::= SEQUENCE {
39 *      nameAssigner            [0]     DirectoryString OPTIONAL,
40 *      partyName               [1]     DirectoryString }
41 *
42 * Name ::= CHOICE { RDNSequence }
43 * </pre>
44 */
45public class GeneralName
46    extends ASN1Object
47    implements ASN1Choice
48{
49    public static final int otherName                     = 0;
50    public static final int rfc822Name                    = 1;
51    public static final int dNSName                       = 2;
52    public static final int x400Address                   = 3;
53    public static final int directoryName                 = 4;
54    public static final int ediPartyName                  = 5;
55    public static final int uniformResourceIdentifier     = 6;
56    public static final int iPAddress                     = 7;
57    public static final int registeredID                  = 8;
58
59    private ASN1Encodable obj;
60    private int           tag;
61
62    /**
63     * @deprecated use X500Name constructor.
64     * @param dirName
65     */
66        public GeneralName(
67        X509Name  dirName)
68    {
69        this.obj = X500Name.getInstance(dirName);
70        this.tag = 4;
71    }
72
73    public GeneralName(
74        X500Name  dirName)
75    {
76        this.obj = dirName;
77        this.tag = 4;
78    }
79
80    /**
81     * When the subjectAltName extension contains an Internet mail address,
82     * the address MUST be included as an rfc822Name. The format of an
83     * rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822].
84     *
85     * When the subjectAltName extension contains a domain name service
86     * label, the domain name MUST be stored in the dNSName (an IA5String).
87     * The name MUST be in the "preferred name syntax," as specified by RFC
88     * 1034 [RFC 1034].
89     *
90     * When the subjectAltName extension contains a URI, the name MUST be
91     * stored in the uniformResourceIdentifier (an IA5String). The name MUST
92     * be a non-relative URL, and MUST follow the URL syntax and encoding
93     * rules specified in [RFC 1738].  The name must include both a scheme
94     * (e.g., "http" or "ftp") and a scheme-specific-part.  The scheme-
95     * specific-part must include a fully qualified domain name or IP
96     * address as the host.
97     *
98     * When the subjectAltName extension contains a iPAddress, the address
99     * MUST be stored in the octet string in "network byte order," as
100     * specified in RFC 791 [RFC 791]. The least significant bit (LSB) of
101     * each octet is the LSB of the corresponding byte in the network
102     * address. For IP Version 4, as specified in RFC 791, the octet string
103     * MUST contain exactly four octets.  For IP Version 6, as specified in
104     * RFC 1883, the octet string MUST contain exactly sixteen octets [RFC
105     * 1883].
106     */
107    public GeneralName(
108        int           tag,
109        ASN1Encodable name)
110    {
111        this.obj = name;
112        this.tag = tag;
113    }
114
115    /**
116     * Create a GeneralName for the given tag from the passed in String.
117     * <p>
118     * This constructor can handle:
119     * <ul>
120     * <li>rfc822Name
121     * <li>iPAddress
122     * <li>directoryName
123     * <li>dNSName
124     * <li>uniformResourceIdentifier
125     * <li>registeredID
126     * </ul>
127     * For x400Address, otherName and ediPartyName there is no common string
128     * format defined.
129     * <p>
130     * Note: A directory name can be encoded in different ways into a byte
131     * representation. Be aware of this if the byte representation is used for
132     * comparing results.
133     *
134     * @param tag tag number
135     * @param name string representation of name
136     * @throws IllegalArgumentException if the string encoding is not correct or     *             not supported.
137     */
138    public GeneralName(
139        int       tag,
140        String    name)
141    {
142        this.tag = tag;
143
144        if (tag == rfc822Name || tag == dNSName || tag == uniformResourceIdentifier)
145        {
146            this.obj = new DERIA5String(name);
147        }
148        else if (tag == registeredID)
149        {
150            this.obj = new ASN1ObjectIdentifier(name);
151        }
152        else if (tag == directoryName)
153        {
154            this.obj = new X500Name(name);
155        }
156        else if (tag == iPAddress)
157        {
158            byte[] enc = toGeneralNameEncoding(name);
159            if (enc != null)
160            {
161                this.obj = new DEROctetString(enc);
162            }
163            else
164            {
165                throw new IllegalArgumentException("IP Address is invalid");
166            }
167        }
168        else
169        {
170            throw new IllegalArgumentException("can't process String for tag: " + tag);
171        }
172    }
173
174    public static GeneralName getInstance(
175        Object obj)
176    {
177        if (obj == null || obj instanceof GeneralName)
178        {
179            return (GeneralName)obj;
180        }
181
182        if (obj instanceof ASN1TaggedObject)
183        {
184            ASN1TaggedObject    tagObj = (ASN1TaggedObject)obj;
185            int                 tag = tagObj.getTagNo();
186
187            switch (tag)
188            {
189            case otherName:
190                return new GeneralName(tag, ASN1Sequence.getInstance(tagObj, false));
191            case rfc822Name:
192                return new GeneralName(tag, DERIA5String.getInstance(tagObj, false));
193            case dNSName:
194                return new GeneralName(tag, DERIA5String.getInstance(tagObj, false));
195            case x400Address:
196                throw new IllegalArgumentException("unknown tag: " + tag);
197            case directoryName:
198                return new GeneralName(tag, X500Name.getInstance(tagObj, true));
199            case ediPartyName:
200                return new GeneralName(tag, ASN1Sequence.getInstance(tagObj, false));
201            case uniformResourceIdentifier:
202                return new GeneralName(tag, DERIA5String.getInstance(tagObj, false));
203            case iPAddress:
204                return new GeneralName(tag, ASN1OctetString.getInstance(tagObj, false));
205            case registeredID:
206                return new GeneralName(tag, ASN1ObjectIdentifier.getInstance(tagObj, false));
207            }
208        }
209
210        if (obj instanceof byte[])
211        {
212            try
213            {
214                return getInstance(ASN1Primitive.fromByteArray((byte[])obj));
215            }
216            catch (IOException e)
217            {
218                throw new IllegalArgumentException("unable to parse encoded general name");
219            }
220        }
221
222        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
223    }
224
225    public static GeneralName getInstance(
226        ASN1TaggedObject tagObj,
227        boolean          explicit)
228    {
229        return GeneralName.getInstance(ASN1TaggedObject.getInstance(tagObj, true));
230    }
231
232    public int getTagNo()
233    {
234        return tag;
235    }
236
237    public ASN1Encodable getName()
238    {
239        return obj;
240    }
241
242    public String toString()
243    {
244        StringBuffer buf = new StringBuffer();
245
246        buf.append(tag);
247        buf.append(": ");
248        switch (tag)
249        {
250        case rfc822Name:
251        case dNSName:
252        case uniformResourceIdentifier:
253            buf.append(DERIA5String.getInstance(obj).getString());
254            break;
255        case directoryName:
256            buf.append(X500Name.getInstance(obj).toString());
257            break;
258        default:
259            buf.append(obj.toString());
260        }
261        return buf.toString();
262    }
263
264    private byte[] toGeneralNameEncoding(String ip)
265    {
266        if (IPAddress.isValidIPv6WithNetmask(ip) || IPAddress.isValidIPv6(ip))
267        {
268            int    slashIndex = ip.indexOf('/');
269
270            if (slashIndex < 0)
271            {
272                byte[] addr = new byte[16];
273                int[]  parsedIp = parseIPv6(ip);
274                copyInts(parsedIp, addr, 0);
275
276                return addr;
277            }
278            else
279            {
280                byte[] addr = new byte[32];
281                int[]  parsedIp = parseIPv6(ip.substring(0, slashIndex));
282                copyInts(parsedIp, addr, 0);
283                String mask = ip.substring(slashIndex + 1);
284                if (mask.indexOf(':') > 0)
285                {
286                    parsedIp = parseIPv6(mask);
287                }
288                else
289                {
290                    parsedIp = parseMask(mask);
291                }
292                copyInts(parsedIp, addr, 16);
293
294                return addr;
295            }
296        }
297        else if (IPAddress.isValidIPv4WithNetmask(ip) || IPAddress.isValidIPv4(ip))
298        {
299            int    slashIndex = ip.indexOf('/');
300
301            if (slashIndex < 0)
302            {
303                byte[] addr = new byte[4];
304
305                parseIPv4(ip, addr, 0);
306
307                return addr;
308            }
309            else
310            {
311                byte[] addr = new byte[8];
312
313                parseIPv4(ip.substring(0, slashIndex), addr, 0);
314
315                String mask = ip.substring(slashIndex + 1);
316                if (mask.indexOf('.') > 0)
317                {
318                    parseIPv4(mask, addr, 4);
319                }
320                else
321                {
322                    parseIPv4Mask(mask, addr, 4);
323                }
324
325                return addr;
326            }
327        }
328
329        return null;
330    }
331
332    private void parseIPv4Mask(String mask, byte[] addr, int offset)
333    {
334        int   maskVal = Integer.parseInt(mask);
335
336        for (int i = 0; i != maskVal; i++)
337        {
338            addr[(i / 8) + offset] |= 1 << (7 - (i % 8));
339        }
340    }
341
342    private void parseIPv4(String ip, byte[] addr, int offset)
343    {
344        StringTokenizer sTok = new StringTokenizer(ip, "./");
345        int    index = 0;
346
347        while (sTok.hasMoreTokens())
348        {
349            addr[offset + index++] = (byte)Integer.parseInt(sTok.nextToken());
350        }
351    }
352
353    private int[] parseMask(String mask)
354    {
355        int[] res = new int[8];
356        int   maskVal = Integer.parseInt(mask);
357
358        for (int i = 0; i != maskVal; i++)
359        {
360            res[i / 16] |= 1 << (15 - (i % 16));
361        }
362        return res;
363    }
364
365    private void copyInts(int[] parsedIp, byte[] addr, int offSet)
366    {
367        for (int i = 0; i != parsedIp.length; i++)
368        {
369            addr[(i * 2) + offSet] = (byte)(parsedIp[i] >> 8);
370            addr[(i * 2 + 1) + offSet] = (byte)parsedIp[i];
371        }
372    }
373
374    private int[] parseIPv6(String ip)
375    {
376        StringTokenizer sTok = new StringTokenizer(ip, ":", true);
377        int index = 0;
378        int[] val = new int[8];
379
380        if (ip.charAt(0) == ':' && ip.charAt(1) == ':')
381        {
382           sTok.nextToken(); // skip the first one
383        }
384
385        int doubleColon = -1;
386
387        while (sTok.hasMoreTokens())
388        {
389            String e = sTok.nextToken();
390
391            if (e.equals(":"))
392            {
393                doubleColon = index;
394                val[index++] = 0;
395            }
396            else
397            {
398                if (e.indexOf('.') < 0)
399                {
400                    val[index++] = Integer.parseInt(e, 16);
401                    if (sTok.hasMoreTokens())
402                    {
403                        sTok.nextToken();
404                    }
405                }
406                else
407                {
408                    StringTokenizer eTok = new StringTokenizer(e, ".");
409
410                    val[index++] = (Integer.parseInt(eTok.nextToken()) << 8) | Integer.parseInt(eTok.nextToken());
411                    val[index++] = (Integer.parseInt(eTok.nextToken()) << 8) | Integer.parseInt(eTok.nextToken());
412                }
413            }
414        }
415
416        if (index != val.length)
417        {
418            System.arraycopy(val, doubleColon, val, val.length - (index - doubleColon), index - doubleColon);
419            for (int i = doubleColon; i != val.length - (index - doubleColon); i++)
420            {
421                val[i] = 0;
422            }
423        }
424
425        return val;
426    }
427
428    public ASN1Primitive toASN1Primitive()
429    {
430        if (tag == directoryName)       // directoryName is explicitly tagged as it is a CHOICE
431        {
432            return new DERTaggedObject(true, tag, obj);
433        }
434        else
435        {
436            return new DERTaggedObject(false, tag, obj);
437        }
438    }
439}
440