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