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 
SignerInfo(X500Name issuerName, BigInteger serial, AlgorithmId digestAlgorithmId, AlgorithmId digestEncryptionAlgorithmId, byte[] encryptedDigest)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 
SignerInfo(X500Name issuerName, BigInteger serial, AlgorithmId digestAlgorithmId, PKCS9Attributes authenticatedAttributes, AlgorithmId digestEncryptionAlgorithmId, byte[] encryptedDigest, PKCS9Attributes unauthenticatedAttributes)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      */
SignerInfo(DerInputStream derin)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      */
SignerInfo(DerInputStream derin, boolean oldStyle)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 
encode(DerOutputStream out)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      */
derEncode(OutputStream out)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      */
getCertificate(PKCS7 block)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      */
getCertificateChain(PKCS7 block)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.
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 
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. */
verify(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 
getVersion()501     public BigInteger getVersion() {
502             return version;
503     }
504 
getIssuerName()505     public X500Name getIssuerName() {
506         return issuerName;
507     }
508 
getCertificateSerialNumber()509     public BigInteger getCertificateSerialNumber() {
510         return certificateSerialNumber;
511     }
512 
getDigestAlgorithmId()513     public AlgorithmId getDigestAlgorithmId() {
514         return digestAlgorithmId;
515     }
516 
getAuthenticatedAttributes()517     public PKCS9Attributes getAuthenticatedAttributes() {
518         return authenticatedAttributes;
519     }
520 
getDigestEncryptionAlgorithmId()521     public AlgorithmId getDigestEncryptionAlgorithmId() {
522         return digestEncryptionAlgorithmId;
523     }
524 
getEncryptedDigest()525     public byte[] getEncryptedDigest() {
526         return encryptedDigest;
527     }
528 
getUnauthenticatedAttributes()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      */
getTimestamp()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      */
verifyTimestamp(TimestampToken token)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 
toString()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