001/* 002 * Copyright 2017-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2019 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; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.LinkedHashSet; 029import java.util.List; 030import java.util.Set; 031 032import com.unboundid.asn1.ASN1Element; 033import com.unboundid.asn1.ASN1ObjectIdentifier; 034import com.unboundid.asn1.ASN1Sequence; 035import com.unboundid.util.Debug; 036import com.unboundid.util.NotMutable; 037import com.unboundid.util.OID; 038import com.unboundid.util.StaticUtils; 039import com.unboundid.util.ThreadSafety; 040import com.unboundid.util.ThreadSafetyLevel; 041 042import static com.unboundid.util.ssl.cert.CertMessages.*; 043 044 045 046/** 047 * This class provides an implementation of the extended key usage X.509 048 * certificate extension as described in 049 * <A HREF="https://www.ietf.org/rfc/rfc5280.txt">RFC 5280</A> section 4.2.1.12. 050 * This can be used to provide an extensible list of OIDs that identify ways 051 * that a certificate is intended to be used. 052 * <BR><BR> 053 * The OID for this extension is 2.5.29.37 and the value has the following 054 * encoding: 055 * <PRE> 056 * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId 057 * 058 * KeyPurposeId ::= OBJECT IDENTIFIER 059 * </PRE> 060 * 061 * @see ExtendedKeyUsageID 062 */ 063@NotMutable() 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class ExtendedKeyUsageExtension 066 extends X509CertificateExtension 067{ 068 /** 069 * The OID (2.5.29.37) for extended key usage extensions. 070 */ 071 public static final OID EXTENDED_KEY_USAGE_OID = new OID("2.5.29.37"); 072 073 074 075 /** 076 * The serial version UID for this serializable class. 077 */ 078 private static final long serialVersionUID = -8208115548961483723L; 079 080 081 082 // The set of key purpose IDs included in this extension. 083 private final Set<OID> keyPurposeIDs; 084 085 086 087 /** 088 * Creates a new extended key usage extension with the provided set of key 089 * purpose IDs. 090 * 091 * @param isCritical Indicates whether this extension should be 092 * considered critical. 093 * @param keyPurposeIDs The set of key purpose IDs included in this 094 * extension. It must not be {@code null}. 095 * 096 * @throws CertException If a problem is encountered while encoding the 097 * value for this extension. 098 */ 099 ExtendedKeyUsageExtension(final boolean isCritical, 100 final List<OID> keyPurposeIDs) 101 throws CertException 102 { 103 super(EXTENDED_KEY_USAGE_OID, isCritical, encodeValue(keyPurposeIDs)); 104 105 this.keyPurposeIDs = 106 Collections.unmodifiableSet(new LinkedHashSet<>(keyPurposeIDs)); 107 } 108 109 110 111 /** 112 * Creates a new extended key usage extension from the provided generic 113 * extension. 114 * 115 * @param extension The extension to decode as an extended key usage 116 * extension. 117 * 118 * @throws CertException If the provided extension cannot be decoded as an 119 * extended key usage extension. 120 */ 121 ExtendedKeyUsageExtension(final X509CertificateExtension extension) 122 throws CertException 123 { 124 super(extension); 125 126 try 127 { 128 final ASN1Element[] elements = 129 ASN1Sequence.decodeAsSequence(extension.getValue()).elements(); 130 final LinkedHashSet<OID> ids = 131 new LinkedHashSet<>(StaticUtils.computeMapCapacity(elements.length)); 132 for (final ASN1Element e : elements) 133 { 134 ids.add(e.decodeAsObjectIdentifier().getOID()); 135 } 136 137 keyPurposeIDs = Collections.unmodifiableSet(ids); 138 } 139 catch (final Exception e) 140 { 141 Debug.debugException(e); 142 throw new CertException( 143 ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_PARSE.get( 144 String.valueOf(extension), StaticUtils.getExceptionMessage(e)), 145 e); 146 } 147 } 148 149 150 151 /** 152 * Encodes the provided information for use as the value of this extension. 153 * 154 * @param keyPurposeIDs The set of key purpose IDs included in this 155 * extension. It must not be {@code null}. 156 * 157 * @return The encoded value for this extension. 158 * 159 * @throws CertException If a problem is encountered while encoding the 160 * value. 161 */ 162 private static byte[] encodeValue(final List<OID> keyPurposeIDs) 163 throws CertException 164 { 165 try 166 { 167 final ArrayList<ASN1Element> elements = 168 new ArrayList<>(keyPurposeIDs.size()); 169 for (final OID oid : keyPurposeIDs) 170 { 171 elements.add(new ASN1ObjectIdentifier(oid)); 172 } 173 174 return new ASN1Sequence(elements).encode(); 175 } 176 catch (final Exception e) 177 { 178 Debug.debugException(e); 179 throw new CertException( 180 ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_ENCODE.get( 181 String.valueOf(keyPurposeIDs), 182 StaticUtils.getExceptionMessage(e)), 183 e); 184 } 185 } 186 187 188 189 /** 190 * Retrieves the OIDs of the key purpose values contained in this extension. 191 * Some, all, or none of the OIDs contained in this extension may correspond 192 * to values in the {@link ExtendedKeyUsageID} enumeration. 193 * 194 * @return The OIDs of the key purpose values contained in this extension. 195 */ 196 public Set<OID> getKeyPurposeIDs() 197 { 198 return keyPurposeIDs; 199 } 200 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override() 207 public String getExtensionName() 208 { 209 return INFO_EXTENDED_KEY_USAGE_EXTENSION_NAME.get(); 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 public void toString(final StringBuilder buffer) 219 { 220 buffer.append("ExtendedKeyUsageExtension(oid='"); 221 buffer.append(getOID()); 222 buffer.append("', isCritical="); 223 buffer.append(isCritical()); 224 buffer.append(", keyPurposeIDs={"); 225 226 final Iterator<OID> oidIterator = keyPurposeIDs.iterator(); 227 while (oidIterator.hasNext()) 228 { 229 buffer.append('\''); 230 buffer.append(ExtendedKeyUsageID.getNameOrOID(oidIterator.next())); 231 buffer.append('\''); 232 233 if (oidIterator.hasNext()) 234 { 235 buffer.append(", "); 236 } 237 } 238 239 buffer.append("})"); 240 } 241}