Home | History | Annotate | Download | only in style
      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 {
     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 
    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 }
    596