/* * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.tools.packager; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.CodeSigner; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.Timestamp; import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Locale; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; import sun.security.pkcs.PKCS9Attribute; import sun.security.pkcs.PKCS9Attributes; import sun.security.pkcs.ParsingException; import sun.security.pkcs.SignerInfo; import sun.security.timestamp.TimestampToken; import sun.security.x509.AlgorithmId; import sun.security.x509.X500Name; /** * (Source copied from: * com.sun.deploy.security.JarSignature in Deploy workspace) * * This class is an abstraction of signature that is currently used * for implementation of jar signing as BLOBs. * * There are 2 modes of use for this class - signing and validation * and same instance of JarSignature object can not be reused. * * Signing mode: * - create new instance using JarSignature.create() * - add entries you want to include into the signature using * updateWithEntry(). (Note the order is important.) * - use getEncoded() to get bytes for the result signature * * Validation mode: * - create new instance using JarSignature.load() * - add entries using updateWithEntry() * - use isValid() to validate result * - use getCodeSigners() to get list of code signers used * * @deprecated use {@link ToolProvider} to locate the {@code "javapackager"} tool instead. */ @Deprecated(since="10", forRemoval=true) public class JarSignature { //name of jar manifest attribute that contains signature public static final String BLOB_SIGNATURE = "META-INF/SIGNATURE.BSF"; private final Signature sig; private final X509Certificate certChain[]; // for singing scenarios only private final CodeSigner codeSigners[]; // for validation only private final SignerInfo signerInfos[]; // for validation only /** * Loads jar signature from given byte array. * If signature could not be reconstructed then exceptions are thrown. */ public static JarSignature load(byte[] rawSignature) throws ParsingException, CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { PKCS7 pkcs7 = new PKCS7(rawSignature); SignerInfo[] infos = pkcs7.getSignerInfos(); if (infos == null || infos.length != 1) { throw new IllegalArgumentException( "BLOB signature currently only support single signer."); } X509Certificate cert = infos[0].getCertificate(pkcs7); PublicKey publicKey = cert.getPublicKey(); CodeSigner[] signers = extractCodeSigners(infos, pkcs7); Signature sig = getSignature(infos[0]); sig.initVerify(publicKey); return new JarSignature(sig, infos, signers); } /** * Creates new signature for signing. * * @param privateKey Key to be used for signing. * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ public static JarSignature create( PrivateKey privateKey, X509Certificate chain[]) throws NoSuchAlgorithmException, InvalidKeyException { Signature signature = getSignature(privateKey.getAlgorithm()); signature.initSign(privateKey); return new JarSignature(signature, chain); } private JarSignature(Signature signature, X509Certificate chain[]) { certChain = chain; signerInfos = null; codeSigners = null; sig = signature; } private JarSignature(Signature signature, SignerInfo[] infos, CodeSigner[] signers) { certChain = null; signerInfos = infos; codeSigners = signers; sig = signature; } public boolean isValidationMode() { return certChain == null; } /** * Get default signature object base on default signature algorithm derived * from given key algorithm for use in encoding usage. */ private static Signature getSignature(String keyAlgorithm) throws NoSuchAlgorithmException { if (keyAlgorithm.equalsIgnoreCase("DSA")) { return Signature.getInstance("SHA1withDSA"); } else if (keyAlgorithm.equalsIgnoreCase("RSA")) { return Signature.getInstance("SHA256withRSA"); } else if (keyAlgorithm.equalsIgnoreCase("EC")) { return Signature.getInstance("SHA256withECDSA"); } throw new IllegalArgumentException( "Key algorithm should be either DSA, RSA or EC"); } /** * Derive Signature from signer info for use in validation. */ private static Signature getSignature(SignerInfo info) throws NoSuchAlgorithmException { String digestAlgorithm = info.getDigestAlgorithmId().getName(); String keyAlgorithm = info.getDigestEncryptionAlgorithmId().getName(); String signatureAlgorithm = makeSigAlg( digestAlgorithm, keyAlgorithm); return Signature.getInstance(signatureAlgorithm); } String getSignatureAlgorithm() throws NoSuchAlgorithmException { return sig.getAlgorithm(); } AlgorithmId getDigestAlgorithm() throws NoSuchAlgorithmException { String name = getDigAlgFromSigAlg(sig.getAlgorithm()); return AlgorithmId.get(name); } AlgorithmId getKeyAlgorithm() throws NoSuchAlgorithmException { String name = getEncAlgFromSigAlg(sig.getAlgorithm()); return AlgorithmId.get(name); } private static String makeSigAlg(String digAlg, String encAlg) { digAlg = digAlg.replace("-", "").toUpperCase(Locale.ENGLISH); if (digAlg.equalsIgnoreCase("SHA")) digAlg = "SHA1"; encAlg = encAlg.toUpperCase(Locale.ENGLISH); if (encAlg.equals("EC")) encAlg = "ECDSA"; return digAlg + "with" + encAlg; } private static String getDigAlgFromSigAlg(String signatureAlgorithm) { signatureAlgorithm = signatureAlgorithm.toUpperCase(Locale.ENGLISH); int with = signatureAlgorithm.indexOf("WITH"); if (with > 0) { return signatureAlgorithm.substring(0, with); } return null; } private static String getEncAlgFromSigAlg(String signatureAlgorithm) { signatureAlgorithm = signatureAlgorithm.toUpperCase(Locale.ENGLISH); int with = signatureAlgorithm.indexOf("WITH"); String keyAlgorithm = null; if (with > 0) { int and = signatureAlgorithm.indexOf("AND", with + 4); if (and > 0) { keyAlgorithm = signatureAlgorithm.substring(with + 4, and); } else { keyAlgorithm = signatureAlgorithm.substring(with + 4); } if (keyAlgorithm.equalsIgnoreCase("ECDSA")) { keyAlgorithm = "EC"; } } return keyAlgorithm; } /** * Returns encoded representation of signature. * @throws UnsupportedOperationException if called in validation mode. */ public byte[] getEncoded() throws NoSuchAlgorithmException, SignatureException, IOException { if (isValidationMode()) { throw new UnsupportedOperationException( "Method is not for validation mode."); } AlgorithmId digestAlgId = getDigestAlgorithm(); AlgorithmId[] digestAlgIds = {digestAlgId}; ContentInfo contentInfo = new ContentInfo(ContentInfo.DATA_OID, null); Principal issuerName = certChain[0].getIssuerDN(); BigInteger serialNumber = certChain[0].getSerialNumber(); byte[] signature = sig.sign(); SignerInfo signerInfo = new SignerInfo((X500Name) issuerName, serialNumber, digestAlgId, getKeyAlgorithm(), signature); SignerInfo[] signerInfos = {signerInfo}; PKCS7 pkcs7 = new PKCS7(digestAlgIds, contentInfo, certChain, signerInfos); ByteArrayOutputStream bos = new ByteArrayOutputStream(8192); pkcs7.encodeSignedData(bos); return bos.toByteArray(); } private class ValidationStream extends InputStream { InputStream dataStream = null; public ValidationStream( InputStream is) { dataStream = is; } public int read() throws IOException { int v = dataStream.read(); if (v > -1) { try { JarSignature.this.sig.update((byte) v); } catch (SignatureException ex) { throw new RuntimeException(ex); } } return v; } public int read(byte[] b, int off, int len) throws IOException { len = dataStream.read(b, off, len); if (len > 0) { try { JarSignature.this.sig.update(b, off, len); } catch (SignatureException ex) { throw new RuntimeException(ex); } } return len; } public void close() throws IOException { dataStream.close(); } } /** * Performs partial validation of zip/jar entry with given name and * data stream. Return wrapped version of input stream to complete validation. * Stream need to be read fully for validation to be complete. * * @throws SignatureException */ public InputStream updateWithZipEntry(String name, InputStream is) throws SignatureException { try { sig.update(name.getBytes("UTF-8")); } catch(UnsupportedEncodingException e) { throw new SignatureException(e); } return new ValidationStream(is); } public void update(byte[] v) throws SignatureException { sig.update(v); } public boolean isValid() { try { return sig.verify(signerInfos[0].getEncryptedDigest()); } catch(Exception e) { return false; } } public CodeSigner[] getCodeSigners() { CodeSigner[] result = new CodeSigner[codeSigners.length]; System.arraycopy(codeSigners, 0, result, 0, result.length); return result; } /* * Implementation is mostly borrowed from * sun.security.util.SignatureFileVerifier */ private static CodeSigner[] extractCodeSigners(SignerInfo infos[], PKCS7 block) throws IOException, NoSuchAlgorithmException, SignatureException, CertificateException { ArrayList s = new ArrayList(); CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); for (int i = 0; i < infos.length; i++) { SignerInfo info = infos[i]; ArrayList chain = info.getCertificateChain(block); CertPath certPath = certificateFactory.generateCertPath(chain); // Append the new code signer CodeSigner signer = new CodeSigner(certPath, getTimestamp(info, certificateFactory)); // if (block.getCRLs() != null) { // SharedSecrets.getJavaSecurityCodeSignerAccess().setCRLs( // signer, block.getCRLs()); // } s.add(signer); } return (CodeSigner[]) s.toArray(new CodeSigner[s.size()]); } /* * Examines a signature timestamp token to generate a timestamp object. * * Examines the signer's unsigned attributes for a * signatureTimestampToken attribute. If present, * then it is parsed to extract the date and time at which the * timestamp was generated. * * @param info A signer information element of a PKCS 7 block. * * @return A timestamp token or null if none is present. * @throws IOException if an error is encountered while parsing the * PKCS7 data. * @throws NoSuchAlgorithmException if an error is encountered while * verifying the PKCS7 object. * @throws SignatureException if an error is encountered while * verifying the PKCS7 object. * @throws CertificateException if an error is encountered while generating * the TSA's certpath. */ private static Timestamp getTimestamp(SignerInfo info, CertificateFactory certificateFactory) throws IOException, NoSuchAlgorithmException, SignatureException, CertificateException { Timestamp timestamp = null; // Extract the signer's unsigned attributes PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes(); if (unsignedAttrs != null) { PKCS9Attribute timestampTokenAttr = unsignedAttrs.getAttribute("signatureTimestampToken"); if (timestampTokenAttr != null) { PKCS7 timestampToken = new PKCS7((byte[]) timestampTokenAttr.getValue()); // Extract the content (an encoded timestamp token info) byte[] encodedTimestampTokenInfo = timestampToken.getContentInfo().getData(); // Extract the signer (the Timestamping Authority) // while verifying the content SignerInfo[] tsa = timestampToken.verify(encodedTimestampTokenInfo); // Expect only one signer ArrayList chain = tsa[0].getCertificateChain(timestampToken); CertPath tsaChain = certificateFactory.generateCertPath( chain); // Create a timestamp token info object TimestampToken timestampTokenInfo = new TimestampToken(encodedTimestampTokenInfo); // Create a timestamp object timestamp = new Timestamp(timestampTokenInfo.getDate(), tsaChain); } } return timestamp; } public interface InputStreamSource { InputStream getInputStream() throws IOException; } public void signJarAsBLOB(InputStreamSource input, ZipOutputStream jos) throws IOException, SignatureException, NoSuchAlgorithmException { byte copyBuf[] = new byte[8000]; int n; ZipEntry e; ZipInputStream jis = new ZipInputStream(input.getInputStream()); try { //calculate signature in the first pass //consider all entries except directories and old signature file (if any) boolean hasManifest = false; boolean hasMetaInf = false; while ((e = jis.getNextEntry()) != null) { if (JarFile.MANIFEST_NAME.equals( e.getName().toUpperCase(Locale.ENGLISH))) { hasManifest = true; } if ("META-INF/".equals( e.getName().toUpperCase(Locale.ENGLISH))) { hasMetaInf = true; } if (!BLOB_SIGNATURE.equals(e.getName()) && !e.getName().endsWith("/")) { readFully(updateWithZipEntry(e.getName(), jis)); } } byte[] signature = getEncoded(); //2 pass: save manifest and other entries //reopen input file jis.close(); jis = new ZipInputStream(input.getInputStream()); while ((e = jis.getNextEntry()) != null) { String name = e.getName(); //special case - jar has no manifest and possibly no META-INF // => add META-INF entry once //Then output manifest if (!hasMetaInf) { ZipEntry ze = new ZipEntry("META-INF/"); ze.setTime(System.currentTimeMillis()); //NOTE: Do we need CRC32? for this entry? jos.putNextEntry(ze); jos.closeEntry(); hasMetaInf = true; } if (!hasManifest) { addSignatureEntry(signature, jos); hasManifest = true; } //copy entry unless it is old signature file if (!BLOB_SIGNATURE.equals(name)) { // do our own compression ZipEntry e2 = new ZipEntry(name); e2.setMethod(e.getMethod()); e2.setTime(e.getTime()); e2.setComment(e.getComment()); e2.setExtra(e.getExtra()); if (e.getMethod() == ZipEntry.STORED) { e2.setSize(e.getSize()); e2.setCrc(e.getCrc()); } jos.putNextEntry(e2); while ((n = jis.read(copyBuf)) != -1) { jos.write(copyBuf, 0, n); } jos.closeEntry(); } String upperName = name.toUpperCase(Locale.ENGLISH); boolean isManifestEntry = JarFile.MANIFEST_NAME.equals(upperName); //output signature after manifest if (isManifestEntry) { addSignatureEntry(signature, jos); } } } finally { jis.close(); jos.close(); } } private void addSignatureEntry(byte[] signature, ZipOutputStream jos) throws IOException { ZipEntry ze = new ZipEntry(BLOB_SIGNATURE); ze.setSize(signature.length); ze.setTime(System.currentTimeMillis()); //NOTE: Do we need a CRC32? jos.putNextEntry(ze); jos.write(signature); jos.closeEntry(); } private static void readFully(InputStream is) throws IOException { byte buf[] = new byte[10000]; while (is.read(buf) != -1) {} } }