1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.provider; 27 28 import sun.security.pkcs12.PKCS12KeyStore; 29 import sun.security.util.KeyStoreDelegator; 30 import sun.security.x509.X509CertImpl; 31 32 import java.io.BufferedReader; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.io.OutputStream; 37 import java.nio.charset.StandardCharsets; 38 import java.security.Key; 39 import java.security.KeyStore; 40 import java.security.KeyStoreSpi; 41 import java.security.cert.Certificate; 42 import java.security.cert.CertificateException; 43 import java.security.cert.X509Certificate; 44 import java.util.Base64; 45 import java.util.Collections; 46 import java.util.Date; 47 import java.util.Enumeration; 48 import java.util.HashSet; 49 import java.util.LinkedHashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 import java.util.Set; 54 55 /** 56 * This class provides the keystore implementation referred to as "PEM". 57 * 58 * @see java.security.KeyStoreSpi 59 * @since 14 60 */ 61 62 public class PemKeyStore extends KeyStoreSpi { 63 64 // special keystore that supports JKS/PKCS12/PEM file formats 65 public static final class MultipleFormatPEM extends KeyStoreDelegator { 66 public MultipleFormatPEM() { 67 super("PEM", PemKeyStore.class, 68 List.of("JKS", "PKCS12"), 69 List.of(JavaKeyStore.JKS.class, PKCS12KeyStore.class)); 70 } 71 } 72 73 private Map<String,Certificate> certs = new LinkedHashMap<>(); 74 private Map<String, Set<KeyStore.Entry.Attribute>> attrs = new LinkedHashMap<>(); 75 76 @Override 77 public Key engineGetKey(String alias, char[] password) { 78 return null; 79 } 80 81 @Override 82 public Certificate[] engineGetCertificateChain(String alias) { 83 return null; 84 } 85 86 @Override 87 public Certificate engineGetCertificate(String alias) { 88 return certs.get(alias); 89 } 90 91 @Override 92 public Date engineGetCreationDate(String alias) { 93 Certificate c = certs.get(alias); 94 if (c instanceof X509Certificate) { 95 return ((X509Certificate) c).getNotBefore(); 96 } else { 97 return null; 98 } 99 } 100 101 @Override 102 public void engineSetKeyEntry( 103 String alias, Key key, char[] password, Certificate[] chain) { 104 throw new UnsupportedOperationException("cannot store key"); 105 } 106 107 @Override 108 public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) { 109 throw new UnsupportedOperationException("cannot store key"); 110 } 111 112 @Override 113 public void engineSetCertificateEntry(String alias, Certificate cert) { 114 certs.put(alias, cert); 115 } 116 117 @Override 118 public void engineDeleteEntry(String alias) { 119 certs.remove(alias); 120 } 121 122 @Override 123 public Enumeration<String> engineAliases() { 124 return Collections.enumeration(certs.keySet()); 125 } 126 127 @Override 128 public boolean engineContainsAlias(String alias) { 129 return certs.containsKey(alias); 130 } 131 132 @Override 133 public int engineSize() { 134 return certs.size(); 135 } 136 137 @Override 138 public boolean engineIsKeyEntry(String alias) { 139 return false; 140 } 141 142 @Override 143 public boolean engineIsCertificateEntry(String alias) { 144 return engineContainsAlias(alias); 145 } 146 147 @Override 148 public String engineGetCertificateAlias(Certificate cert) { 149 for (var entry : certs.entrySet()) { 150 if (entry.getValue().equals(cert)) { 151 return entry.getKey(); 152 } 153 } 154 return null; 155 } 156 157 @Override 158 public void engineStore(OutputStream stream, char[] password) { 159 throw new UnsupportedOperationException("cannot store keystore"); 160 } 161 162 @Override 163 public void engineLoad(InputStream stream, char[] password) 164 throws IOException, CertificateException { 165 166 String alias = null; 167 StringBuilder block = new StringBuilder(); 168 Set<KeyStore.Entry.Attribute> attr = new HashSet<>(); 169 170 boolean inPEM = false; 171 172 try (BufferedReader r = new BufferedReader( 173 new InputStreamReader(stream, StandardCharsets.UTF_8))) { 174 while (true) { 175 String s = r.readLine(); 176 if (s == null) { 177 break; 178 } 179 s = s.trim(); 180 if (s.equals(X509Factory.BEGIN_CERT)) { 181 inPEM = true; 182 } else if (s.equals(X509Factory.END_CERT)) { 183 byte[] data = Base64.getMimeDecoder().decode(block.toString()); 184 X509CertImpl c = new X509CertImpl(data); 185 if (alias == null) { 186 alias = c.getFingerprint("SHA-256"); 187 } 188 certs.put(alias, c); 189 attrs.put(alias, attr); 190 alias = null; 191 block.setLength(0); 192 attr = new HashSet<>(); // must recreate 193 inPEM = false; 194 } else if (inPEM) { 195 block.append(s); 196 } else if (s.charAt(0) == '@') { 197 String[] kv = s.substring(1).split(":\\s*", 2); 198 if (kv[0].equals("alias") && kv.length == 2) { 199 alias = kv[1]; 200 } else { 201 attr.add(new PemAttribute( 202 kv[0], kv.length == 2 ? kv[1] : "true")); 203 } 204 } 205 } 206 } 207 } 208 209 @Override 210 public KeyStore.Entry engineGetEntry( 211 String alias, KeyStore.ProtectionParameter protParam) { 212 Certificate cert = certs.get(alias); 213 if (cert == null) { 214 return null; 215 } else { 216 return new KeyStore.TrustedCertificateEntry(cert, attrs.get(alias)); 217 } 218 } 219 220 @Override 221 public boolean engineProbe(InputStream stream) throws IOException { 222 Objects.requireNonNull(stream); 223 // Ensure the file starts with 5 readable text chars 224 return isReadable(stream.read()) 225 && isReadable(stream.read()) 226 && isReadable(stream.read()) 227 && isReadable(stream.read()) 228 && isReadable(stream.read()); 229 } 230 231 private static boolean isReadable(int i) { 232 return i >= 9 && i <= 13 || i > 31 && i < 128; 233 } 234 235 private static class PemAttribute implements KeyStore.Entry.Attribute { 236 237 private final String name, value; 238 239 public PemAttribute(String name, String value) { 240 this.name = Objects.requireNonNull(name); 241 this.value = Objects.requireNonNull(value); 242 } 243 244 @Override 245 public String getName() { 246 return name; 247 } 248 249 @Override 250 public String getValue() { 251 return value; 252 } 253 254 @Override 255 public int hashCode() { 256 return Objects.hash(name, value); 257 } 258 259 @Override 260 public boolean equals(Object obj) { 261 return Objects.equals(name, value); 262 } 263 } 264 }