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