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.File;
022import java.io.IOException;
023import java.io.Reader;
024import java.io.StringReader;
025import java.nio.charset.StandardCharsets;
026import java.nio.file.Files;
027import java.security.KeyStore;
028import java.security.KeyStoreException;
029import java.security.cert.CertificateException;
030import java.security.cert.X509Certificate;
031import java.text.ParseException;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.UUID;
035
036import org.bouncycastle.cert.X509CertificateHolder;
037import org.bouncycastle.openssl.PEMParser;
038
039
040/**
041 * X.509 certificate chain utilities.
042 *
043 * @author Vladimir Dzhuvinov
044 * @version 2020-02-22
045 */
046public class X509CertChainUtils {
047
048        
049        /**
050         * Converts the specified JSON array of strings to a list of Base64
051         * encoded objects.
052         *
053         * @param jsonArray The JSON array of string, {@code null} if not
054         *                  specified.
055         *
056         * @return The Base64 list, {@code null} if not specified.
057         *
058         * @throws ParseException If parsing failed.
059         */
060        public static List<Base64> toBase64List(final List<Object> jsonArray)
061                throws ParseException {
062                
063                if (jsonArray == null)
064                        return null;
065
066                List<Base64> chain = new LinkedList<>();
067
068                for (int i=0; i < jsonArray.size(); i++) {
069
070                        Object item = jsonArray.get(i);
071
072                        if (item == null) {
073                                throw new ParseException("The X.509 certificate at position " + i + " must not be null", 0);
074                        }
075
076                        if  (! (item instanceof String)) {
077                                throw new ParseException("The X.509 certificate at position " + i + " must be encoded as a Base64 string", 0);
078                        }
079
080                        chain.add(new Base64((String)item));
081                }
082
083                return chain;
084        }
085        
086        
087        /**
088         * Parses a X.509 certificate chain from the specified Base64-encoded
089         * DER-encoded representation.
090         *
091         * @param b64List The Base64-encoded DER-encoded X.509 certificate
092         *                chain, {@code null} if not specified.
093         *
094         * @return The X.509 certificate chain, {@code null} if not specified.
095         *
096         * @throws ParseException If parsing failed.
097         */
098        public static List<X509Certificate> parse(final List<Base64> b64List)
099                throws ParseException {
100                
101                if (b64List == null)
102                        return null;
103                
104                List<X509Certificate> out = new LinkedList<>();
105                
106                for (int i=0; i < b64List.size(); i++) {
107                        
108                        if (b64List.get(i)== null) continue; // skip
109                        
110                        X509Certificate cert = X509CertUtils.parse(b64List.get(i).decode());
111                        
112                        if (cert == null) {
113                                throw new ParseException("Invalid X.509 certificate at position " + i, 0);
114                        }
115                        
116                        out.add(cert);
117                }
118                
119                return out;
120        }
121        
122        
123        /**
124         * Parses a X.509 certificate chain from the specified PEM-encoded
125         * representation. PEM-encoded objects that are not X.509 certificates
126         * are ignored. Requires BouncyCastle.
127         *
128         * @param pemFile The PEM-encoded X.509 certificate chain file. Must
129         *                not be {@code null}.
130         *
131         * @return The X.509 certificate chain, empty list if no certificates
132         *         are found.
133         *
134         * @throws IOException          On I/O exception.
135         * @throws CertificateException On a certificate exception.
136         */
137        public static List<X509Certificate> parse(final File pemFile)
138                throws IOException, CertificateException {
139                
140                final String pemString = new String(Files.readAllBytes(pemFile.toPath()), StandardCharset.UTF_8);
141                return parse(pemString);
142        }
143        
144        
145        /**
146         * Parses a X.509 certificate chain from the specified PEM-encoded
147         * representation. PEM-encoded objects that are not X.509 certificates
148         * are ignored. Requires BouncyCastle.
149         *
150         * @param pemString The PEM-encoded X.509 certificate chain. Must not
151         *                  be {@code null}.
152         *
153         * @return The X.509 certificate chain, empty list if no certificates
154         *         are found.
155         *
156         * @throws IOException          On I/O exception.
157         * @throws CertificateException On a certificate exception.
158         */
159        public static List<X509Certificate> parse(final String pemString)
160                throws IOException, CertificateException {
161                
162                final Reader pemReader = new StringReader(pemString);
163                final PEMParser parser = new PEMParser(pemReader);
164                
165                List<X509Certificate> certChain = new LinkedList<>();
166                
167                Object pemObject;
168                do {
169                        pemObject = parser.readObject();
170                        
171                        if (pemObject instanceof X509CertificateHolder) {
172                                
173                                X509CertificateHolder certHolder = (X509CertificateHolder)pemObject;
174                                byte[] derEncodedCert = certHolder.getEncoded();
175                                certChain.add(X509CertUtils.parseWithException(derEncodedCert));
176                                
177                        }
178                        
179                } while (pemObject != null);
180                
181                return certChain;
182        }
183        
184        
185        /**
186         * Stores a X.509 certificate chain into the specified Java trust (key)
187         * store. The name (alias) for each certificate in the store is a
188         * generated UUID.
189         *
190         * @param trustStore The trust (key) store. Must be initialised and not
191         *                   {@code null}.
192         * @param certChain  The X.509 certificate chain. Must not be
193         *                   {@code null}.
194         *
195         * @return The UUIDs for the stored entry.
196         *
197         * @throws KeyStoreException On a key store exception.
198         */
199        public static List<UUID> store(final KeyStore trustStore, final List<X509Certificate> certChain)
200                throws KeyStoreException {
201                
202                List<UUID> aliases = new LinkedList<>();
203                
204                for (X509Certificate cert: certChain) {
205                        UUID alias = UUID.randomUUID();
206                        trustStore.setCertificateEntry(alias.toString(), cert);
207                        aliases.add(alias);
208                }
209                
210                return aliases;
211        }
212
213        
214        /**
215         * Prevents public instantiation.
216         */
217        private X509CertChainUtils() {}
218}