1    package org.bouncycastle.asn1.x509;
2    
3    import java.io.*;
4    import java.util.*;
5    
6    import org.bouncycastle.asn1.*;
7    
8    public class X509Name
9        implements DEREncodable
10   {
11       /**
12        * country code - StringType(SIZE(2))
13        */
14       public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6");
15   
16       /**
17        * organization - StringType(SIZE(1..64))
18        */
19       public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10");
20   
21       /**
22        * organizational unit name - StringType(SIZE(1..64))
23        */
24       public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11");
25   
26       /**
27        * Title
28        */
29       public static final DERObjectIdentifier T = new DERObjectIdentifier("2.5.4.12");
30   
31       /**
32        * common name - StringType(SIZE(1..64))
33        */
34       public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3");
35   
36       /**
37        * device serial number name - StringType(SIZE(1..64))
38        */
39       public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5");
40   
41       /**
42        * locality name - StringType(SIZE(1..64))
43        */
44       public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7");
45   
46       /**
47        * state, or province name - StringType(SIZE(1..64))
48        */
49       public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8");
50   
51   
52       /**
53        * email address (RSA PKCS#9 extension) - IA5String
54        * <p>
55        * note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.
56        */
57       public static final DERObjectIdentifier EmailAddress = new DERObjectIdentifier("1.2.840.113549.1.9.1");
58           
59           /**
60            * email address in Verisign certificates
61            */
62           public static final DERObjectIdentifier E = EmailAddress;
63           
64       /*
65        * others...
66        */
67       public static final DERObjectIdentifier DC = new DERObjectIdentifier("0.9.2342.19200300.100.1.25");
68   
69       /**
70        * LDAP User id.
71        */
72       public static final DERObjectIdentifier UID = new DERObjectIdentifier("0.9.2342.19200300.100.1.1");
73   
74       /**
75        * look up table translating OID values into their common symbols.
76        */
77       public static Hashtable OIDLookUp = new Hashtable();
78   
79       /**
80        * look up table translating common symbols into their OIDS.
81        */
82       public static Hashtable SymbolLookUp = new Hashtable();
83   
84       static
85       {
86           OIDLookUp.put(C, "C");
87           OIDLookUp.put(O, "O");
88           OIDLookUp.put(T, "T");
89           OIDLookUp.put(OU, "OU");
90           OIDLookUp.put(CN, "CN");
91           OIDLookUp.put(L, "L");
92           OIDLookUp.put(ST, "ST");
93           OIDLookUp.put(SN, "SN");
94           OIDLookUp.put(EmailAddress, "E");
95           OIDLookUp.put(DC, "DC");
96           OIDLookUp.put(UID, "UID");
97   
98           SymbolLookUp.put("c", C);
99           SymbolLookUp.put("o", O);
100          SymbolLookUp.put("t", T);
101          SymbolLookUp.put("ou", OU);
102          SymbolLookUp.put("cn", CN);
103          SymbolLookUp.put("l", L);
104          SymbolLookUp.put("st", ST);
105          SymbolLookUp.put("sn", SN);
106          SymbolLookUp.put("emailaddress", E);
107          SymbolLookUp.put("dc", DC);
108          SymbolLookUp.put("e", E);
109          SymbolLookUp.put("uid", UID);
110      }
111  
112      private Vector                  ordering = new Vector();
113      private Vector                  values = new Vector();
114      private ASN1Sequence            seq;
115  
116      public static X509Name getInstance(
117          ASN1TaggedObject obj,
118          boolean          explicit)
119      {
120          return getInstance(ASN1Sequence.getInstance(obj, explicit));
121      }
122  
123      public static X509Name getInstance(
124          Object  obj)
125      {
126          if (obj == null || obj instanceof X509Name)
127          {
128              return (X509Name)obj;
129          }
130          else if (obj instanceof ASN1Sequence)
131          {
132              return new X509Name((ASN1Sequence)obj);
133          }
134  
135          throw new IllegalArgumentException("unknown object in factory");
136      }
137  
138      /**
139       * Constructor from ASN1Sequence
140       *
141       * the principal will be a list of constructed sets, each containing an (OID, String) pair.
142       */
143      public X509Name(
144          ASN1Sequence  seq)
145      {
146          this.seq = seq;
147  
148          Enumeration e = seq.getObjects();
149  
150          while (e.hasMoreElements())
151          {
152              ASN1Set         set = (ASN1Set)e.nextElement();
153              ASN1Sequence    s = (ASN1Sequence)set.getObjectAt(0);
154  
155              ordering.addElement(s.getObjectAt(0));
156              values.addElement(((DERString)s.getObjectAt(1)).getString());
157          }
158      }
159  
160      /**
161       * constructor from a table of attributes.
162       * <p>
163       * it's is assumed the table contains OID/String pairs, and the contents
164       * of the table are copied into an internal table as part of the 
165       * construction process.
166       * <p>
167       * <b>Note:</b> if the name you are trying to generate should be
168       * following a specific ordering, you should use the constructor
169       * with the ordering specified below.
170       */
171      public X509Name(
172          Hashtable  attributes)
173      {
174          this(null, attributes);
175      }
176  
177      /**
178       * constructor from a table of attributes with ordering.
179       * <p>
180       * it's is assumed the table contains OID/String pairs, and the contents
181       * of the table are copied into an internal table as part of the 
182       * construction process. The ordering vector should contain the OIDs
183       * in the order they are meant to be encoded or printed in toString.
184       */
185      public X509Name(
186          Vector      ordering,
187          Hashtable   attributes)
188      {
189          if (ordering != null)
190          {
191              for (int i = 0; i != ordering.size(); i++)
192              {
193                  this.ordering.addElement(ordering.elementAt(i));
194              }
195          }
196          else
197          {
198              Enumeration     e = attributes.keys();
199  
200              while (e.hasMoreElements())
201              {
202                  this.ordering.addElement(e.nextElement());
203              }
204          }
205  
206          for (int i = 0; i != this.ordering.size(); i++)
207          {
208              DERObjectIdentifier     oid = (DERObjectIdentifier)this.ordering.elementAt(i);
209  
210              if (OIDLookUp.get(oid) == null)
211              {
212                  throw new IllegalArgumentException("Unknown object id - " + oid.getId() + " - passed to distinguished name");
213              }
214  
215              if (attributes.get(oid) == null)
216              {
217                  throw new IllegalArgumentException("No attribute for object id - " + oid.getId() + " - passed to distinguished name");
218              }
219  
220              this.values.addElement(attributes.get(oid)); // copy the hash table
221          }
222      }
223  
224      /**
225       * takes two vectors one of the oids and the other of the values.
226       */
227      public X509Name(
228          Vector  ordering,
229          Vector  values)
230      {
231          if (ordering.size() != values.size())
232          {
233              throw new IllegalArgumentException("ordering vector must be same length as values.");
234          }
235  
236          for (int i = 0; i < ordering.size(); i++)
237          {
238              this.ordering.addElement(ordering.elementAt(i));
239              this.values.addElement(values.elementAt(i));
240          }
241      }
242  
243      /**
244       * takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or
245       * some such, converting it into an ordered set of name attributes.
246       */
247      public X509Name(
248          String  dirName)
249      {
250          X509NameTokenizer   nTok = new X509NameTokenizer(dirName);
251  
252          while (nTok.hasMoreTokens())
253          {
254              String  token = nTok.nextToken();
255              int     index = token.indexOf('=');
256  
257              if (index == -1)
258              {
259                  throw new IllegalArgumentException("badly formated directory string");
260              }
261  
262              String              name = token.substring(0, index);
263              String              value = token.substring(index + 1);
264              DERObjectIdentifier oid = null;
265  
266              if (name.toUpperCase().startsWith("OID."))
267              {
268                  oid = new DERObjectIdentifier(name.substring(4));
269              }
270              else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
271              {
272                  oid = new DERObjectIdentifier(name);
273              }
274              else
275              {
276                  oid = (DERObjectIdentifier)SymbolLookUp.get(name.toLowerCase());
277                  if (oid == null)
278                  {
279                      throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
280                  }
281              }
282  
283              this.ordering.addElement(oid);
284              this.values.addElement(value);
285          }
286      }
287  
288      /**
289       * return false if we have characters out of the range of a printable
290       * string, true otherwise.
291       */
292      private boolean canBePrintable(
293          String  str)
294      {
295          for (int i = str.length() - 1; i >= 0; i--)
296          {
297              if (str.charAt(i) > 0x007f)
298              {
299                  return false;
300              }
301          }
302  
303          return true;
304      }
305  
306      public DERObject getDERObject()
307      {
308          if (seq == null)
309          {
310              DEREncodableVector  vec = new DEREncodableVector();
311  
312              for (int i = 0; i != ordering.size(); i++)
313              {
314                  DEREncodableVector      v = new DEREncodableVector();
315                  DERObjectIdentifier     oid = (DERObjectIdentifier)ordering.elementAt(i);
316  
317                  v.add(oid);
318  
319                  String  str = (String)values.elementAt(i);
320  
321                  if (oid.equals(EmailAddress))
322                  {
323                      v.add(new DERIA5String(str));
324                  }
325                  else
326                  {
327                      if (canBePrintable(str))
328                      {
329                          v.add(new DERPrintableString(str));
330                      }
331                      else
332                      {
333                          v.add(new DERUTF8String(str));
334                      }
335                  }
336  
337                  vec.add(new DERSet(new DERSequence(v)));
338              }
339  
340              seq = new DERSequence(vec);
341          }
342  
343          return seq;
344      }
345  
346      /**
347       * test for equality - note: case is ignored.
348       */
349      public boolean equals(Object _obj) 
350      {
351          if (_obj == this)
352          {
353              return true;
354          }
355  
356          if (_obj == null || !(_obj instanceof X509Name))
357          {
358              return false;
359          }
360          
361          X509Name _oxn          = (X509Name)_obj;
362          int      _orderingSize = ordering.size();
363  
364          if (_orderingSize != _oxn.ordering.size()) 
365          {
366                          return false;
367                  }
368                  
369                  boolean[] _indexes = new boolean[_orderingSize];
370  
371                  for(int i = 0; i < _orderingSize; i++) 
372                  {
373                          boolean _found = false;
374                          String  _oid   = ((DERObjectIdentifier)ordering.elementAt(i)).getId();
375                          String  _val   = (String)values.elementAt(i);
376                          
377                          for(int j = 0; j < _orderingSize; j++) 
378                          {
379                                  if(_indexes[j] == true)
380                                  {
381                                          continue;
382                                  }
383                                  
384                                  String _oOID = ((DERObjectIdentifier)_oxn.ordering.elementAt(j)).getId();
385                                  String _oVal = (String)_oxn.values.elementAt(j);
386  
387                  // was equalsIgnoreCase but MIDP doesn't like that.
388                                  if(_oid.equals(_oOID) && _val.toLowerCase().equals(_oVal.toLowerCase()))
389                                  {
390                                          _indexes[j] = true;
391                                          _found      = true;
392                                          break;
393                                  }
394  
395                          }
396  
397                          if(!_found)
398                          {
399                                  return false;
400                          }
401                  }
402                  
403                  return true;
404          }
405          
406      public int hashCode()
407      {
408          ASN1Sequence  seq = (ASN1Sequence)this.getDERObject();
409          Enumeration   e = seq.getObjects();
410          int           hashCode = 0;
411  
412          while (e.hasMoreElements())
413          {
414              hashCode ^= e.nextElement().hashCode();
415          }
416  
417          return hashCode;
418      }
419  
420      public String toString()
421      {
422          StringBuffer            buf = new StringBuffer();
423          boolean                 first = true;
424          Enumeration             e1 = ordering.elements();
425          Enumeration             e2 = values.elements();
426  
427          while (e1.hasMoreElements())
428          {
429              Object                  oid = e1.nextElement();
430              String                  sym = (String)OIDLookUp.get(oid);
431              
432              if (first)
433              {
434                  first = false;
435              }
436              else
437              {
438                  buf.append(",");
439              }
440  
441              if (sym != null)
442              {
443                  buf.append(sym);
444              }
445              else
446              {
447                  buf.append(((DERObjectIdentifier)oid).getId());
448              }
449  
450              buf.append("=");
451  
452              int     index = buf.length();
453  
454              buf.append((String)e2.nextElement());
455  
456              int     end = buf.length();
457  
458              while (index != end)
459              {
460                  if ((buf.charAt(index) == ',')
461                     || (buf.charAt(index) == '"')
462                     || (buf.charAt(index) == '\\')
463                     || (buf.charAt(index) == '+')
464                     || (buf.charAt(index) == '<')
465                     || (buf.charAt(index) == '>')
466                     || (buf.charAt(index) == ';'))
467                  {
468                      buf.insert(index, "\\");
469                      index++;
470                      end++;
471                  }
472  
473                  index++;
474              }
475          }
476  
477          return buf.toString();
478      }
479  }
480