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