alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (NameConstraintsExtension.java)

This example Java source code file (NameConstraintsExtension.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

attribute, attributenameenumeration, basedistance, certattrset\:nameconstraintsextension, deroutputstream, generalnameinterface, generalsubtrees, invalid, ioexception, nameconstraintsextension, object, rfc822name, string, tag_permitted, util

The NameConstraintsExtension.java Java example source code

/*
 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.x509;

import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

import javax.security.auth.x500.X500Principal;

import sun.security.util.*;
import sun.security.pkcs.PKCS9Attribute;

/**
 * This class defines the Name Constraints Extension.
 * <p>
 * The name constraints extension provides permitted and excluded
 * subtrees that place restrictions on names that may be included within
 * a certificate issued by a given CA.  Restrictions may apply to the
 * subject distinguished name or subject alternative names.  Any name
 * matching a restriction in the excluded subtrees field is invalid
 * regardless of information appearing in the permitted subtrees.
 * <p>
 * The ASN.1 syntax for this is:
 * <pre>
 * NameConstraints ::= SEQUENCE {
 *    permittedSubtrees [0]  GeneralSubtrees OPTIONAL,
 *    excludedSubtrees  [1]  GeneralSubtrees OPTIONAL
 * }
 * GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
 * </pre>
 *
 * @author Amit Kapoor
 * @author Hemma Prafullchandra
 * @see Extension
 * @see CertAttrSet
 */
public class NameConstraintsExtension extends Extension
implements CertAttrSet<String>, Cloneable {
    /**
     * Identifier for this attribute, to be used with the
     * get, set, delete methods of Certificate, x509 type.
     */
    public static final String IDENT = "x509.info.extensions.NameConstraints";
    /**
     * Attribute names.
     */
    public static final String NAME = "NameConstraints";
    public static final String PERMITTED_SUBTREES = "permitted_subtrees";
    public static final String EXCLUDED_SUBTREES = "excluded_subtrees";

    // Private data members
    private static final byte TAG_PERMITTED = 0;
    private static final byte TAG_EXCLUDED = 1;

    private GeneralSubtrees     permitted = null;
    private GeneralSubtrees     excluded = null;

    private boolean hasMin;
    private boolean hasMax;
    private boolean minMaxValid = false;

    // Recalculate hasMin and hasMax flags.
    private void calcMinMax() throws IOException {
        hasMin = false;
        hasMax = false;
        if (excluded != null) {
            for (int i = 0; i < excluded.size(); i++) {
                GeneralSubtree subtree = excluded.get(i);
                if (subtree.getMinimum() != 0)
                    hasMin = true;
                if (subtree.getMaximum() != -1)
                    hasMax = true;
            }
        }

        if (permitted != null) {
            for (int i = 0; i < permitted.size(); i++) {
                GeneralSubtree subtree = permitted.get(i);
                if (subtree.getMinimum() != 0)
                    hasMin = true;
                if (subtree.getMaximum() != -1)
                    hasMax = true;
            }
        }
        minMaxValid = true;
    }

    // Encode this extension value.
    private void encodeThis() throws IOException {
        minMaxValid = false;
        if (permitted == null && excluded == null) {
            this.extensionValue = null;
            return;
        }
        DerOutputStream seq = new DerOutputStream();

        DerOutputStream tagged = new DerOutputStream();
        if (permitted != null) {
            DerOutputStream tmp = new DerOutputStream();
            permitted.encode(tmp);
            tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,
                                 true, TAG_PERMITTED), tmp);
        }
        if (excluded != null) {
            DerOutputStream tmp = new DerOutputStream();
            excluded.encode(tmp);
            tagged.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,
                                 true, TAG_EXCLUDED), tmp);
        }
        seq.write(DerValue.tag_Sequence, tagged);
        this.extensionValue = seq.toByteArray();
    }

    /**
     * The default constructor for this class. Both parameters
     * are optional and can be set to null.  The extension criticality
     * is set to true.
     *
     * @param permitted the permitted GeneralSubtrees (null for optional).
     * @param excluded the excluded GeneralSubtrees (null for optional).
     */
    public NameConstraintsExtension(GeneralSubtrees permitted,
                                    GeneralSubtrees excluded)
    throws IOException {
        this.permitted = permitted;
        this.excluded = excluded;

        this.extensionId = PKIXExtensions.NameConstraints_Id;
        this.critical = true;
        encodeThis();
    }

    /**
     * Create the extension from the passed DER encoded value.
     *
     * @param critical true if the extension is to be treated as critical.
     * @param value an array of DER encoded bytes of the actual value.
     * @exception ClassCastException if value is not an array of bytes
     * @exception IOException on error.
     */
    public NameConstraintsExtension(Boolean critical, Object value)
    throws IOException {
        this.extensionId = PKIXExtensions.NameConstraints_Id;
        this.critical = critical.booleanValue();

        this.extensionValue = (byte[]) value;
        DerValue val = new DerValue(this.extensionValue);
        if (val.tag != DerValue.tag_Sequence) {
            throw new IOException("Invalid encoding for" +
                                  " NameConstraintsExtension.");
        }

        // NB. this is always encoded with the IMPLICIT tag
        // The checks only make sense if we assume implicit tagging,
        // with explicit tagging the form is always constructed.
        // Note that all the fields in NameConstraints are defined as
        // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting
        // in val.data being null.
        if (val.data == null)
            return;
        while (val.data.available() != 0) {
            DerValue opt = val.data.getDerValue();

            if (opt.isContextSpecific(TAG_PERMITTED) && opt.isConstructed()) {
                if (permitted != null) {
                    throw new IOException("Duplicate permitted " +
                         "GeneralSubtrees in NameConstraintsExtension.");
                }
                opt.resetTag(DerValue.tag_Sequence);
                permitted = new GeneralSubtrees(opt);

            } else if (opt.isContextSpecific(TAG_EXCLUDED) &&
                       opt.isConstructed()) {
                if (excluded != null) {
                    throw new IOException("Duplicate excluded " +
                             "GeneralSubtrees in NameConstraintsExtension.");
                }
                opt.resetTag(DerValue.tag_Sequence);
                excluded = new GeneralSubtrees(opt);
            } else
                throw new IOException("Invalid encoding of " +
                                      "NameConstraintsExtension.");
        }
        minMaxValid = false;
    }

    /**
     * Return the printable string.
     */
    public String toString() {
        return (super.toString() + "NameConstraints: [" +
                ((permitted == null) ? "" :
                     ("\n    Permitted:" + permitted.toString())) +
                ((excluded == null) ? "" :
                     ("\n    Excluded:" + excluded.toString()))
                + "   ]\n");
    }

    /**
     * Write the extension to the OutputStream.
     *
     * @param out the OutputStream to write the extension to.
     * @exception IOException on encoding errors.
     */
    public void encode(OutputStream out) throws IOException {
        DerOutputStream tmp = new DerOutputStream();
        if (this.extensionValue == null) {
            this.extensionId = PKIXExtensions.NameConstraints_Id;
            this.critical = true;
            encodeThis();
        }
        super.encode(tmp);
        out.write(tmp.toByteArray());
    }

    /**
     * Set the attribute value.
     */
    public void set(String name, Object obj) throws IOException {
        if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
            if (!(obj instanceof GeneralSubtrees)) {
                throw new IOException("Attribute value should be"
                                    + " of type GeneralSubtrees.");
            }
            permitted = (GeneralSubtrees)obj;
        } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
            if (!(obj instanceof GeneralSubtrees)) {
                throw new IOException("Attribute value should be "
                                    + "of type GeneralSubtrees.");
            }
            excluded = (GeneralSubtrees)obj;
        } else {
          throw new IOException("Attribute name not recognized by " +
                        "CertAttrSet:NameConstraintsExtension.");
        }
        encodeThis();
    }

    /**
     * Get the attribute value.
     */
    public GeneralSubtrees get(String name) throws IOException {
        if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
            return (permitted);
        } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
            return (excluded);
        } else {
          throw new IOException("Attribute name not recognized by " +
                        "CertAttrSet:NameConstraintsExtension.");
        }
    }

    /**
     * Delete the attribute value.
     */
    public void delete(String name) throws IOException {
        if (name.equalsIgnoreCase(PERMITTED_SUBTREES)) {
            permitted = null;
        } else if (name.equalsIgnoreCase(EXCLUDED_SUBTREES)) {
            excluded = null;
        } else {
          throw new IOException("Attribute name not recognized by " +
                        "CertAttrSet:NameConstraintsExtension.");
        }
        encodeThis();
    }

    /**
     * Return an enumeration of names of attributes existing within this
     * attribute.
     */
    public Enumeration<String> getElements() {
        AttributeNameEnumeration elements = new AttributeNameEnumeration();
        elements.addElement(PERMITTED_SUBTREES);
        elements.addElement(EXCLUDED_SUBTREES);

        return (elements.elements());
    }

    /**
     * Return the name of this attribute.
     */
    public String getName() {
        return (NAME);
    }

    /**
     * Merge additional name constraints with existing ones.
     * This function is used in certification path processing
     * to accumulate name constraints from successive certificates
     * in the path.  Note that NameConstraints can never be
     * expanded by a merge, just remain constant or become more
     * limiting.
     * <p>
     * IETF RFC2459 specifies the processing of Name Constraints as
     * follows:
     * <p>
     * (j)  If permittedSubtrees is present in the certificate, set the
     * constrained subtrees state variable to the intersection of its
     * previous value and the value indicated in the extension field.
     * <p>
     * (k)  If excludedSubtrees is present in the certificate, set the
     * excluded subtrees state variable to the union of its previous
     * value and the value indicated in the extension field.
     * <p>
     * @param newConstraints additional NameConstraints to be applied
     * @throws IOException on error
     */
    public void merge(NameConstraintsExtension newConstraints)
            throws IOException {

        if (newConstraints == null) {
            // absence of any explicit constraints implies unconstrained
            return;
        }

        /*
         * If excludedSubtrees is present in the certificate, set the
         * excluded subtrees state variable to the union of its previous
         * value and the value indicated in the extension field.
         */

        GeneralSubtrees newExcluded = newConstraints.get(EXCLUDED_SUBTREES);
        if (excluded == null) {
            excluded = (newExcluded != null) ?
                        (GeneralSubtrees)newExcluded.clone() : null;
        } else {
            if (newExcluded != null) {
                // Merge new excluded with current excluded (union)
                excluded.union(newExcluded);
            }
        }

        /*
         * If permittedSubtrees is present in the certificate, set the
         * constrained subtrees state variable to the intersection of its
         * previous value and the value indicated in the extension field.
         */

        GeneralSubtrees newPermitted = newConstraints.get(PERMITTED_SUBTREES);
        if (permitted == null) {
            permitted = (newPermitted != null) ?
                        (GeneralSubtrees)newPermitted.clone() : null;
        } else {
            if (newPermitted != null) {
                // Merge new permitted with current permitted (intersection)
                newExcluded = permitted.intersect(newPermitted);

                // Merge new excluded subtrees to current excluded (union)
                if (newExcluded != null) {
                    if (excluded != null) {
                        excluded.union(newExcluded);
                    } else {
                        excluded = (GeneralSubtrees)newExcluded.clone();
                    }
                }
            }
        }

        // Optional optimization: remove permitted subtrees that are excluded.
        // This is not necessary for algorithm correctness, but it makes
        // subsequent operations on the NameConstraints faster and require
        // less space.
        if (permitted != null) {
            permitted.reduce(excluded);
        }

        // The NameConstraints have been changed, so re-encode them.  Methods in
        // this class assume that the encodings have already been done.
        encodeThis();

    }

    /**
     * check whether a certificate conforms to these NameConstraints.
     * This involves verifying that the subject name and subjectAltName
     * extension (critical or noncritical) is consistent with the permitted
     * subtrees state variables.  Also verify that the subject name and
     * subjectAltName extension (critical or noncritical) is consistent with
     * the excluded subtrees state variables.
     *
     * @param cert X509Certificate to be verified
     * @returns true if certificate verifies successfully
     * @throws IOException on error
     */
    public boolean verify(X509Certificate cert) throws IOException {

        if (cert == null) {
            throw new IOException("Certificate is null");
        }

        // Calculate hasMin and hasMax booleans (if necessary)
        if (!minMaxValid) {
            calcMinMax();
        }

        if (hasMin) {
            throw new IOException("Non-zero minimum BaseDistance in"
                                + " name constraints not supported");
        }

        if (hasMax) {
            throw new IOException("Maximum BaseDistance in"
                                + " name constraints not supported");
        }

        X500Principal subjectPrincipal = cert.getSubjectX500Principal();
        X500Name subject = X500Name.asX500Name(subjectPrincipal);

        if (subject.isEmpty() == false) {
            if (verify(subject) == false) {
                return false;
            }
        }

        GeneralNames altNames = null;
        // extract altNames
        try {
            // extract extensions, if any, from certInfo
            // following returns null if certificate contains no extensions
            X509CertImpl certImpl = X509CertImpl.toImpl(cert);
            SubjectAlternativeNameExtension altNameExt =
                certImpl.getSubjectAlternativeNameExtension();
            if (altNameExt != null) {
                // extract altNames from extension; this call does not
                // return an IOException on null altnames
                altNames = altNameExt.get(
                        SubjectAlternativeNameExtension.SUBJECT_NAME);
            }
        } catch (CertificateException ce) {
            throw new IOException("Unable to extract extensions from " +
                        "certificate: " + ce.getMessage());
        }

        // If there are no subjectAlternativeNames, perform the special-case
        // check where if the subjectName contains any EMAILADDRESS
        // attributes, they must be checked against RFC822 constraints.
        // If that passes, we're fine.
        if (altNames == null) {
            return verifyRFC822SpecialCase(subject);
        }

        // verify each subjectAltName
        for (int i = 0; i < altNames.size(); i++) {
            GeneralNameInterface altGNI = altNames.get(i).getName();
            if (!verify(altGNI)) {
                return false;
            }
        }

        // All tests passed.
        return true;
    }

    /**
     * check whether a name conforms to these NameConstraints.
     * This involves verifying that the name is consistent with the
     * permitted and excluded subtrees variables.
     *
     * @param name GeneralNameInterface name to be verified
     * @returns true if certificate verifies successfully
     * @throws IOException on error
     */
    public boolean verify(GeneralNameInterface name) throws IOException {
        if (name == null) {
            throw new IOException("name is null");
        }

        // Verify that the name is consistent with the excluded subtrees
        if (excluded != null && excluded.size() > 0) {

            for (int i = 0; i < excluded.size(); i++) {
                GeneralSubtree gs = excluded.get(i);
                if (gs == null)
                    continue;
                GeneralName gn = gs.getName();
                if (gn == null)
                    continue;
                GeneralNameInterface exName = gn.getName();
                if (exName == null)
                    continue;

                // if name matches or narrows any excluded subtree,
                // return false
                switch (exName.constrains(name)) {
                case GeneralNameInterface.NAME_DIFF_TYPE:
                case GeneralNameInterface.NAME_WIDENS: // name widens excluded
                case GeneralNameInterface.NAME_SAME_TYPE:
                    break;
                case GeneralNameInterface.NAME_MATCH:
                case GeneralNameInterface.NAME_NARROWS: // subject name excluded
                    return false;
                }
            }
        }

        // Verify that the name is consistent with the permitted subtrees
        if (permitted != null && permitted.size() > 0) {

            boolean sameType = false;

            for (int i = 0; i < permitted.size(); i++) {
                GeneralSubtree gs = permitted.get(i);
                if (gs == null)
                    continue;
                GeneralName gn = gs.getName();
                if (gn == null)
                    continue;
                GeneralNameInterface perName = gn.getName();
                if (perName == null)
                    continue;

                // if Name matches any type in permitted,
                // and Name does not match or narrow some permitted subtree,
                // return false
                switch (perName.constrains(name)) {
                case GeneralNameInterface.NAME_DIFF_TYPE:
                    continue; // continue checking other permitted names
                case GeneralNameInterface.NAME_WIDENS: // name widens permitted
                case GeneralNameInterface.NAME_SAME_TYPE:
                    sameType = true;
                    continue; // continue to look for a match or narrow
                case GeneralNameInterface.NAME_MATCH:
                case GeneralNameInterface.NAME_NARROWS:
                    // name narrows permitted
                    return true; // name is definitely OK, so break out of loop
                }
            }
            if (sameType) {
                return false;
            }
        }
        return true;
    }

    /**
     * Perform the RFC 822 special case check. We have a certificate
     * that does not contain any subject alternative names. Check that
     * any EMAILADDRESS attributes in its subject name conform to these
     * NameConstraints.
     *
     * @param subject the certificate's subject name
     * @returns true if certificate verifies successfully
     * @throws IOException on error
     */
    public boolean verifyRFC822SpecialCase(X500Name subject) throws IOException {
        for (AVA ava : subject.allAvas()) {
            ObjectIdentifier attrOID = ava.getObjectIdentifier();
            if (attrOID.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID)) {
                String attrValue = ava.getValueString();
                if (attrValue != null) {
                    RFC822Name emailName;
                    try {
                        emailName = new RFC822Name(attrValue);
                    } catch (IOException ioe) {
                        continue;
                    }
                    if (!verify(emailName)) {
                        return(false);
                    }
                }
             }
        }
        return true;
    }

    /**
     * Clone all objects that may be modified during certificate validation.
     */
    public Object clone() {
        try {
            NameConstraintsExtension newNCE =
                (NameConstraintsExtension) super.clone();

            if (permitted != null) {
                newNCE.permitted = (GeneralSubtrees) permitted.clone();
            }
            if (excluded != null) {
                newNCE.excluded = (GeneralSubtrees) excluded.clone();
            }
            return newNCE;
        } catch (CloneNotSupportedException cnsee) {
            throw new RuntimeException("CloneNotSupportedException while " +
                "cloning NameConstraintsException. This should never happen.");
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java NameConstraintsExtension.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.