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}