001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexander V. Esin, Stepan M. Mishura
020: * @version $Revision$
021: */package org.apache.harmony.security.x509;
022:
023: import java.io.IOException;
024: import java.util.ArrayList;
025: import java.util.List;
026:
027: import org.apache.harmony.security.internal.nls.Messages;
028: import org.apache.harmony.security.x501.AttributeTypeAndValue;
029: import org.apache.harmony.security.x501.AttributeValue;
030:
031: /**
032: * Distinguished Name Parser.
033: *
034: * Parses a distinguished name(DN) string according
035: * BNF syntax specified in RFC 2253 and RFC 1779
036: *
037: * RFC 2253: Lightweight Directory Access Protocol (v3):
038: * UTF-8 String Representation of Distinguished Names
039: * http://www.ietf.org/rfc/rfc2253.txt
040: *
041: * RFC 1779: A String Representation of Distinguished Names
042: * http://www.ietf.org/rfc/rfc1779.txt
043: */
044: public class DNParser {
045:
046: // length of distinguished name string
047: protected final int length;
048:
049: protected int pos, beg, end;
050:
051: // tmp vars to store positions of the currently parsed item
052: protected int cur;
053:
054: // distinguished name chars
055: protected char[] chars;
056:
057: // raw string contains '"' or '\'
058: protected boolean hasQE;
059:
060: // DER encoding of currently parsed item
061: protected byte[] encoded;
062:
063: /**
064: * Constructs DN parser
065: *
066: * @param dn - distinguished name string to be parsed
067: */
068: public DNParser(String dn) throws IOException {
069: this .length = dn.length();
070: chars = dn.toCharArray();
071: }
072:
073: // gets next attribute type: (ALPHA 1*keychar) / oid
074: protected String nextAT() throws IOException {
075:
076: hasQE = false; // reset
077:
078: // skip preceding space chars, they can present after
079: // comma or semicolon (compatibility with RFC 1779)
080: for (; pos < length && chars[pos] == ' '; pos++) {
081: }
082: if (pos == length) {
083: return null; // reached the end of DN
084: }
085:
086: // mark the beginning of attribute type
087: beg = pos;
088:
089: // attribute type chars
090: pos++;
091: for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
092: // we don't follow exact BNF syntax here:
093: // accept any char except space and '='
094: }
095: if (pos >= length) {
096: // unexpected end of DN
097: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
098: }
099:
100: // mark the end of attribute type
101: end = pos;
102:
103: // skip trailing space chars between attribute type and '='
104: // (compatibility with RFC 1779)
105: if (chars[pos] == ' ') {
106: for (; pos < length && chars[pos] != '='
107: && chars[pos] == ' '; pos++) {
108: }
109:
110: if (chars[pos] != '=' || pos == length) {
111: // unexpected end of DN
112: throw new IOException(Messages
113: .getString("security.192")); //$NON-NLS-1$
114: }
115: }
116:
117: pos++; //skip '=' char
118:
119: // skip space chars between '=' and attribute value
120: // (compatibility with RFC 1779)
121: for (; pos < length && chars[pos] == ' '; pos++) {
122: }
123:
124: // in case of oid attribute type skip its prefix: "oid." or "OID."
125: // (compatibility with RFC 1779)
126: if ((end - beg > 4) && (chars[beg + 3] == '.')
127: && (chars[beg] == 'O' || chars[beg] == 'o')
128: && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
129: && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
130: beg += 4;
131: }
132:
133: return new String(chars, beg, end - beg);
134: }
135:
136: // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
137: protected String quotedAV() throws IOException {
138:
139: pos++;
140: beg = pos;
141: end = beg;
142: while (true) {
143:
144: if (pos == length) {
145: // unexpected end of DN
146: throw new IOException(Messages
147: .getString("security.192")); //$NON-NLS-1$
148: }
149:
150: if (chars[pos] == '"') {
151: // enclosing quotation was found
152: pos++;
153: break;
154: } else if (chars[pos] == '\\') {
155: chars[end] = getEscaped();
156: } else {
157: // shift char: required for string with escaped chars
158: chars[end] = chars[pos];
159: }
160: pos++;
161: end++;
162: }
163:
164: // skip trailing space chars before comma or semicolon.
165: // (compatibility with RFC 1779)
166: for (; pos < length && chars[pos] == ' '; pos++) {
167: }
168:
169: return new String(chars, beg, end - beg);
170: }
171:
172: // gets hex string attribute value: "#" hexstring
173: private String hexAV() throws IOException {
174:
175: if (pos + 4 >= length) {
176: // encoded byte array must be not less then 4 c
177: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
178: }
179:
180: beg = pos; // store '#' position
181: pos++;
182: while (true) {
183:
184: // check for end of attribute value
185: // looks for space and component separators
186: if (pos == length || chars[pos] == '+' || chars[pos] == ','
187: || chars[pos] == ';') {
188: end = pos;
189: break;
190: }
191:
192: if (chars[pos] == ' ') {
193: end = pos;
194: pos++;
195: // skip trailing space chars before comma or semicolon.
196: // (compatibility with RFC 1779)
197: for (; pos < length && chars[pos] == ' '; pos++) {
198: }
199: break;
200: } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
201: chars[pos] += 32; //to low case
202: }
203:
204: pos++;
205: }
206:
207: // verify length of hex string
208: // encoded byte array must be not less then 4 and must be even number
209: int hexLen = end - beg; // skip first '#' char
210: if (hexLen < 5 || (hexLen & 1) == 0) {
211: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
212: }
213:
214: // get byte encoding from string representation
215: encoded = new byte[hexLen / 2];
216: for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
217: encoded[i] = (byte) getByte(p);
218: }
219:
220: return new String(chars, beg, hexLen);
221: }
222:
223: // gets string attribute value: *( stringchar / pair )
224: protected String escapedAV() throws IOException {
225:
226: beg = pos;
227: end = pos;
228: while (true) {
229:
230: if (pos >= length) {
231: // the end of DN has been found
232: return new String(chars, beg, end - beg);
233: }
234:
235: switch (chars[pos]) {
236: case '+':
237: case ',':
238: case ';':
239: // separator char has beed found
240: return new String(chars, beg, end - beg);
241: case '\\':
242: // escaped char
243: chars[end++] = getEscaped();
244: pos++;
245: break;
246: case ' ':
247: // need to figure out whether space defines
248: // the end of attribute value or not
249: cur = end;
250:
251: pos++;
252: chars[end++] = ' ';
253:
254: for (; pos < length && chars[pos] == ' '; pos++) {
255: chars[end++] = ' ';
256: }
257: if (pos == length || chars[pos] == ','
258: || chars[pos] == '+' || chars[pos] == ';') {
259: // separator char or the end of DN has beed found
260: return new String(chars, beg, cur - beg);
261: }
262: break;
263: default:
264: chars[end++] = chars[pos];
265: pos++;
266: }
267: }
268: }
269:
270: // returns escaped char
271: private char getEscaped() throws IOException {
272:
273: pos++;
274: if (pos == length) {
275: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
276: }
277:
278: switch (chars[pos]) {
279: case '"':
280: case '\\':
281: hasQE = true;
282: case ',':
283: case '=':
284: case '+':
285: case '<':
286: case '>':
287: case '#':
288: case ';':
289: case ' ':
290: //FIXME: escaping is allowed only for leading or trailing space char
291: return chars[pos];
292: default:
293: // RFC doesn't explicitly say that escaped hex pair is
294: // interpreted as UTF-8 char. It only contains an example of such DN.
295: return getUTF8();
296: }
297: }
298:
299: // decodes UTF-8 char
300: // see http://www.unicode.org for UTF-8 bit distribution table
301: protected char getUTF8() throws IOException {
302:
303: int res = getByte(pos);
304: pos++; //FIXME tmp
305:
306: if (res < 128) { // one byte: 0-7F
307: return (char) res;
308: } else if (res >= 192 && res <= 247) {
309:
310: int count;
311: if (res <= 223) { // two bytes: C0-DF
312: count = 1;
313: res = res & 0x1F;
314: } else if (res <= 239) { // three bytes: E0-EF
315: count = 2;
316: res = res & 0x0F;
317: } else { // four bytes: F0-F7
318: count = 3;
319: res = res & 0x07;
320: }
321:
322: int b;
323: for (int i = 0; i < count; i++) {
324: pos++;
325: if (pos == length || chars[pos] != '\\') {
326: return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
327: }
328: pos++;
329:
330: b = getByte(pos);
331: pos++; //FIXME tmp
332: if ((b & 0xC0) != 0x80) {
333: return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
334: }
335:
336: res = (res << 6) + (b & 0x3F);
337: }
338: return (char) res;
339: } else {
340: return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
341: }
342: }
343:
344: // Returns byte representation of a char pair
345: // The char pair is composed of DN char in
346: // specified 'position' and the next char
347: // According to BNF syntax:
348: // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
349: // / "a" / "b" / "c" / "d" / "e" / "f"
350: protected int getByte(int position) throws IOException {
351:
352: if ((position + 1) >= length) {
353: // to avoid ArrayIndexOutOfBoundsException
354: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
355: }
356:
357: int b1, b2;
358:
359: b1 = chars[position];
360: if (b1 >= '0' && b1 <= '9') {
361: b1 = b1 - '0';
362: } else if (b1 >= 'a' && b1 <= 'f') {
363: b1 = b1 - 87; // 87 = 'a' - 10
364: } else if (b1 >= 'A' && b1 <= 'F') {
365: b1 = b1 - 55; // 55 = 'A' - 10
366: } else {
367: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
368: }
369:
370: b2 = chars[position + 1];
371: if (b2 >= '0' && b2 <= '9') {
372: b2 = b2 - '0';
373: } else if (b2 >= 'a' && b2 <= 'f') {
374: b2 = b2 - 87; // 87 = 'a' - 10
375: } else if (b2 >= 'A' && b2 <= 'F') {
376: b2 = b2 - 55; // 55 = 'A' - 10
377: } else {
378: throw new IOException(Messages.getString("security.192")); //$NON-NLS-1$
379: }
380:
381: return (b1 << 4) + b2;
382: }
383:
384: /**
385: * Parses DN
386: *
387: * @return a list of Relative Distinguished Names(RND),
388: * each RDN is represented as a list of AttributeTypeAndValue objects
389: */
390: public List parse() throws IOException {
391:
392: List list = new ArrayList();
393:
394: String attValue;
395: String attType = nextAT();
396: if (attType == null) {
397: return list; //empty list of RDNs
398: }
399:
400: List atav = new ArrayList();
401: while (true) {
402:
403: if (pos == length) {
404:
405: //empty Attribute Value
406: atav.add(new AttributeTypeAndValue(attType,
407: new AttributeValue("", false))); //$NON-NLS-1$
408: list.add(0, atav);
409:
410: return list;
411: }
412:
413: switch (chars[pos]) {
414: case '"':
415: attValue = quotedAV();
416: atav.add(new AttributeTypeAndValue(attType,
417: new AttributeValue(attValue, hasQE)));
418: break;
419: case '#':
420: attValue = hexAV();
421:
422: atav.add(new AttributeTypeAndValue(attType,
423: new AttributeValue(attValue, encoded)));
424: break;
425: case '+':
426: case ',':
427: case ';': // compatibility with RFC 1779: semicolon can separate RDNs
428: //empty attribute value
429: atav.add(new AttributeTypeAndValue(attType,
430: new AttributeValue("", false))); //$NON-NLS-1$
431: break;
432: default:
433: attValue = escapedAV();
434: atav.add(new AttributeTypeAndValue(attType,
435: new AttributeValue(attValue, hasQE)));
436: }
437:
438: if (pos >= length) {
439: list.add(0, atav);
440: return list;
441: }
442:
443: if (chars[pos] == ',' || chars[pos] == ';') {
444: list.add(0, atav);
445: atav = new ArrayList();
446: } else if (chars[pos] != '+') {
447: throw new IOException(Messages
448: .getString("security.192")); //$NON-NLS-1$
449: }
450:
451: pos++;
452: attType = nextAT();
453: if (attType == null) {
454: throw new IOException(Messages
455: .getString("security.192")); //$NON-NLS-1$
456: }
457: }
458: }
459: }
|