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 org.bouncycastle.cert.X509CertificateHolder; 037import org.bouncycastle.openssl.PEMParser; 038 039 040/** 041 * X.509 certificate chain utilities. 042 * 043 * @author Vladimir Dzhuvinov 044 * @version 2020-02-22 045 */ 046public class X509CertChainUtils { 047 048 049 /** 050 * Converts the specified JSON array of strings to a list of Base64 051 * encoded objects. 052 * 053 * @param jsonArray The JSON array of string, {@code null} if not 054 * specified. 055 * 056 * @return The Base64 list, {@code null} if not specified. 057 * 058 * @throws ParseException If parsing failed. 059 */ 060 public static List<Base64> toBase64List(final List<Object> jsonArray) 061 throws ParseException { 062 063 if (jsonArray == null) 064 return null; 065 066 List<Base64> chain = new LinkedList<>(); 067 068 for (int i=0; i < jsonArray.size(); i++) { 069 070 Object item = jsonArray.get(i); 071 072 if (item == null) { 073 throw new ParseException("The X.509 certificate at position " + i + " must not be null", 0); 074 } 075 076 if (! (item instanceof String)) { 077 throw new ParseException("The X.509 certificate at position " + i + " must be encoded as a Base64 string", 0); 078 } 079 080 chain.add(new Base64((String)item)); 081 } 082 083 return chain; 084 } 085 086 087 /** 088 * Parses a X.509 certificate chain from the specified Base64-encoded 089 * DER-encoded representation. 090 * 091 * @param b64List The Base64-encoded DER-encoded X.509 certificate 092 * chain, {@code null} if not specified. 093 * 094 * @return The X.509 certificate chain, {@code null} if not specified. 095 * 096 * @throws ParseException If parsing failed. 097 */ 098 public static List<X509Certificate> parse(final List<Base64> b64List) 099 throws ParseException { 100 101 if (b64List == null) 102 return null; 103 104 List<X509Certificate> out = new LinkedList<>(); 105 106 for (int i=0; i < b64List.size(); i++) { 107 108 if (b64List.get(i)== null) continue; // skip 109 110 X509Certificate cert = X509CertUtils.parse(b64List.get(i).decode()); 111 112 if (cert == null) { 113 throw new ParseException("Invalid X.509 certificate at position " + i, 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()), StandardCharsets.UTF_8); 141 return parse(pemString); 142 } 143 144 145 /** 146 * Parses a 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 a X.509 certificate chain into the specified Java trust (key) 187 * 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}