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 org.bouncycastle.cert.X509CertificateHolder;
022import org.bouncycastle.openssl.PEMParser;
023
024import java.io.File;
025import java.io.IOException;
026import java.io.Reader;
027import java.io.StringReader;
028import java.nio.file.Files;
029import java.security.KeyStore;
030import java.security.KeyStoreException;
031import java.security.cert.CertificateException;
032import java.security.cert.X509Certificate;
033import java.text.ParseException;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.UUID;
037
038
039/**
040 * X.509 certificate chain utilities.
041 *
042 * @author Vladimir Dzhuvinov
043 * @version 2024-09-12
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 an 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;
110                        try {
111                                cert = X509CertUtils.parseWithException(b64List.get(i).decode());
112                        } catch (CertificateException e) {
113                                throw new ParseException("Invalid X.509 certificate at position " + i + ": " + e.getMessage(), 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 an 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 an X.509 certificate chain into the specified Java trust
187         * (key) 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}