001: /*
002: * @(#)NameConstraintsExtension.java 1.22 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.security.x509;
029:
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.OutputStream;
033: import java.lang.reflect.Array;
034: import java.security.Principal;
035: import java.security.cert.CertificateEncodingException;
036: import java.security.cert.CertificateException;
037: import java.security.cert.CertificateParsingException;
038: import java.security.cert.X509Certificate;
039: import java.util.*;
040:
041: import javax.security.auth.x500.X500Principal;
042:
043: import sun.security.util.*;
044: import sun.security.pkcs.PKCS9Attribute;
045:
046: /**
047: * This class defines the Name Constraints Extension.
048: * <p>
049: * The name constraints extension provides permitted and excluded
050: * subtrees that place restrictions on names that may be included within
051: * a certificate issued by a given CA. Restrictions may apply to the
052: * subject distinguished name or subject alternative names. Any name
053: * matching a restriction in the excluded subtrees field is invalid
054: * regardless of information appearing in the permitted subtrees.
055: * <p>
056: * The ASN.1 syntax for this is:
057: * <pre>
058: * NameConstraints ::= SEQUENCE {
059: * permittedSubtrees [0] GeneralSubtrees OPTIONAL,
060: * excludedSubtrees [1] GeneralSubtrees OPTIONAL
061: * }
062: * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
063: * </pre>
064: *
065: * @author Amit Kapoor
066: * @author Hemma Prafullchandra
067: * @version 1.15
068: * @see Extension
069: * @see CertAttrSet
070: */
071: public class NameConstraintsExtension extends Extension implements
072: CertAttrSet {
073: /**
074: * Identifier for this attribute, to be used with the
075: * get, set, delete methods of Certificate, x509 type.
076: */
077: public static final String IDENT = "x509.info.extensions.NameConstraints";
078: /**
079: * Attribute names.
080: */
081: public static final String NAME = "NameConstraints";
082: public static final String PERMITTED_SUBTREES = "permitted_subtrees";
083: public static final String EXCLUDED_SUBTREES = "excluded_subtrees";
084:
085: // Private data members
086: private static final byte TAG_PERMITTED = 0;
087: private static final byte TAG_EXCLUDED = 1;
088:
089: private GeneralSubtrees permitted = null;
090: private GeneralSubtrees excluded = null;
091:
092: private boolean hasMin;
093: private boolean hasMax;
094: private boolean minMaxValid = false;
095:
096: // Recalculate hasMin and hasMax flags.
097: private void calcMinMax() throws IOException {
098: hasMin = false;
099: hasMax = false;
100: if (excluded != null) {
101: for (int i = 0; i < excluded.size(); i++) {
102: GeneralSubtree subtree = excluded.get(i);
103: if (subtree.getMinimum() != 0)
104: hasMin = true;
105: if (subtree.getMaximum() != -1)
106: hasMax = true;
107: }
108: }
109:
110: if (permitted != null) {
111: for (int i = 0; i < permitted.size(); i++) {
112: GeneralSubtree subtree = permitted.get(i);
113: if (subtree.getMinimum() != 0)
114: hasMin = true;
115: if (subtree.getMaximum() != -1)
116: hasMax = true;
117: }
118: }
119: minMaxValid = true;
120: }
121:
122: // Encode this extension value.
123: private void encodeThis() throws IOException {
124: minMaxValid = false;
125: if (permitted == null && excluded == null) {
126: this .extensionValue = null;
127: return;
128: }
129: DerOutputStream seq = new DerOutputStream();
130:
131: DerOutputStream tagged = new DerOutputStream();
132: if (permitted != null) {
133: DerOutputStream tmp = new DerOutputStream();
134: permitted.encode(tmp);
135: tagged.writeImplicit(DerValue.createTag(
136: DerValue.TAG_CONTEXT, true, TAG_PERMITTED), tmp);
137: }
138: if (excluded != null) {
139: DerOutputStream tmp = new DerOutputStream();
140: excluded.encode(tmp);
141: tagged.writeImplicit(DerValue.createTag(
142: DerValue.TAG_CONTEXT, true, TAG_EXCLUDED), tmp);
143: }
144: seq.write(DerValue.tag_Sequence, tagged);
145: this .extensionValue = seq.toByteArray();
146: }
147:
148: /**
149: * The default constructor for this class. Both parameters
150: * are optional and can be set to null. The extension criticality
151: * is set to true.
152: *
153: * @param permitted the permitted GeneralSubtrees (null for optional).
154: * @param excluded the excluded GeneralSubtrees (null for optional).
155: */
156: public NameConstraintsExtension(GeneralSubtrees permitted,
157: GeneralSubtrees excluded) throws IOException {
158: this .permitted = permitted;
159: this .excluded = excluded;
160:
161: this .extensionId = PKIXExtensions.NameConstraints_Id;
162: this .critical = true;
163: encodeThis();
164: }
165:
166: /**
167: * Create the extension from the passed DER encoded value.
168: *
169: * @param critical true if the extension is to be treated as critical.
170: * @param value Array of DER encoded bytes of the actual value.
171: * @exception IOException on error.
172: */
173: public NameConstraintsExtension(Boolean critical, Object value)
174: throws IOException {
175: this .extensionId = PKIXExtensions.NameConstraints_Id;
176: this .critical = critical.booleanValue();
177:
178: if (!(value instanceof byte[]))
179: throw new IOException("Illegal argument type");
180:
181: int len = Array.getLength(value);
182: byte[] extValue = new byte[len];
183: System.arraycopy(value, 0, extValue, 0, len);
184:
185: this .extensionValue = extValue;
186: DerValue val = new DerValue(extValue);
187: if (val.tag != DerValue.tag_Sequence) {
188: throw new IOException("Invalid encoding for"
189: + " NameConstraintsExtension.");
190: }
191:
192: // NB. this is always encoded with the IMPLICIT tag
193: // The checks only make sense if we assume implicit tagging,
194: // with explicit tagging the form is always constructed.
195: // Note that all the fields in NameConstraints are defined as
196: // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting
197: // in val.data being null.
198: if (val.data == null)
199: return;
200: while (val.data.available() != 0) {
201: DerValue opt = val.data.getDerValue();
202:
203: if (opt.isContextSpecific(TAG_PERMITTED)
204: && opt.isConstructed()) {
205: if (permitted != null) {
206: throw new IOException(
207: "Duplicate permitted "
208: + "GeneralSubtrees in NameConstraintsExtension.");
209: }
210: opt.resetTag(DerValue.tag_Sequence);
211: permitted = new GeneralSubtrees(opt);
212:
213: } else if (opt.isContextSpecific(TAG_EXCLUDED)
214: && opt.isConstructed()) {
215: if (excluded != null) {
216: throw new IOException(
217: "Duplicate excluded "
218: + "GeneralSubtrees in NameConstraintsExtension.");
219: }
220: opt.resetTag(DerValue.tag_Sequence);
221: excluded = new GeneralSubtrees(opt);
222: } else
223: throw new IOException("Invalid encoding of "
224: + "NameConstraintsExtension.");
225: }
226: minMaxValid = false;
227: }
228:
229: /**
230: * Return the printable string.
231: */
232: public String toString() {
233: return (super .toString()
234: + "NameConstraints: ["
235: + ((permitted == null) ? ""
236: : ("\n Permitted:" + permitted.toString()))
237: + ((excluded == null) ? ""
238: : ("\n Excluded:" + excluded.toString())) + " ]\n");
239: }
240:
241: /**
242: * Decode the extension from the InputStream.
243: *
244: * @param in the InputStream to unmarshal the contents from.
245: * @exception IOException on decoding or validity errors.
246: */
247: public void decode(InputStream in) throws IOException {
248: throw new IOException("Method not to be called directly.");
249: }
250:
251: /**
252: * Write the extension to the OutputStream.
253: *
254: * @param out the OutputStream to write the extension to.
255: * @exception IOException on encoding errors.
256: */
257: public void encode(OutputStream out) throws IOException {
258: DerOutputStream tmp = new DerOutputStream();
259: if (this .extensionValue == null) {
260: this .extensionId = PKIXExtensions.NameConstraints_Id;
261: this .critical = true;
262: encodeThis();
263: }
264: super .encode(tmp);
265: out.write(tmp.toByteArray());
266: }
267:
268: /**
269: * Set the attribute value.
270: */
271: public void set(String name, Object obj) throws IOException {
272: if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
273: if (!(obj instanceof GeneralSubtrees)) {
274: throw new IOException("Attribute value should be"
275: + " of type GeneralSubtrees.");
276: }
277: permitted = (GeneralSubtrees) obj;
278: } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
279: if (!(obj instanceof GeneralSubtrees)) {
280: throw new IOException("Attribute value should be "
281: + "of type GeneralSubtrees.");
282: }
283: excluded = (GeneralSubtrees) obj;
284: } else {
285: throw new IOException("Attribute name not recognized by "
286: + "CertAttrSet:NameConstraintsExtension.");
287: }
288: encodeThis();
289: }
290:
291: /**
292: * Get the attribute value.
293: */
294: public Object get(String name) throws IOException {
295: if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
296: return (permitted);
297: } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
298: return (excluded);
299: } else {
300: throw new IOException("Attribute name not recognized by "
301: + "CertAttrSet:NameConstraintsExtension.");
302: }
303: }
304:
305: /**
306: * Delete the attribute value.
307: */
308: public void delete(String name) throws IOException {
309: if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
310: permitted = null;
311: } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
312: excluded = null;
313: } else {
314: throw new IOException("Attribute name not recognized by "
315: + "CertAttrSet:NameConstraintsExtension.");
316: }
317: encodeThis();
318: }
319:
320: /**
321: * Return an enumeration of names of attributes existing within this
322: * attribute.
323: */
324: public Enumeration getElements() {
325: AttributeNameEnumeration elements = new AttributeNameEnumeration();
326: elements.addElement(PERMITTED_SUBTREES);
327: elements.addElement(EXCLUDED_SUBTREES);
328:
329: return (elements.elements());
330: }
331:
332: /**
333: * Return the name of this attribute.
334: */
335: public String getName() {
336: return (NAME);
337: }
338:
339: /**
340: * Merge additional name constraints with existing ones.
341: * This function is used in certification path processing
342: * to accumulate name constraints from successive certificates
343: * in the path. Note that NameConstraints can never be
344: * expanded by a merge, just remain constant or become more
345: * limiting.
346: * <p>
347: * IETF RFC2459 specifies the processing of Name Constraints as
348: * follows:
349: * <p>
350: * (j) If permittedSubtrees is present in the certificate, set the
351: * constrained subtrees state variable to the intersection of its
352: * previous value and the value indicated in the extension field.
353: * <p>
354: * (k) If excludedSubtrees is present in the certificate, set the
355: * excluded subtrees state variable to the union of its previous
356: * value and the value indicated in the extension field.
357: * <p>
358: * @param newConstraints additional NameConstraints to be applied
359: * @throws IOException on error
360: */
361: public void merge(NameConstraintsExtension newConstraints)
362: throws IOException {
363:
364: if (newConstraints == null) {
365: // absence of any explicit constraints implies unconstrained
366: return;
367: }
368:
369: /*
370: * If excludedSubtrees is present in the certificate, set the
371: * excluded subtrees state variable to the union of its previous
372: * value and the value indicated in the extension field.
373: */
374:
375: GeneralSubtrees newExcluded = (GeneralSubtrees) newConstraints
376: .get(EXCLUDED_SUBTREES);
377: if (excluded == null) {
378: excluded = (newExcluded != null) ? (GeneralSubtrees) newExcluded
379: .clone()
380: : null;
381: } else {
382: if (newExcluded != null) {
383: // Merge new excluded with current excluded (union)
384: excluded.union(newExcluded);
385: }
386: }
387:
388: /*
389: * If permittedSubtrees is present in the certificate, set the
390: * constrained subtrees state variable to the intersection of its
391: * previous value and the value indicated in the extension field.
392: */
393:
394: GeneralSubtrees newPermitted = (GeneralSubtrees) newConstraints
395: .get(PERMITTED_SUBTREES);
396: if (permitted == null) {
397: permitted = (newPermitted != null) ? (GeneralSubtrees) newPermitted
398: .clone()
399: : null;
400: } else {
401: if (newPermitted != null) {
402: // Merge new permitted with current permitted (intersection)
403: newExcluded = permitted.intersect(newPermitted);
404:
405: // Merge new excluded subtrees to current excluded (union)
406: if (newExcluded != null) {
407: if (excluded != null) {
408: excluded.union(newExcluded);
409: } else {
410: excluded = (GeneralSubtrees) newExcluded
411: .clone();
412: }
413: }
414: }
415: }
416:
417: // Optional optimization: remove permitted subtrees that are excluded.
418: // This is not necessary for algorithm correctness, but it makes
419: // subsequent operations on the NameConstraints faster and require
420: // less space.
421: if (permitted != null) {
422: permitted.reduce(excluded);
423: }
424:
425: // The NameConstraints have been changed, so re-encode them. Methods in
426: // this class assume that the encodings have already been done.
427: encodeThis();
428:
429: }
430:
431: /**
432: * check whether a certificate conforms to these NameConstraints.
433: * This involves verifying that the subject name and subjectAltName
434: * extension (critical or noncritical) is consistent with the permitted
435: * subtrees state variables. Also verify that the subject name and
436: * subjectAltName extension (critical or noncritical) is consistent with
437: * the excluded subtrees state variables.
438: *
439: * @param cert X509Certificate to be verified
440: * @returns true if certificate verifies successfully
441: * @throws IOException on error
442: */
443: public boolean verify(X509Certificate cert) throws IOException {
444:
445: if (cert == null) {
446: throw new IOException("Certificate is null");
447: }
448:
449: // Calculate hasMin and hasMax booleans (if necessary)
450: if (!minMaxValid) {
451: calcMinMax();
452: }
453:
454: if (hasMin) {
455: throw new IOException("Non-zero minimum BaseDistance in"
456: + " name constraints not supported");
457: }
458:
459: if (hasMax) {
460: throw new IOException("Maximum BaseDistance in"
461: + " name constraints not supported");
462: }
463:
464: X500Principal subjectPrincipal = cert.getSubjectX500Principal();
465: X500Name subject = X500Name.asX500Name(subjectPrincipal);
466:
467: if (subject.isEmpty() == false) {
468: if (verify(subject) == false) {
469: return false;
470: }
471: }
472:
473: GeneralNames altNames = null;
474: // extract altNames
475: try {
476: // extract extensions, if any, from certInfo
477: // following returns null if certificate contains no extensions
478: X509CertImpl certImpl = X509CertImpl.toImpl(cert);
479: SubjectAlternativeNameExtension altNameExt = certImpl
480: .getSubjectAlternativeNameExtension();
481: if (altNameExt != null) {
482: // extract altNames from extension; this call does not
483: // return an IOException on null altnames
484: altNames = (GeneralNames) (altNameExt
485: .get(altNameExt.SUBJECT_NAME));
486: }
487: } catch (CertificateException ce) {
488: throw new IOException("Unable to extract extensions from "
489: + "certificate: " + ce.getMessage());
490: }
491:
492: // If there are no subjectAlternativeNames, perform the special-case
493: // check where if the subjectName contains any EMAILADDRESS
494: // attributes, they must be checked against RFC822 constraints.
495: // If that passes, we're fine.
496: if (altNames == null) {
497: return verifyRFC822SpecialCase(subject);
498: }
499:
500: // verify each subjectAltName
501: for (int i = 0; i < altNames.size(); i++) {
502: GeneralNameInterface altGNI = altNames.get(i).getName();
503: if (!verify(altGNI)) {
504: return false;
505: }
506: }
507:
508: // All tests passed.
509: return true;
510: }
511:
512: /**
513: * check whether a name conforms to these NameConstraints.
514: * This involves verifying that the name is consistent with the
515: * permitted and excluded subtrees variables.
516: *
517: * @param name GeneralNameInterface name to be verified
518: * @returns true if certificate verifies successfully
519: * @throws IOException on error
520: */
521: public boolean verify(GeneralNameInterface name) throws IOException {
522: if (name == null) {
523: throw new IOException("name is null");
524: }
525:
526: // Verify that the name is consistent with the excluded subtrees
527: if (excluded != null && excluded.size() > 0) {
528:
529: for (int i = 0; i < excluded.size(); i++) {
530: GeneralSubtree gs = (GeneralSubtree) (excluded.get(i));
531: if (gs == null)
532: continue;
533: GeneralName gn = gs.getName();
534: if (gn == null)
535: continue;
536: GeneralNameInterface exName = gn.getName();
537: if (exName == null)
538: continue;
539:
540: // if name matches or narrows any excluded subtree,
541: // return false
542: switch (exName.constrains(name)) {
543: case GeneralNameInterface.NAME_DIFF_TYPE:
544: case GeneralNameInterface.NAME_WIDENS: // name widens excluded
545: case GeneralNameInterface.NAME_SAME_TYPE:
546: break;
547: case GeneralNameInterface.NAME_MATCH:
548: case GeneralNameInterface.NAME_NARROWS: // subject name excluded
549: return false;
550: }
551: }
552: }
553:
554: // Verify that the name is consistent with the permitted subtrees
555: if (permitted != null && permitted.size() > 0) {
556:
557: boolean sameType = false;
558:
559: for (int i = 0; i < permitted.size(); i++) {
560: GeneralSubtree gs = (GeneralSubtree) (permitted.get(i));
561: if (gs == null)
562: continue;
563: GeneralName gn = gs.getName();
564: if (gn == null)
565: continue;
566: GeneralNameInterface perName = gn.getName();
567: if (perName == null)
568: continue;
569:
570: // if Name matches any type in permitted,
571: // and Name does not match or narrow some permitted subtree,
572: // return false
573: switch (perName.constrains(name)) {
574: case GeneralNameInterface.NAME_DIFF_TYPE:
575: continue; // continue checking other permitted names
576: case GeneralNameInterface.NAME_WIDENS: // name widens permitted
577: case GeneralNameInterface.NAME_SAME_TYPE:
578: sameType = true;
579: continue; // continue to look for a match or narrow
580: case GeneralNameInterface.NAME_MATCH:
581: case GeneralNameInterface.NAME_NARROWS:
582: // name narrows permitted
583: return true; // name is definitely OK, so break out of loop
584: }
585: }
586: if (sameType) {
587: return false;
588: }
589: }
590: return true;
591: }
592:
593: /**
594: * Perform the RFC 822 special case check. We have a certificate
595: * that does not contain any subject alternative names. Check that
596: * any EMAILADDRESS attributes in its subject name conform to these
597: * NameConstraints.
598: *
599: * @param subject the certificate's subject name
600: * @returns true if certificate verifies successfully
601: * @throws IOException on error
602: */
603: public boolean verifyRFC822SpecialCase(X500Name subject)
604: throws IOException {
605: for (Iterator t = subject.allAvas().iterator(); t.hasNext();) {
606: AVA ava = (AVA) t.next();
607: ObjectIdentifier attrOID = ava.getObjectIdentifier();
608: if (attrOID.equals(PKCS9Attribute.EMAIL_ADDRESS_OID)) {
609: String attrValue = ava.getValueString();
610: if (attrValue != null) {
611: RFC822Name emailName;
612: try {
613: emailName = new RFC822Name(attrValue);
614: } catch (IOException ioe) {
615: continue;
616: }
617: if (!verify(emailName)) {
618: return (false);
619: }
620: }
621: }
622: }
623: return true;
624: }
625: }
|