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}