Home | History | Annotate | Download | only in x509
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Vladimir N. Molotkov, Alexander Y. Kleymenov
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.x509;
     24 
     25 import java.io.IOException;
     26 import java.security.cert.X509Certificate;
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 import org.apache.harmony.security.asn1.ASN1Implicit;
     30 import org.apache.harmony.security.asn1.ASN1OctetString;
     31 import org.apache.harmony.security.asn1.ASN1Sequence;
     32 import org.apache.harmony.security.asn1.ASN1Type;
     33 import org.apache.harmony.security.asn1.BerInputStream;
     34 
     35 /**
     36  * The class encapsulates the ASN.1 DER encoding/decoding work
     37  * with the following structure which is a part of X.509 certificate
     38  * (as specified in RFC 3280 -
     39  *  Internet X.509 Public Key Infrastructure.
     40  *  Certificate and Certificate Revocation List (CRL) Profile.
     41  *  http://www.ietf.org/rfc/rfc3280.txt):
     42  *
     43  * <pre>
     44  *
     45  *   NameConstraints ::= SEQUENCE {
     46  *        permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
     47  *        excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
     48  *
     49  *   GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
     50  *
     51  * </pre>
     52  *
     53  * @see org.apache.harmony.security.x509.GeneralSubtree
     54  * @see org.apache.harmony.security.x509.GeneralName
     55  */
     56 public final class NameConstraints extends ExtensionValue {
     57     /** the value of permittedSubtrees field of the structure */
     58     private final GeneralSubtrees permittedSubtrees;
     59     /** the value of excludedSubtrees field of the structure */
     60     private final GeneralSubtrees excludedSubtrees;
     61     /** the ASN.1 encoded form of NameConstraints */
     62     private byte[] encoding;
     63 
     64     private ArrayList<GeneralName>[] permitted_names;
     65     private ArrayList<GeneralName>[] excluded_names;
     66 
     67     /**
     68      * Constructs <code>NameConstrains</code> object
     69      */
     70     public NameConstraints(GeneralSubtrees permittedSubtrees,
     71                            GeneralSubtrees excludedSubtrees) {
     72         if (permittedSubtrees != null) {
     73             List<GeneralSubtree> ps = permittedSubtrees.getSubtrees();
     74             if (ps == null || ps.isEmpty()) {
     75                 throw new IllegalArgumentException("permittedSubtrees are empty");
     76             }
     77         }
     78         if (excludedSubtrees != null) {
     79             List<GeneralSubtree> es = excludedSubtrees.getSubtrees();
     80             if (es == null || es.isEmpty()) {
     81                 throw new IllegalArgumentException("excludedSubtrees are empty");
     82             }
     83         }
     84         this.permittedSubtrees = permittedSubtrees;
     85         this.excludedSubtrees = excludedSubtrees;
     86     }
     87 
     88     private NameConstraints(GeneralSubtrees permittedSubtrees,
     89                             GeneralSubtrees excludedSubtrees, byte[] encoding) {
     90         this(permittedSubtrees, excludedSubtrees);
     91         this.encoding = encoding;
     92     }
     93 
     94     public static NameConstraints decode(byte[] encoding) throws IOException {
     95         return (NameConstraints) ASN1.decode(encoding);
     96     }
     97 
     98     @Override public byte[] getEncoded() {
     99         if (encoding == null) {
    100             encoding = ASN1.encode(this);
    101         }
    102         return encoding;
    103     }
    104 
    105     /**
    106      * Prepare the data structure to speed up the checking process.
    107      */
    108     private void prepareNames() {
    109         // array of lists with permitted General Names divided by type
    110         permitted_names = new ArrayList[9];
    111         if (permittedSubtrees != null) {
    112             for (GeneralSubtree generalSubtree : permittedSubtrees.getSubtrees()) {
    113                 GeneralName name = generalSubtree.getBase();
    114                 int tag = name.getTag();
    115                 if (permitted_names[tag] == null) {
    116                     permitted_names[tag] = new ArrayList<GeneralName>();
    117                 }
    118                 permitted_names[tag].add(name);
    119             }
    120         }
    121         // array of lists with excluded General Names divided by type
    122         excluded_names = new ArrayList[9];
    123         if (excludedSubtrees != null) {
    124             for (GeneralSubtree generalSubtree : excludedSubtrees.getSubtrees()) {
    125                 GeneralName name = generalSubtree.getBase();
    126                 int tag = name.getTag();
    127                 if (excluded_names[tag] == null) {
    128                     excluded_names[tag] = new ArrayList<GeneralName>();
    129                 }
    130                 excluded_names[tag].add(name);
    131             }
    132         }
    133     }
    134 
    135     /**
    136      * Returns the value of certificate extension
    137      */
    138     private byte[] getExtensionValue(X509Certificate cert, String OID) {
    139         try {
    140             byte[] bytes = cert.getExtensionValue(OID);
    141             if (bytes == null) {
    142                 return null;
    143             }
    144             return (byte[]) ASN1OctetString.getInstance().decode(bytes);
    145         } catch (IOException e) {
    146             return null;
    147         }
    148     }
    149 
    150     /**
    151      * Apply the name restrictions specified by this NameConstraints
    152      * instance to the subject distinguished name and subject alternative
    153      * names of specified X509Certificate. Restrictions apply only
    154      * if specified name form is present in the certificate.
    155      * The restrictions are applied according the RFC 3280
    156      * (see 4.2.1.11 Name Constraints), excepting that restrictions are applied
    157      * and to CA certificates, and to certificates which issuer and subject
    158      * names the same (i.e. method does not check if it CA's certificate or not,
    159      * or if the names differ or not. This check if it is needed should be done
    160      * by caller before calling this method).
    161      * @param   cert X.509 Certificate to be checked.
    162      * @return  true if the certificate is acceptable according
    163      *          these NameConstraints restrictions
    164      */
    165     public boolean isAcceptable(X509Certificate cert) {
    166         if (permitted_names == null) {
    167             prepareNames();
    168         }
    169 
    170         byte[] bytes = getExtensionValue(cert, "2.5.29.17");
    171         List<GeneralName> names;
    172         try {
    173             names = (bytes == null)
    174                 ? new ArrayList<GeneralName>(1) // will check the subject field only
    175                 : ((GeneralNames) GeneralNames.ASN1.decode(bytes)).getNames();
    176         } catch (IOException e) {
    177             // the certificate is broken;
    178             e.printStackTrace();
    179             return false;
    180         }
    181         if ((excluded_names[4] != null) || (permitted_names[4] != null)) {
    182             try {
    183                 names.add(new GeneralName(4,
    184                         cert.getSubjectX500Principal().getName()));
    185             } catch (IOException e) {
    186                 // should never be happened
    187             }
    188         }
    189         return isAcceptable(names);
    190     }
    191 
    192     /**
    193      * Check if this list of names is acceptable according to this
    194      * NameConstraints object.
    195      */
    196     public boolean isAcceptable(List<GeneralName> names) {
    197         if (permitted_names == null) {
    198             prepareNames();
    199         }
    200 
    201         // check map: shows which types of permitted alternative names are
    202         // presented in the certificate
    203         boolean[] types_presented = new boolean[9];
    204         // check map: shows if permitted name of presented type is found
    205         // among the certificate's alternative names
    206         boolean[] permitted_found = new boolean[9];
    207         for (GeneralName name : names) {
    208             int type = name.getTag();
    209             // search the name in excluded names
    210             if (excluded_names[type] != null) {
    211                 for (int i = 0; i < excluded_names[type].size(); i++) {
    212                     if (excluded_names[type].get(i).isAcceptable(name)) {
    213                         return false;
    214                     }
    215                 }
    216             }
    217             // Search the name in permitted names
    218             // (if we already found the name of such type between the alt
    219             // names - we do not need to check others)
    220             if ((permitted_names[type] != null) && (!permitted_found[type])) {
    221                 types_presented[type] = true;
    222                 for (int i = 0; i < permitted_names[type].size(); i++) {
    223                     if (permitted_names[type].get(i).isAcceptable(name)) {
    224                         // found one permitted name of such type
    225                         permitted_found[type] = true;
    226                     }
    227                 }
    228             }
    229         }
    230         for (int type = 0; type < 9; type++) {
    231             if (types_presented[type] && !permitted_found[type]) {
    232                 return false;
    233             }
    234         }
    235         return true;
    236     }
    237 
    238     @Override public void dumpValue(StringBuilder sb, String prefix) {
    239         sb.append(prefix).append("Name Constraints: [\n");
    240         if (permittedSubtrees != null) {
    241             sb.append(prefix).append("  Permitted: [\n");
    242             for (GeneralSubtree generalSubtree : permittedSubtrees.getSubtrees()) {
    243                 generalSubtree.dumpValue(sb, prefix + "    ");
    244             }
    245             sb.append(prefix).append("  ]\n");
    246         }
    247         if (excludedSubtrees != null) {
    248             sb.append(prefix).append("  Excluded: [\n");
    249             for (GeneralSubtree generalSubtree : excludedSubtrees.getSubtrees()) {
    250                 generalSubtree.dumpValue(sb, prefix + "    ");
    251             }
    252             sb.append(prefix).append("  ]\n");
    253         }
    254         sb.append('\n').append(prefix).append("]\n");
    255     }
    256 
    257     /**
    258      * X.509 NameConstraints encoder/decoder.
    259      */
    260     public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
    261             new ASN1Implicit(0, GeneralSubtrees.ASN1),
    262             new ASN1Implicit(1, GeneralSubtrees.ASN1) }) {
    263         {
    264             setOptional(0);
    265             setOptional(1);
    266         }
    267 
    268         @Override protected Object getDecodedObject(BerInputStream in) {
    269             Object[] values = (Object[]) in.content;
    270             return new NameConstraints(
    271                     (GeneralSubtrees) values[0],
    272                     (GeneralSubtrees) values[1],
    273                     in.getEncoded());
    274         }
    275 
    276         @Override protected void getValues(Object object, Object[] values) {
    277             NameConstraints nc = (NameConstraints) object;
    278             values[0] = nc.permittedSubtrees;
    279             values[1] = nc.excludedSubtrees;
    280         }
    281     };
    282 }
    283