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.file.Files; 026import java.security.KeyStore; 027import java.security.KeyStoreException; 028import java.security.cert.CertificateException; 029import java.security.cert.X509Certificate; 030import java.text.ParseException; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.UUID; 034 035import org.bouncycastle.cert.X509CertificateHolder; 036import org.bouncycastle.openssl.PEMParser; 037 038 039/** 040 * X.509 certificate chain utilities. 041 * 042 * @author Vladimir Dzhuvinov 043 * @version 2020-02-22 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 a 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 = X509CertUtils.parse(b64List.get(i).decode()); 110 111 if (cert == null) { 112 throw new ParseException("Invalid X.509 certificate at position " + i, 0); 113 } 114 115 out.add(cert); 116 } 117 118 return out; 119 } 120 121 122 /** 123 * Parses a X.509 certificate chain from the specified PEM-encoded 124 * representation. PEM-encoded objects that are not X.509 certificates 125 * are ignored. Requires BouncyCastle. 126 * 127 * @param pemFile The PEM-encoded X.509 certificate chain file. Must 128 * not be {@code null}. 129 * 130 * @return The X.509 certificate chain, empty list if no certificates 131 * are found. 132 * 133 * @throws IOException On I/O exception. 134 * @throws CertificateException On a certificate exception. 135 */ 136 public static List<X509Certificate> parse(final File pemFile) 137 throws IOException, CertificateException { 138 139 final String pemString = new String(Files.readAllBytes(pemFile.toPath()), StandardCharset.UTF_8); 140 return parse(pemString); 141 } 142 143 144 /** 145 * Parses a X.509 certificate chain from the specified PEM-encoded 146 * representation. PEM-encoded objects that are not X.509 certificates 147 * are ignored. Requires BouncyCastle. 148 * 149 * @param pemString The PEM-encoded X.509 certificate chain. Must not 150 * be {@code null}. 151 * 152 * @return The X.509 certificate chain, empty list if no certificates 153 * are found. 154 * 155 * @throws IOException On I/O exception. 156 * @throws CertificateException On a certificate exception. 157 */ 158 public static List<X509Certificate> parse(final String pemString) 159 throws IOException, CertificateException { 160 161 final Reader pemReader = new StringReader(pemString); 162 final PEMParser parser = new PEMParser(pemReader); 163 164 List<X509Certificate> certChain = new LinkedList<>(); 165 166 Object pemObject; 167 do { 168 pemObject = parser.readObject(); 169 170 if (pemObject instanceof X509CertificateHolder) { 171 172 X509CertificateHolder certHolder = (X509CertificateHolder)pemObject; 173 byte[] derEncodedCert = certHolder.getEncoded(); 174 certChain.add(X509CertUtils.parseWithException(derEncodedCert)); 175 176 } 177 178 } while (pemObject != null); 179 180 return certChain; 181 } 182 183 184 /** 185 * Stores a X.509 certificate chain into the specified Java trust (key) 186 * store. The name (alias) for each certificate in the store is a 187 * generated UUID. 188 * 189 * @param trustStore The trust (key) store. Must be initialised and not 190 * {@code null}. 191 * @param certChain The X.509 certificate chain. Must not be 192 * {@code null}. 193 * 194 * @return The UUIDs for the stored entry. 195 * 196 * @throws KeyStoreException On a key store exception. 197 */ 198 public static List<UUID> store(final KeyStore trustStore, final List<X509Certificate> certChain) 199 throws KeyStoreException { 200 201 List<UUID> aliases = new LinkedList<>(); 202 203 for (X509Certificate cert: certChain) { 204 UUID alias = UUID.randomUUID(); 205 trustStore.setCertificateEntry(alias.toString(), cert); 206 aliases.add(alias); 207 } 208 209 return aliases; 210 } 211 212 213 /** 214 * Prevents public instantiation. 215 */ 216 private X509CertChainUtils() {} 217}