Home | History | Annotate | Download | only in pkcs
      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 // BEGIN Android-added
     30 import java.io.InputStream;
     31 import java.io.ByteArrayInputStream;
     32 // END Android-added
     33 import java.io.OutputStream;
     34 import java.io.IOException;
     35 import java.math.BigInteger;
     36 import java.security.CryptoPrimitive;
     37 import java.security.InvalidKeyException;
     38 import java.security.MessageDigest;
     39 import java.security.NoSuchAlgorithmException;
     40 import java.security.Principal;
     41 import java.security.PublicKey;
     42 import java.security.Signature;
     43 import java.security.SignatureException;
     44 import java.security.Timestamp;
     45 import java.security.cert.CertificateException;
     46 import java.security.cert.CertificateFactory;
     47 import java.security.cert.CertPath;
     48 import java.security.cert.X509Certificate;
     49 import java.util.ArrayList;
     50 import java.util.Arrays;
     51 import java.util.Collections;
     52 import java.util.EnumSet;
     53 import java.util.Set;
     54 
     55 import sun.misc.HexDumpEncoder;
     56 import sun.security.timestamp.TimestampToken;
     57 import sun.security.util.Debug;
     58 import sun.security.util.DerEncoder;
     59 import sun.security.util.DerInputStream;
     60 import sun.security.util.DerOutputStream;
     61 import sun.security.util.DerValue;
     62 import sun.security.util.DisabledAlgorithmConstraints;
     63 import sun.security.util.KeyUtil;
     64 import sun.security.util.ObjectIdentifier;
     65 import sun.security.x509.AlgorithmId;
     66 import sun.security.x509.X500Name;
     67 import sun.security.x509.KeyUsageExtension;
     68 import sun.security.x509.PKIXExtensions;
     69 
     70 /**
     71  * A SignerInfo, as defined in PKCS#7's signedData type.
     72  *
     73  * @author Benjamin Renaud
     74  */
     75 public class SignerInfo implements DerEncoder {
     76 
     77     // Digest and Signature restrictions
     78     private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET =
     79             Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
     80 
     81     private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET =
     82             Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
     83 
     84     private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
     85             new DisabledAlgorithmConstraints(
     86                     DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
     87 
     88     BigInteger version;
     89     X500Name issuerName;
     90     BigInteger certificateSerialNumber;
     91     AlgorithmId digestAlgorithmId;
     92     AlgorithmId digestEncryptionAlgorithmId;
     93     byte[] encryptedDigest;
     94     Timestamp timestamp;
     95     private boolean hasTimestamp = true;
     96     // Android-removed
     97     // private static final Debug debug = Debug.getInstance("jar");
     98 
     99     PKCS9Attributes authenticatedAttributes;
    100     PKCS9Attributes unauthenticatedAttributes;
    101 
    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 
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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
    320     // Originally there's no overloading for InputStream.
    321     SignerInfo verify(PKCS7 block, byte[] data)
    322     throws NoSuchAlgorithmException, SignatureException {
    323       try {
    324         return verify(block, new ByteArrayInputStream(data));
    325       } catch (IOException e) {
    326         // Ignore
    327         return null;
    328       }
    329     }
    330 
    331     /* Returns null if verify fails, this signerInfo if
    332        verify succeeds. */
    333     SignerInfo verify(PKCS7 block, InputStream inputStream)
    334     throws NoSuchAlgorithmException, SignatureException, IOException {
    335 
    336        try {
    337 
    338             ContentInfo content = block.getContentInfo();
    339             if (inputStream == null) {
    340                 inputStream = new ByteArrayInputStream(content.getContentBytes());
    341             }
    342 
    343             String digestAlgname = getDigestAlgorithmId().getName();
    344 
    345             InputStream dataSigned;
    346 
    347             // if there are authenticate attributes, get the message
    348             // digest and compare it with the digest of data
    349             if (authenticatedAttributes == null) {
    350                 dataSigned = inputStream;
    351             } else {
    352 
    353                 // first, check content type
    354                 ObjectIdentifier contentType = (ObjectIdentifier)
    355                        authenticatedAttributes.getAttributeValue(
    356                          PKCS9Attribute.CONTENT_TYPE_OID);
    357                 if (contentType == null ||
    358                     !contentType.equals((Object)content.contentType))
    359                     return null;  // contentType does not match, bad SignerInfo
    360 
    361                 // now, check message digest
    362                 byte[] messageDigest = (byte[])
    363                     authenticatedAttributes.getAttributeValue(
    364                          PKCS9Attribute.MESSAGE_DIGEST_OID);
    365 
    366                 if (messageDigest == null) // fail if there is no message digest
    367                     return null;
    368 
    369                 // check that algorithm is not restricted
    370                 if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET,
    371                         digestAlgname, null)) {
    372                     throw new SignatureException("Digest check failed. " +
    373                             "Disabled algorithm used: " + digestAlgname);
    374                 }
    375 
    376                 MessageDigest md = MessageDigest.getInstance(digestAlgname);
    377 
    378                 byte[] buffer = new byte[4096];
    379                 int read = 0;
    380                 while ((read = inputStream.read(buffer)) != -1) {
    381                   md.update(buffer, 0 , read);
    382                 }
    383                 byte[] computedMessageDigest = md.digest();
    384 
    385                 if (messageDigest.length != computedMessageDigest.length)
    386                     return null;
    387                 for (int i = 0; i < messageDigest.length; i++) {
    388                     if (messageDigest[i] != computedMessageDigest[i])
    389                         return null;
    390                 }
    391 
    392                 // message digest attribute matched
    393                 // digest of original data
    394 
    395                 // the data actually signed is the DER encoding of
    396                 // the authenticated attributes (tagged with
    397                 // the "SET OF" tag, not 0xA0).
    398                 dataSigned = new ByteArrayInputStream(authenticatedAttributes.getDerEncoding());
    399             }
    400 
    401             // put together digest algorithm and encryption algorithm
    402             // to form signing algorithm
    403             String encryptionAlgname =
    404                 getDigestEncryptionAlgorithmId().getName();
    405 
    406             // Workaround: sometimes the encryptionAlgname is actually
    407             // a signature name
    408             String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname);
    409             if (tmp != null) encryptionAlgname = tmp;
    410             String algname = AlgorithmId.makeSigAlg(
    411                     digestAlgname, encryptionAlgname);
    412 
    413             // check that algorithm is not restricted
    414             if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, algname, null)) {
    415                 throw new SignatureException("Signature check failed. " +
    416                         "Disabled algorithm used: " + algname);
    417             }
    418 
    419             X509Certificate cert = getCertificate(block);
    420             PublicKey key = cert.getPublicKey();
    421             if (cert == null) {
    422                 return null;
    423             }
    424 
    425             // check if the public key is restricted
    426             if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
    427                 throw new SignatureException("Public key check failed. " +
    428                         "Disabled key used: " +
    429                         KeyUtil.getKeySize(key) + " bit " +
    430                         key.getAlgorithm());
    431             }
    432 
    433             if (cert.hasUnsupportedCriticalExtension()) {
    434                 throw new SignatureException("Certificate has unsupported "
    435                                              + "critical extension(s)");
    436             }
    437 
    438             // Make sure that if the usage of the key in the certificate is
    439             // restricted, it can be used for digital signatures.
    440             // XXX We may want to check for additional extensions in the
    441             // future.
    442             boolean[] keyUsageBits = cert.getKeyUsage();
    443             if (keyUsageBits != null) {
    444                 KeyUsageExtension keyUsage;
    445                 try {
    446                     // We don't care whether or not this extension was marked
    447                     // critical in the certificate.
    448                     // We're interested only in its value (i.e., the bits set)
    449                     // and treat the extension as critical.
    450                     keyUsage = new KeyUsageExtension(keyUsageBits);
    451                 } catch (IOException ioe) {
    452                     throw new SignatureException("Failed to parse keyUsage "
    453                                                  + "extension");
    454                 }
    455 
    456                 boolean digSigAllowed = keyUsage.get(
    457                         KeyUsageExtension.DIGITAL_SIGNATURE).booleanValue();
    458 
    459                 boolean nonRepuAllowed = keyUsage.get(
    460                         KeyUsageExtension.NON_REPUDIATION).booleanValue();
    461 
    462                 if (!digSigAllowed && !nonRepuAllowed) {
    463                     throw new SignatureException("Key usage restricted: "
    464                                                  + "cannot be used for "
    465                                                  + "digital signatures");
    466                 }
    467             }
    468 
    469             Signature sig = Signature.getInstance(algname);
    470             sig.initVerify(key);
    471 
    472             byte[] buffer = new byte[4096];
    473             int read = 0;
    474             while ((read = dataSigned.read(buffer)) != -1) {
    475               sig.update(buffer, 0 , read);
    476             }
    477             if (sig.verify(encryptedDigest)) {
    478                 return this;
    479             }
    480 
    481         } catch (IOException e) {
    482             throw new SignatureException("IO error verifying signature:\n" +
    483                                          e.getMessage());
    484 
    485         } catch (InvalidKeyException e) {
    486             throw new SignatureException("InvalidKey: " + e.getMessage());
    487 
    488         }
    489         return null;
    490     }
    491     // END Android-changed
    492 
    493     /* Verify the content of the pkcs7 block. */
    494     SignerInfo verify(PKCS7 block)
    495     throws NoSuchAlgorithmException, SignatureException {
    496       // BEGIN Android-changed
    497       // Was: return verify(block, null);
    498       // As in Android the method is overloaded, we need to disambiguate with a cast
    499       return verify(block, (byte[])null);
    500       // END Android-changed
    501     }
    502 
    503 
    504     public BigInteger getVersion() {
    505             return version;
    506     }
    507 
    508     public X500Name getIssuerName() {
    509         return issuerName;
    510     }
    511 
    512     public BigInteger getCertificateSerialNumber() {
    513         return certificateSerialNumber;
    514     }
    515 
    516     public AlgorithmId getDigestAlgorithmId() {
    517         return digestAlgorithmId;
    518     }
    519 
    520     public PKCS9Attributes getAuthenticatedAttributes() {
    521         return authenticatedAttributes;
    522     }
    523 
    524     public AlgorithmId getDigestEncryptionAlgorithmId() {
    525         return digestEncryptionAlgorithmId;
    526     }
    527 
    528     public byte[] getEncryptedDigest() {
    529         return encryptedDigest;
    530     }
    531 
    532     public PKCS9Attributes getUnauthenticatedAttributes() {
    533         return unauthenticatedAttributes;
    534     }
    535 
    536     /**
    537      * Returns the timestamp PKCS7 data unverified.
    538      * @return a PKCS7 object
    539      */
    540     public PKCS7 getTsToken() throws IOException {
    541         if (unauthenticatedAttributes == null) {
    542             return null;
    543         }
    544         PKCS9Attribute tsTokenAttr =
    545                 unauthenticatedAttributes.getAttribute(
    546                         PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
    547         if (tsTokenAttr == null) {
    548             return null;
    549         }
    550         return new PKCS7((byte[])tsTokenAttr.getValue());
    551     }
    552 
    553     /*
    554      * Extracts a timestamp from a PKCS7 SignerInfo.
    555      *
    556      * Examines the signer's unsigned attributes for a
    557      * <tt>signatureTimestampToken</tt> attribute. If present,
    558      * then it is parsed to extract the date and time at which the
    559      * timestamp was generated.
    560      *
    561      * @param info A signer information element of a PKCS 7 block.
    562      *
    563      * @return A timestamp token or null if none is present.
    564      * @throws IOException if an error is encountered while parsing the
    565      *         PKCS7 data.
    566      * @throws NoSuchAlgorithmException if an error is encountered while
    567      *         verifying the PKCS7 object.
    568      * @throws SignatureException if an error is encountered while
    569      *         verifying the PKCS7 object.
    570      * @throws CertificateException if an error is encountered while generating
    571      *         the TSA's certpath.
    572      */
    573     public Timestamp getTimestamp()
    574         throws IOException, NoSuchAlgorithmException, SignatureException,
    575                CertificateException
    576     {
    577 
    578         if (timestamp != null || !hasTimestamp)
    579             return timestamp;
    580 
    581         PKCS7 tsToken = getTsToken();
    582         if (tsToken == null) {
    583             hasTimestamp = false;
    584             return null;
    585         }
    586 
    587         // Extract the content (an encoded timestamp token info)
    588         byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
    589         // Extract the signer (the Timestamping Authority)
    590         // while verifying the content
    591         SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);
    592         // Expect only one signer
    593         ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);
    594         CertificateFactory cf = CertificateFactory.getInstance("X.509");
    595         CertPath tsaChain = cf.generateCertPath(chain);
    596         // Create a timestamp token info object
    597         TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
    598         // Check that the signature timestamp applies to this signature
    599         verifyTimestamp(tsTokenInfo);
    600         // Create a timestamp object
    601         timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);
    602         return timestamp;
    603     }
    604 
    605     /*
    606      * Check that the signature timestamp applies to this signature.
    607      * Match the hash present in the signature timestamp token against the hash
    608      * of this signature.
    609      */
    610     private void verifyTimestamp(TimestampToken token)
    611         throws NoSuchAlgorithmException, SignatureException {
    612         String digestAlgname = token.getHashAlgorithm().getName();
    613         // check that algorithm is not restricted
    614         if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname,
    615                 null)) {
    616             throw new SignatureException("Timestamp token digest check failed. " +
    617                     "Disabled algorithm used: " + digestAlgname);
    618         }
    619 
    620         MessageDigest md =
    621             MessageDigest.getInstance(digestAlgname);
    622 
    623         if (!Arrays.equals(token.getHashedMessage(),
    624             md.digest(encryptedDigest))) {
    625 
    626             throw new SignatureException("Signature timestamp (#" +
    627                 token.getSerialNumber() + ") generated on " + token.getDate() +
    628                 " is inapplicable");
    629         }
    630 
    631         // BEGIN Android-removed
    632         /*
    633         if (debug != null) {
    634             debug.println();
    635             debug.println("Detected signature timestamp (#" +
    636                 token.getSerialNumber() + ") generated on " + token.getDate());
    637             debug.println();
    638         }
    639         */
    640         // END Android-removed
    641     }
    642 
    643     public String toString() {
    644         HexDumpEncoder hexDump = new HexDumpEncoder();
    645 
    646         String out = "";
    647 
    648         out += "Signer Info for (issuer): " + issuerName + "\n";
    649         out += "\tversion: " + Debug.toHexString(version) + "\n";
    650         out += "\tcertificateSerialNumber: " +
    651                Debug.toHexString(certificateSerialNumber) + "\n";
    652         out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
    653         if (authenticatedAttributes != null) {
    654             out += "\tauthenticatedAttributes: " + authenticatedAttributes +
    655                    "\n";
    656         }
    657         out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
    658             "\n";
    659 
    660         out += "\tencryptedDigest: " + "\n" +
    661             hexDump.encodeBuffer(encryptedDigest) + "\n";
    662         if (unauthenticatedAttributes != null) {
    663             out += "\tunauthenticatedAttributes: " +
    664                    unauthenticatedAttributes + "\n";
    665         }
    666         return out;
    667     }
    668 }
    669