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 * Creates a new basic constraints extension from the provided generic 112 * extension. 113 * 114 * @param extension The extension to decode as a basic constraints 115 * extension. 116 * 117 * @throws CertException If the provided extension cannot be decoded as a 118 * basic constraints extension. 119 */ 120 BasicConstraintsExtension(final X509CertificateExtension extension) 121 throws CertException 122 { 123 super(extension); 124 125 try 126 { 127 boolean ca = false; 128 Integer lengthConstraint = null; 129 for (final ASN1Element e : 130 ASN1Sequence.decodeAsSequence(extension.getValue()).elements()) 131 { 132 switch (e.getType()) 133 { 134 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 135 ca = e.decodeAsBoolean().booleanValue(); 136 break; 137 case ASN1Constants.UNIVERSAL_INTEGER_TYPE: 138 lengthConstraint = e.decodeAsInteger().intValue(); 139 break; 140 } 141 } 142 143 isCA = ca; 144 pathLengthConstraint = lengthConstraint; 145 } 146 catch (final Exception e) 147 { 148 Debug.debugException(e); 149 throw new CertException( 150 ERR_BASIC_CONSTRAINTS_EXTENSION_CANNOT_PARSE.get( 151 String.valueOf(extension), StaticUtils.getExceptionMessage(e)), 152 e); 153 } 154 } 155 156 157 158 /** 159 * Encodes the provided information into a value for this extension. 160 * 161 * @param isCA Indicates whether the associated certificate 162 * is a certification authority. 163 * @param pathLengthConstraint The path length constraint for paths that 164 * include the certificate. This may be 165 * {@code null} if it should not be included in 166 * the extension. 167 * 168 * @return The encoded extension value. 169 */ 170 private static byte[] encodeValue(final boolean isCA, 171 final Integer pathLengthConstraint) 172 { 173 final ArrayList<ASN1Element> elements = new ArrayList<>(2); 174 if (isCA) 175 { 176 elements.add(new ASN1Boolean(isCA)); 177 } 178 179 if (pathLengthConstraint != null) 180 { 181 elements.add(new ASN1Integer(pathLengthConstraint)); 182 } 183 184 return new ASN1Sequence(elements).encode(); 185 } 186 187 188 /** 189 * Indicates whether the associated certificate is a certification authority 190 * (that is, can be used to sign other certificates). 191 * 192 * @return {@code true} if the associated certificate is a certification 193 * authority, or {@code false} if not. 194 */ 195 public boolean isCA() 196 { 197 return isCA; 198 } 199 200 201 202 /** 203 * Retrieves the path length constraint for the associated certificate, if 204 * defined. If {@link #isCA()} returns {@code true} and this method returns 205 * a non-{@code null} value, then any certificate chain that includes the 206 * associated certificate should not be trusted if the chain contains more 207 * than this number of certificates. 208 * 209 * @return The path length constraint for the associated certificate, or 210 * {@code null} if no path length constraint is defined. 211 */ 212 public Integer getPathLengthConstraint() 213 { 214 return pathLengthConstraint; 215 } 216 217 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override() 223 public String getExtensionName() 224 { 225 return INFO_BASIC_CONSTRAINTS_EXTENSION_NAME.get(); 226 } 227 228 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override() 234 public void toString(final StringBuilder buffer) 235 { 236 buffer.append("BasicConstraintsExtension(oid='"); 237 buffer.append(getOID()); 238 buffer.append("', isCritical="); 239 buffer.append(isCritical()); 240 buffer.append(", isCA="); 241 buffer.append(isCA); 242 243 if (pathLengthConstraint != null) 244 { 245 buffer.append(", pathLengthConstraint="); 246 buffer.append(pathLengthConstraint); 247 } 248 249 buffer.append(')'); 250 } 251}