001/* 002 * Copyright 2017-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2017-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2017-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util.ssl.cert; 037 038 039 040import java.io.Serializable; 041import java.util.ArrayList; 042 043import com.unboundid.asn1.ASN1Boolean; 044import com.unboundid.asn1.ASN1Constants; 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1ObjectIdentifier; 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.asn1.ASN1Sequence; 049import com.unboundid.util.Debug; 050import com.unboundid.util.NotExtensible; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.OID; 054import com.unboundid.util.StaticUtils; 055import com.unboundid.util.ThreadSafety; 056import com.unboundid.util.ThreadSafetyLevel; 057 058import static com.unboundid.util.ssl.cert.CertMessages.*; 059 060 061 062/** 063 * This class represents a data structure that holds information about an X.509 064 * certificate extension. 065 */ 066@NotExtensible() 067@NotMutable() 068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 069public class X509CertificateExtension 070 implements Serializable 071{ 072 /** 073 * The serial version UID for this serializable class. 074 */ 075 private static final long serialVersionUID = -4044598072050168580L; 076 077 078 079 // Indicates whether this extension is considered critical. 080 private final boolean isCritical; 081 082 // The value for this extension. 083 @NotNull private final byte[] value; 084 085 // The OID for this extension. 086 @NotNull private final OID oid; 087 088 089 090 /** 091 * Creates a new X.509 certificate extension that wraps the provided 092 * extension. 093 * 094 * @param extension The extension to wrap. 095 */ 096 protected X509CertificateExtension( 097 @NotNull final X509CertificateExtension extension) 098 { 099 oid = extension.oid; 100 isCritical = extension.isCritical; 101 value = extension.value; 102 } 103 104 105 106 /** 107 * Creates a new X.509 certificate extension with the provided information. 108 * 109 * @param oid The OID for this extension. 110 * @param isCritical Indicates whether this extension is considered 111 * critical. 112 * @param value The value for this extension. 113 */ 114 public X509CertificateExtension(@NotNull final OID oid, 115 final boolean isCritical, 116 @NotNull final byte[] value) 117 { 118 this.oid = oid; 119 this.isCritical = isCritical; 120 this.value = value; 121 } 122 123 124 125 /** 126 * Decodes the provided ASN.1 element as an X.509 certificate extension. 127 * 128 * @param extensionElement The ASN.1 element containing the encoded 129 * extension. 130 * 131 * @return The decoded extension. 132 * 133 * @throws CertException If a problem is encountered while attempting to 134 * decode the extension. 135 */ 136 @NotNull() 137 static X509CertificateExtension decode( 138 @NotNull final ASN1Element extensionElement) 139 throws CertException 140 { 141 final OID oid; 142 final X509CertificateExtension extension; 143 try 144 { 145 final ASN1Element[] elements = 146 extensionElement.decodeAsSequence().elements(); 147 oid = elements[0].decodeAsObjectIdentifier().getOID(); 148 149 final boolean isCritical; 150 final byte[] value; 151 if (elements[1].getType() == ASN1Constants.UNIVERSAL_BOOLEAN_TYPE) 152 { 153 isCritical = elements[1].decodeAsBoolean().booleanValue(); 154 value = elements[2].decodeAsOctetString().getValue(); 155 } 156 else 157 { 158 isCritical = false; 159 value = elements[1].decodeAsOctetString().getValue(); 160 } 161 162 extension = new X509CertificateExtension(oid, isCritical, value); 163 } 164 catch (final Exception e) 165 { 166 Debug.debugException(e); 167 throw new CertException( 168 ERR_EXTENSION_DECODE_ERROR.get( 169 StaticUtils.getExceptionMessage(e)), 170 e); 171 } 172 173 if (oid.equals(AuthorityKeyIdentifierExtension. 174 AUTHORITY_KEY_IDENTIFIER_OID)) 175 { 176 try 177 { 178 return new AuthorityKeyIdentifierExtension(extension); 179 } 180 catch (final Exception e) 181 { 182 Debug.debugException(e); 183 } 184 } 185 else if (oid.equals(SubjectKeyIdentifierExtension. 186 SUBJECT_KEY_IDENTIFIER_OID)) 187 { 188 try 189 { 190 return new SubjectKeyIdentifierExtension(extension); 191 } 192 catch (final Exception e) 193 { 194 Debug.debugException(e); 195 } 196 } 197 else if (oid.equals(KeyUsageExtension.KEY_USAGE_OID)) 198 { 199 try 200 { 201 return new KeyUsageExtension(extension); 202 } 203 catch (final Exception e) 204 { 205 Debug.debugException(e); 206 } 207 } 208 else if (oid.equals(SubjectAlternativeNameExtension. 209 SUBJECT_ALTERNATIVE_NAME_OID)) 210 { 211 try 212 { 213 return new SubjectAlternativeNameExtension(extension); 214 } 215 catch (final Exception e) 216 { 217 Debug.debugException(e); 218 } 219 } 220 else if (oid.equals(IssuerAlternativeNameExtension. 221 ISSUER_ALTERNATIVE_NAME_OID)) 222 { 223 try 224 { 225 return new IssuerAlternativeNameExtension(extension); 226 } 227 catch (final Exception e) 228 { 229 Debug.debugException(e); 230 } 231 } 232 else if (oid.equals(BasicConstraintsExtension. 233 BASIC_CONSTRAINTS_OID)) 234 { 235 try 236 { 237 return new BasicConstraintsExtension(extension); 238 } 239 catch (final Exception e) 240 { 241 Debug.debugException(e); 242 } 243 } 244 else if (oid.equals(ExtendedKeyUsageExtension. 245 EXTENDED_KEY_USAGE_OID)) 246 { 247 try 248 { 249 return new ExtendedKeyUsageExtension(extension); 250 } 251 catch (final Exception e) 252 { 253 Debug.debugException(e); 254 } 255 } 256 else if (oid.equals(CRLDistributionPointsExtension. 257 CRL_DISTRIBUTION_POINTS_OID)) 258 { 259 try 260 { 261 return new CRLDistributionPointsExtension(extension); 262 } 263 catch (final Exception e) 264 { 265 Debug.debugException(e); 266 } 267 } 268 269 return extension; 270 } 271 272 273 274 /** 275 * Retrieves the OID for this extension. 276 * 277 * @return The OID for this extension. 278 */ 279 @NotNull() 280 public final OID getOID() 281 { 282 return oid; 283 } 284 285 286 287 /** 288 * Indicates whether this extension is considered critical. 289 * 290 * @return {@code true} if this extension is considered critical, or 291 * {@code false} if not. 292 */ 293 public final boolean isCritical() 294 { 295 return isCritical; 296 } 297 298 299 300 /** 301 * Retrieves the value for this extension. 302 * 303 * @return The value for this extension. 304 */ 305 @NotNull() 306 public final byte[] getValue() 307 { 308 return value; 309 } 310 311 312 313 /** 314 * Encodes this extension to an ASN.1 element suitable for inclusion in an 315 * encoded X.509 certificate. 316 * 317 * @return The encoded representation of this extension. 318 * 319 * @throws CertException If a problem is encountered while encoding the 320 * extension. 321 */ 322 @NotNull() 323 ASN1Sequence encode() 324 throws CertException 325 { 326 try 327 { 328 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 329 elements.add(new ASN1ObjectIdentifier(oid)); 330 331 if (isCritical) 332 { 333 elements.add(ASN1Boolean.UNIVERSAL_BOOLEAN_TRUE_ELEMENT); 334 } 335 336 elements.add(new ASN1OctetString(value)); 337 return new ASN1Sequence(elements); 338 } 339 catch (final Exception e) 340 { 341 Debug.debugException(e); 342 throw new CertException( 343 ERR_EXTENSION_ENCODE_ERROR.get(toString(), 344 StaticUtils.getExceptionMessage(e)), 345 e); 346 } 347 } 348 349 350 351 /** 352 * Retrieves the name for this extension. 353 * 354 * @return The name for this extension. 355 */ 356 @NotNull() 357 public String getExtensionName() 358 { 359 return oid.toString(); 360 } 361 362 363 364 /** 365 * Retrieves a string representation of this extension. 366 * 367 * @return A string representation of this extension. 368 */ 369 @NotNull() 370 public final String toString() 371 { 372 final StringBuilder buffer = new StringBuilder(); 373 toString(buffer); 374 return buffer.toString(); 375 } 376 377 378 379 /** 380 * Appends a string representation of this certificate extension to the 381 * provided buffer. 382 * 383 * @param buffer The buffer to which the information should be appended. 384 */ 385 public void toString(@NotNull final StringBuilder buffer) 386 { 387 buffer.append("X509CertificateExtension(oid='"); 388 buffer.append(oid.toString()); 389 buffer.append("', isCritical="); 390 buffer.append(isCritical); 391 392 if (StaticUtils.isPrintableString(value)) 393 { 394 buffer.append(", value='"); 395 buffer.append(StaticUtils.toUTF8String(value)); 396 buffer.append('\''); 397 } 398 else 399 { 400 buffer.append(", valueLength="); 401 buffer.append(value.length); 402 } 403 404 buffer.append(')'); 405 } 406}