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