rev 15874 : 8163304: jarsigner -verbose -verify should print the algorithms used to sign the jar

   1 /*
   2  * Copyright (c) 1996, 2016, 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.pkcs;
  27 
  28 import java.io.OutputStream;
  29 import java.io.IOException;
  30 import java.math.BigInteger;
  31 import java.security.CryptoPrimitive;
  32 import java.security.InvalidKeyException;
  33 import java.security.MessageDigest;
  34 import java.security.NoSuchAlgorithmException;
  35 import java.security.Principal;
  36 import java.security.PublicKey;
  37 import java.security.Signature;
  38 import java.security.SignatureException;
  39 import java.security.Timestamp;
  40 import java.security.cert.CertificateException;
  41 import java.security.cert.CertificateFactory;
  42 import java.security.cert.CertPath;
  43 import java.security.cert.X509Certificate;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.Collections;
  47 import java.util.EnumSet;
  48 import java.util.Set;
  49 
  50 import sun.security.timestamp.TimestampToken;
  51 import sun.security.util.Debug;
  52 import sun.security.util.DerEncoder;
  53 import sun.security.util.DerInputStream;
  54 import sun.security.util.DerOutputStream;
  55 import sun.security.util.DerValue;
  56 import sun.security.util.DisabledAlgorithmConstraints;
  57 import sun.security.util.HexDumpEncoder;
  58 import sun.security.util.KeyUtil;
  59 import sun.security.util.ObjectIdentifier;
  60 import sun.security.x509.AlgorithmId;
  61 import sun.security.x509.X500Name;
  62 import sun.security.x509.KeyUsageExtension;
  63 
  64 /**
  65  * A SignerInfo, as defined in PKCS#7's signedData type.
  66  *
  67  * @author Benjamin Renaud
  68  */
  69 public class SignerInfo implements DerEncoder {
  70 
  71     // Digest and Signature restrictions
  72     private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET =
  73             Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
  74 
  75     private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET =
  76             Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
  77 
  78     private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
  79             new DisabledAlgorithmConstraints(
  80                     DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
  81 
  82     BigInteger version;
  83     X500Name issuerName;
  84     BigInteger certificateSerialNumber;
  85     AlgorithmId digestAlgorithmId;
  86     AlgorithmId digestEncryptionAlgorithmId;
  87     byte[] encryptedDigest;
  88     Timestamp timestamp;
  89     private boolean hasTimestamp = true;
  90     private static final Debug debug = Debug.getInstance("jar");
  91 
  92     PKCS9Attributes authenticatedAttributes;
  93     PKCS9Attributes unauthenticatedAttributes;
  94 
  95     public SignerInfo(X500Name  issuerName,
  96                       BigInteger serial,
  97                       AlgorithmId digestAlgorithmId,
  98                       AlgorithmId digestEncryptionAlgorithmId,
  99                       byte[] encryptedDigest) {
 100         this.version = BigInteger.ONE;
 101         this.issuerName = issuerName;
 102         this.certificateSerialNumber = serial;
 103         this.digestAlgorithmId = digestAlgorithmId;
 104         this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
 105         this.encryptedDigest = encryptedDigest;
 106     }
 107 
 108     public SignerInfo(X500Name  issuerName,
 109                       BigInteger serial,
 110                       AlgorithmId digestAlgorithmId,
 111                       PKCS9Attributes authenticatedAttributes,
 112                       AlgorithmId digestEncryptionAlgorithmId,
 113                       byte[] encryptedDigest,
 114                       PKCS9Attributes unauthenticatedAttributes) {
 115         this.version = BigInteger.ONE;
 116         this.issuerName = issuerName;
 117         this.certificateSerialNumber = serial;
 118         this.digestAlgorithmId = digestAlgorithmId;
 119         this.authenticatedAttributes = authenticatedAttributes;
 120         this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
 121         this.encryptedDigest = encryptedDigest;
 122         this.unauthenticatedAttributes = unauthenticatedAttributes;
 123     }
 124 
 125     /**
 126      * Parses a PKCS#7 signer info.
 127      */
 128     public SignerInfo(DerInputStream derin)
 129         throws IOException, ParsingException
 130     {
 131         this(derin, false);
 132     }
 133 
 134     /**
 135      * Parses a PKCS#7 signer info.
 136      *
 137      * <p>This constructor is used only for backwards compatibility with
 138      * PKCS#7 blocks that were generated using JDK1.1.x.
 139      *
 140      * @param derin the ASN.1 encoding of the signer info.
 141      * @param oldStyle flag indicating whether or not the given signer info
 142      * is encoded according to JDK1.1.x.
 143      */
 144     public SignerInfo(DerInputStream derin, boolean oldStyle)
 145         throws IOException, ParsingException
 146     {
 147         // version
 148         version = derin.getBigInteger();
 149 
 150         // issuerAndSerialNumber
 151         DerValue[] issuerAndSerialNumber = derin.getSequence(2);
 152         byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
 153         issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
 154                                                issuerBytes));
 155         certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();
 156 
 157         // digestAlgorithmId
 158         DerValue tmp = derin.getDerValue();
 159 
 160         digestAlgorithmId = AlgorithmId.parse(tmp);
 161 
 162         // authenticatedAttributes
 163         if (oldStyle) {
 164             // In JDK1.1.x, the authenticatedAttributes are always present,
 165             // encoded as an empty Set (Set of length zero)
 166             derin.getSet(0);
 167         } else {
 168             // check if set of auth attributes (implicit tag) is provided
 169             // (auth attributes are OPTIONAL)
 170             if ((byte)(derin.peekByte()) == (byte)0xA0) {
 171                 authenticatedAttributes = new PKCS9Attributes(derin);
 172             }
 173         }
 174 
 175         // digestEncryptionAlgorithmId - little RSA naming scheme -
 176         // signature == encryption...
 177         tmp = derin.getDerValue();
 178 
 179         digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
 180 
 181         // encryptedDigest
 182         encryptedDigest = derin.getOctetString();
 183 
 184         // unauthenticatedAttributes
 185         if (oldStyle) {
 186             // In JDK1.1.x, the unauthenticatedAttributes are always present,
 187             // encoded as an empty Set (Set of length zero)
 188             derin.getSet(0);
 189         } else {
 190             // check if set of unauth attributes (implicit tag) is provided
 191             // (unauth attributes are OPTIONAL)
 192             if (derin.available() != 0
 193                 && (byte)(derin.peekByte()) == (byte)0xA1) {
 194                 unauthenticatedAttributes =
 195                     new PKCS9Attributes(derin, true);// ignore unsupported attrs
 196             }
 197         }
 198 
 199         // all done
 200         if (derin.available() != 0) {
 201             throw new ParsingException("extra data at the end");
 202         }
 203     }
 204 
 205     public void encode(DerOutputStream out) throws IOException {
 206 
 207         derEncode(out);
 208     }
 209 
 210     /**
 211      * DER encode this object onto an output stream.
 212      * Implements the {@code DerEncoder} interface.
 213      *
 214      * @param out
 215      * the output stream on which to write the DER encoding.
 216      *
 217      * @exception IOException on encoding error.
 218      */
 219     public void derEncode(OutputStream out) throws IOException {
 220         DerOutputStream seq = new DerOutputStream();
 221         seq.putInteger(version);
 222         DerOutputStream issuerAndSerialNumber = new DerOutputStream();
 223         issuerName.encode(issuerAndSerialNumber);
 224         issuerAndSerialNumber.putInteger(certificateSerialNumber);
 225         seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
 226 
 227         digestAlgorithmId.encode(seq);
 228 
 229         // encode authenticated attributes if there are any
 230         if (authenticatedAttributes != null)
 231             authenticatedAttributes.encode((byte)0xA0, seq);
 232 
 233         digestEncryptionAlgorithmId.encode(seq);
 234 
 235         seq.putOctetString(encryptedDigest);
 236 
 237         // encode unauthenticated attributes if there are any
 238         if (unauthenticatedAttributes != null)
 239             unauthenticatedAttributes.encode((byte)0xA1, seq);
 240 
 241         DerOutputStream tmp = new DerOutputStream();
 242         tmp.write(DerValue.tag_Sequence, seq);
 243 
 244         out.write(tmp.toByteArray());
 245     }
 246 
 247 
 248 
 249     /*
 250      * Returns the (user) certificate pertaining to this SignerInfo.
 251      */
 252     public X509Certificate getCertificate(PKCS7 block)
 253         throws IOException
 254     {
 255         return block.getCertificate(certificateSerialNumber, issuerName);
 256     }
 257 
 258     /*
 259      * Returns the certificate chain pertaining to this SignerInfo.
 260      */
 261     public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
 262         throws IOException
 263     {
 264         X509Certificate userCert;
 265         userCert = block.getCertificate(certificateSerialNumber, issuerName);
 266         if (userCert == null)
 267             return null;
 268 
 269         ArrayList<X509Certificate> certList = new ArrayList<>();
 270         certList.add(userCert);
 271 
 272         X509Certificate[] pkcsCerts = block.getCertificates();
 273         if (pkcsCerts == null
 274             || userCert.getSubjectDN().equals(userCert.getIssuerDN())) {
 275             return certList;
 276         }
 277 
 278         Principal issuer = userCert.getIssuerDN();
 279         int start = 0;
 280         while (true) {
 281             boolean match = false;
 282             int i = start;
 283             while (i < pkcsCerts.length) {
 284                 if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
 285                     // next cert in chain found
 286                     certList.add(pkcsCerts[i]);
 287                     // if selected cert is self-signed, we're done
 288                     // constructing the chain
 289                     if (pkcsCerts[i].getSubjectDN().equals(
 290                                             pkcsCerts[i].getIssuerDN())) {
 291                         start = pkcsCerts.length;
 292                     } else {
 293                         issuer = pkcsCerts[i].getIssuerDN();
 294                         X509Certificate tmpCert = pkcsCerts[start];
 295                         pkcsCerts[start] = pkcsCerts[i];
 296                         pkcsCerts[i] = tmpCert;
 297                         start++;
 298                     }
 299                     match = true;
 300                     break;
 301                 } else {
 302                     i++;
 303                 }
 304             }
 305             if (!match)
 306                 break;
 307         }
 308 
 309         return certList;
 310     }
 311 
 312     /* Returns null if verify fails, this signerInfo if
 313        verify succeeds. */
 314     SignerInfo verify(PKCS7 block, byte[] data)
 315     throws NoSuchAlgorithmException, SignatureException {
 316 
 317         try {
 318 
 319             ContentInfo content = block.getContentInfo();
 320             if (data == null) {
 321                 data = content.getContentBytes();
 322             }
 323 
 324             String digestAlgname = getDigestAlgorithmId().getName();
 325 
 326             byte[] dataSigned;
 327 
 328             // if there are authenticate attributes, get the message
 329             // digest and compare it with the digest of data
 330             if (authenticatedAttributes == null) {
 331                 dataSigned = data;
 332             } else {
 333 
 334                 // first, check content type
 335                 ObjectIdentifier contentType = (ObjectIdentifier)
 336                        authenticatedAttributes.getAttributeValue(
 337                          PKCS9Attribute.CONTENT_TYPE_OID);
 338                 if (contentType == null ||
 339                     !contentType.equals(content.contentType))
 340                     return null;  // contentType does not match, bad SignerInfo
 341 
 342                 // now, check message digest
 343                 byte[] messageDigest = (byte[])
 344                     authenticatedAttributes.getAttributeValue(
 345                          PKCS9Attribute.MESSAGE_DIGEST_OID);
 346 
 347                 if (messageDigest == null) // fail if there is no message digest
 348                     return null;
 349 
 350                 // check that algorithm is not restricted
 351                 if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET,
 352                         digestAlgname, null)) {
 353                     throw new SignatureException("Digest check failed. " +
 354                             "Disabled algorithm used: " + digestAlgname);
 355                 }
 356 
 357                 MessageDigest md = MessageDigest.getInstance(digestAlgname);
 358                 byte[] computedMessageDigest = md.digest(data);
 359 
 360                 if (messageDigest.length != computedMessageDigest.length)
 361                     return null;
 362                 for (int i = 0; i < messageDigest.length; i++) {
 363                     if (messageDigest[i] != computedMessageDigest[i])
 364                         return null;
 365                 }
 366 
 367                 // message digest attribute matched
 368                 // digest of original data
 369 
 370                 // the data actually signed is the DER encoding of
 371                 // the authenticated attributes (tagged with
 372                 // the "SET OF" tag, not 0xA0).
 373                 dataSigned = authenticatedAttributes.getDerEncoding();
 374             }
 375 
 376             // put together digest algorithm and encryption algorithm
 377             // to form signing algorithm
 378             String encryptionAlgname =
 379                 getDigestEncryptionAlgorithmId().getName();
 380 
 381             // Workaround: sometimes the encryptionAlgname is actually
 382             // a signature name
 383             String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname);
 384             if (tmp != null) encryptionAlgname = tmp;
 385             String algname = AlgorithmId.makeSigAlg(
 386                     digestAlgname, encryptionAlgname);
 387 
 388             // check that algorithm is not restricted
 389             if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, algname, null)) {
 390                 throw new SignatureException("Signature check failed. " +
 391                         "Disabled algorithm used: " + algname);
 392             }
 393 
 394             X509Certificate cert = getCertificate(block);
 395             PublicKey key = cert.getPublicKey();
 396             if (cert == null) {
 397                 return null;
 398             }
 399 
 400             // check if the public key is restricted
 401             if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
 402                 throw new SignatureException("Public key check failed. " +
 403                         "Disabled key used: " +
 404                         KeyUtil.getKeySize(key) + " bit " +
 405                         key.getAlgorithm());
 406             }
 407 
 408             if (cert.hasUnsupportedCriticalExtension()) {
 409                 throw new SignatureException("Certificate has unsupported "
 410                                              + "critical extension(s)");
 411             }
 412 
 413             // Make sure that if the usage of the key in the certificate is
 414             // restricted, it can be used for digital signatures.
 415             // XXX We may want to check for additional extensions in the
 416             // future.
 417             boolean[] keyUsageBits = cert.getKeyUsage();
 418             if (keyUsageBits != null) {
 419                 KeyUsageExtension keyUsage;
 420                 try {
 421                     // We don't care whether or not this extension was marked
 422                     // critical in the certificate.
 423                     // We're interested only in its value (i.e., the bits set)
 424                     // and treat the extension as critical.
 425                     keyUsage = new KeyUsageExtension(keyUsageBits);
 426                 } catch (IOException ioe) {
 427                     throw new SignatureException("Failed to parse keyUsage "
 428                                                  + "extension");
 429                 }
 430 
 431                 boolean digSigAllowed = keyUsage.get(
 432                         KeyUsageExtension.DIGITAL_SIGNATURE).booleanValue();
 433 
 434                 boolean nonRepuAllowed = keyUsage.get(
 435                         KeyUsageExtension.NON_REPUDIATION).booleanValue();
 436 
 437                 if (!digSigAllowed && !nonRepuAllowed) {
 438                     throw new SignatureException("Key usage restricted: "
 439                                                  + "cannot be used for "
 440                                                  + "digital signatures");
 441                 }
 442             }
 443 
 444             Signature sig = Signature.getInstance(algname);
 445             sig.initVerify(key);
 446             sig.update(dataSigned);
 447             if (sig.verify(encryptedDigest)) {
 448                 return this;
 449             }
 450 
 451         } catch (IOException e) {
 452             throw new SignatureException("IO error verifying signature:\n" +
 453                                          e.getMessage());
 454 
 455         } catch (InvalidKeyException e) {
 456             throw new SignatureException("InvalidKey: " + e.getMessage());
 457 
 458         }
 459         return null;
 460     }
 461 
 462     /* Verify the content of the pkcs7 block. */
 463     SignerInfo verify(PKCS7 block)
 464     throws NoSuchAlgorithmException, SignatureException {
 465         return verify(block, null);
 466     }
 467 
 468 
 469     public BigInteger getVersion() {
 470             return version;
 471     }
 472 
 473     public X500Name getIssuerName() {
 474         return issuerName;
 475     }
 476 
 477     public BigInteger getCertificateSerialNumber() {
 478         return certificateSerialNumber;
 479     }
 480 
 481     public AlgorithmId getDigestAlgorithmId() {
 482         return digestAlgorithmId;
 483     }
 484 
 485     public PKCS9Attributes getAuthenticatedAttributes() {
 486         return authenticatedAttributes;
 487     }
 488 
 489     public AlgorithmId getDigestEncryptionAlgorithmId() {
 490         return digestEncryptionAlgorithmId;
 491     }
 492 
 493     public byte[] getEncryptedDigest() {
 494         return encryptedDigest;
 495     }
 496 
 497     public PKCS9Attributes getUnauthenticatedAttributes() {
 498         return unauthenticatedAttributes;
 499     }
 500 

















 501     /*
 502      * Extracts a timestamp from a PKCS7 SignerInfo.
 503      *
 504      * Examines the signer's unsigned attributes for a
 505      * {@code signatureTimestampToken} attribute. If present,
 506      * then it is parsed to extract the date and time at which the
 507      * timestamp was generated.
 508      *
 509      * @param info A signer information element of a PKCS 7 block.
 510      *
 511      * @return A timestamp token or null if none is present.
 512      * @throws IOException if an error is encountered while parsing the
 513      *         PKCS7 data.
 514      * @throws NoSuchAlgorithmException if an error is encountered while
 515      *         verifying the PKCS7 object.
 516      * @throws SignatureException if an error is encountered while
 517      *         verifying the PKCS7 object.
 518      * @throws CertificateException if an error is encountered while generating
 519      *         the TSA's certpath.
 520      */
 521     public Timestamp getTimestamp()
 522         throws IOException, NoSuchAlgorithmException, SignatureException,
 523                CertificateException
 524     {
 525         if (timestamp != null || !hasTimestamp)
 526             return timestamp;
 527 
 528         if (unauthenticatedAttributes == null) {
 529             hasTimestamp = false;
 530             return null;
 531         }
 532         PKCS9Attribute tsTokenAttr =
 533             unauthenticatedAttributes.getAttribute(
 534                 PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
 535         if (tsTokenAttr == null) {
 536             hasTimestamp = false;
 537             return null;
 538         }
 539 
 540         PKCS7 tsToken = new PKCS7((byte[])tsTokenAttr.getValue());
 541         // Extract the content (an encoded timestamp token info)
 542         byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
 543         // Extract the signer (the Timestamping Authority)
 544         // while verifying the content
 545         SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);
 546         // Expect only one signer
 547         ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);
 548         CertificateFactory cf = CertificateFactory.getInstance("X.509");
 549         CertPath tsaChain = cf.generateCertPath(chain);
 550         // Create a timestamp token info object
 551         TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
 552         // Check that the signature timestamp applies to this signature
 553         verifyTimestamp(tsTokenInfo);
 554         // Create a timestamp object
 555         timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);
 556         return timestamp;
 557     }
 558 
 559     /*
 560      * Check that the signature timestamp applies to this signature.
 561      * Match the hash present in the signature timestamp token against the hash
 562      * of this signature.
 563      */
 564     private void verifyTimestamp(TimestampToken token)
 565         throws NoSuchAlgorithmException, SignatureException {
 566         String digestAlgname = token.getHashAlgorithm().getName();
 567         // check that algorithm is not restricted
 568         if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname,
 569                 null)) {
 570             throw new SignatureException("Timestamp token digest check failed. " +
 571                     "Disabled algorithm used: " + digestAlgname);
 572         }
 573 
 574         MessageDigest md =
 575             MessageDigest.getInstance(digestAlgname);
 576 
 577         if (!Arrays.equals(token.getHashedMessage(),
 578             md.digest(encryptedDigest))) {
 579 
 580             throw new SignatureException("Signature timestamp (#" +
 581                 token.getSerialNumber() + ") generated on " + token.getDate() +
 582                 " is inapplicable");
 583         }
 584 
 585         if (debug != null) {
 586             debug.println();
 587             debug.println("Detected signature timestamp (#" +
 588                 token.getSerialNumber() + ") generated on " + token.getDate());
 589             debug.println();
 590         }
 591     }
 592 
 593     public String toString() {
 594         HexDumpEncoder hexDump = new HexDumpEncoder();
 595 
 596         String out = "";
 597 
 598         out += "Signer Info for (issuer): " + issuerName + "\n";
 599         out += "\tversion: " + Debug.toHexString(version) + "\n";
 600         out += "\tcertificateSerialNumber: " +
 601                Debug.toHexString(certificateSerialNumber) + "\n";
 602         out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
 603         if (authenticatedAttributes != null) {
 604             out += "\tauthenticatedAttributes: " + authenticatedAttributes +
 605                    "\n";
 606         }
 607         out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
 608             "\n";
 609 
 610         out += "\tencryptedDigest: " + "\n" +
 611             hexDump.encodeBuffer(encryptedDigest) + "\n";
 612         if (unauthenticatedAttributes != null) {
 613             out += "\tunauthenticatedAttributes: " +
 614                    unauthenticatedAttributes + "\n";
 615         }
 616         return out;
 617     }
 618 }
--- EOF ---