1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.ws.security.components.crypto;
21
22 import java.math.BigInteger;
23 import org.apache.ws.security.WSSecurityException;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 /**
29 * Provides the means to navigate through a DER-encoded byte array, to help
30 * in decoding the contents.
31 * <p>
32 * It maintains a "current position" in the array that advances with each
33 * operation, providing a simple means to handle the type-length-value
34 * encoding of DER. For example
35 * <pre>
36 * decoder.expect(TYPE);
37 * int length = decoder.getLength();
38 * byte[] value = decoder.getBytes(len);
39 * </pre>
40 */
41 public class DERDecoder {
42 private static Log log = LogFactory.getLog(DERDecoder.class);
43
44 /** DER type identifier for a bit string value */
45 public static final byte TYPE_BIT_STRING = 0x03;
46 /** DER type identifier for a octet string value */
47 public static final byte TYPE_OCTET_STRING = 0x04;
48 /** DER type identifier for a sequence value */
49 public static final byte TYPE_SEQUENCE = 0x30;
50
51 private byte[] arr;
52 private int pos;
53
54 /**
55 * Construct a DERDecoder for the given byte array.
56 *
57 * @param derEncoded the DER-encoded array to decode.
58 * @throws WSSecurityException if the given array is null.
59 */
60 public DERDecoder(byte[] derEncoded) throws WSSecurityException {
61 if (derEncoded == null) {
62 throw new WSSecurityException(
63 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
64 "noSKIHandling",
65 new Object[] { "Invalid DER string" }
66 );
67 }
68 arr = derEncoded;
69 reset();
70 }
71
72
73 /**
74 * Reset the current position to the start of the array.
75 */
76 public void reset() {
77 pos = 0;
78 }
79
80 /**
81 * Advance the current position by the given number of bytes.
82 *
83 * @param length the number of bytes to skip.
84 * @throws WSSecurityException if length is negative.
85 */
86 public void skip(int length) throws WSSecurityException {
87 if (length < 0) {
88 throw new WSSecurityException(
89 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
90 "noSKIHandling",
91 new Object[] { "Unsupported DER format" }
92 );
93 }
94 pos += length;
95 }
96
97 /**
98 * Confirm that the byte at the current position matches the given value.
99 *
100 * @param val the expected next byte.
101 * @throws WSSecurityException
102 * if the current position is at the end of the array, or if the
103 * byte at the current position doesn't match the expected value.
104 */
105 public void expect(int val) throws WSSecurityException {
106 expect((byte)(val & 0xFF));
107 }
108
109 /**
110 * Confirm that the byte at the current position matches the given value.
111 *
112 * @param val the expected next byte.
113 * @throws WSSecurityException
114 * if the current position is at the end of the array, or if the
115 * byte at the current position doesn't match the expected value.
116 */
117 public void expect(byte val) throws WSSecurityException {
118 if (!test(val)) {
119 log.debug("DER mismatch: expected " + val + ", got " + arr[pos]);
120 throw new WSSecurityException(
121 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
122 "noSKIHandling",
123 new Object[] { "Invalid DER format" }
124 );
125 }
126 pos++;
127 }
128
129 /**
130 * Test if the byte at the current position matches the given value.
131 *
132 * @param val the value to test for a match with the current byte.
133 * @return true if the byte at the current position matches the given value.
134 * @throws WSSecurityException if the current position is at the end of
135 * the array.
136 */
137 public boolean test(byte val) throws WSSecurityException {
138 if (pos >= arr.length) {
139 throw new WSSecurityException(
140 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
141 "noSKIHandling",
142 new Object[] { "Invalid DER format" }
143 );
144 }
145 return (arr[pos] == val);
146 }
147
148 /**
149 * Get the DER length at the current position.
150 * <p>
151 * DER length is encoded as
152 * <ul>
153 * <li>If the first byte is 0x00 to 0x7F, it describes the actual length.
154 * <li>If the first byte is 0x80 + n with 0<n<0x7F, the actual length is
155 * described in the following 'n' bytes.
156 * <li>The length value 0x80, used only in constructed types, is
157 * defined as "indefinite length".
158 * </ul>
159 *
160 * @return the length, -1 for indefinite length.
161 * @throws WSSecurityException
162 * if the current position is at the end of the array or there is
163 * an incomplete length specification.
164 */
165 public int getLength() throws WSSecurityException {
166 if (pos >= arr.length) {
167 throw new WSSecurityException(
168 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
169 "noSKIHandling",
170 new Object[] { "Invalid DER format" }
171 );
172 }
173 int len;
174 if ((arr[pos] & 0xFF) <= 0x7F) {
175 len = arr[pos++];
176 } else if (arr[pos] == 0x80) {
177 len = -1;
178 pos++;
179 } else {
180 int nbytes = arr[pos++] & 0x7F;
181 if (pos + nbytes > arr.length) {
182 throw new WSSecurityException(
183 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
184 "noSKIHandling",
185 new Object[] { "Invalid DER format" }
186 );
187 }
188 byte[] lenBytes = new byte[nbytes];
189 System.arraycopy(arr, pos, lenBytes, 0, lenBytes.length);
190 len = new BigInteger(1, lenBytes).intValue();
191 pos += nbytes;
192 }
193 return len;
194 }
195
196 /**
197 * Return an array of bytes from the current position.
198 *
199 * @param length the number of bytes to return.
200 * @return an array of the requested number of bytes from the current
201 * position.
202 * @throws WSSecurityException
203 * if the current position is at the end of the array, or the
204 * length is negative.
205 */
206 public byte[] getBytes(int length) throws WSSecurityException {
207 if (pos + length > arr.length) {
208 throw new WSSecurityException(
209 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
210 "noSKIHandling",
211 new Object[] { "Invalid DER format" }
212 );
213 } else if (length < 0) {
214 throw new WSSecurityException(
215 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
216 "noSKIHandling",
217 new Object[] { "Unsupported DER format" }
218 );
219 }
220 byte[] value = new byte[length];
221 System.arraycopy(arr, pos, value, 0, length);
222 pos += length;
223 return value;
224 }
225
226 }