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