Home | History | Annotate | Download | only in ssl
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package javax.net.ssl;
     19 
     20 import javax.security.auth.x500.X500Principal;
     21 
     22 /**
     23  * A distinguished name (DN) parser. This parser only supports extracting a
     24  * string value from a DN. It doesn't support values in the hex-string style.
     25  *
     26  * @hide
     27  */
     28 public final class DistinguishedNameParser {
     29     private final String dn;
     30     private final int length;
     31     private int pos;
     32     private int beg;
     33     private int end;
     34 
     35     /** tmp vars to store positions of the currently parsed item */
     36     private int cur;
     37 
     38     /** distinguished name chars */
     39     private char[] chars;
     40 
     41     public DistinguishedNameParser(X500Principal principal) {
     42         this.dn = principal.getName(X500Principal.RFC2253);
     43         this.length = this.dn.length();
     44     }
     45 
     46     // gets next attribute type: (ALPHA 1*keychar) / oid
     47     private String nextAT() {
     48         // skip preceding space chars, they can present after
     49         // comma or semicolon (compatibility with RFC 1779)
     50         for (; pos < length && chars[pos] == ' '; pos++) {
     51         }
     52         if (pos == length) {
     53             return null; // reached the end of DN
     54         }
     55 
     56         // mark the beginning of attribute type
     57         beg = pos;
     58 
     59         // attribute type chars
     60         pos++;
     61         for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
     62             // we don't follow exact BNF syntax here:
     63             // accept any char except space and '='
     64         }
     65         if (pos >= length) {
     66             throw new IllegalStateException("Unexpected end of DN: " + dn);
     67         }
     68 
     69         // mark the end of attribute type
     70         end = pos;
     71 
     72         // skip trailing space chars between attribute type and '='
     73         // (compatibility with RFC 1779)
     74         if (chars[pos] == ' ') {
     75             for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
     76             }
     77 
     78             if (chars[pos] != '=' || pos == length) {
     79                 throw new IllegalStateException("Unexpected end of DN: " + dn);
     80             }
     81         }
     82 
     83         pos++; //skip '=' char
     84 
     85         // skip space chars between '=' and attribute value
     86         // (compatibility with RFC 1779)
     87         for (; pos < length && chars[pos] == ' '; pos++) {
     88         }
     89 
     90         // in case of oid attribute type skip its prefix: "oid." or "OID."
     91         // (compatibility with RFC 1779)
     92         if ((end - beg > 4) && (chars[beg + 3] == '.')
     93                 && (chars[beg] == 'O' || chars[beg] == 'o')
     94                 && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
     95                 && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
     96             beg += 4;
     97         }
     98 
     99         return new String(chars, beg, end - beg);
    100     }
    101 
    102     // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
    103     private String quotedAV() {
    104         pos++;
    105         beg = pos;
    106         end = beg;
    107         while (true) {
    108 
    109             if (pos == length) {
    110                 throw new IllegalStateException("Unexpected end of DN: " + dn);
    111             }
    112 
    113             if (chars[pos] == '"') {
    114                 // enclosing quotation was found
    115                 pos++;
    116                 break;
    117             } else if (chars[pos] == '\\') {
    118                 chars[end] = getEscaped();
    119             } else {
    120                 // shift char: required for string with escaped chars
    121                 chars[end] = chars[pos];
    122             }
    123             pos++;
    124             end++;
    125         }
    126 
    127         // skip trailing space chars before comma or semicolon.
    128         // (compatibility with RFC 1779)
    129         for (; pos < length && chars[pos] == ' '; pos++) {
    130         }
    131 
    132         return new String(chars, beg, end - beg);
    133     }
    134 
    135     // gets hex string attribute value: "#" hexstring
    136     private String hexAV() {
    137         if (pos + 4 >= length) {
    138             // encoded byte array  must be not less then 4 c
    139             throw new IllegalStateException("Unexpected end of DN: " + dn);
    140         }
    141 
    142         beg = pos; // store '#' position
    143         pos++;
    144         while (true) {
    145 
    146             // check for end of attribute value
    147             // looks for space and component separators
    148             if (pos == length || chars[pos] == '+' || chars[pos] == ','
    149                     || chars[pos] == ';') {
    150                 end = pos;
    151                 break;
    152             }
    153 
    154             if (chars[pos] == ' ') {
    155                 end = pos;
    156                 pos++;
    157                 // skip trailing space chars before comma or semicolon.
    158                 // (compatibility with RFC 1779)
    159                 for (; pos < length && chars[pos] == ' '; pos++) {
    160                 }
    161                 break;
    162             } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
    163                 chars[pos] += 32; //to low case
    164             }
    165 
    166             pos++;
    167         }
    168 
    169         // verify length of hex string
    170         // encoded byte array  must be not less then 4 and must be even number
    171         int hexLen = end - beg; // skip first '#' char
    172         if (hexLen < 5 || (hexLen & 1) == 0) {
    173             throw new IllegalStateException("Unexpected end of DN: " + dn);
    174         }
    175 
    176         // get byte encoding from string representation
    177         byte[] encoded = new byte[hexLen / 2];
    178         for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
    179             encoded[i] = (byte) getByte(p);
    180         }
    181 
    182         return new String(chars, beg, hexLen);
    183     }
    184 
    185     // gets string attribute value: *( stringchar / pair )
    186     private String escapedAV() {
    187         beg = pos;
    188         end = pos;
    189         while (true) {
    190             if (pos >= length) {
    191                 // the end of DN has been found
    192                 return new String(chars, beg, end - beg);
    193             }
    194 
    195             switch (chars[pos]) {
    196             case '+':
    197             case ',':
    198             case ';':
    199                 // separator char has beed found
    200                 return new String(chars, beg, end - beg);
    201             case '\\':
    202                 // escaped char
    203                 chars[end++] = getEscaped();
    204                 pos++;
    205                 break;
    206             case ' ':
    207                 // need to figure out whether space defines
    208                 // the end of attribute value or not
    209                 cur = end;
    210 
    211                 pos++;
    212                 chars[end++] = ' ';
    213 
    214                 for (; pos < length && chars[pos] == ' '; pos++) {
    215                     chars[end++] = ' ';
    216                 }
    217                 if (pos == length || chars[pos] == ',' || chars[pos] == '+'
    218                         || chars[pos] == ';') {
    219                     // separator char or the end of DN has beed found
    220                     return new String(chars, beg, cur - beg);
    221                 }
    222                 break;
    223             default:
    224                 chars[end++] = chars[pos];
    225                 pos++;
    226             }
    227         }
    228     }
    229 
    230     // returns escaped char
    231     private char getEscaped() {
    232         pos++;
    233         if (pos == length) {
    234             throw new IllegalStateException("Unexpected end of DN: " + dn);
    235         }
    236 
    237         switch (chars[pos]) {
    238         case '"':
    239         case '\\':
    240         case ',':
    241         case '=':
    242         case '+':
    243         case '<':
    244         case '>':
    245         case '#':
    246         case ';':
    247         case ' ':
    248         case '*':
    249         case '%':
    250         case '_':
    251             //FIXME: escaping is allowed only for leading or trailing space char
    252             return chars[pos];
    253         default:
    254             // RFC doesn't explicitly say that escaped hex pair is
    255             // interpreted as UTF-8 char. It only contains an example of such DN.
    256             return getUTF8();
    257         }
    258     }
    259 
    260     // decodes UTF-8 char
    261     // see http://www.unicode.org for UTF-8 bit distribution table
    262     private char getUTF8() {
    263         int res = getByte(pos);
    264         pos++; //FIXME tmp
    265 
    266         if (res < 128) { // one byte: 0-7F
    267             return (char) res;
    268         } else if (res >= 192 && res <= 247) {
    269 
    270             int count;
    271             if (res <= 223) { // two bytes: C0-DF
    272                 count = 1;
    273                 res = res & 0x1F;
    274             } else if (res <= 239) { // three bytes: E0-EF
    275                 count = 2;
    276                 res = res & 0x0F;
    277             } else { // four bytes: F0-F7
    278                 count = 3;
    279                 res = res & 0x07;
    280             }
    281 
    282             int b;
    283             for (int i = 0; i < count; i++) {
    284                 pos++;
    285                 if (pos == length || chars[pos] != '\\') {
    286                     return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    287                 }
    288                 pos++;
    289 
    290                 b = getByte(pos);
    291                 pos++; //FIXME tmp
    292                 if ((b & 0xC0) != 0x80) {
    293                     return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    294                 }
    295 
    296                 res = (res << 6) + (b & 0x3F);
    297             }
    298             return (char) res;
    299         } else {
    300             return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    301         }
    302     }
    303 
    304     // Returns byte representation of a char pair
    305     // The char pair is composed of DN char in
    306     // specified 'position' and the next char
    307     // According to BNF syntax:
    308     // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
    309     //                    / "a" / "b" / "c" / "d" / "e" / "f"
    310     private int getByte(int position) {
    311         if (position + 1 >= length) {
    312             throw new IllegalStateException("Malformed DN: " + dn);
    313         }
    314 
    315         int b1, b2;
    316 
    317         b1 = chars[position];
    318         if (b1 >= '0' && b1 <= '9') {
    319             b1 = b1 - '0';
    320         } else if (b1 >= 'a' && b1 <= 'f') {
    321             b1 = b1 - 87; // 87 = 'a' - 10
    322         } else if (b1 >= 'A' && b1 <= 'F') {
    323             b1 = b1 - 55; // 55 = 'A' - 10
    324         } else {
    325             throw new IllegalStateException("Malformed DN: " + dn);
    326         }
    327 
    328         b2 = chars[position + 1];
    329         if (b2 >= '0' && b2 <= '9') {
    330             b2 = b2 - '0';
    331         } else if (b2 >= 'a' && b2 <= 'f') {
    332             b2 = b2 - 87; // 87 = 'a' - 10
    333         } else if (b2 >= 'A' && b2 <= 'F') {
    334             b2 = b2 - 55; // 55 = 'A' - 10
    335         } else {
    336             throw new IllegalStateException("Malformed DN: " + dn);
    337         }
    338 
    339         return (b1 << 4) + b2;
    340     }
    341 
    342     /**
    343      * Parses the DN and returns the attribute value for an attribute type.
    344      *
    345      * @param attributeType attribute type to look for (e.g. "ca")
    346      * @return value of the attribute that first found, or null if none found
    347      */
    348     public String find(String attributeType) {
    349         // Initialize internal state.
    350         pos = 0;
    351         beg = 0;
    352         end = 0;
    353         cur = 0;
    354         chars = dn.toCharArray();
    355 
    356         String attType = nextAT();
    357         if (attType == null) {
    358             return null;
    359         }
    360         while (true) {
    361             String attValue = "";
    362 
    363             if (pos == length) {
    364                 return null;
    365             }
    366 
    367             switch (chars[pos]) {
    368             case '"':
    369                 attValue = quotedAV();
    370                 break;
    371             case '#':
    372                 attValue = hexAV();
    373                 break;
    374             case '+':
    375             case ',':
    376             case ';': // compatibility with RFC 1779: semicolon can separate RDNs
    377                 //empty attribute value
    378                 break;
    379             default:
    380                 attValue = escapedAV();
    381             }
    382 
    383             if (attributeType.equalsIgnoreCase(attType)) {
    384                 return attValue;
    385             }
    386 
    387             if (pos >= length) {
    388                 return null;
    389             }
    390 
    391             if (chars[pos] == ',' || chars[pos] == ';') {
    392             } else if (chars[pos] != '+') {
    393                 throw new IllegalStateException("Malformed DN: " + dn);
    394             }
    395 
    396             pos++;
    397             attType = nextAT();
    398             if (attType == null) {
    399                 throw new IllegalStateException("Malformed DN: " + dn);
    400             }
    401         }
    402     }
    403 }
    404