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 org.apache.http.conn.ssl;
     19 
     20 import java.util.ArrayList;
     21 import java.util.Collections;
     22 import java.util.List;
     23 import javax.security.auth.x500.X500Principal;
     24 
     25 /**
     26  * A distinguished name (DN) parser. This parser only supports extracting a
     27  * string value from a DN. It doesn't support values in the hex-string style.
     28  *
     29  * @hide
     30  */
     31 @Deprecated
     32 final class AndroidDistinguishedNameParser {
     33     private final String dn;
     34     private final int length;
     35     private int pos;
     36     private int beg;
     37     private int end;
     38 
     39     /** tmp vars to store positions of the currently parsed item */
     40     private int cur;
     41 
     42     /** distinguished name chars */
     43     private char[] chars;
     44 
     45     public AndroidDistinguishedNameParser(X500Principal principal) {
     46         // RFC2253 is used to ensure we get attributes in the reverse
     47         // order of the underlying ASN.1 encoding, so that the most
     48         // significant values of repeated attributes occur first.
     49         this.dn = principal.getName(X500Principal.RFC2253);
     50         this.length = this.dn.length();
     51     }
     52 
     53     // gets next attribute type: (ALPHA 1*keychar) / oid
     54     private String nextAT() {
     55         // skip preceding space chars, they can present after
     56         // comma or semicolon (compatibility with RFC 1779)
     57         for (; pos < length && chars[pos] == ' '; pos++) {
     58         }
     59         if (pos == length) {
     60             return null; // reached the end of DN
     61         }
     62 
     63         // mark the beginning of attribute type
     64         beg = pos;
     65 
     66         // attribute type chars
     67         pos++;
     68         for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
     69             // we don't follow exact BNF syntax here:
     70             // accept any char except space and '='
     71         }
     72         if (pos >= length) {
     73             throw new IllegalStateException("Unexpected end of DN: " + dn);
     74         }
     75 
     76         // mark the end of attribute type
     77         end = pos;
     78 
     79         // skip trailing space chars between attribute type and '='
     80         // (compatibility with RFC 1779)
     81         if (chars[pos] == ' ') {
     82             for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
     83             }
     84 
     85             if (chars[pos] != '=' || pos == length) {
     86                 throw new IllegalStateException("Unexpected end of DN: " + dn);
     87             }
     88         }
     89 
     90         pos++; //skip '=' char
     91 
     92         // skip space chars between '=' and attribute value
     93         // (compatibility with RFC 1779)
     94         for (; pos < length && chars[pos] == ' '; pos++) {
     95         }
     96 
     97         // in case of oid attribute type skip its prefix: "oid." or "OID."
     98         // (compatibility with RFC 1779)
     99         if ((end - beg > 4) && (chars[beg + 3] == '.')
    100                 && (chars[beg] == 'O' || chars[beg] == 'o')
    101                 && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
    102                 && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
    103             beg += 4;
    104         }
    105 
    106         return new String(chars, beg, end - beg);
    107     }
    108 
    109     // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
    110     private String quotedAV() {
    111         pos++;
    112         beg = pos;
    113         end = beg;
    114         while (true) {
    115 
    116             if (pos == length) {
    117                 throw new IllegalStateException("Unexpected end of DN: " + dn);
    118             }
    119 
    120             if (chars[pos] == '"') {
    121                 // enclosing quotation was found
    122                 pos++;
    123                 break;
    124             } else if (chars[pos] == '\\') {
    125                 chars[end] = getEscaped();
    126             } else {
    127                 // shift char: required for string with escaped chars
    128                 chars[end] = chars[pos];
    129             }
    130             pos++;
    131             end++;
    132         }
    133 
    134         // skip trailing space chars before comma or semicolon.
    135         // (compatibility with RFC 1779)
    136         for (; pos < length && chars[pos] == ' '; pos++) {
    137         }
    138 
    139         return new String(chars, beg, end - beg);
    140     }
    141 
    142     // gets hex string attribute value: "#" hexstring
    143     private String hexAV() {
    144         if (pos + 4 >= length) {
    145             // encoded byte array  must be not less then 4 c
    146             throw new IllegalStateException("Unexpected end of DN: " + dn);
    147         }
    148 
    149         beg = pos; // store '#' position
    150         pos++;
    151         while (true) {
    152 
    153             // check for end of attribute value
    154             // looks for space and component separators
    155             if (pos == length || chars[pos] == '+' || chars[pos] == ','
    156                     || chars[pos] == ';') {
    157                 end = pos;
    158                 break;
    159             }
    160 
    161             if (chars[pos] == ' ') {
    162                 end = pos;
    163                 pos++;
    164                 // skip trailing space chars before comma or semicolon.
    165                 // (compatibility with RFC 1779)
    166                 for (; pos < length && chars[pos] == ' '; pos++) {
    167                 }
    168                 break;
    169             } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
    170                 chars[pos] += 32; //to low case
    171             }
    172 
    173             pos++;
    174         }
    175 
    176         // verify length of hex string
    177         // encoded byte array  must be not less then 4 and must be even number
    178         int hexLen = end - beg; // skip first '#' char
    179         if (hexLen < 5 || (hexLen & 1) == 0) {
    180             throw new IllegalStateException("Unexpected end of DN: " + dn);
    181         }
    182 
    183         // get byte encoding from string representation
    184         byte[] encoded = new byte[hexLen / 2];
    185         for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
    186             encoded[i] = (byte) getByte(p);
    187         }
    188 
    189         return new String(chars, beg, hexLen);
    190     }
    191 
    192     // gets string attribute value: *( stringchar / pair )
    193     private String escapedAV() {
    194         beg = pos;
    195         end = pos;
    196         while (true) {
    197             if (pos >= length) {
    198                 // the end of DN has been found
    199                 return new String(chars, beg, end - beg);
    200             }
    201 
    202             switch (chars[pos]) {
    203             case '+':
    204             case ',':
    205             case ';':
    206                 // separator char has been found
    207                 return new String(chars, beg, end - beg);
    208             case '\\':
    209                 // escaped char
    210                 chars[end++] = getEscaped();
    211                 pos++;
    212                 break;
    213             case ' ':
    214                 // need to figure out whether space defines
    215                 // the end of attribute value or not
    216                 cur = end;
    217 
    218                 pos++;
    219                 chars[end++] = ' ';
    220 
    221                 for (; pos < length && chars[pos] == ' '; pos++) {
    222                     chars[end++] = ' ';
    223                 }
    224                 if (pos == length || chars[pos] == ',' || chars[pos] == '+'
    225                         || chars[pos] == ';') {
    226                     // separator char or the end of DN has been found
    227                     return new String(chars, beg, cur - beg);
    228                 }
    229                 break;
    230             default:
    231                 chars[end++] = chars[pos];
    232                 pos++;
    233             }
    234         }
    235     }
    236 
    237     // returns escaped char
    238     private char getEscaped() {
    239         pos++;
    240         if (pos == length) {
    241             throw new IllegalStateException("Unexpected end of DN: " + dn);
    242         }
    243 
    244         switch (chars[pos]) {
    245         case '"':
    246         case '\\':
    247         case ',':
    248         case '=':
    249         case '+':
    250         case '<':
    251         case '>':
    252         case '#':
    253         case ';':
    254         case ' ':
    255         case '*':
    256         case '%':
    257         case '_':
    258             //FIXME: escaping is allowed only for leading or trailing space char
    259             return chars[pos];
    260         default:
    261             // RFC doesn't explicitly say that escaped hex pair is
    262             // interpreted as UTF-8 char. It only contains an example of such DN.
    263             return getUTF8();
    264         }
    265     }
    266 
    267     // decodes UTF-8 char
    268     // see http://www.unicode.org for UTF-8 bit distribution table
    269     private char getUTF8() {
    270         int res = getByte(pos);
    271         pos++; //FIXME tmp
    272 
    273         if (res < 128) { // one byte: 0-7F
    274             return (char) res;
    275         } else if (res >= 192 && res <= 247) {
    276 
    277             int count;
    278             if (res <= 223) { // two bytes: C0-DF
    279                 count = 1;
    280                 res = res & 0x1F;
    281             } else if (res <= 239) { // three bytes: E0-EF
    282                 count = 2;
    283                 res = res & 0x0F;
    284             } else { // four bytes: F0-F7
    285                 count = 3;
    286                 res = res & 0x07;
    287             }
    288 
    289             int b;
    290             for (int i = 0; i < count; i++) {
    291                 pos++;
    292                 if (pos == length || chars[pos] != '\\') {
    293                     return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    294                 }
    295                 pos++;
    296 
    297                 b = getByte(pos);
    298                 pos++; //FIXME tmp
    299                 if ((b & 0xC0) != 0x80) {
    300                     return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    301                 }
    302 
    303                 res = (res << 6) + (b & 0x3F);
    304             }
    305             return (char) res;
    306         } else {
    307             return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
    308         }
    309     }
    310 
    311     // Returns byte representation of a char pair
    312     // The char pair is composed of DN char in
    313     // specified 'position' and the next char
    314     // According to BNF syntax:
    315     // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
    316     //                    / "a" / "b" / "c" / "d" / "e" / "f"
    317     private int getByte(int position) {
    318         if (position + 1 >= length) {
    319             throw new IllegalStateException("Malformed DN: " + dn);
    320         }
    321 
    322         int b1, b2;
    323 
    324         b1 = chars[position];
    325         if (b1 >= '0' && b1 <= '9') {
    326             b1 = b1 - '0';
    327         } else if (b1 >= 'a' && b1 <= 'f') {
    328             b1 = b1 - 87; // 87 = 'a' - 10
    329         } else if (b1 >= 'A' && b1 <= 'F') {
    330             b1 = b1 - 55; // 55 = 'A' - 10
    331         } else {
    332             throw new IllegalStateException("Malformed DN: " + dn);
    333         }
    334 
    335         b2 = chars[position + 1];
    336         if (b2 >= '0' && b2 <= '9') {
    337             b2 = b2 - '0';
    338         } else if (b2 >= 'a' && b2 <= 'f') {
    339             b2 = b2 - 87; // 87 = 'a' - 10
    340         } else if (b2 >= 'A' && b2 <= 'F') {
    341             b2 = b2 - 55; // 55 = 'A' - 10
    342         } else {
    343             throw new IllegalStateException("Malformed DN: " + dn);
    344         }
    345 
    346         return (b1 << 4) + b2;
    347     }
    348 
    349     /**
    350      * Parses the DN and returns the most significant attribute value
    351      * for an attribute type, or null if none found.
    352      *
    353      * @param attributeType attribute type to look for (e.g. "ca")
    354      */
    355     public String findMostSpecific(String attributeType) {
    356         // Initialize internal state.
    357         pos = 0;
    358         beg = 0;
    359         end = 0;
    360         cur = 0;
    361         chars = dn.toCharArray();
    362 
    363         String attType = nextAT();
    364         if (attType == null) {
    365             return null;
    366         }
    367         while (true) {
    368             String attValue = "";
    369 
    370             if (pos == length) {
    371                 return null;
    372             }
    373 
    374             switch (chars[pos]) {
    375             case '"':
    376                 attValue = quotedAV();
    377                 break;
    378             case '#':
    379                 attValue = hexAV();
    380                 break;
    381             case '+':
    382             case ',':
    383             case ';': // compatibility with RFC 1779: semicolon can separate RDNs
    384                 //empty attribute value
    385                 break;
    386             default:
    387                 attValue = escapedAV();
    388             }
    389 
    390             // Values are ordered from most specific to least specific
    391             // due to the RFC2253 formatting. So take the first match
    392             // we see.
    393             if (attributeType.equalsIgnoreCase(attType)) {
    394                 return attValue;
    395             }
    396 
    397             if (pos >= length) {
    398                 return null;
    399             }
    400 
    401             if (chars[pos] == ',' || chars[pos] == ';') {
    402             } else if (chars[pos] != '+') {
    403                 throw new IllegalStateException("Malformed DN: " + dn);
    404             }
    405 
    406             pos++;
    407             attType = nextAT();
    408             if (attType == null) {
    409                 throw new IllegalStateException("Malformed DN: " + dn);
    410             }
    411         }
    412     }
    413 
    414     /**
    415      * Parses the DN and returns all values for an attribute type, in
    416      * the order of decreasing significance (most significant first).
    417      *
    418      * @param attributeType attribute type to look for (e.g. "ca")
    419      */
    420     public List<String> getAllMostSpecificFirst(String attributeType) {
    421         // Initialize internal state.
    422         pos = 0;
    423         beg = 0;
    424         end = 0;
    425         cur = 0;
    426         chars = dn.toCharArray();
    427         List<String> result = Collections.emptyList();
    428 
    429         String attType = nextAT();
    430         if (attType == null) {
    431             return result;
    432         }
    433         while (pos < length) {
    434             String attValue = "";
    435 
    436             switch (chars[pos]) {
    437             case '"':
    438                 attValue = quotedAV();
    439                 break;
    440             case '#':
    441                 attValue = hexAV();
    442                 break;
    443             case '+':
    444             case ',':
    445             case ';': // compatibility with RFC 1779: semicolon can separate RDNs
    446                 //empty attribute value
    447                 break;
    448             default:
    449                 attValue = escapedAV();
    450             }
    451 
    452             // Values are ordered from most specific to least specific
    453             // due to the RFC2253 formatting. So take the first match
    454             // we see.
    455             if (attributeType.equalsIgnoreCase(attType)) {
    456                 if (result.isEmpty()) {
    457                     result = new ArrayList<String>();
    458                 }
    459                 result.add(attValue);
    460             }
    461 
    462             if (pos >= length) {
    463                 break;
    464             }
    465 
    466             if (chars[pos] == ',' || chars[pos] == ';') {
    467             } else if (chars[pos] != '+') {
    468                 throw new IllegalStateException("Malformed DN: " + dn);
    469             }
    470 
    471             pos++;
    472             attType = nextAT();
    473             if (attType == null) {
    474                 throw new IllegalStateException("Malformed DN: " + dn);
    475             }
    476         }
    477 
    478         return result;
    479     }
    480 }
    481