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}