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