1 /* 2 * Copyright (c) 2011, 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 org.openjdk.jigsaw.cli; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.DataInputStream; 30 import java.io.DataOutput; 31 import java.io.DataOutputStream; 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.IOException; 35 import java.io.RandomAccessFile; 36 import java.net.URI; 37 import java.nio.channels.FileChannel; 38 import java.nio.file.Files; 39 import java.nio.file.StandardCopyOption; 40 import java.security.GeneralSecurityException; 41 import java.security.KeyStore; 42 import java.security.KeyStoreException; 43 import java.security.MessageDigest; 44 import java.security.NoSuchAlgorithmException; 45 import java.security.PrivateKey; 46 import java.security.Signature; 47 import java.security.SignatureException; 48 import java.security.UnrecoverableKeyException; 49 import java.security.cert.X509Certificate; 50 import java.util.Arrays; 51 import java.util.List; 52 import javax.security.auth.DestroyFailedException; 53 54 import static java.lang.System.err; 55 import static java.lang.System.in; 56 import static java.lang.System.out; 57 import static java.security.KeyStore.PasswordProtection; 58 import static java.security.KeyStore.PrivateKeyEntry; 59 60 import org.openjdk.jigsaw.*; 61 import static org.openjdk.jigsaw.ModuleFile.*; 62 import static org.openjdk.jigsaw.SignedModule.SignerParameters; 63 import org.openjdk.internal.joptsimple.OptionException; 64 import org.openjdk.internal.joptsimple.OptionParser; 65 import org.openjdk.internal.joptsimple.OptionSet; 66 import org.openjdk.internal.joptsimple.OptionSpec; 67 68 import sun.security.util.Password; 69 70 /* Interface: 71 72 jsign [-v] [--keystore <keystore-location>] \ 73 [--storetype <keystore-type>] [--protected] \ 74 [--tsa <url>] [--signedmodulefile <signed-module-file>] \ 75 <module-file> <signer-alias> 76 77 */ 78 79 public class Signer { 80 81 private OptionParser parser; 82 private boolean verbose = false; 83 84 // Do not prompt for a keystore password (for example, with a keystore 85 // provider that is configured with its own PIN entry device). 86 private boolean protectedPath = false; 87 88 // Module signer's alias 89 private String signer; 90 91 // Module signer's keystore location 92 private String keystore; 93 94 // Module signer's keystore type 95 private String storetype; 96 97 // Time Stamping Authority URI 98 private URI tsaURI; 99 100 // Signed Module File (if not specified, use module file path) 101 private File signedModuleFile; 102 103 public static void main(String[] args) throws Exception { 104 try { 105 run(args); 106 } catch (OptionException | Command.Exception x) { 107 err.println(x.getMessage()); 108 System.exit(1); 109 } 110 } 111 112 public static void run(String[] args) 113 throws OptionException, Command.Exception 114 { 115 new Signer().exec(args); 116 } 117 118 private Signer() { } 119 120 private void exec(String[] args) throws OptionException, Command.Exception 121 { 122 parser = new OptionParser(); 123 124 parser.acceptsAll(Arrays.asList("v", "verbose"), 125 "Enable verbose output"); 126 parser.acceptsAll(Arrays.asList("h", "?", "help"), 127 "Show this help message"); 128 parser.acceptsAll(Arrays.asList("p", "protected"), 129 "Do not prompt for a keystore password"); 130 131 OptionSpec<String> keystoreUrl 132 = (parser.acceptsAll(Arrays.asList("k", "keystore"), 133 "URL or file name of module signer's" 134 + " keystore location") 135 .withRequiredArg() 136 .describedAs("location") 137 .ofType(String.class)); 138 139 OptionSpec<String> keystoreType 140 = (parser.acceptsAll(Arrays.asList("s", "storetype"), 141 "Module signer's keystore type") 142 .withRequiredArg() 143 .describedAs("type") 144 .ofType(String.class)); 145 146 OptionSpec<URI> tsa 147 = (parser.acceptsAll(Arrays.asList("t", "tsa"), 148 "URL of Time Stamping Authority") 149 .withRequiredArg() 150 .describedAs("location") 151 .ofType(URI.class)); 152 153 OptionSpec<File> signedModule 154 = (parser.acceptsAll(Arrays.asList("f", "signedmodulefile"), 155 "File name of signed module file") 156 .withRequiredArg() 157 .describedAs("path") 158 .ofType(File.class)); 159 160 if (args.length == 0) { 161 usage(); 162 return; 163 } 164 165 OptionSet opts = parser.parse(args); 166 if (opts.has("h")) { 167 usage(); 168 return; 169 } 170 if (opts.has("v")) 171 verbose = true; 172 173 if (opts.has(keystoreUrl)) { 174 keystore = opts.valueOf(keystoreUrl); 175 // Compatibility with keytool and jarsigner options 176 if (keystore.equals("NONE")) 177 keystore = null; 178 } 179 if (opts.has(keystoreType)) 180 storetype = opts.valueOf(keystoreType); 181 if (opts.has("protected")) 182 protectedPath = true; 183 if (opts.has(tsa)) 184 tsaURI = opts.valueOf(tsa); 185 if (opts.has(signedModule)) 186 signedModuleFile = opts.valueOf(signedModule); 187 188 new Jsign().run(null, opts); 189 } 190 191 private void usage() { 192 out.format("%n"); 193 out.format("usage: jsign [-v] [--keystore <keystore-location>] " 194 + "[--storetype <keystore-type>] [--protected] " 195 + "[--tsa <url>] [--signedmodulefile <signed-module-file>] " 196 + "<module-file> <signer-alias>%n"); 197 out.format("%n"); 198 try { 199 parser.printHelpOn(out); 200 } catch (IOException x) { 201 throw new AssertionError(x); 202 } 203 out.format("%n"); 204 } 205 206 class Jsign extends Command<SimpleLibrary> { 207 protected void go(SimpleLibrary lib) 208 throws Command.Exception 209 { 210 String moduleFile = command; 211 String signer = takeArg(); 212 if (verbose) 213 out.println("Signing module using '" + signer + "' from " 214 + " keystore " + keystore); 215 216 SignerParameters params = null; 217 218 File tmpFile = (signedModuleFile == null) 219 ? new File(moduleFile + ".sig") : signedModuleFile; 220 221 // First, read in module file and calculate hashes 222 List<byte[]> hashes = null; 223 byte[] moduleInfoBytes = null; 224 try (FileInputStream mfis = new FileInputStream(moduleFile); 225 Reader reader = new Reader(new DataInputStream(mfis))) 226 { 227 params = createSignerParameters(signer); 228 moduleInfoBytes = reader.readStart(); 229 if (reader.hasSignature()) 230 throw new Command.Exception("module file is already signed"); 231 reader.readRest(); 232 hashes = reader.getCalculatedHashes(); 233 } catch (IOException | GeneralSecurityException x) { 234 throw new Command.Exception(x); 235 } 236 237 // Next, generate signature and insert into signed module file 238 try (RandomAccessFile mraf = new RandomAccessFile(moduleFile, "r"); 239 RandomAccessFile raf = new RandomAccessFile(tmpFile, "rw")) 240 { 241 raf.setLength(0); 242 243 // Transfer header and module-info from module file 244 // to signed module file. 245 long remainderStart = ModuleFileHeader.LENGTH 246 + SectionHeader.LENGTH 247 + moduleInfoBytes.length; 248 FileChannel source = mraf.getChannel(); 249 FileChannel dest = raf.getChannel(); 250 for (long pos = 0; pos < remainderStart;) { 251 pos += source.transferTo(pos, remainderStart - pos, dest); 252 } 253 254 // Write out the Signature Section 255 writeSignatureSection(raf, hashes, params); 256 257 // Transfer the remainder of the file 258 for (long pos = remainderStart; pos < mraf.length();) { 259 pos += source.transferTo(pos, mraf.length() - pos, dest); 260 } 261 262 } catch (IOException | GeneralSecurityException x) { 263 try { 264 Files.deleteIfExists(tmpFile.toPath()); 265 } catch (IOException ioe) { 266 if (verbose) 267 err.println(x); 268 throw new Command.Exception(ioe); 269 } 270 throw new Command.Exception(x); 271 } 272 273 if (signedModuleFile == null) { 274 try { 275 Files.move(tmpFile.toPath(), new File(moduleFile).toPath(), 276 StandardCopyOption.REPLACE_EXISTING); 277 } catch (IOException ioe) { 278 throw new Command.Exception(ioe); 279 } 280 } 281 } 282 283 private SignerParameters createSignerParameters(String signer) 284 throws IOException, GeneralSecurityException 285 { 286 PasswordProtection storePassword = null; 287 PasswordProtection keyPassword = null; 288 if (keystore == null) 289 keystore = System.getProperty("user.home") 290 + File.separator + ".keystore"; 291 292 try (FileInputStream inStream = new FileInputStream(keystore)) { 293 294 if (storetype == null) 295 storetype = KeyStore.getDefaultType(); 296 297 KeyStore ks = KeyStore.getInstance(storetype); 298 299 // Prompt user for the keystore password (except when 300 // prohibited or when using Windows MY native keystore) 301 if (!protectedPath || !isWindowsKeyStore(storetype)) { 302 err.print("Enter password for " + storetype 303 + " keystore: "); 304 err.flush(); 305 storePassword 306 = new PasswordProtection(Password.readPassword(in)); 307 } 308 309 // Load the keystore 310 ks.load(inStream, storePassword.getPassword()); 311 312 if (!ks.containsAlias(signer)) 313 throw new KeyStoreException("Signer alias " + signer 314 + "does not exist"); 315 316 if (!ks.entryInstanceOf(signer, PrivateKeyEntry.class)) 317 throw new KeyStoreException("Signer alias " + signer 318 + "is not a private key"); 319 320 // First try to recover the key using keystore password 321 PrivateKeyEntry pke = null; 322 try { 323 pke = (PrivateKeyEntry)ks.getEntry(signer, storePassword); 324 } catch (UnrecoverableKeyException e) { 325 if (protectedPath || 326 storetype.equalsIgnoreCase("PKCS11") || 327 storetype.equalsIgnoreCase("Windows-MY")) { 328 throw e; 329 } 330 // Otherwise prompt the user for key password 331 err.print("Enter password for '" + signer + "' key: "); 332 err.flush(); 333 keyPassword = 334 new PasswordProtection(Password.readPassword(in)); 335 pke = (PrivateKeyEntry)ks.getEntry(signer, keyPassword); 336 } 337 338 // Create the signing mechanism 339 PrivateKey privateKey = pke.getPrivateKey(); 340 Signature signature = Signature.getInstance( 341 getSignatureAlgorithm(privateKey)); 342 signature.initSign(privateKey); 343 344 X509Certificate[] signerChain 345 = (X509Certificate[])pke.getCertificateChain(); 346 return new SignerParameters(signature, signerChain, tsaURI); 347 } finally { 348 try { 349 if (storePassword != null) { 350 storePassword.destroy(); 351 } 352 if (keyPassword != null) { 353 keyPassword.destroy(); 354 } 355 } catch (DestroyFailedException x) { 356 if (verbose) 357 err.println("Could not destroy password: " + x); 358 } 359 } 360 } 361 362 /* 363 * The signature algorithm is derived from the signer key. 364 */ 365 private String getSignatureAlgorithm(PrivateKey privateKey) 366 throws SignatureException { 367 switch (privateKey.getAlgorithm()) { 368 case "RSA": 369 return "SHA256withRSA"; 370 case "DSA": 371 return "SHA256withDSA"; 372 case "EC": 373 return "SHA256withECDSA"; 374 } 375 throw new SignatureException(privateKey.getAlgorithm() 376 + " private keys are not supported"); 377 } 378 379 /* 380 * Generates the module file signature and writes the Signature Section. 381 * 382 * The data to be signed is a list of hash values: 383 * 384 * ToBeSignedContent { 385 * u2 moduleHeaderHashLength; 386 * b* moduleHeaderHash; 387 * u2 moduleInfoHashLength; 388 * b* moduleInfoHash; 389 * u2 sectionHashLength; 390 * b* sectionHash; 391 * ... 392 * // other section hashes (in same order as module file) 393 * ... 394 * u2 moduleFileHashLength; 395 * b* moduleFileHash; 396 * } 397 * 398 */ 399 private void writeSignatureSection(DataOutput out, 400 List<byte[]> hashes, 401 SignerParameters params) 402 throws IOException, SignatureException, NoSuchAlgorithmException { 403 404 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 405 short hashLength; 406 for (byte[] hash : hashes) { 407 hashLength = (short) hash.length; 408 baos.write((byte) ((hashLength >>> 8) & 0xFF)); 409 baos.write((byte) ((hashLength >>> 0) & 0xFF)); 410 baos.write(hash, 0, hashLength); 411 } 412 byte[] toBeSigned = baos.toByteArray(); 413 414 // Compute the signature 415 SignedModule.PKCS7Signer signer = new SignedModule.PKCS7Signer(); 416 byte[] signature = signer.generateSignature(toBeSigned, params); 417 418 // Generate the hash for the signature header and content 419 baos = new ByteArrayOutputStream(); 420 DataOutputStream dos = new DataOutputStream(baos); 421 short signatureType = (short)signer.getSignatureType().value(); 422 dos.writeShort(signatureType); 423 short signatureLength = (short)signature.length; 424 dos.writeInt(signature.length); 425 byte[] signatureHeader = baos.toByteArray(); 426 MessageDigest md = MessageDigest.getInstance("SHA-256"); 427 md.update(signatureHeader); 428 md.update(signature); 429 byte[] hash = md.digest(); 430 431 // Write out the Signature Section 432 SectionHeader header = 433 new SectionHeader(FileConstants.ModuleFile.SectionType.SIGNATURE, 434 FileConstants.ModuleFile.Compressor.NONE, 435 signature.length + 6, 436 (short)0, hash); 437 header.write(out); 438 out.write(signatureHeader); 439 out.write(signature); 440 } 441 } 442 443 /** 444 * Returns true if KeyStore has a password. This is true except for 445 * MSCAPI KeyStores 446 */ 447 private static boolean isWindowsKeyStore(String storetype) { 448 return storetype.equalsIgnoreCase("Windows-MY") 449 || storetype.equalsIgnoreCase("Windows-ROOT"); 450 } 451 } --- EOF ---