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-06-07
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 parsing failed.
055         */
056        public static X509Certificate parse(final byte[] derEncodedCert) {
057
058                if (derEncodedCert == null || derEncodedCert.length == 0) {
059                        return null;
060                }
061
062                final Certificate cert;
063                try {
064                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
065                        cert = cf.generateCertificate(new ByteArrayInputStream(derEncodedCert));
066                } catch (CertificateException e) {
067                        return null;
068                }
069
070                if (! (cert instanceof X509Certificate)) {
071                        return null;
072                }
073
074                return (X509Certificate)cert;
075        }
076
077
078        /**
079         * Parses a PEM-encoded X.509 certificate.
080         *
081         * @param pemEncodedCert The PEM-encoded X.509 certificate, as a
082         *                       string. May be {@code null}.
083         *
084         * @return The X.509 certificate, {@code null} if parsing failed.
085         */
086        public static X509Certificate parse(final String pemEncodedCert) {
087
088                if (pemEncodedCert == null || pemEncodedCert.isEmpty()) {
089                        return null;
090                }
091
092                final int markerStart = pemEncodedCert.indexOf(PEM_BEGIN_MARKER);
093
094                if (markerStart < 0) {
095                        return null;
096                }
097
098                String buf = pemEncodedCert.substring(markerStart + PEM_BEGIN_MARKER.length());
099
100                final int markerEnd = buf.indexOf(PEM_END_MARKER);
101
102                if (markerEnd < 0) {
103                        return null;
104                }
105
106                buf = buf.substring(0, markerEnd);
107
108                buf = buf.replaceAll("\\s", "");
109
110                return parse(new Base64(buf).decode());
111        }
112        
113        
114        /**
115         * Returns the specified X.509 certificate as PEM-encoded string.
116         *
117         * @param cert The X.509 certificate. Must not be {@code null}.
118         *
119         * @return The PEM-encoded X.509 certificate, {@code null} if encoding
120         *         failed.
121         */
122        public static String toPEMString(final X509Certificate cert) {
123        
124                return toPEMString(cert, true);
125        }
126        
127        
128        /**
129         * Returns the specified X.509 certificate as PEM-encoded string.
130         *
131         * @param cert           The X.509 certificate. Must not be
132         *                       {@code null}.
133         * @param withLineBreaks {@code false} to suppress line breaks.
134         *
135         * @return The PEM-encoded X.509 certificate, {@code null} if encoding
136         *         failed.
137         */
138        public static String toPEMString(final X509Certificate cert, final boolean withLineBreaks) {
139        
140                StringBuilder sb = new StringBuilder();
141                sb.append(PEM_BEGIN_MARKER);
142                
143                if (withLineBreaks)
144                        sb.append('\n');
145                
146                try {
147                        sb.append(Base64.encode(cert.getEncoded()).toString());
148                } catch (CertificateEncodingException e) {
149                        return null;
150                }
151                
152                if (withLineBreaks)
153                        sb.append('\n');
154                
155                sb.append(PEM_END_MARKER);
156                return sb.toString();
157        }
158        
159        
160        /**
161         * Computes the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}).
162         *
163         * @param cert The X.509 certificate. Must not be {@code null}.
164         *
165         * @return The SHA-256 thumbprint, BASE64URL-encoded, {@code null} if
166         *         a certificate encoding exception is encountered.
167         */
168        public static Base64URL computeSHA256Thumbprint(final X509Certificate cert) {
169        
170                try {
171                        byte[] derEncodedCert = cert.getEncoded();
172                        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
173                        return Base64URL.encode(sha256.digest(derEncodedCert));
174                } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
175                        return null;
176                }
177        }
178}