Path: blob/master/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java
41159 views
/*1* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.x509;2627import java.io.IOException;28import java.io.OutputStream;29import java.security.cert.CertificateException;30import java.security.cert.X509Certificate;31import java.util.*;3233import javax.security.auth.x500.X500Principal;3435import sun.net.util.IPAddressUtil;36import sun.security.util.*;37import sun.security.pkcs.PKCS9Attribute;3839/**40* This class defines the Name Constraints Extension.41* <p>42* The name constraints extension provides permitted and excluded43* subtrees that place restrictions on names that may be included within44* a certificate issued by a given CA. Restrictions may apply to the45* subject distinguished name or subject alternative names. Any name46* matching a restriction in the excluded subtrees field is invalid47* 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 OPTIONAL54* }55* GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree56* </pre>57*58* @author Amit Kapoor59* @author Hemma Prafullchandra60* @see Extension61* @see CertAttrSet62*/63public class NameConstraintsExtension extends Extension64implements CertAttrSet<String>, Cloneable {65/**66* Identifier for this attribute, to be used with the67* get, set, delete methods of Certificate, x509 type.68*/69public static final String IDENT = "x509.info.extensions.NameConstraints";70/**71* Attribute names.72*/73public static final String NAME = "NameConstraints";74public static final String PERMITTED_SUBTREES = "permitted_subtrees";75public static final String EXCLUDED_SUBTREES = "excluded_subtrees";7677// Private data members78private static final byte TAG_PERMITTED = 0;79private static final byte TAG_EXCLUDED = 1;8081private GeneralSubtrees permitted = null;82private GeneralSubtrees excluded = null;8384private boolean hasMin;85private boolean hasMax;86private boolean minMaxValid = false;8788// Recalculate hasMin and hasMax flags.89private void calcMinMax() throws IOException {90hasMin = false;91hasMax = false;92if (excluded != null) {93for (int i = 0; i < excluded.size(); i++) {94GeneralSubtree subtree = excluded.get(i);95if (subtree.getMinimum() != 0)96hasMin = true;97if (subtree.getMaximum() != -1)98hasMax = true;99}100}101102if (permitted != null) {103for (int i = 0; i < permitted.size(); i++) {104GeneralSubtree subtree = permitted.get(i);105if (subtree.getMinimum() != 0)106hasMin = true;107if (subtree.getMaximum() != -1)108hasMax = true;109}110}111minMaxValid = true;112}113114// Encode this extension value.115private void encodeThis() throws IOException {116minMaxValid = false;117if (permitted == null && excluded == null) {118this.extensionValue = null;119return;120}121DerOutputStream seq = new DerOutputStream();122123DerOutputStream tagged = new DerOutputStream();124if (permitted != null) {125DerOutputStream tmp = new DerOutputStream();126permitted.encode(tmp);127tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,128true, TAG_PERMITTED), tmp);129}130if (excluded != null) {131DerOutputStream tmp = new DerOutputStream();132excluded.encode(tmp);133tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,134true, TAG_EXCLUDED), tmp);135}136seq.write(DerValue.tag_Sequence, tagged);137this.extensionValue = seq.toByteArray();138}139140/**141* The default constructor for this class. Both parameters142* are optional and can be set to null. The extension criticality143* is set to true.144*145* @param permitted the permitted GeneralSubtrees (null for optional).146* @param excluded the excluded GeneralSubtrees (null for optional).147*/148public NameConstraintsExtension(GeneralSubtrees permitted,149GeneralSubtrees excluded)150throws IOException {151this.permitted = permitted;152this.excluded = excluded;153154this.extensionId = PKIXExtensions.NameConstraints_Id;155this.critical = true;156encodeThis();157}158159/**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 bytes165* @exception IOException on error.166*/167public NameConstraintsExtension(Boolean critical, Object value)168throws IOException {169this.extensionId = PKIXExtensions.NameConstraints_Id;170this.critical = critical.booleanValue();171172this.extensionValue = (byte[]) value;173DerValue val = new DerValue(this.extensionValue);174if (val.tag != DerValue.tag_Sequence) {175throw new IOException("Invalid encoding for" +176" NameConstraintsExtension.");177}178179// NB. this is always encoded with the IMPLICIT tag180// 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 as183// being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting184// in val.data being null.185if (val.data == null)186return;187while (val.data.available() != 0) {188DerValue opt = val.data.getDerValue();189190if (opt.isContextSpecific(TAG_PERMITTED) && opt.isConstructed()) {191if (permitted != null) {192throw new IOException("Duplicate permitted " +193"GeneralSubtrees in NameConstraintsExtension.");194}195opt.resetTag(DerValue.tag_Sequence);196permitted = new GeneralSubtrees(opt);197198} else if (opt.isContextSpecific(TAG_EXCLUDED) &&199opt.isConstructed()) {200if (excluded != null) {201throw new IOException("Duplicate excluded " +202"GeneralSubtrees in NameConstraintsExtension.");203}204opt.resetTag(DerValue.tag_Sequence);205excluded = new GeneralSubtrees(opt);206} else207throw new IOException("Invalid encoding of " +208"NameConstraintsExtension.");209}210minMaxValid = false;211}212213/**214* Return the printable string.215*/216public String toString() {217StringBuilder sb = new StringBuilder();218sb.append(super.toString())219.append("NameConstraints: [");220if (permitted != null) {221sb.append("\n Permitted:")222.append(permitted);223}224if (excluded != null) {225sb.append("\n Excluded:")226.append(excluded);227}228sb.append(" ]\n");229return sb.toString();230}231232/**233* Write the extension to the OutputStream.234*235* @param out the OutputStream to write the extension to.236* @exception IOException on encoding errors.237*/238public void encode(OutputStream out) throws IOException {239DerOutputStream tmp = new DerOutputStream();240if (this.extensionValue == null) {241this.extensionId = PKIXExtensions.NameConstraints_Id;242this.critical = true;243encodeThis();244}245super.encode(tmp);246out.write(tmp.toByteArray());247}248249/**250* Set the attribute value.251*/252public void set(String name, Object obj) throws IOException {253if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {254if (!(obj instanceof GeneralSubtrees)) {255throw new IOException("Attribute value should be"256+ " of type GeneralSubtrees.");257}258permitted = (GeneralSubtrees)obj;259} else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {260if (!(obj instanceof GeneralSubtrees)) {261throw new IOException("Attribute value should be "262+ "of type GeneralSubtrees.");263}264excluded = (GeneralSubtrees)obj;265} else {266throw new IOException("Attribute name not recognized by " +267"CertAttrSet:NameConstraintsExtension.");268}269encodeThis();270}271272/**273* Get the attribute value.274*/275public GeneralSubtrees get(String name) throws IOException {276if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {277return (permitted);278} else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {279return (excluded);280} else {281throw new IOException("Attribute name not recognized by " +282"CertAttrSet:NameConstraintsExtension.");283}284}285286/**287* Delete the attribute value.288*/289public void delete(String name) throws IOException {290if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {291permitted = null;292} else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {293excluded = null;294} else {295throw new IOException("Attribute name not recognized by " +296"CertAttrSet:NameConstraintsExtension.");297}298encodeThis();299}300301/**302* Return an enumeration of names of attributes existing within this303* attribute.304*/305public Enumeration<String> getElements() {306AttributeNameEnumeration elements = new AttributeNameEnumeration();307elements.addElement(PERMITTED_SUBTREES);308elements.addElement(EXCLUDED_SUBTREES);309310return (elements.elements());311}312313/**314* Return the name of this attribute.315*/316public String getName() {317return (NAME);318}319320/**321* Merge additional name constraints with existing ones.322* This function is used in certification path processing323* to accumulate name constraints from successive certificates324* in the path. Note that NameConstraints can never be325* expanded by a merge, just remain constant or become more326* limiting.327* <p>328* IETF RFC 5280 specifies the processing of Name Constraints as329* follows:330* <p>331* (j) If permittedSubtrees is present in the certificate, set the332* constrained subtrees state variable to the intersection of its333* previous value and the value indicated in the extension field.334* <p>335* (k) If excludedSubtrees is present in the certificate, set the336* excluded subtrees state variable to the union of its previous337* value and the value indicated in the extension field.338*339* @param newConstraints additional NameConstraints to be applied340* @throws IOException on error341*/342public void merge(NameConstraintsExtension newConstraints)343throws IOException {344345if (newConstraints == null) {346// absence of any explicit constraints implies unconstrained347return;348}349350/*351* If excludedSubtrees is present in the certificate, set the352* excluded subtrees state variable to the union of its previous353* value and the value indicated in the extension field.354*/355356GeneralSubtrees newExcluded = newConstraints.get(EXCLUDED_SUBTREES);357if (excluded == null) {358excluded = (newExcluded != null) ?359(GeneralSubtrees)newExcluded.clone() : null;360} else {361if (newExcluded != null) {362// Merge new excluded with current excluded (union)363excluded.union(newExcluded);364}365}366367/*368* If permittedSubtrees is present in the certificate, set the369* constrained subtrees state variable to the intersection of its370* previous value and the value indicated in the extension field.371*/372373GeneralSubtrees newPermitted = newConstraints.get(PERMITTED_SUBTREES);374if (permitted == null) {375permitted = (newPermitted != null) ?376(GeneralSubtrees)newPermitted.clone() : null;377} else {378if (newPermitted != null) {379// Merge new permitted with current permitted (intersection)380newExcluded = permitted.intersect(newPermitted);381382// Merge new excluded subtrees to current excluded (union)383if (newExcluded != null) {384if (excluded != null) {385excluded.union(newExcluded);386} else {387excluded = (GeneralSubtrees)newExcluded.clone();388}389}390}391}392393// Optional optimization: remove permitted subtrees that are excluded.394// This is not necessary for algorithm correctness, but it makes395// subsequent operations on the NameConstraints faster and require396// less space.397if (permitted != null) {398permitted.reduce(excluded);399}400401// The NameConstraints have been changed, so re-encode them. Methods in402// this class assume that the encodings have already been done.403encodeThis();404405}406407/**408* check whether a certificate conforms to these NameConstraints.409* This involves verifying that the subject name and subjectAltName410* extension (critical or noncritical) is consistent with the permitted411* subtrees state variables. Also verify that the subject name and412* subjectAltName extension (critical or noncritical) is consistent with413* the excluded subtrees state variables.414*415* @param cert X509Certificate to be verified416* @return true if certificate verifies successfully417* @throws IOException on error418*/419public boolean verify(X509Certificate cert) throws IOException {420421if (cert == null) {422throw new IOException("Certificate is null");423}424425// Calculate hasMin and hasMax booleans (if necessary)426if (!minMaxValid) {427calcMinMax();428}429430if (hasMin) {431throw new IOException("Non-zero minimum BaseDistance in"432+ " name constraints not supported");433}434435if (hasMax) {436throw new IOException("Maximum BaseDistance in"437+ " name constraints not supported");438}439440X500Principal subjectPrincipal = cert.getSubjectX500Principal();441X500Name subject = X500Name.asX500Name(subjectPrincipal);442443// Check subject as an X500Name444if (subject.isEmpty() == false) {445if (verify(subject) == false) {446return false;447}448}449450GeneralNames altNames = null;451// extract altNames452try {453// extract extensions, if any, from certInfo454// following returns null if certificate contains no extensions455X509CertImpl certImpl = X509CertImpl.toImpl(cert);456SubjectAlternativeNameExtension altNameExt =457certImpl.getSubjectAlternativeNameExtension();458if (altNameExt != null) {459// extract altNames from extension; this call does not460// return an IOException on null altnames461altNames = altNameExt.get(462SubjectAlternativeNameExtension.SUBJECT_NAME);463}464} catch (CertificateException ce) {465throw new IOException("Unable to extract extensions from " +466"certificate: " + ce.getMessage());467}468469if (altNames == null) {470altNames = new GeneralNames();471472// RFC 5280 4.2.1.10:473// When constraints are imposed on the rfc822Name name form,474// but the certificate does not include a subject alternative name,475// the rfc822Name constraint MUST be applied to the attribute of476// type emailAddress in the subject distinguished name.477for (AVA ava : subject.allAvas()) {478ObjectIdentifier attrOID = ava.getObjectIdentifier();479if (attrOID.equals(PKCS9Attribute.EMAIL_ADDRESS_OID)) {480String attrValue = ava.getValueString();481if (attrValue != null) {482try {483altNames.add(new GeneralName(484new RFC822Name(attrValue)));485} catch (IOException ioe) {486continue;487}488}489}490}491}492493// If there is no IPAddressName or DNSName in subjectAlternativeNames,494// see if the last CN inside subjectName can be used instead.495DerValue derValue = subject.findMostSpecificAttribute496(X500Name.commonName_oid);497String cn = derValue == null ? null : derValue.getAsString();498499if (cn != null) {500try {501if (IPAddressUtil.isIPv4LiteralAddress(cn) ||502IPAddressUtil.isIPv6LiteralAddress(cn)) {503if (!hasNameType(altNames, GeneralNameInterface.NAME_IP)) {504altNames.add(new GeneralName(new IPAddressName(cn)));505}506} else {507if (!hasNameType(altNames, GeneralNameInterface.NAME_DNS)) {508altNames.add(new GeneralName(new DNSName(cn)));509}510}511} catch (IOException ioe) {512// OK, cn is neither IP nor DNS513}514}515516// verify each subjectAltName517for (int i = 0; i < altNames.size(); i++) {518GeneralNameInterface altGNI = altNames.get(i).getName();519if (!verify(altGNI)) {520return false;521}522}523524// All tests passed.525return true;526}527528private static boolean hasNameType(GeneralNames names, int type) {529for (GeneralName name : names.names()) {530if (name.getType() == type) {531return true;532}533}534return false;535}536537/**538* check whether a name conforms to these NameConstraints.539* This involves verifying that the name is consistent with the540* permitted and excluded subtrees variables.541*542* @param name GeneralNameInterface name to be verified543* @return true if certificate verifies successfully544* @throws IOException on error545*/546public boolean verify(GeneralNameInterface name) throws IOException {547if (name == null) {548throw new IOException("name is null");549}550551// Verify that the name is consistent with the excluded subtrees552if (excluded != null && excluded.size() > 0) {553554for (int i = 0; i < excluded.size(); i++) {555GeneralSubtree gs = excluded.get(i);556if (gs == null)557continue;558GeneralName gn = gs.getName();559if (gn == null)560continue;561GeneralNameInterface exName = gn.getName();562if (exName == null)563continue;564565// if name matches or narrows any excluded subtree,566// return false567switch (exName.constrains(name)) {568case GeneralNameInterface.NAME_DIFF_TYPE:569case GeneralNameInterface.NAME_WIDENS: // name widens excluded570case GeneralNameInterface.NAME_SAME_TYPE:571break;572case GeneralNameInterface.NAME_MATCH:573case GeneralNameInterface.NAME_NARROWS: // subject name excluded574return false;575}576}577}578579// Verify that the name is consistent with the permitted subtrees580if (permitted != null && permitted.size() > 0) {581582boolean sameType = false;583584for (int i = 0; i < permitted.size(); i++) {585GeneralSubtree gs = permitted.get(i);586if (gs == null)587continue;588GeneralName gn = gs.getName();589if (gn == null)590continue;591GeneralNameInterface perName = gn.getName();592if (perName == null)593continue;594595// if Name matches any type in permitted,596// and Name does not match or narrow some permitted subtree,597// return false598switch (perName.constrains(name)) {599case GeneralNameInterface.NAME_DIFF_TYPE:600continue; // continue checking other permitted names601case GeneralNameInterface.NAME_WIDENS: // name widens permitted602case GeneralNameInterface.NAME_SAME_TYPE:603sameType = true;604continue; // continue to look for a match or narrow605case GeneralNameInterface.NAME_MATCH:606case GeneralNameInterface.NAME_NARROWS:607// name narrows permitted608return true; // name is definitely OK, so break out of loop609}610}611if (sameType) {612return false;613}614}615return true;616}617618/**619* Clone all objects that may be modified during certificate validation.620*/621public Object clone() {622try {623NameConstraintsExtension newNCE =624(NameConstraintsExtension) super.clone();625626if (permitted != null) {627newNCE.permitted = (GeneralSubtrees) permitted.clone();628}629if (excluded != null) {630newNCE.excluded = (GeneralSubtrees) excluded.clone();631}632return newNCE;633} catch (CloneNotSupportedException cnsee) {634throw new RuntimeException("CloneNotSupportedException while " +635"cloning NameConstraintsException. This should never happen.");636}637}638}639640641