001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 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.oauth2.sdk.util; 019 020 021import java.io.IOException; 022import java.math.BigInteger; 023import java.security.Principal; 024import java.security.PrivateKey; 025import java.security.PublicKey; 026import java.security.SecureRandom; 027import java.security.cert.X509Certificate; 028import java.util.Arrays; 029import java.util.Date; 030import javax.security.auth.x500.X500Principal; 031 032import org.bouncycastle.cert.X509CertificateHolder; 033import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 034import org.bouncycastle.operator.OperatorCreationException; 035import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 036 037import com.nimbusds.jose.util.X509CertUtils; 038import com.nimbusds.oauth2.sdk.id.Issuer; 039import com.nimbusds.oauth2.sdk.id.Subject; 040 041 042/** 043 * X.509 certificate utilities. 044 */ 045public final class X509CertificateUtils { 046 047 048 /** 049 * Checks if the issuer DN and the subject DN of the specified X.509 050 * certificate match. The matched DNs are not normalised. 051 * 052 * @param cert The X.509 certificate. Must not be {@code null}. 053 * 054 * @return {@code true} if the issuer DN and and subject DN match, else 055 * {@code false}. 056 */ 057 public static boolean hasMatchingIssuerAndSubject(final X509Certificate cert) { 058 059 Principal issuer = cert.getIssuerDN(); 060 Principal subject = cert.getSubjectDN(); 061 062 return issuer != null && issuer.equals(subject); 063 } 064 065 066 /** 067 * Checks if the specified X.509 certificate is self-issued, i.e. it 068 * has a matching issuer and subject, and the public key can be used to 069 * successfully validate the certificate's digital signature. 070 * 071 * @param cert The X.509 certificate. Must not be {@code null}. 072 * 073 * @return {@code true} if the X.509 certificate is self-issued, else 074 * {@code false}. 075 */ 076 public static boolean isSelfIssued(final X509Certificate cert) { 077 078 return hasMatchingIssuerAndSubject(cert) && isSelfSigned(cert); 079 } 080 081 082 /** 083 * Checks if the specified X.509 certificate is self-signed, i.e. the 084 * public key can be used to successfully validate the certificate's 085 * digital signature. 086 * 087 * @param cert The X.509 certificate. Must not be {@code null}. 088 * 089 * @return {@code true} if the X.509 certificate is self-signed, else 090 * {@code false}. 091 */ 092 public static boolean isSelfSigned(final X509Certificate cert) { 093 094 PublicKey publicKey = cert.getPublicKey(); 095 096 return hasValidSignature(cert, publicKey); 097 } 098 099 100 /** 101 * Validates the signature of a X.509 certificate with the specified 102 * public key. 103 * 104 * @param cert The X.509 certificate. Must not be {@code null}. 105 * @param pubKey The public key to use for the validation. Must not be 106 * {@code null}. 107 * 108 * @return {@code true} if the signature is valid, else {@code false}. 109 */ 110 public static boolean hasValidSignature(final X509Certificate cert, 111 final PublicKey pubKey) { 112 113 try { 114 cert.verify(pubKey); 115 } catch (Exception e) { 116 return false; 117 } 118 119 return true; 120 } 121 122 123 /** 124 * Returns {@code true} if the public key of the X.509 certificate 125 * matches the specified public key. 126 * 127 * @param cert The X.509 certificate. Must not be {@code null}. 128 * @param pubKey The public key to compare. Must not be {@code null}. 129 * 130 * @return {@code true} if the two public keys match, else 131 * {@code false}. 132 */ 133 public static boolean publicKeyMatches(final X509Certificate cert, 134 final PublicKey pubKey) { 135 136 PublicKey certPubKey = cert.getPublicKey(); 137 138 return Arrays.equals(certPubKey.getEncoded(), pubKey.getEncoded()); 139 } 140 141 142 /** 143 * Generates a new X.509 certificate. The certificate is provisioned 144 * with a 64-bit random serial number. 145 * 146 * <p>Signing algorithm: 147 * 148 * <ul> 149 * <li>For RSA signing keys: SHA256withRSA 150 * <li>For EC signing keys: SHA256withECDSA 151 * </ul> 152 * 153 * @param issuer The issuer. Will be prepended by {@code cn=} in 154 * the certificate to ensure a valid Distinguished 155 * Name (DN). Must not be {@code null}. 156 * @param subject The subject. Will be prepended by {@code cn=} in 157 * the certificate to ensure a valid Distinguished 158 * Name (DN). Must not be {@code null}. 159 * @param nbf Date before which the certificate is not valid. 160 * Must not be {@code null}. 161 * @param exp Date after which the certificate is not valid. 162 * Must not be {@code null}. 163 * @param certKey The public key to include in the certificate. Must 164 * not be {@code null}. 165 * @param signingKey The signing private key. Must not be {@code null}. 166 * 167 * @return The X.509 certificate. 168 * 169 * @throws OperatorCreationException On a generation exception. 170 * @throws IOException On a byte buffer exception. 171 */ 172 public static X509Certificate generate(final X500Principal issuer, 173 final X500Principal subject, 174 final Date nbf, 175 final Date exp, 176 final PublicKey certKey, 177 final PrivateKey signingKey) 178 throws OperatorCreationException, IOException { 179 180 BigInteger serialNumber = new BigInteger(64, new SecureRandom()); 181 182 final String signingAlg; 183 if ("RSA".equalsIgnoreCase(signingKey.getAlgorithm())) { 184 signingAlg = "SHA256withRSA"; 185 } else if ("EC".equalsIgnoreCase(signingKey.getAlgorithm())) { 186 signingAlg = "SHA256withECDSA"; 187 } else { 188 throw new OperatorCreationException("Unsupported signing key algorithm: " + signingKey.getAlgorithm()); 189 } 190 191 X509CertificateHolder certHolder = new JcaX509v3CertificateBuilder( 192 issuer, 193 serialNumber, 194 nbf, 195 exp, 196 subject, 197 certKey) 198 .build(new JcaContentSignerBuilder(signingAlg).build(signingKey)); 199 200 return X509CertUtils.parse(certHolder.getEncoded()); 201 } 202 203 204 /** 205 * Generates a new X.509 certificate. The certificate is provisioned 206 * with a 64-bit random serial number. 207 * 208 * <p>Signing algorithm: 209 * 210 * <ul> 211 * <li>For RSA signing keys: SHA256withRSA 212 * <li>For EC signing keys: SHA256withECDSA 213 * </ul> 214 * 215 * @param issuer The issuer. Will be prepended by {@code cn=} in 216 * the certificate to ensure a valid Distinguished 217 * Name (DN). Must not be {@code null}. 218 * @param subject The subject. Will be prepended by {@code cn=} in 219 * the certificate to ensure a valid Distinguished 220 * Name (DN). Must not be {@code null}. 221 * @param nbf Date before which the certificate is not valid. 222 * Must not be {@code null}. 223 * @param exp Date after which the certificate is not valid. 224 * Must not be {@code null}. 225 * @param certKey The public key to include in the certificate. Must 226 * not be {@code null}. 227 * @param signingKey The signing private key. Must not be {@code null}. 228 * 229 * @return The X.509 certificate. 230 * 231 * @throws OperatorCreationException On a generation exception. 232 * @throws IOException On a byte buffer exception. 233 */ 234 public static X509Certificate generate(final Issuer issuer, 235 final Subject subject, 236 final Date nbf, 237 final Date exp, 238 final PublicKey certKey, 239 final PrivateKey signingKey) 240 throws OperatorCreationException, IOException { 241 242 return generate(new X500Principal("cn=" + issuer), new X500Principal("cn=" + subject), nbf, exp, certKey, signingKey); 243 } 244 245 246 /** 247 * Generates a new self-signed and self-issued X.509 certificate. The 248 * certificate is provisioned with a 64-bit random serial number. 249 * 250 * <p>Signing algorithm: 251 * 252 * <ul> 253 * <li>For RSA signing keys: SHA256withRSA 254 * <li>For EC signing keys: SHA256withECDSA 255 * </ul> 256 * 257 * @param issuer The issuer, also used to set the subject. Will be 258 * prepended by {@code cn=} in the certificate to 259 * ensure a valid Distinguished Name (DN). Must not 260 * be {@code null}. 261 * @param nbf Date before which the certificate is not valid. 262 * Must not be {@code null}. 263 * @param exp Date after which the certificate is not valid. 264 * Must not be {@code null}. 265 * @param certKey The public key to include in the certificate. Must 266 * not be {@code null}. 267 * @param signingKey The signing private key. Must not be {@code null}. 268 * 269 * @return The X.509 certificate. 270 * 271 * @throws OperatorCreationException On a generation exception. 272 * @throws IOException On a byte buffer exception. 273 */ 274 public static X509Certificate generateSelfSigned(final Issuer issuer, 275 final Date nbf, 276 final Date exp, 277 final PublicKey certKey, 278 final PrivateKey signingKey) 279 throws OperatorCreationException, IOException { 280 281 return generate(issuer, new Subject(issuer.getValue()), nbf, exp, certKey, signingKey); 282 } 283 284 285 /** 286 * Prevents public instantiation. 287 */ 288 private X509CertificateUtils() {} 289}