1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package sun.security.pkcs; 28 29 import java.io.InputStream; 30 import java.io.ByteArrayInputStream; 31 import java.io.OutputStream; 32 import java.io.IOException; 33 import java.math.BigInteger; 34 import java.security.CryptoPrimitive; 35 import java.security.InvalidKeyException; 36 import java.security.MessageDigest; 37 import java.security.NoSuchAlgorithmException; 38 import java.security.Principal; 39 import java.security.PublicKey; 40 import java.security.Signature; 41 import java.security.SignatureException; 42 import java.security.Timestamp; 43 import java.security.cert.CertificateException; 44 import java.security.cert.CertificateFactory; 45 import java.security.cert.CertPath; 46 import java.security.cert.X509Certificate; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.EnumSet; 51 import java.util.Set; 52 53 import sun.misc.HexDumpEncoder; 54 import sun.security.timestamp.TimestampToken; 55 import sun.security.util.Debug; 56 import sun.security.util.DerEncoder; 57 import sun.security.util.DerInputStream; 58 import sun.security.util.DerOutputStream; 59 import sun.security.util.DerValue; 60 import sun.security.util.DisabledAlgorithmConstraints; 61 import sun.security.util.KeyUtil; 62 import sun.security.util.ObjectIdentifier; 63 import sun.security.x509.AlgorithmId; 64 import sun.security.x509.X500Name; 65 import sun.security.x509.KeyUsageExtension; 66 67 /** 68 * A SignerInfo, as defined in PKCS#7's signedData type. 69 * 70 * @author Benjamin Renaud 71 */ 72 public class SignerInfo implements DerEncoder { 73 74 // Digest and Signature restrictions 75 private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET = 76 Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST)); 77 78 private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET = 79 Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE)); 80 81 private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK = 82 new DisabledAlgorithmConstraints( 83 DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS); 84 85 BigInteger version; 86 X500Name issuerName; 87 BigInteger certificateSerialNumber; 88 AlgorithmId digestAlgorithmId; 89 AlgorithmId digestEncryptionAlgorithmId; 90 byte[] encryptedDigest; 91 Timestamp timestamp; 92 private boolean hasTimestamp = true; 93 // Android-removed: This debugging mechanism is not supported in Android. 94 // private static final Debug debug = Debug.getInstance("jar"); 95 96 PKCS9Attributes authenticatedAttributes; 97 PKCS9Attributes unauthenticatedAttributes; 98 99 // Android-added: No-arg constructor to use in @libcore.api.CorePlatformApi SignerInfo()100 public SignerInfo() {} 101 SignerInfo(X500Name issuerName, BigInteger serial, AlgorithmId digestAlgorithmId, AlgorithmId digestEncryptionAlgorithmId, byte[] encryptedDigest)102 public SignerInfo(X500Name issuerName, 103 BigInteger serial, 104 AlgorithmId digestAlgorithmId, 105 AlgorithmId digestEncryptionAlgorithmId, 106 byte[] encryptedDigest) { 107 this.version = BigInteger.ONE; 108 this.issuerName = issuerName; 109 this.certificateSerialNumber = serial; 110 this.digestAlgorithmId = digestAlgorithmId; 111 this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId; 112 this.encryptedDigest = encryptedDigest; 113 } 114 SignerInfo(X500Name issuerName, BigInteger serial, AlgorithmId digestAlgorithmId, PKCS9Attributes authenticatedAttributes, AlgorithmId digestEncryptionAlgorithmId, byte[] encryptedDigest, PKCS9Attributes unauthenticatedAttributes)115 public SignerInfo(X500Name issuerName, 116 BigInteger serial, 117 AlgorithmId digestAlgorithmId, 118 PKCS9Attributes authenticatedAttributes, 119 AlgorithmId digestEncryptionAlgorithmId, 120 byte[] encryptedDigest, 121 PKCS9Attributes unauthenticatedAttributes) { 122 this.version = BigInteger.ONE; 123 this.issuerName = issuerName; 124 this.certificateSerialNumber = serial; 125 this.digestAlgorithmId = digestAlgorithmId; 126 this.authenticatedAttributes = authenticatedAttributes; 127 this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId; 128 this.encryptedDigest = encryptedDigest; 129 this.unauthenticatedAttributes = unauthenticatedAttributes; 130 } 131 132 /** 133 * Parses a PKCS#7 signer info. 134 */ SignerInfo(DerInputStream derin)135 public SignerInfo(DerInputStream derin) 136 throws IOException, ParsingException 137 { 138 this(derin, false); 139 } 140 141 /** 142 * Parses a PKCS#7 signer info. 143 * 144 * <p>This constructor is used only for backwards compatibility with 145 * PKCS#7 blocks that were generated using JDK1.1.x. 146 * 147 * @param derin the ASN.1 encoding of the signer info. 148 * @param oldStyle flag indicating whether or not the given signer info 149 * is encoded according to JDK1.1.x. 150 */ SignerInfo(DerInputStream derin, boolean oldStyle)151 public SignerInfo(DerInputStream derin, boolean oldStyle) 152 throws IOException, ParsingException 153 { 154 // version 155 version = derin.getBigInteger(); 156 157 // issuerAndSerialNumber 158 DerValue[] issuerAndSerialNumber = derin.getSequence(2); 159 byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray(); 160 issuerName = new X500Name(new DerValue(DerValue.tag_Sequence, 161 issuerBytes)); 162 certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger(); 163 164 // digestAlgorithmId 165 DerValue tmp = derin.getDerValue(); 166 167 digestAlgorithmId = AlgorithmId.parse(tmp); 168 169 // authenticatedAttributes 170 if (oldStyle) { 171 // In JDK1.1.x, the authenticatedAttributes are always present, 172 // encoded as an empty Set (Set of length zero) 173 derin.getSet(0); 174 } else { 175 // check if set of auth attributes (implicit tag) is provided 176 // (auth attributes are OPTIONAL) 177 if ((byte)(derin.peekByte()) == (byte)0xA0) { 178 authenticatedAttributes = new PKCS9Attributes(derin); 179 } 180 } 181 182 // digestEncryptionAlgorithmId - little RSA naming scheme - 183 // signature == encryption... 184 tmp = derin.getDerValue(); 185 186 digestEncryptionAlgorithmId = AlgorithmId.parse(tmp); 187 188 // encryptedDigest 189 encryptedDigest = derin.getOctetString(); 190 191 // unauthenticatedAttributes 192 if (oldStyle) { 193 // In JDK1.1.x, the unauthenticatedAttributes are always present, 194 // encoded as an empty Set (Set of length zero) 195 derin.getSet(0); 196 } else { 197 // check if set of unauth attributes (implicit tag) is provided 198 // (unauth attributes are OPTIONAL) 199 if (derin.available() != 0 200 && (byte)(derin.peekByte()) == (byte)0xA1) { 201 unauthenticatedAttributes = 202 new PKCS9Attributes(derin, true);// ignore unsupported attrs 203 } 204 } 205 206 // all done 207 if (derin.available() != 0) { 208 throw new ParsingException("extra data at the end"); 209 } 210 } 211 encode(DerOutputStream out)212 public void encode(DerOutputStream out) throws IOException { 213 214 derEncode(out); 215 } 216 217 /** 218 * DER encode this object onto an output stream. 219 * Implements the <code>DerEncoder</code> interface. 220 * 221 * @param out 222 * the output stream on which to write the DER encoding. 223 * 224 * @exception IOException on encoding error. 225 */ derEncode(OutputStream out)226 public void derEncode(OutputStream out) throws IOException { 227 DerOutputStream seq = new DerOutputStream(); 228 seq.putInteger(version); 229 DerOutputStream issuerAndSerialNumber = new DerOutputStream(); 230 issuerName.encode(issuerAndSerialNumber); 231 issuerAndSerialNumber.putInteger(certificateSerialNumber); 232 seq.write(DerValue.tag_Sequence, issuerAndSerialNumber); 233 234 digestAlgorithmId.encode(seq); 235 236 // encode authenticated attributes if there are any 237 if (authenticatedAttributes != null) 238 authenticatedAttributes.encode((byte)0xA0, seq); 239 240 digestEncryptionAlgorithmId.encode(seq); 241 242 seq.putOctetString(encryptedDigest); 243 244 // encode unauthenticated attributes if there are any 245 if (unauthenticatedAttributes != null) 246 unauthenticatedAttributes.encode((byte)0xA1, seq); 247 248 DerOutputStream tmp = new DerOutputStream(); 249 tmp.write(DerValue.tag_Sequence, seq); 250 251 out.write(tmp.toByteArray()); 252 } 253 254 255 256 /* 257 * Returns the (user) certificate pertaining to this SignerInfo. 258 */ getCertificate(PKCS7 block)259 public X509Certificate getCertificate(PKCS7 block) 260 throws IOException 261 { 262 return block.getCertificate(certificateSerialNumber, issuerName); 263 } 264 265 /* 266 * Returns the certificate chain pertaining to this SignerInfo. 267 */ getCertificateChain(PKCS7 block)268 public ArrayList<X509Certificate> getCertificateChain(PKCS7 block) 269 throws IOException 270 { 271 X509Certificate userCert; 272 userCert = block.getCertificate(certificateSerialNumber, issuerName); 273 if (userCert == null) 274 return null; 275 276 ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(); 277 certList.add(userCert); 278 279 X509Certificate[] pkcsCerts = block.getCertificates(); 280 if (pkcsCerts == null 281 || userCert.getSubjectDN().equals(userCert.getIssuerDN())) { 282 return certList; 283 } 284 285 Principal issuer = userCert.getIssuerDN(); 286 int start = 0; 287 while (true) { 288 boolean match = false; 289 int i = start; 290 while (i < pkcsCerts.length) { 291 if (issuer.equals(pkcsCerts[i].getSubjectDN())) { 292 // next cert in chain found 293 certList.add(pkcsCerts[i]); 294 // if selected cert is self-signed, we're done 295 // constructing the chain 296 if (pkcsCerts[i].getSubjectDN().equals( 297 pkcsCerts[i].getIssuerDN())) { 298 start = pkcsCerts.length; 299 } else { 300 issuer = pkcsCerts[i].getIssuerDN(); 301 X509Certificate tmpCert = pkcsCerts[start]; 302 pkcsCerts[start] = pkcsCerts[i]; 303 pkcsCerts[i] = tmpCert; 304 start++; 305 } 306 match = true; 307 break; 308 } else { 309 i++; 310 } 311 } 312 if (!match) 313 break; 314 } 315 316 return certList; 317 } 318 319 // BEGIN Android-changed: Add verify() overload that takes an InputStream. verify(PKCS7 block, byte[] data)320 SignerInfo verify(PKCS7 block, byte[] data) 321 throws NoSuchAlgorithmException, SignatureException { 322 try { 323 return verify(block, new ByteArrayInputStream(data)); 324 } catch (IOException e) { 325 // Ignore 326 return null; 327 } 328 } 329 330 /* Returns null if verify fails, this signerInfo if 331 verify succeeds. */ verify(PKCS7 block, InputStream inputStream)332 SignerInfo verify(PKCS7 block, InputStream inputStream) 333 throws NoSuchAlgorithmException, SignatureException, IOException { 334 // END Android-changed: Add verify() overload that takes an InputStream. 335 336 try { 337 338 ContentInfo content = block.getContentInfo(); 339 // BEGIN Android-changed: Our implementation uses InputStream instead of byte[]. 340 if (inputStream == null) { 341 inputStream = new ByteArrayInputStream(content.getContentBytes()); 342 } 343 // END Android-changed: Our implementation uses InputStream instead of byte[]. 344 345 String digestAlgname = getDigestAlgorithmId().getName(); 346 347 // Android-changed: Our implementation uses InputStream instead of byte[]. 348 // byte[] dataSigned; 349 InputStream dataSigned; 350 351 // if there are authenticate attributes, get the message 352 // digest and compare it with the digest of data 353 if (authenticatedAttributes == null) { 354 // Android-changed: Our implementation uses InputStream instead of byte[]. 355 // dataSigned = data; 356 dataSigned = inputStream; 357 } else { 358 359 // first, check content type 360 ObjectIdentifier contentType = (ObjectIdentifier) 361 authenticatedAttributes.getAttributeValue( 362 PKCS9Attribute.CONTENT_TYPE_OID); 363 if (contentType == null || 364 !contentType.equals((Object)content.contentType)) 365 return null; // contentType does not match, bad SignerInfo 366 367 // now, check message digest 368 byte[] messageDigest = (byte[]) 369 authenticatedAttributes.getAttributeValue( 370 PKCS9Attribute.MESSAGE_DIGEST_OID); 371 372 if (messageDigest == null) // fail if there is no message digest 373 return null; 374 375 // check that algorithm is not restricted 376 if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, 377 digestAlgname, null)) { 378 throw new SignatureException("Digest check failed. " + 379 "Disabled algorithm used: " + digestAlgname); 380 } 381 382 MessageDigest md = MessageDigest.getInstance(digestAlgname); 383 384 // BEGIN Android-changed: Our implementation uses InputStream instead of byte[]. 385 byte[] buffer = new byte[4096]; 386 int read = 0; 387 while ((read = inputStream.read(buffer)) != -1) { 388 md.update(buffer, 0 , read); 389 } 390 byte[] computedMessageDigest = md.digest(); 391 // END Android-changed: Our implementation uses InputStream instead of byte[]. 392 393 if (messageDigest.length != computedMessageDigest.length) 394 return null; 395 for (int i = 0; i < messageDigest.length; i++) { 396 if (messageDigest[i] != computedMessageDigest[i]) 397 return null; 398 } 399 400 // message digest attribute matched 401 // digest of original data 402 403 // the data actually signed is the DER encoding of 404 // the authenticated attributes (tagged with 405 // the "SET OF" tag, not 0xA0). 406 // Android-changed: Our implementation uses InputStream instead of byte[]. 407 // dataSigned = authenticatedAttributes.getDerEncoding(); 408 dataSigned = new ByteArrayInputStream(authenticatedAttributes.getDerEncoding()); 409 } 410 411 // put together digest algorithm and encryption algorithm 412 // to form signing algorithm 413 String encryptionAlgname = 414 getDigestEncryptionAlgorithmId().getName(); 415 416 // Workaround: sometimes the encryptionAlgname is actually 417 // a signature name 418 String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname); 419 if (tmp != null) encryptionAlgname = tmp; 420 String algname = AlgorithmId.makeSigAlg( 421 digestAlgname, encryptionAlgname); 422 423 // check that algorithm is not restricted 424 if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, algname, null)) { 425 throw new SignatureException("Signature check failed. " + 426 "Disabled algorithm used: " + algname); 427 } 428 429 X509Certificate cert = getCertificate(block); 430 PublicKey key = cert.getPublicKey(); 431 if (cert == null) { 432 return null; 433 } 434 435 // check if the public key is restricted 436 if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) { 437 throw new SignatureException("Public key check failed. " + 438 "Disabled key used: " + 439 KeyUtil.getKeySize(key) + " bit " + 440 key.getAlgorithm()); 441 } 442 443 if (cert.hasUnsupportedCriticalExtension()) { 444 throw new SignatureException("Certificate has unsupported " 445 + "critical extension(s)"); 446 } 447 448 // Make sure that if the usage of the key in the certificate is 449 // restricted, it can be used for digital signatures. 450 // XXX We may want to check for additional extensions in the 451 // future. 452 boolean[] keyUsageBits = cert.getKeyUsage(); 453 if (keyUsageBits != null) { 454 KeyUsageExtension keyUsage; 455 try { 456 // We don't care whether or not this extension was marked 457 // critical in the certificate. 458 // We're interested only in its value (i.e., the bits set) 459 // and treat the extension as critical. 460 keyUsage = new KeyUsageExtension(keyUsageBits); 461 } catch (IOException ioe) { 462 throw new SignatureException("Failed to parse keyUsage " 463 + "extension"); 464 } 465 466 boolean digSigAllowed = keyUsage.get( 467 KeyUsageExtension.DIGITAL_SIGNATURE).booleanValue(); 468 469 boolean nonRepuAllowed = keyUsage.get( 470 KeyUsageExtension.NON_REPUDIATION).booleanValue(); 471 472 if (!digSigAllowed && !nonRepuAllowed) { 473 throw new SignatureException("Key usage restricted: " 474 + "cannot be used for " 475 + "digital signatures"); 476 } 477 } 478 479 Signature sig = Signature.getInstance(algname); 480 sig.initVerify(key); 481 482 // BEGIN Android-changed: Our implementation uses InputStream instead of byte[]. 483 byte[] buffer = new byte[4096]; 484 int read = 0; 485 while ((read = dataSigned.read(buffer)) != -1) { 486 sig.update(buffer, 0 , read); 487 } 488 // END Android-changed: Our implementation uses InputStream instead of byte[]. 489 if (sig.verify(encryptedDigest)) { 490 return this; 491 } 492 493 } catch (IOException e) { 494 throw new SignatureException("IO error verifying signature:\n" + 495 e.getMessage()); 496 497 } catch (InvalidKeyException e) { 498 throw new SignatureException("InvalidKey: " + e.getMessage()); 499 500 } 501 return null; 502 } 503 504 /* Verify the content of the pkcs7 block. */ verify(PKCS7 block)505 SignerInfo verify(PKCS7 block) 506 throws NoSuchAlgorithmException, SignatureException { 507 // Android-changed: Overload disambiguation. 508 // return verify(block, null); 509 return verify(block, (byte[]) null); 510 } 511 512 getVersion()513 public BigInteger getVersion() { 514 return version; 515 } 516 getIssuerName()517 public X500Name getIssuerName() { 518 return issuerName; 519 } 520 getCertificateSerialNumber()521 public BigInteger getCertificateSerialNumber() { 522 return certificateSerialNumber; 523 } 524 getDigestAlgorithmId()525 public AlgorithmId getDigestAlgorithmId() { 526 return digestAlgorithmId; 527 } 528 getAuthenticatedAttributes()529 public PKCS9Attributes getAuthenticatedAttributes() { 530 return authenticatedAttributes; 531 } 532 getDigestEncryptionAlgorithmId()533 public AlgorithmId getDigestEncryptionAlgorithmId() { 534 return digestEncryptionAlgorithmId; 535 } 536 getEncryptedDigest()537 public byte[] getEncryptedDigest() { 538 return encryptedDigest; 539 } 540 getUnauthenticatedAttributes()541 public PKCS9Attributes getUnauthenticatedAttributes() { 542 return unauthenticatedAttributes; 543 } 544 545 /** 546 * Returns the timestamp PKCS7 data unverified. 547 * @return a PKCS7 object 548 */ getTsToken()549 public PKCS7 getTsToken() throws IOException { 550 if (unauthenticatedAttributes == null) { 551 return null; 552 } 553 PKCS9Attribute tsTokenAttr = 554 unauthenticatedAttributes.getAttribute( 555 PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID); 556 if (tsTokenAttr == null) { 557 return null; 558 } 559 return new PKCS7((byte[])tsTokenAttr.getValue()); 560 } 561 562 /* 563 * Extracts a timestamp from a PKCS7 SignerInfo. 564 * 565 * Examines the signer's unsigned attributes for a 566 * <tt>signatureTimestampToken</tt> attribute. If present, 567 * then it is parsed to extract the date and time at which the 568 * timestamp was generated. 569 * 570 * @param info A signer information element of a PKCS 7 block. 571 * 572 * @return A timestamp token or null if none is present. 573 * @throws IOException if an error is encountered while parsing the 574 * PKCS7 data. 575 * @throws NoSuchAlgorithmException if an error is encountered while 576 * verifying the PKCS7 object. 577 * @throws SignatureException if an error is encountered while 578 * verifying the PKCS7 object. 579 * @throws CertificateException if an error is encountered while generating 580 * the TSA's certpath. 581 */ getTimestamp()582 public Timestamp getTimestamp() 583 throws IOException, NoSuchAlgorithmException, SignatureException, 584 CertificateException 585 { 586 if (timestamp != null || !hasTimestamp) 587 return timestamp; 588 589 PKCS7 tsToken = getTsToken(); 590 if (tsToken == null) { 591 hasTimestamp = false; 592 return null; 593 } 594 595 // Extract the content (an encoded timestamp token info) 596 byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); 597 // Extract the signer (the Timestamping Authority) 598 // while verifying the content 599 SignerInfo[] tsa = tsToken.verify(encTsTokenInfo); 600 // Expect only one signer 601 ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken); 602 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 603 CertPath tsaChain = cf.generateCertPath(chain); 604 // Create a timestamp token info object 605 TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo); 606 // Check that the signature timestamp applies to this signature 607 verifyTimestamp(tsTokenInfo); 608 // Create a timestamp object 609 timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain); 610 return timestamp; 611 } 612 613 /* 614 * Check that the signature timestamp applies to this signature. 615 * Match the hash present in the signature timestamp token against the hash 616 * of this signature. 617 */ verifyTimestamp(TimestampToken token)618 private void verifyTimestamp(TimestampToken token) 619 throws NoSuchAlgorithmException, SignatureException { 620 String digestAlgname = token.getHashAlgorithm().getName(); 621 // check that algorithm is not restricted 622 if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname, 623 null)) { 624 throw new SignatureException("Timestamp token digest check failed. " + 625 "Disabled algorithm used: " + digestAlgname); 626 } 627 628 MessageDigest md = 629 MessageDigest.getInstance(digestAlgname); 630 631 if (!Arrays.equals(token.getHashedMessage(), 632 md.digest(encryptedDigest))) { 633 634 throw new SignatureException("Signature timestamp (#" + 635 token.getSerialNumber() + ") generated on " + token.getDate() + 636 " is inapplicable"); 637 } 638 639 // BEGIN Android-removed: This debugging mechanism is not supported in Android. 640 /* 641 if (debug != null) { 642 debug.println(); 643 debug.println("Detected signature timestamp (#" + 644 token.getSerialNumber() + ") generated on " + token.getDate()); 645 debug.println(); 646 } 647 */ 648 // END Android-removed: This debugging mechanism is not supported in Android. 649 } 650 toString()651 public String toString() { 652 HexDumpEncoder hexDump = new HexDumpEncoder(); 653 654 String out = ""; 655 656 out += "Signer Info for (issuer): " + issuerName + "\n"; 657 out += "\tversion: " + Debug.toHexString(version) + "\n"; 658 out += "\tcertificateSerialNumber: " + 659 Debug.toHexString(certificateSerialNumber) + "\n"; 660 out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n"; 661 if (authenticatedAttributes != null) { 662 out += "\tauthenticatedAttributes: " + authenticatedAttributes + 663 "\n"; 664 } 665 out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId + 666 "\n"; 667 668 out += "\tencryptedDigest: " + "\n" + 669 hexDump.encodeBuffer(encryptedDigest) + "\n"; 670 if (unauthenticatedAttributes != null) { 671 out += "\tunauthenticatedAttributes: " + 672 unauthenticatedAttributes + "\n"; 673 } 674 return out; 675 } 676 } 677