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.MessageDigest; 023import java.security.NoSuchAlgorithmException; 024import java.security.cert.*; 025 026 027/** 028 * X.509 certificate utilities. 029 * 030 * @author Vladimir Dzhuvinov 031 * @version 2018-07-24 032 */ 033public class X509CertUtils { 034 035 036 /** 037 * The PEM start marker. 038 */ 039 private static final String PEM_BEGIN_MARKER = "-----BEGIN CERTIFICATE-----"; 040 041 042 /** 043 * The PEM end marker. 044 */ 045 private static final String PEM_END_MARKER = "-----END CERTIFICATE-----"; 046 047 048 /** 049 * Parses a DER-encoded X.509 certificate. 050 * 051 * @param derEncodedCert The DER-encoded X.509 certificate, as a byte 052 * array. May be {@code null}. 053 * 054 * @return The X.509 certificate, {@code null} if not specified or 055 * parsing failed. 056 */ 057 public static X509Certificate parse(final byte[] derEncodedCert) { 058 059 try { 060 return parseWithException(derEncodedCert); 061 } catch (CertificateException e) { 062 return null; 063 } 064 } 065 066 067 /** 068 * Parses a DER-encoded X.509 certificate with exception handling. 069 * 070 * @param derEncodedCert The DER-encoded X.509 certificate, as a byte 071 * array. Empty or {@code null} if not specified. 072 * 073 * @return The X.509 certificate, {@code null} if not specified. 074 * 075 * @throws CertificateException If parsing failed. 076 */ 077 public static X509Certificate parseWithException(final byte[] derEncodedCert) 078 throws CertificateException { 079 080 if (derEncodedCert == null || derEncodedCert.length == 0) { 081 return null; 082 } 083 084 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 085 final Certificate cert = cf.generateCertificate(new ByteArrayInputStream(derEncodedCert)); 086 087 if (! (cert instanceof X509Certificate)) { 088 throw new CertificateException("Not a X.509 certificate: " + cert.getType()); 089 } 090 091 return (X509Certificate)cert; 092 } 093 094 095 /** 096 * Parses a PEM-encoded X.509 certificate. 097 * 098 * @param pemEncodedCert The PEM-encoded X.509 certificate, as a 099 * string. Empty or {@code null} if not 100 * specified. 101 * 102 * @return The X.509 certificate, {@code null} if parsing failed. 103 */ 104 public static X509Certificate parse(final String pemEncodedCert) { 105 106 if (pemEncodedCert == null || pemEncodedCert.isEmpty()) { 107 return null; 108 } 109 110 final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER); 111 112 if (markerStart < 0) { 113 return null; 114 } 115 116 String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length()); 117 118 final int markerEnd = buf.indexOf(PEM_END_MARKER); 119 120 if (markerEnd < 0) { 121 return null; 122 } 123 124 buf = buf.substring(0, markerEnd); 125 126 buf = buf.replaceAll("\\s", ""); 127 128 return parse(new Base64(buf).decode()); 129 } 130 131 132 /** 133 * Parses a PEM-encoded X.509 certificate with exception handling. 134 * 135 * @param pemEncodedCert The PEM-encoded X.509 certificate, as a 136 * string. Empty or {@code null} if not 137 * specified. 138 * 139 * @return The X.509 certificate, {@code null} if parsing failed. 140 */ 141 public static X509Certificate parseWithException(final String pemEncodedCert) 142 throws CertificateException { 143 144 if (pemEncodedCert == null || pemEncodedCert.isEmpty()) { 145 return null; 146 } 147 148 final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER); 149 150 if (markerStart < 0) { 151 throw new CertificateException("PEM begin marker not found"); 152 } 153 154 String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length()); 155 156 final int markerEnd = buf.indexOf(PEM_END_MARKER); 157 158 if (markerEnd < 0) { 159 throw new CertificateException("PEM end marker not found"); 160 } 161 162 buf = buf.substring(0, markerEnd); 163 164 buf = buf.replaceAll("\\s", ""); 165 166 return parseWithException(new Base64(buf).decode()); 167 } 168 169 170 /** 171 * Returns the specified X.509 certificate as PEM-encoded string. 172 * 173 * @param cert The X.509 certificate. Must not be {@code null}. 174 * 175 * @return The PEM-encoded X.509 certificate, {@code null} if encoding 176 * failed. 177 */ 178 public static String toPEMString(final X509Certificate cert) { 179 180 return toPEMString(cert, true); 181 } 182 183 184 /** 185 * Returns the specified X.509 certificate as PEM-encoded string. 186 * 187 * @param cert The X.509 certificate. Must not be 188 * {@code null}. 189 * @param withLineBreaks {@code false} to suppress line breaks. 190 * 191 * @return The PEM-encoded X.509 certificate, {@code null} if encoding 192 * failed. 193 */ 194 public static String toPEMString(final X509Certificate cert, final boolean withLineBreaks) { 195 196 StringBuilder sb = new StringBuilder(); 197 sb.append(PEM_BEGIN_MARKER); 198 199 if (withLineBreaks) 200 sb.append('\n'); 201 202 try { 203 sb.append(Base64.encode(cert.getEncoded()).toString()); 204 } catch (CertificateEncodingException e) { 205 return null; 206 } 207 208 if (withLineBreaks) 209 sb.append('\n'); 210 211 sb.append(PEM_END_MARKER); 212 return sb.toString(); 213 } 214 215 216 /** 217 * Computes the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}). 218 * 219 * @param cert The X.509 certificate. Must not be {@code null}. 220 * 221 * @return The SHA-256 thumbprint, BASE64URL-encoded, {@code null} if 222 * a certificate encoding exception is encountered. 223 */ 224 public static Base64URL computeSHA256Thumbprint(final X509Certificate cert) { 225 226 try { 227 byte[] derEncodedCert = cert.getEncoded(); 228 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 229 return Base64URL.encode(sha256.digest(derEncodedCert)); 230 } catch (NoSuchAlgorithmException | CertificateEncodingException e) { 231 return null; 232 } 233 } 234}