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 ---