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}