001/* 002 * Copyright 2017-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.ssl.cert; 022 023 024 025import java.util.ArrayList; 026 027import com.unboundid.asn1.ASN1Boolean; 028import com.unboundid.asn1.ASN1Constants; 029import com.unboundid.asn1.ASN1Element; 030import com.unboundid.asn1.ASN1Integer; 031import com.unboundid.asn1.ASN1Sequence; 032import com.unboundid.util.Debug; 033import com.unboundid.util.NotMutable; 034import com.unboundid.util.OID; 035import com.unboundid.util.StaticUtils; 036import com.unboundid.util.ThreadSafety; 037import com.unboundid.util.ThreadSafetyLevel; 038 039import static com.unboundid.util.ssl.cert.CertMessages.*; 040 041 042 043/** 044 * This class provides an implementation of the basic constraints X.509 045 * certificate extension as described in 046 * <A HREF="https://www.ietf.org/rfc/rfc5280.txt">RFC 5280</A> section 4.2.1.9. 047 * This can be used to indicate whether a certificate is a certification 048 * authority (CA), and the maximum depth of certification paths that include 049 * this certificate. 050 * <BR><BR> 051 * The OID for this extension is 2.5.29.19 and the value has the following 052 * encoding: 053 * <PRE> 054 * BasicConstraints ::= SEQUENCE { 055 * cA BOOLEAN DEFAULT FALSE, 056 * pathLenConstraint INTEGER (0..MAX) OPTIONAL } 057 * </PRE> 058 */ 059@NotMutable() 060@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 061public final class BasicConstraintsExtension 062 extends X509CertificateExtension 063{ 064 /** 065 * The OID (2.5.29.19) for basic constraints extensions. 066 */ 067 public static final OID BASIC_CONSTRAINTS_OID = new OID("2.5.29.19"); 068 069 070 071 /** 072 * The serial version UID for this serializable class. 073 */ 074 private static final long serialVersionUID = 7597324354728536247L; 075 076 077 078 // Indicates whether the certificate is a certification authority. 079 private final boolean isCA; 080 081 // The path length constraint for paths that include the certificate. 082 private final Integer pathLengthConstraint; 083 084 085 086 /** 087 * Creates a new basic constraints extension from the provided information. 088 * 089 * @param isCritical Indicates whether this extension should be 090 * considered critical. 091 * @param isCA Indicates whether the associated certificate 092 * is a certification authority. 093 * @param pathLengthConstraint The path length constraint for paths that 094 * include the certificate. This may be 095 * {@code null} if it should not be included in 096 * the extension. 097 */ 098 BasicConstraintsExtension(final boolean isCritical, final boolean isCA, 099 final Integer pathLengthConstraint) 100 { 101 super(BASIC_CONSTRAINTS_OID, isCritical, 102 encodeValue(isCA, pathLengthConstraint)); 103 104 this.isCA = isCA; 105 this.pathLengthConstraint = pathLengthConstraint; 106 } 107 108 109 110 111 /** 112 * Creates a new basic constraints extension from the provided generic 113 * extension. 114 * 115 * @param extension The extension to decode as a basic constraints 116 * extension. 117 * 118 * @throws CertException If the provided extension cannot be decoded as a 119 * basic constraints extension. 120 */ 121 BasicConstraintsExtension(final X509CertificateExtension extension) 122 throws CertException 123 { 124 super(extension); 125 126 try 127 { 128 boolean ca = false; 129 Integer lengthConstraint = null; 130 for (final ASN1Element e : 131 ASN1Sequence.decodeAsSequence(extension.getValue()).elements()) 132 { 133 switch (e.getType()) 134 { 135 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 136 ca = e.decodeAsBoolean().booleanValue(); 137 break; 138 case ASN1Constants.UNIVERSAL_INTEGER_TYPE: 139 lengthConstraint = e.decodeAsInteger().intValue(); 140 break; 141 } 142 } 143 144 isCA = ca; 145 pathLengthConstraint = lengthConstraint; 146 } 147 catch (final Exception e) 148 { 149 Debug.debugException(e); 150 throw new CertException( 151 ERR_BASIC_CONSTRAINTS_EXTENSION_CANNOT_PARSE.get( 152 String.valueOf(extension), StaticUtils.getExceptionMessage(e)), 153 e); 154 } 155 } 156 157 158 159 /** 160 * Encodes the provided information into a value for this extension. 161 * 162 * @param isCA Indicates whether the associated certificate 163 * is a certification authority. 164 * @param pathLengthConstraint The path length constraint for paths that 165 * include the certificate. This may be 166 * {@code null} if it should not be included in 167 * the extension. 168 * 169 * @return The encoded extension value. 170 */ 171 private static byte[] encodeValue(final boolean isCA, 172 final Integer pathLengthConstraint) 173 { 174 final ArrayList<ASN1Element> elements = new ArrayList<>(2); 175 if (isCA) 176 { 177 elements.add(new ASN1Boolean(isCA)); 178 } 179 180 if (pathLengthConstraint != null) 181 { 182 elements.add(new ASN1Integer(pathLengthConstraint)); 183 } 184 185 return new ASN1Sequence(elements).encode(); 186 } 187 188 189 /** 190 * Indicates whether the associated certificate is a certification authority 191 * (that is, can be used to sign other certificates). 192 * 193 * @return {@code true} if the associated certificate is a certification 194 * authority, or {@code false} if not. 195 */ 196 public boolean isCA() 197 { 198 return isCA; 199 } 200 201 202 203 /** 204 * Retrieves the path length constraint for the associated certificate, if 205 * defined. If {@link #isCA()} returns {@code true} and this method returns 206 * a non-{@code null} value, then any certificate chain that includes the 207 * associated certificate should not be trusted if the chain contains more 208 * than this number of certificates. 209 * 210 * @return The path length constraint for the associated certificate, or 211 * {@code null} if no path length constraint is defined. 212 */ 213 public Integer getPathLengthConstraint() 214 { 215 return pathLengthConstraint; 216 } 217 218 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override() 224 public String getExtensionName() 225 { 226 return INFO_BASIC_CONSTRAINTS_EXTENSION_NAME.get(); 227 } 228 229 230 231 /** 232 * {@inheritDoc} 233 */ 234 @Override() 235 public void toString(final StringBuilder buffer) 236 { 237 buffer.append("BasicConstraintsExtension(oid='"); 238 buffer.append(getOID()); 239 buffer.append("', isCritical="); 240 buffer.append(isCritical()); 241 buffer.append(", isCA="); 242 buffer.append(isCA); 243 244 if (pathLengthConstraint != null) 245 { 246 buffer.append(", pathLengthConstraint="); 247 buffer.append(pathLengthConstraint); 248 } 249 250 buffer.append(')'); 251 } 252}