001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2026, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017package com.nimbusds.jose.util; 018 019 020import java.io.ByteArrayInputStream; 021import java.security.*; 022import java.security.cert.Certificate; 023import java.security.cert.*; 024import java.util.UUID; 025 026 027/** 028 * X.509 certificate utilities. 029 * 030 * @author Vladimir Dzhuvinov 031 * @author Simon Kissane 032 * @version 2026-04-02 033 */ 034public class X509CertUtils { 035 036 037 /** 038 * The PEM start marker. 039 */ 040 public static final String PEM_BEGIN_MARKER = "-----BEGIN CERTIFICATE-----"; 041 042 043 /** 044 * The PEM end marker. 045 */ 046 public static final String PEM_END_MARKER = "-----END CERTIFICATE-----"; 047 048 049 /** 050 * The JCA provider to use for certificate operations, {@code null} 051 * implies the default provider. 052 */ 053 private static Provider jcaProvider; 054 055 056 /** 057 * Returns the JCA provider to use for certification operations. 058 * 059 * @return The JCA provider to use for certificate operations, 060 * {@code null} implies the default provider. 061 */ 062 public static Provider getProvider() { 063 return jcaProvider; 064 } 065 066 067 /** 068 * Sets the JCA provider to use for certification operations. 069 * 070 * @param provider The JCA provider to use for certificate operations, 071 * {@code null} implies the default. 072 */ 073 public static void setProvider(final Provider provider) { 074 jcaProvider = provider; 075 } 076 077 078 /** 079 * Parses a DER-encoded X.509 certificate. 080 * 081 * @param derEncodedCert The DER-encoded X.509 certificate, as a byte 082 * array. May be {@code null}. 083 * 084 * @return The X.509 certificate, {@code null} if not specified or 085 * parsing failed. 086 */ 087 public static X509Certificate parse(final byte[] derEncodedCert) { 088 089 try { 090 return parseWithException(derEncodedCert); 091 } catch (CertificateException e) { 092 return null; 093 } 094 } 095 096 097 /** 098 * Parses a DER-encoded X.509 certificate with exception handling. 099 * 100 * @param derEncodedCert The DER-encoded X.509 certificate, as a byte 101 * array. Empty or {@code null} if not specified. 102 * 103 * @return The X.509 certificate, {@code null} if not specified. 104 * 105 * @throws CertificateException If parsing failed. 106 */ 107 public static X509Certificate parseWithException(final byte[] derEncodedCert) 108 throws CertificateException { 109 110 if (derEncodedCert == null || derEncodedCert.length == 0) { 111 return null; 112 } 113 114 CertificateFactory cf = jcaProvider != null ? 115 CertificateFactory.getInstance("X.509", jcaProvider) : 116 CertificateFactory.getInstance("X.509"); 117 final Certificate cert = cf.generateCertificate(new ByteArrayInputStream(derEncodedCert)); 118 119 if (! (cert instanceof X509Certificate)) { 120 throw new CertificateException("Not a X.509 certificate: " + cert.getType()); 121 } 122 123 return (X509Certificate)cert; 124 } 125 126 127 /** 128 * Parses a PEM-encoded X.509 certificate. 129 * 130 * @param pemEncodedCert The PEM-encoded X.509 certificate, as a 131 * string. Empty or {@code null} if not 132 * specified. 133 * 134 * @return The X.509 certificate, {@code null} if parsing failed. 135 */ 136 public static X509Certificate parse(final String pemEncodedCert) { 137 138 if (pemEncodedCert == null || pemEncodedCert.isEmpty()) { 139 return null; 140 } 141 142 final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER); 143 144 if (markerStart < 0) { 145 return null; 146 } 147 148 String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length()); 149 150 final int markerEnd = buf.indexOf(PEM_END_MARKER); 151 152 if (markerEnd < 0) { 153 return null; 154 } 155 156 buf = buf.substring(0, markerEnd); 157 158 buf = buf.replaceAll("\\s", ""); 159 160 return parse(new Base64(buf).decode()); 161 } 162 163 164 /** 165 * Parses a PEM-encoded X.509 certificate with exception handling. 166 * 167 * @param pemEncodedCert The PEM-encoded X.509 certificate, as a 168 * string. Empty or {@code null} if not 169 * specified. 170 * 171 * @return The X.509 certificate, {@code null} if parsing failed. 172 */ 173 public static X509Certificate parseWithException(final String pemEncodedCert) 174 throws CertificateException { 175 176 if (pemEncodedCert == null || pemEncodedCert.isEmpty()) { 177 return null; 178 } 179 180 final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER); 181 182 if (markerStart < 0) { 183 throw new CertificateException("PEM begin marker not found"); 184 } 185 186 String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length()); 187 188 final int markerEnd = buf.indexOf(PEM_END_MARKER); 189 190 if (markerEnd < 0) { 191 throw new CertificateException("PEM end marker not found"); 192 } 193 194 buf = buf.substring(0, markerEnd); 195 196 buf = buf.replaceAll("\\s", ""); 197 198 return parseWithException(new Base64(buf).decode()); 199 } 200 201 202 /** 203 * Returns the specified X.509 certificate as PEM-encoded string. 204 * 205 * @param cert The X.509 certificate. Must not be {@code null}. 206 * 207 * @return The PEM-encoded X.509 certificate, {@code null} if encoding 208 * failed. 209 */ 210 public static String toPEMString(final X509Certificate cert) { 211 212 return toPEMString(cert, true); 213 } 214 215 216 /** 217 * Returns the specified X.509 certificate as PEM-encoded string. 218 * 219 * @param cert The X.509 certificate. Must not be 220 * {@code null}. 221 * @param withLineBreaks {@code false} to suppress line breaks. 222 * 223 * @return The PEM-encoded X.509 certificate, {@code null} if encoding 224 * failed. 225 */ 226 public static String toPEMString(final X509Certificate cert, final boolean withLineBreaks) { 227 228 StringBuilder sb = new StringBuilder(); 229 sb.append(PEM_BEGIN_MARKER); 230 231 if (withLineBreaks) 232 sb.append('\n'); 233 234 try { 235 sb.append(Base64.encode(cert.getEncoded())); 236 } catch (CertificateEncodingException e) { 237 return null; 238 } 239 240 if (withLineBreaks) 241 sb.append('\n'); 242 243 sb.append(PEM_END_MARKER); 244 return sb.toString(); 245 } 246 247 248 /** 249 * Computes the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}). 250 * 251 * @param cert The X.509 certificate. Must not be {@code null}. 252 * 253 * @return The SHA-256 thumbprint, BASE64URL-encoded, {@code null} if 254 * a certificate encoding exception is encountered. 255 */ 256 public static Base64URL computeSHA256Thumbprint(final X509Certificate cert) { 257 258 try { 259 byte[] derEncodedCert = cert.getEncoded(); 260 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 261 return Base64URL.encode(sha256.digest(derEncodedCert)); 262 } catch (NoSuchAlgorithmException | CertificateEncodingException e) { 263 return null; 264 } 265 } 266 267 268 /** 269 * Computes the X.509 certificate SHA-1 thumbprint ({@code x5t}). 270 * 271 * @param cert The X.509 certificate. Must not be {@code null}. 272 * 273 * @return The SHA-1 thumbprint, BASE64URL-encoded, {@code null} if a 274 * certificate encoding exception is encountered. 275 */ 276 public static Base64URL computeSHA1Thumbprint(final X509Certificate cert) { 277 278 try { 279 byte[] derEncodedCert = cert.getEncoded(); 280 MessageDigest sha256 = MessageDigest.getInstance("SHA-1"); 281 return Base64URL.encode(sha256.digest(derEncodedCert)); 282 } catch (NoSuchAlgorithmException | CertificateEncodingException e) { 283 return null; 284 } 285 } 286 287 288 /** 289 * Stores a private key with its associated X.509 certificate in a 290 * Java key store. The name (alias) for the stored entry is a given a 291 * random UUID. 292 * 293 * @param keyStore The key store. Must be initialised and not 294 * {@code null}. 295 * @param privateKey The private key. Must not be {@code null}. 296 * @param keyPassword The password to protect the private key, empty 297 * array for none. Must not be {@code null}. 298 * @param cert The X.509 certificate, its public key and the 299 * private key should form a pair. Must not be 300 * {@code null}. 301 * 302 * @return The UUID for the stored entry. 303 */ 304 public static UUID store(final KeyStore keyStore, 305 final PrivateKey privateKey, 306 final char[] keyPassword, 307 final X509Certificate cert) 308 throws KeyStoreException { 309 310 UUID alias = UUID.randomUUID(); 311 keyStore.setKeyEntry(alias.toString(), privateKey, keyPassword, new Certificate[]{cert}); 312 return alias; 313 } 314}