1 /* 2 * Copyright (c) 2005, 2020, 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.tools; 27 28 29 import java.io.BufferedReader; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.IOException; 33 import java.io.InputStreamReader; 34 35 import java.io.StreamTokenizer; 36 import java.io.StringReader; 37 import java.net.URL; 38 39 import java.security.KeyStore; 40 41 import java.security.Provider; 42 import java.security.Security; 43 import java.security.cert.X509Certificate; 44 import java.text.Collator; 45 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.Properties; 51 import java.util.ResourceBundle; 52 import java.util.ServiceLoader; 53 54 import sun.security.util.FilePaths; 55 import sun.security.util.PropertyExpander; 56 57 /** 58 * <p> This class provides several utilities to <code>KeyStore</code>. 59 * 60 * @since 1.6.0 61 */ 62 public class KeyStoreUtil { 63 KeyStoreUtil()64 private KeyStoreUtil() { 65 // this class is not meant to be instantiated 66 } 67 68 /** 69 * Returns true if the certificate is self-signed, false otherwise. 70 */ isSelfSigned(X509Certificate cert)71 public static boolean isSelfSigned(X509Certificate cert) { 72 return signedBy(cert, cert); 73 } 74 signedBy(X509Certificate end, X509Certificate ca)75 public static boolean signedBy(X509Certificate end, X509Certificate ca) { 76 if (!ca.getSubjectX500Principal().equals(end.getIssuerX500Principal())) { 77 return false; 78 } 79 try { 80 end.verify(ca.getPublicKey()); 81 return true; 82 } catch (Exception e) { 83 return false; 84 } 85 } 86 87 /** 88 * Returns true if KeyStore has a password. This is true except for 89 * MSCAPI KeyStores 90 */ isWindowsKeyStore(String storetype)91 public static boolean isWindowsKeyStore(String storetype) { 92 return storetype != null 93 && (storetype.equalsIgnoreCase("Windows-MY") 94 || storetype.equalsIgnoreCase("Windows-ROOT")); 95 } 96 97 /** 98 * Returns standard-looking names for storetype 99 */ niceStoreTypeName(String storetype)100 public static String niceStoreTypeName(String storetype) { 101 if (storetype.equalsIgnoreCase("Windows-MY")) { 102 return "Windows-MY"; 103 } else if(storetype.equalsIgnoreCase("Windows-ROOT")) { 104 return "Windows-ROOT"; 105 } else { 106 return storetype.toUpperCase(Locale.ENGLISH); 107 } 108 } 109 110 /** 111 * Returns the file name of the keystore with the configured CA certificates. 112 */ getCacerts()113 public static String getCacerts() { 114 return FilePaths.cacerts(); 115 } 116 117 /** 118 * Returns the keystore with the configured CA certificates. 119 */ getCacertsKeyStore()120 public static KeyStore getCacertsKeyStore() throws Exception { 121 File file = new File(getCacerts()); 122 if (!file.exists()) { 123 return null; 124 } 125 return KeyStore.getInstance(file, (char[])null); 126 } 127 getPassWithModifier(String modifier, String arg, ResourceBundle rb, Collator collator)128 public static char[] getPassWithModifier(String modifier, String arg, 129 ResourceBundle rb, 130 Collator collator) { 131 if (modifier == null) { 132 return arg.toCharArray(); 133 } else if (collator.compare(modifier, "env") == 0) { 134 String value = System.getenv(arg); 135 if (value == null) { 136 System.err.println(rb.getString( 137 "Cannot.find.environment.variable.") + arg); 138 return null; 139 } else { 140 return value.toCharArray(); 141 } 142 } else if (collator.compare(modifier, "file") == 0) { 143 try { 144 URL url = null; 145 try { 146 url = new URL(arg); 147 } catch (java.net.MalformedURLException mue) { 148 File f = new File(arg); 149 if (f.exists()) { 150 url = f.toURI().toURL(); 151 } else { 152 System.err.println(rb.getString( 153 "Cannot.find.file.") + arg); 154 return null; 155 } 156 } 157 158 try (BufferedReader br = 159 new BufferedReader(new InputStreamReader( 160 url.openStream()))) { 161 String value = br.readLine(); 162 163 if (value == null) { 164 return new char[0]; 165 } 166 167 return value.toCharArray(); 168 } 169 } catch (IOException ioe) { 170 System.err.println(ioe); 171 return null; 172 } 173 } else { 174 System.err.println(rb.getString("Unknown.password.type.") + 175 modifier); 176 return null; 177 } 178 } 179 180 /** 181 * Parses a option line likes 182 * -genkaypair -dname "CN=Me" 183 * and add the results into a list 184 * @param list the list to fill into 185 * @param s the line 186 */ parseArgsLine(List<String> list, String s)187 private static void parseArgsLine(List<String> list, String s) 188 throws IOException, PropertyExpander.ExpandException { 189 StreamTokenizer st = new StreamTokenizer(new StringReader(s)); 190 191 st.resetSyntax(); 192 st.whitespaceChars(0x00, 0x20); 193 st.wordChars(0x21, 0xFF); 194 // Everything is a word char except for quotation and apostrophe 195 st.quoteChar('"'); 196 st.quoteChar('\''); 197 198 while (true) { 199 if (st.nextToken() == StreamTokenizer.TT_EOF) { 200 break; 201 } 202 list.add(PropertyExpander.expand(st.sval)); 203 } 204 } 205 206 /** 207 * Prepends matched options from a pre-configured options file. 208 * 209 * @param tool the name of the tool, can be "keytool" or "jarsigner" 210 * @param file the pre-configured options file 211 * @param c1 the name of the command, with the "-" prefix, 212 * must not be null 213 * @param c2 the alternative command name, with the "-" prefix, 214 * null if none. For example, "genkey" is alt name for 215 * "genkeypair". A command can only have one alt name now. 216 * @param args existing arguments 217 * @return arguments combined 218 * @throws IOException if there is a file I/O or format error 219 * @throws PropertyExpander.ExpandException 220 * if there is a property expansion error 221 */ expandArgs(String tool, String file, String c1, String c2, String[] args)222 public static String[] expandArgs(String tool, String file, 223 String c1, String c2, String[] args) 224 throws IOException, PropertyExpander.ExpandException { 225 226 List<String> result = new ArrayList<>(); 227 Properties p = new Properties(); 228 p.load(new FileInputStream(file)); 229 230 String s = p.getProperty(tool + ".all"); 231 if (s != null) { 232 parseArgsLine(result, s); 233 } 234 235 // Cannot provide both -genkey and -genkeypair 236 String s1 = p.getProperty(tool + "." + c1.substring(1)); 237 String s2 = null; 238 if (c2 != null) { 239 s2 = p.getProperty(tool + "." + c2.substring(1)); 240 } 241 if (s1 != null && s2 != null) { 242 throw new IOException("Cannot have both " + c1 + " and " 243 + c2 + " as pre-configured options"); 244 } 245 if (s1 == null) { 246 s1 = s2; 247 } 248 if (s1 != null) { 249 parseArgsLine(result, s1); 250 } 251 252 if (result.isEmpty()) { 253 return args; 254 } else { 255 result.addAll(Arrays.asList(args)); 256 return result.toArray(new String[result.size()]); 257 } 258 } 259 260 /** 261 * Loads a security provider as a service. 262 * 263 * @param provName the name 264 * @param arg optional arg 265 * @throws IllegalArgumentException if no provider matches the name 266 */ loadProviderByName(String provName, String arg)267 public static void loadProviderByName(String provName, String arg) { 268 Provider loaded = Security.getProvider(provName); 269 if (loaded != null) { 270 if (arg != null) { 271 loaded = loaded.configure(arg); 272 Security.addProvider(loaded); 273 } 274 return; 275 } 276 for (Provider p : ServiceLoader.load(Provider.class, 277 ClassLoader.getSystemClassLoader())) { 278 if (p.getName().equals(provName)) { 279 if (arg != null) { 280 p = p.configure(arg); 281 } 282 Security.addProvider(p); 283 return; 284 } 285 } 286 throw new IllegalArgumentException("No provider found"); 287 } 288 289 /** 290 * Loads a security provider by a fully-qualified class name. 291 * 292 * @param provClass the class name 293 * @param arg optional arg 294 * @param cl optional class loader 295 * @throws IllegalArgumentException if no provider matches the class name 296 * @throws ClassCastException if the class has not extended Provider 297 */ loadProviderByClass( String provClass, String arg, ClassLoader cl)298 public static void loadProviderByClass( 299 String provClass, String arg, ClassLoader cl) { 300 301 // For compatibility, SunPKCS11, and SunMSCAPI 302 // can still be loadable with -providerClass. 303 if (provClass.equals("sun.security.pkcs11.SunPKCS11")) { 304 loadProviderByName("SunPKCS11", arg); 305 return; 306 } else if (provClass.equals("sun.security.mscapi.SunMSCAPI")) { 307 loadProviderByName("SunMSCAPI", arg); 308 return; 309 } 310 311 Provider prov; 312 try { 313 Class<?> clazz = Class.forName(provClass, false, cl); 314 prov = (Provider) clazz.getConstructor().newInstance(); 315 } catch (ReflectiveOperationException e) { 316 throw new IllegalArgumentException(e); 317 } 318 if (arg != null) { 319 prov = prov.configure(arg); 320 } 321 Security.addProvider(prov); 322 } 323 } 324