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}