Search code examples
c#itextitext7

Read TimeStampToken in iText 8 from TimeStampTokenInfo


I'm trying to read TimeStampToken in iText 8 (C#) but I lost myself in the ASN1 structure conversion.

I was trying to implement the following code:

byte[] timestampTokenInfoBytes = pkcs7.GetTimeStampTokenInfo().ToASN1Primitive().GetEncoded();

var content = Asn1Object.FromByteArray(pkcs7.GetTimeStampTokenInfo().ToASN1Primitive().GetEncoded());
// >>>>>>>>  How to create Content Info from Asn1object? <<<<<<<<<<< 
// The following line raises a cast exception...

ContentInfo contentInfo = new ContentInfo(new DerObjectIdentifier(PkcsObjectIdentifiers.IdAAEtsContentTimestamp.Id), content);

Solution

  • Indeed, there does not seem to be an explicit method to extract the full time stamp token. Thus, one has to extract and analyze the signature container oneself.

    There are two obvious options for this, using either the BouncyCastle abstraction of iText or a specific BouncyCastle variant directly.

    Using the former has the advantage that one's code works with both BouncyCastle and BouncyCastle-FIPS. It has the disadvantage, though, that the abstraction only contains the BouncyCastle functionality used in the iText code.

    Also there are two kinds of time stamps in PDFs, document time stamps (stand-alone time stamps) and signature time stamps (time stamps embedded in CMS signature containers).

    In the following example code, document time stamps are extracted using the BouncyCastle abstraction and signature time stamps are extracted using BouncyCastle directly.

    Document time stamps

    Given a SignatureUtil signatureUtil for one's PDF and a string name of a signature field that contains a document time stamp, one can extract the time stamp token like this using iText's BouncyCastle abstraction:

    PdfSignature pdfSignature = signatureUtil.GetSignature(name);
    PdfString contents = pdfSignature.GetContents();
    byte[] bytes = PdfEncodings.ConvertToBytes(contents.GetValue(), null);
    
    IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.GetFactory();
    IAsn1Object asn1Object = BOUNCY_CASTLE_FACTORY.CreateASN1InputStream(bytes).ReadObject();
    IAsn1Sequence tokenSequence = BOUNCY_CASTLE_FACTORY.CreateASN1Sequence(asn1Object);
    IContentInfo contentInfo = BOUNCY_CASTLE_FACTORY.CreateContentInfo(tokenSequence);
    ITimeStampToken timeStampToken = BOUNCY_CASTLE_FACTORY.CreateTimeStampToken(contentInfo);
    byte[] tstBytes = timeStampToken.GetEncoded();
    

    Signature time stamps

    Again given a SignatureUtil signatureUtil for one's PDF and a string name of a signature field that contains a signature time stamp, one can extract the time stamp token like this using BouncyCastle:

    PdfSignature pdfSignature = signatureUtil.GetSignature(name);
    PdfString contents = pdfSignature.GetContents();
    byte[] bytes = PdfEncodings.ConvertToBytes(contents.GetValue(), null);
    
    CmsSignedData cmsSignedData = new CmsSignedData(bytes);
    ICollection signerInfos = cmsSignedData.GetSignerInfos().GetSigners();
    foreach (SignerInformation signer in signerInfos.Cast<SignerInformation>())
    {
        Org.BouncyCastle.Asn1.Cms.Attribute attribute = signer.UnsignedAttributes[PkcsObjectIdentifiers.IdAASignatureTimeStampToken];
        if (attribute != null)
        {
            foreach (Asn1Encodable asn1Encodable in attribute.AttrValues)
            {
                CmsSignedData tstSignedData = new CmsSignedData(asn1Encodable.GetEncoded());
                TimeStampToken timeStampToken = new TimeStampToken(tstSignedData);
                byte[] tstBytes = timeStampToken.GetEncoded();
            }
        }
    }
    

    Putting it together

    The question is how to determine whether one has a document time stamp or a signature which may have a signature time stamp. The obvious way is to check the sub filter of the signature dictionary, a document time stamp has the sub filter ETSI.RFC3161. As iText also executes that test, I leave that comparison to iText:

    using (PdfReader pdfReader = new PdfReader(...))
    using (PdfDocument pdfDocument = new PdfDocument(pdfReader))
    {
        SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
        foreach (string name in signatureUtil.GetSignatureNames())
        {
            var pdfPkcs7 = signatureUtil.ReadSignatureData(name);
            var tstInfo = pdfPkcs7.GetTimeStampTokenInfo();
            Console.WriteLine(name + ": " + tstInfo.GetGenTime());
    
            PdfSignature pdfSignature = signatureUtil.GetSignature(name);
            PdfString contents = pdfSignature.GetContents();
            byte[] bytes = PdfEncodings.ConvertToBytes(contents.GetValue(), null);
            if (pdfPkcs7.IsTsp())
            {
                IBouncyCastleFactory BOUNCY_CASTLE_FACTORY = BouncyCastleFactoryCreator.GetFactory();
                IAsn1Object asn1Object = BOUNCY_CASTLE_FACTORY.CreateASN1InputStream(bytes).ReadObject();
                IAsn1Sequence tokenSequence = BOUNCY_CASTLE_FACTORY.CreateASN1Sequence(asn1Object);
                IContentInfo contentInfo = BOUNCY_CASTLE_FACTORY.CreateContentInfo(tokenSequence);
                ITimeStampToken timeStampToken = BOUNCY_CASTLE_FACTORY.CreateTimeStampToken(contentInfo);
                byte[] tstBytes = timeStampToken.GetEncoded();
                File.WriteAllBytes(name + "-doc.tst", tstBytes);
            }
            else
            {
                CmsSignedData cmsSignedData = new CmsSignedData(bytes);
                ICollection signerInfos = cmsSignedData.GetSignerInfos().GetSigners();
                foreach (SignerInformation signer in signerInfos.Cast<SignerInformation>())
                {
                    Org.BouncyCastle.Asn1.Cms.Attribute attribute = signer.UnsignedAttributes[PkcsObjectIdentifiers.IdAASignatureTimeStampToken];
                    if (attribute != null)
                    {
                        foreach (Asn1Encodable asn1Encodable in attribute.AttrValues)
                        {
                            CmsSignedData tstSignedData = new CmsSignedData(asn1Encodable.GetEncoded());
                            TimeStampToken timeStampToken = new TimeStampToken(tstSignedData);
                            byte[] tstBytes = timeStampToken.GetEncoded();
                            File.WriteAllBytes(name + "-sig.tst", tstBytes);
                        }
                    }
                }
            }
        }
    }
    

    (RetrieveTimeStampToken test retrieveFromLoremIpsumLta)

    Of course, that code is missing a number of sanity checks for production use...