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 && subject != 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 Issuer issuer,
173                                               final Subject 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                X500Principal certIssuer = new X500Principal("cn=" + issuer);
183                X500Principal certSubject = new X500Principal("cn=" + subject);
184                
185                final String signingAlg;
186                if ("RSA".equalsIgnoreCase(signingKey.getAlgorithm())) {
187                        signingAlg = "SHA256withRSA";
188                } else if ("EC".equalsIgnoreCase(signingKey.getAlgorithm())) {
189                        signingAlg = "SHA256withECDSA";
190                } else {
191                        throw new OperatorCreationException("Unsupported signing key algorithm: " + signingKey.getAlgorithm());
192                }
193                
194                X509CertificateHolder certHolder = new JcaX509v3CertificateBuilder(
195                        certIssuer,
196                        serialNumber,
197                        nbf,
198                        exp,
199                        certSubject,
200                        certKey)
201                        .build(new JcaContentSignerBuilder(signingAlg).build(signingKey));
202                
203                return X509CertUtils.parse(certHolder.getEncoded());
204        }
205        
206        
207        /**
208         * Generates a new self-signed and self-issued X.509 certificate. The
209         * certificate is provisioned with a 64-bit random serial number.
210         *
211         * <p>Signing algorithm:
212         *
213         * <ul>
214         *     <li>For RSA signing keys: SHA256withRSA
215         *     <li>For EC signing keys: SHA256withECDSA
216         * </ul>
217         *
218         * @param issuer     The issuer, also used to set the subject. Will be
219         *                   prepended by {@code cn=} in the certificate to
220         *                   ensure a valid Distinguished Name (DN). Must not
221         *                   be {@code null}.
222         * @param nbf        Date before which the certificate is not valid.
223         *                   Must not be {@code null}.
224         * @param exp        Date after which the certificate is not valid.
225         *                   Must not be {@code null}.
226         * @param certKey    The public key to include in the certificate. Must
227         *                   not be {@code null}.
228         * @param signingKey The signing private key. Must not be {@code null}.
229         *
230         * @return The X.509 certificate.
231         *
232         * @throws OperatorCreationException On a generation exception.
233         * @throws IOException               On a byte buffer exception.
234         */
235        public static X509Certificate generateSelfSigned(final Issuer issuer,
236                                                         final Date nbf,
237                                                         final Date exp,
238                                                         final PublicKey certKey,
239                                                         final PrivateKey signingKey)
240                throws OperatorCreationException, IOException {
241                
242                return generate(issuer, new Subject(issuer.getValue()), nbf, exp, certKey, signingKey);
243        }
244        
245        
246        /**
247         * Prevents public instantiation.
248         */
249        private X509CertificateUtils() {}
250}