Home | History | Annotate | Download | only in x509
      1 /*
      2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package sun.security.x509;
     27 
     28 import java.io.IOException;
     29 import java.io.OutputStream;
     30 import java.security.cert.CertificateException;
     31 import java.security.cert.X509Certificate;
     32 import java.util.*;
     33 
     34 import javax.security.auth.x500.X500Principal;
     35 
     36 import sun.security.util.*;
     37 import sun.security.pkcs.PKCS9Attribute;
     38 
     39 /**
     40  * This class defines the Name Constraints Extension.
     41  * <p>
     42  * The name constraints extension provides permitted and excluded
     43  * subtrees that place restrictions on names that may be included within
     44  * a certificate issued by a given CA.  Restrictions may apply to the
     45  * subject distinguished name or subject alternative names.  Any name
     46  * matching a restriction in the excluded subtrees field is invalid
     47  * regardless of information appearing in the permitted subtrees.
     48  * <p>
     49  * The ASN.1 syntax for this is:
     50  * <pre>
     51  * NameConstraints ::= SEQUENCE {
     52  *    permittedSubtrees [0]  GeneralSubtrees OPTIONAL,
     53  *    excludedSubtrees  [1]  GeneralSubtrees OPTIONAL
     54  * }
     55  * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
     56  * </pre>
     57  *
     58  * @author Amit Kapoor
     59  * @author Hemma Prafullchandra
     60  * @see Extension
     61  * @see CertAttrSet
     62  */
     63 public class NameConstraintsExtension extends Extension
     64 implements CertAttrSet<String>, Cloneable {
     65     /**
     66      * Identifier for this attribute, to be used with the
     67      * get, set, delete methods of Certificate, x509 type.
     68      */
     69     public static final String IDENT = "x509.info.extensions.NameConstraints";
     70     /**
     71      * Attribute names.
     72      */
     73     public static final String NAME = "NameConstraints";
     74     public static final String PERMITTED_SUBTREES = "permitted_subtrees";
     75     public static final String EXCLUDED_SUBTREES = "excluded_subtrees";
     76 
     77     // Private data members
     78     private static final byte TAG_PERMITTED = 0;
     79     private static final byte TAG_EXCLUDED = 1;
     80 
     81     private GeneralSubtrees     permitted = null;
     82     private GeneralSubtrees     excluded = null;
     83 
     84     private boolean hasMin;
     85     private boolean hasMax;
     86     private boolean minMaxValid = false;
     87 
     88     // Recalculate hasMin and hasMax flags.
     89     private void calcMinMax() throws IOException {
     90         hasMin = false;
     91         hasMax = false;
     92         if (excluded != null) {
     93             for (int i = 0; i < excluded.size(); i++) {
     94                 GeneralSubtree subtree = excluded.get(i);
     95                 if (subtree.getMinimum() != 0)
     96                     hasMin = true;
     97                 if (subtree.getMaximum() != -1)
     98                     hasMax = true;
     99             }
    100         }
    101 
    102         if (permitted != null) {
    103             for (int i = 0; i < permitted.size(); i++) {
    104                 GeneralSubtree subtree = permitted.get(i);
    105                 if (subtree.getMinimum() != 0)
    106                     hasMin = true;
    107                 if (subtree.getMaximum() != -1)
    108                     hasMax = true;
    109             }
    110         }
    111         minMaxValid = true;
    112     }
    113 
    114     // Encode this extension value.
    115     private void encodeThis() throws IOException {
    116         minMaxValid = false;
    117         if (permitted == null && excluded == null) {
    118             this.extensionValue = null;
    119             return;
    120         }
    121         DerOutputStream seq = new DerOutputStream();
    122 
    123         DerOutputStream tagged = new DerOutputStream();
    124         if (permitted != null) {
    125             DerOutputStream tmp = new DerOutputStream();
    126             permitted.encode(tmp);
    127             tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,
    128                                  true, TAG_PERMITTED), tmp);
    129         }
    130         if (excluded != null) {
    131             DerOutputStream tmp = new DerOutputStream();
    132             excluded.encode(tmp);
    133             tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,
    134                                  true, TAG_EXCLUDED), tmp);
    135         }
    136         seq.write(DerValue.tag_Sequence, tagged);
    137         this.extensionValue = seq.toByteArray();
    138     }
    139 
    140     /**
    141      * The default constructor for this class. Both parameters
    142      * are optional and can be set to null.  The extension criticality
    143      * is set to true.
    144      *
    145      * @param permitted the permitted GeneralSubtrees (null for optional).
    146      * @param excluded the excluded GeneralSubtrees (null for optional).
    147      */
    148     public NameConstraintsExtension(GeneralSubtrees permitted,
    149                                     GeneralSubtrees excluded)
    150     throws IOException {
    151         this.permitted = permitted;
    152         this.excluded = excluded;
    153 
    154         this.extensionId = PKIXExtensions.NameConstraints_Id;
    155         this.critical = true;
    156         encodeThis();
    157     }
    158 
    159     /**
    160      * Create the extension from the passed DER encoded value.
    161      *
    162      * @param critical true if the extension is to be treated as critical.
    163      * @param value an array of DER encoded bytes of the actual value.
    164      * @exception ClassCastException if value is not an array of bytes
    165      * @exception IOException on error.
    166      */
    167     public NameConstraintsExtension(Boolean critical, Object value)
    168     throws IOException {
    169         this.extensionId = PKIXExtensions.NameConstraints_Id;
    170         this.critical = critical.booleanValue();
    171 
    172         this.extensionValue = (byte[]) value;
    173         DerValue val = new DerValue(this.extensionValue);
    174         if (val.tag != DerValue.tag_Sequence) {
    175             throw new IOException("Invalid encoding for" +
    176                                   " NameConstraintsExtension.");
    177         }
    178 
    179         // NB. this is always encoded with the IMPLICIT tag
    180         // The checks only make sense if we assume implicit tagging,
    181         // with explicit tagging the form is always constructed.
    182         // Note that all the fields in NameConstraints are defined as
    183         // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting
    184         // in val.data being null.
    185         if (val.data == null)
    186             return;
    187         while (val.data.available() != 0) {
    188             DerValue opt = val.data.getDerValue();
    189 
    190             if (opt.isContextSpecific(TAG_PERMITTED) && opt.isConstructed()) {
    191                 if (permitted != null) {
    192                     throw new IOException("Duplicate permitted " +
    193                          "GeneralSubtrees in NameConstraintsExtension.");
    194                 }
    195                 opt.resetTag(DerValue.tag_Sequence);
    196                 permitted = new GeneralSubtrees(opt);
    197 
    198             } else if (opt.isContextSpecific(TAG_EXCLUDED) &&
    199                        opt.isConstructed()) {
    200                 if (excluded != null) {
    201                     throw new IOException("Duplicate excluded " +
    202                              "GeneralSubtrees in NameConstraintsExtension.");
    203                 }
    204                 opt.resetTag(DerValue.tag_Sequence);
    205                 excluded = new GeneralSubtrees(opt);
    206             } else
    207                 throw new IOException("Invalid encoding of " +
    208                                       "NameConstraintsExtension.");
    209         }
    210         minMaxValid = false;
    211     }
    212 
    213     /**
    214      * Return the printable string.
    215      */
    216     public String toString() {
    217         return (super.toString() + "NameConstraints: [" +
    218                 ((permitted == null) ? "" :
    219                      ("\n    Permitted:" + permitted.toString())) +
    220                 ((excluded == null) ? "" :
    221                      ("\n    Excluded:" + excluded.toString()))
    222                 + "   ]\n");
    223     }
    224 
    225     /**
    226      * Write the extension to the OutputStream.
    227      *
    228      * @param out the OutputStream to write the extension to.
    229      * @exception IOException on encoding errors.
    230      */
    231     public void encode(OutputStream out) throws IOException {
    232         DerOutputStream tmp = new DerOutputStream();
    233         if (this.extensionValue == null) {
    234             this.extensionId = PKIXExtensions.NameConstraints_Id;
    235             this.critical = true;
    236             encodeThis();
    237         }
    238         super.encode(tmp);
    239         out.write(tmp.toByteArray());
    240     }
    241 
    242     /**
    243      * Set the attribute value.
    244      */
    245     public void set(String name, Object obj) throws IOException {
    246         if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
    247             if (!(obj instanceof GeneralSubtrees)) {
    248                 throw new IOException("Attribute value should be"
    249                                     + " of type GeneralSubtrees.");
    250             }
    251             permitted = (GeneralSubtrees)obj;
    252         } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
    253             if (!(obj instanceof GeneralSubtrees)) {
    254                 throw new IOException("Attribute value should be "
    255                                     + "of type GeneralSubtrees.");
    256             }
    257             excluded = (GeneralSubtrees)obj;
    258         } else {
    259           throw new IOException("Attribute name not recognized by " +
    260                         "CertAttrSet:NameConstraintsExtension.");
    261         }
    262         encodeThis();
    263     }
    264 
    265     /**
    266      * Get the attribute value.
    267      */
    268     public GeneralSubtrees get(String name) throws IOException {
    269         if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
    270             return (permitted);
    271         } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
    272             return (excluded);
    273         } else {
    274           throw new IOException("Attribute name not recognized by " +
    275                         "CertAttrSet:NameConstraintsExtension.");
    276         }
    277     }
    278 
    279     /**
    280      * Delete the attribute value.
    281      */
    282     public void delete(String name) throws IOException {
    283         if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
    284             permitted = null;
    285         } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
    286             excluded = null;
    287         } else {
    288           throw new IOException("Attribute name not recognized by " +
    289                         "CertAttrSet:NameConstraintsExtension.");
    290         }
    291         encodeThis();
    292     }
    293 
    294     /**
    295      * Return an enumeration of names of attributes existing within this
    296      * attribute.
    297      */
    298     public Enumeration<String> getElements() {
    299         AttributeNameEnumeration elements = new AttributeNameEnumeration();
    300         elements.addElement(PERMITTED_SUBTREES);
    301         elements.addElement(EXCLUDED_SUBTREES);
    302 
    303         return (elements.elements());
    304     }
    305 
    306     /**
    307      * Return the name of this attribute.
    308      */
    309     public String getName() {
    310         return (NAME);
    311     }
    312 
    313     /**
    314      * Merge additional name constraints with existing ones.
    315      * This function is used in certification path processing
    316      * to accumulate name constraints from successive certificates
    317      * in the path.  Note that NameConstraints can never be
    318      * expanded by a merge, just remain constant or become more
    319      * limiting.
    320      * <p>
    321      * IETF RFC2459 specifies the processing of Name Constraints as
    322      * follows:
    323      * <p>
    324      * (j)  If permittedSubtrees is present in the certificate, set the
    325      * constrained subtrees state variable to the intersection of its
    326      * previous value and the value indicated in the extension field.
    327      * <p>
    328      * (k)  If excludedSubtrees is present in the certificate, set the
    329      * excluded subtrees state variable to the union of its previous
    330      * value and the value indicated in the extension field.
    331      * <p>
    332      * @param newConstraints additional NameConstraints to be applied
    333      * @throws IOException on error
    334      */
    335     public void merge(NameConstraintsExtension newConstraints)
    336             throws IOException {
    337 
    338         if (newConstraints == null) {
    339             // absence of any explicit constraints implies unconstrained
    340             return;
    341         }
    342 
    343         /*
    344          * If excludedSubtrees is present in the certificate, set the
    345          * excluded subtrees state variable to the union of its previous
    346          * value and the value indicated in the extension field.
    347          */
    348 
    349         GeneralSubtrees newExcluded = newConstraints.get(EXCLUDED_SUBTREES);
    350         if (excluded == null) {
    351             excluded = (newExcluded != null) ?
    352                         (GeneralSubtrees)newExcluded.clone() : null;
    353         } else {
    354             if (newExcluded != null) {
    355                 // Merge new excluded with current excluded (union)
    356                 excluded.union(newExcluded);
    357             }
    358         }
    359 
    360         /*
    361          * If permittedSubtrees is present in the certificate, set the
    362          * constrained subtrees state variable to the intersection of its
    363          * previous value and the value indicated in the extension field.
    364          */
    365 
    366         GeneralSubtrees newPermitted = newConstraints.get(PERMITTED_SUBTREES);
    367         if (permitted == null) {
    368             permitted = (newPermitted != null) ?
    369                         (GeneralSubtrees)newPermitted.clone() : null;
    370         } else {
    371             if (newPermitted != null) {
    372                 // Merge new permitted with current permitted (intersection)
    373                 newExcluded = permitted.intersect(newPermitted);
    374 
    375                 // Merge new excluded subtrees to current excluded (union)
    376                 if (newExcluded != null) {
    377                     if (excluded != null) {
    378                         excluded.union(newExcluded);
    379                     } else {
    380                         excluded = (GeneralSubtrees)newExcluded.clone();
    381                     }
    382                 }
    383             }
    384         }
    385 
    386         // Optional optimization: remove permitted subtrees that are excluded.
    387         // This is not necessary for algorithm correctness, but it makes
    388         // subsequent operations on the NameConstraints faster and require
    389         // less space.
    390         if (permitted != null) {
    391             permitted.reduce(excluded);
    392         }
    393 
    394         // The NameConstraints have been changed, so re-encode them.  Methods in
    395         // this class assume that the encodings have already been done.
    396         encodeThis();
    397 
    398     }
    399 
    400     /**
    401      * check whether a certificate conforms to these NameConstraints.
    402      * This involves verifying that the subject name and subjectAltName
    403      * extension (critical or noncritical) is consistent with the permitted
    404      * subtrees state variables.  Also verify that the subject name and
    405      * subjectAltName extension (critical or noncritical) is consistent with
    406      * the excluded subtrees state variables.
    407      *
    408      * @param cert X509Certificate to be verified
    409      * @returns true if certificate verifies successfully
    410      * @throws IOException on error
    411      */
    412     public boolean verify(X509Certificate cert) throws IOException {
    413 
    414         if (cert == null) {
    415             throw new IOException("Certificate is null");
    416         }
    417 
    418         // Calculate hasMin and hasMax booleans (if necessary)
    419         if (!minMaxValid) {
    420             calcMinMax();
    421         }
    422 
    423         if (hasMin) {
    424             throw new IOException("Non-zero minimum BaseDistance in"
    425                                 + " name constraints not supported");
    426         }
    427 
    428         if (hasMax) {
    429             throw new IOException("Maximum BaseDistance in"
    430                                 + " name constraints not supported");
    431         }
    432 
    433         X500Principal subjectPrincipal = cert.getSubjectX500Principal();
    434         X500Name subject = X500Name.asX500Name(subjectPrincipal);
    435 
    436         if (subject.isEmpty() == false) {
    437             if (verify(subject) == false) {
    438                 return false;
    439             }
    440         }
    441 
    442         GeneralNames altNames = null;
    443         // extract altNames
    444         try {
    445             // extract extensions, if any, from certInfo
    446             // following returns null if certificate contains no extensions
    447             X509CertImpl certImpl = X509CertImpl.toImpl(cert);
    448             SubjectAlternativeNameExtension altNameExt =
    449                 certImpl.getSubjectAlternativeNameExtension();
    450             if (altNameExt != null) {
    451                 // extract altNames from extension; this call does not
    452                 // return an IOException on null altnames
    453                 altNames = altNameExt.get(
    454                         SubjectAlternativeNameExtension.SUBJECT_NAME);
    455             }
    456         } catch (CertificateException ce) {
    457             throw new IOException("Unable to extract extensions from " +
    458                         "certificate: " + ce.getMessage());
    459         }
    460 
    461         // If there are no subjectAlternativeNames, perform the special-case
    462         // check where if the subjectName contains any EMAILADDRESS
    463         // attributes, they must be checked against RFC822 constraints.
    464         // If that passes, we're fine.
    465         if (altNames == null) {
    466             return verifyRFC822SpecialCase(subject);
    467         }
    468 
    469         // verify each subjectAltName
    470         for (int i = 0; i < altNames.size(); i++) {
    471             GeneralNameInterface altGNI = altNames.get(i).getName();
    472             if (!verify(altGNI)) {
    473                 return false;
    474             }
    475         }
    476 
    477         // All tests passed.
    478         return true;
    479     }
    480 
    481     /**
    482      * check whether a name conforms to these NameConstraints.
    483      * This involves verifying that the name is consistent with the
    484      * permitted and excluded subtrees variables.
    485      *
    486      * @param name GeneralNameInterface name to be verified
    487      * @returns true if certificate verifies successfully
    488      * @throws IOException on error
    489      */
    490     public boolean verify(GeneralNameInterface name) throws IOException {
    491         if (name == null) {
    492             throw new IOException("name is null");
    493         }
    494 
    495         // Verify that the name is consistent with the excluded subtrees
    496         if (excluded != null && excluded.size() > 0) {
    497 
    498             for (int i = 0; i < excluded.size(); i++) {
    499                 GeneralSubtree gs = excluded.get(i);
    500                 if (gs == null)
    501                     continue;
    502                 GeneralName gn = gs.getName();
    503                 if (gn == null)
    504                     continue;
    505                 GeneralNameInterface exName = gn.getName();
    506                 if (exName == null)
    507                     continue;
    508 
    509                 // if name matches or narrows any excluded subtree,
    510                 // return false
    511                 switch (exName.constrains(name)) {
    512                 case GeneralNameInterface.NAME_DIFF_TYPE:
    513                 case GeneralNameInterface.NAME_WIDENS: // name widens excluded
    514                 case GeneralNameInterface.NAME_SAME_TYPE:
    515                     break;
    516                 case GeneralNameInterface.NAME_MATCH:
    517                 case GeneralNameInterface.NAME_NARROWS: // subject name excluded
    518                     return false;
    519                 }
    520             }
    521         }
    522 
    523         // Verify that the name is consistent with the permitted subtrees
    524         if (permitted != null && permitted.size() > 0) {
    525 
    526             boolean sameType = false;
    527 
    528             for (int i = 0; i < permitted.size(); i++) {
    529                 GeneralSubtree gs = permitted.get(i);
    530                 if (gs == null)
    531                     continue;
    532                 GeneralName gn = gs.getName();
    533                 if (gn == null)
    534                     continue;
    535                 GeneralNameInterface perName = gn.getName();
    536                 if (perName == null)
    537                     continue;
    538 
    539                 // if Name matches any type in permitted,
    540                 // and Name does not match or narrow some permitted subtree,
    541                 // return false
    542                 switch (perName.constrains(name)) {
    543                 case GeneralNameInterface.NAME_DIFF_TYPE:
    544                     continue; // continue checking other permitted names
    545                 case GeneralNameInterface.NAME_WIDENS: // name widens permitted
    546                 case GeneralNameInterface.NAME_SAME_TYPE:
    547                     sameType = true;
    548                     continue; // continue to look for a match or narrow
    549                 case GeneralNameInterface.NAME_MATCH:
    550                 case GeneralNameInterface.NAME_NARROWS:
    551                     // name narrows permitted
    552                     return true; // name is definitely OK, so break out of loop
    553                 }
    554             }
    555             if (sameType) {
    556                 return false;
    557             }
    558         }
    559         return true;
    560     }
    561 
    562     /**
    563      * Perform the RFC 822 special case check. We have a certificate
    564      * that does not contain any subject alternative names. Check that
    565      * any EMAILADDRESS attributes in its subject name conform to these
    566      * NameConstraints.
    567      *
    568      * @param subject the certificate's subject name
    569      * @returns true if certificate verifies successfully
    570      * @throws IOException on error
    571      */
    572     public boolean verifyRFC822SpecialCase(X500Name subject) throws IOException {
    573         for (AVA ava : subject.allAvas()) {
    574             ObjectIdentifier attrOID = ava.getObjectIdentifier();
    575             if (attrOID.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID)) {
    576                 String attrValue = ava.getValueString();
    577                 if (attrValue != null) {
    578                     RFC822Name emailName;
    579                     try {
    580                         emailName = new RFC822Name(attrValue);
    581                     } catch (IOException ioe) {
    582                         continue;
    583                     }
    584                     if (!verify(emailName)) {
    585                         return(false);
    586                     }
    587                 }
    588              }
    589         }
    590         return true;
    591     }
    592 
    593     /**
    594      * Clone all objects that may be modified during certificate validation.
    595      */
    596     public Object clone() {
    597         try {
    598             NameConstraintsExtension newNCE =
    599                 (NameConstraintsExtension) super.clone();
    600 
    601             if (permitted != null) {
    602                 newNCE.permitted = (GeneralSubtrees) permitted.clone();
    603             }
    604             if (excluded != null) {
    605                 newNCE.excluded = (GeneralSubtrees) excluded.clone();
    606             }
    607             return newNCE;
    608         } catch (CloneNotSupportedException cnsee) {
    609             throw new RuntimeException("CloneNotSupportedException while " +
    610                 "cloning NameConstraintsException. This should never happen.");
    611         }
    612     }
    613 }
    614