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 Vladimir N. Molotkov, Alexander Y. Kleymenov
020: * @version $Revision$
021: */package org.apache.harmony.security.x509;
022:
023: import java.io.IOException;
024: import java.security.cert.X509Certificate;
025: import java.util.ArrayList;
026: import java.util.Iterator;
027: import java.util.List;
028:
029: import org.apache.harmony.security.asn1.ASN1Implicit;
030: import org.apache.harmony.security.asn1.ASN1OctetString;
031: import org.apache.harmony.security.asn1.ASN1Sequence;
032: import org.apache.harmony.security.asn1.ASN1Type;
033: import org.apache.harmony.security.asn1.BerInputStream;
034: import org.apache.harmony.security.internal.nls.Messages;
035:
036: /**
037: * The class encapsulates the ASN.1 DER encoding/decoding work
038: * with the following structure which is a part of X.509 certificate
039: * (as specified in RFC 3280 -
040: * Internet X.509 Public Key Infrastructure.
041: * Certificate and Certificate Revocation List (CRL) Profile.
042: * http://www.ietf.org/rfc/rfc3280.txt):
043: *
044: * <pre>
045: *
046: * NameConstraints ::= SEQUENCE {
047: * permittedSubtrees [0] GeneralSubtrees OPTIONAL,
048: * excludedSubtrees [1] GeneralSubtrees OPTIONAL }
049: *
050: * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
051: *
052: * </pre>
053: *
054: *
055: * @see org.apache.harmony.security.x509.GeneralSubtree
056: * @see org.apache.harmony.security.x509.GeneralName
057: */
058: public class NameConstraints extends ExtensionValue {
059:
060: // the value of permittedSubtrees field of the structure
061: private final GeneralSubtrees permittedSubtrees;
062: // the value of excludedSubtrees field of the structure
063: private final GeneralSubtrees excludedSubtrees;
064: // the ASN.1 encoded form of NameConstraints
065: private byte[] encoding;
066:
067: // helper fields
068: private ArrayList[] permitted_names;
069: private ArrayList[] excluded_names;
070:
071: /**
072: * Default ctor
073: */
074: public NameConstraints() {
075: this (null, null);
076: }
077:
078: /**
079: * Constructs <code>NameConstrains</code> object
080: * @param permittedSubtrees: GeneralSubtrees
081: * @param excludedSubtrees: GeneralSubtrees
082: */
083: public NameConstraints(GeneralSubtrees permittedSubtrees,
084: GeneralSubtrees excludedSubtrees) {
085: if (permittedSubtrees != null) {
086: List ps = permittedSubtrees.getSubtrees();
087: if ((ps == null) || (ps.size() == 0)) {
088: throw new IllegalArgumentException(Messages
089: .getString("security.17D")); //$NON-NLS-1$
090: }
091: }
092: if (excludedSubtrees != null) {
093: List es = excludedSubtrees.getSubtrees();
094: if ((es == null) || (es.size() == 0)) {
095: throw new IllegalArgumentException(Messages
096: .getString("security.17E")); //$NON-NLS-1$
097: }
098: }
099: this .permittedSubtrees = permittedSubtrees;
100: this .excludedSubtrees = excludedSubtrees;
101: }
102:
103: //
104: // Constructs NameConstrains object
105: // @param permittedSubtrees: GeneralSubtrees
106: // @param excludedSubtrees: GeneralSubtrees
107: // @param encoding: byte[]
108: //
109: private NameConstraints(GeneralSubtrees permittedSubtrees,
110: GeneralSubtrees excludedSubtrees, byte[] encoding) {
111: this (permittedSubtrees, excludedSubtrees);
112: this .encoding = encoding;
113: }
114:
115: public static NameConstraints decode(byte[] encoding)
116: throws IOException {
117: return (NameConstraints) ASN1.decode(encoding);
118: }
119:
120: /**
121: * Returns ASN.1 encoded form of this X.509 NameConstraints value.
122: * @return a byte array containing ASN.1 encode form.
123: */
124: public byte[] getEncoded() {
125: if (encoding == null) {
126: encoding = ASN1.encode(this );
127: }
128: return encoding;
129: }
130:
131: //
132: // Prepare the data structure to speed up the checking process.
133: //
134: private void prepareNames() {
135: // array of lists with permitted General Names divided by type
136: permitted_names = new ArrayList[9];
137: if (permittedSubtrees != null) {
138: Iterator it = permittedSubtrees.getSubtrees().iterator();
139: while (it.hasNext()) {
140: GeneralName name = ((GeneralSubtree) it.next())
141: .getBase();
142: //System.out.println("PERMITTED: "+name);
143: int tag = name.getTag();
144: if (permitted_names[tag] == null) {
145: permitted_names[tag] = new ArrayList();
146: }
147: permitted_names[tag].add(name);
148: }
149: }
150: // array of lists with excluded General Names divided by type
151: excluded_names = new ArrayList[9];
152: if (excludedSubtrees != null) {
153: Iterator it = excludedSubtrees.getSubtrees().iterator();
154: while (it.hasNext()) {
155: GeneralName name = ((GeneralSubtree) it.next())
156: .getBase();
157: //System.out.println("EXCLUDED: "+name);
158: int tag = name.getTag();
159: if (excluded_names[tag] == null) {
160: excluded_names[tag] = new ArrayList();
161: }
162: excluded_names[tag].add(name);
163: }
164: }
165: }
166:
167: //
168: // Returns the value of certificate extension
169: //
170: private byte[] getExtensionValue(X509Certificate cert, String OID) {
171: try {
172: byte[] bytes = cert.getExtensionValue(OID);
173: if (bytes == null) {
174: return null;
175: }
176: return (byte[]) ASN1OctetString.getInstance().decode(bytes);
177: } catch (IOException e) {
178: return null;
179: }
180: }
181:
182: /**
183: * Apply the name restrictions specified by this NameConstraints
184: * instance to the subject distinguished name and subject alternative
185: * names of specified X509Certificate. Restrictions apply only
186: * if specified name form is present in the certificate.
187: * The restrictions are applied according the RFC 3280
188: * (see 4.2.1.11 Name Constraints), excepting that restrictions are applied
189: * and to CA certificates, and to certificates which issuer and subject
190: * names the same (i.e. method does not check if it CA's certificate or not,
191: * or if the names differ or not. This check if it is needed should be done
192: * by caller before calling this method).
193: * @param X509Certificate : X.509 Certificate to be checked.
194: * @return true, if the certificate is acceptable according
195: * these NameConstraints restrictions, and false otherwise.
196: */
197: public boolean isAcceptable(X509Certificate cert) {
198: if (permitted_names == null) {
199: prepareNames();
200: }
201:
202: byte[] bytes = getExtensionValue(cert, "2.5.29.17"); //$NON-NLS-1$
203: List names;
204: try {
205: names = (bytes == null) ? new ArrayList(1) // will check the subject field only
206: : ((GeneralNames) GeneralNames.ASN1.decode(bytes))
207: .getNames();
208: } catch (IOException e) {
209: // the certificate is broken;
210: e.printStackTrace();
211: return false;
212: }
213: if ((excluded_names[4] != null) || (permitted_names[4] != null)) {
214: try {
215: names.add(new GeneralName(4, cert
216: .getSubjectX500Principal().getName()));
217: } catch (IOException e) {
218: // should never be happened
219: }
220: }
221: return isAcceptable(names);
222: }
223:
224: /**
225: * Check if this list of names is acceptable accoring to this
226: * NameConstraints object.
227: * @param names: List
228: * @return
229: */
230: public boolean isAcceptable(List names) {
231: if (permitted_names == null) {
232: prepareNames();
233: }
234:
235: Iterator it = names.iterator();
236: // check map: shows which types of permitted alternative names are
237: // presented in the certificate
238: boolean[] types_presented = new boolean[9];
239: // check map: shows if permitted name of presented type is found
240: // among the certificate's alternative names
241: boolean[] permitted_found = new boolean[9];
242: while (it.hasNext()) {
243: GeneralName name = (GeneralName) it.next();
244: int type = name.getTag();
245: // search the name in excluded names
246: if (excluded_names[type] != null) {
247: for (int i = 0; i < excluded_names[type].size(); i++) {
248: if (((GeneralName) excluded_names[type].get(i))
249: .isAcceptable(name)) {
250: return false;
251: }
252: }
253: }
254: // Search the name in permitted names
255: // (if we already found the name of such type between the alt
256: // names - we do not need to check others)
257: if ((permitted_names[type] != null)
258: && (!permitted_found[type])) {
259: types_presented[type] = true;
260: for (int i = 0; i < permitted_names[type].size(); i++) {
261: if (((GeneralName) permitted_names[type].get(i))
262: .isAcceptable(name)) {
263: // found one permitted name of such type
264: permitted_found[type] = true;
265: }
266: }
267: }
268: }
269: for (int type = 0; type < 9; type++) {
270: if (types_presented[type] && !permitted_found[type]) {
271: return false;
272: }
273: }
274: return true;
275: }
276:
277: /**
278: * Places the string representation of extension value
279: * into the StringBuffer object.
280: */
281: public void dumpValue(StringBuffer buffer, String prefix) {
282: buffer.append(prefix).append("Name Constraints: [\n"); //$NON-NLS-1$
283: if (permittedSubtrees != null) {
284: buffer.append(prefix).append(" Permitted: [\n"); //$NON-NLS-1$
285: for (Iterator it = permittedSubtrees.getSubtrees()
286: .iterator(); it.hasNext();) {
287: ((GeneralSubtree) it.next()).dumpValue(buffer, prefix
288: + " "); //$NON-NLS-1$
289: }
290: buffer.append(prefix).append(" ]\n"); //$NON-NLS-1$
291: }
292: if (excludedSubtrees != null) {
293: buffer.append(prefix).append(" Excluded: [\n"); //$NON-NLS-1$
294: for (Iterator it = excludedSubtrees.getSubtrees()
295: .iterator(); it.hasNext();) {
296: ((GeneralSubtree) it.next()).dumpValue(buffer, prefix
297: + " "); //$NON-NLS-1$
298: }
299: buffer.append(prefix).append(" ]\n"); //$NON-NLS-1$
300: }
301: buffer.append('\n').append(prefix).append("]\n"); //$NON-NLS-1$
302: }
303:
304: /**
305: * X.509 NameConstraints encoder/decoder.
306: */
307: public static final ASN1Sequence ASN1 = new ASN1Sequence(
308: new ASN1Type[] { new ASN1Implicit(0, GeneralSubtrees.ASN1),
309: new ASN1Implicit(1, GeneralSubtrees.ASN1) }) {
310: {
311: setOptional(0);
312: setOptional(1);
313: }
314:
315: protected Object getDecodedObject(BerInputStream in) {
316: Object[] values = (Object[]) in.content;
317: return new NameConstraints((GeneralSubtrees) values[0],
318: (GeneralSubtrees) values[1], in.getEncoded());
319: }
320:
321: protected void getValues(Object object, Object[] values) {
322:
323: NameConstraints nc = (NameConstraints) object;
324:
325: values[0] = nc.permittedSubtrees;
326: values[1] = nc.excludedSubtrees;
327: }
328: };
329: }
|