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 23package org.apache.harmony.security.x509; 24 25import java.io.IOException; 26import java.security.cert.X509Certificate; 27import java.util.ArrayList; 28import java.util.List; 29import org.apache.harmony.security.asn1.ASN1Implicit; 30import org.apache.harmony.security.asn1.ASN1OctetString; 31import org.apache.harmony.security.asn1.ASN1Sequence; 32import org.apache.harmony.security.asn1.ASN1Type; 33import 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 */ 56public 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