Включение полномочий по установке отметки времени при установке отметки времени в подписи PDF с помощью PDFBox - PullRequest
0 голосов
/ 13 июля 2020

Я пытаюсь добавить метку времени к своей подписи в PDF-документе с помощью PDFBox 1.8.9. Я получаю сообщение об ошибке, в котором говорится, что отметка времени не может быть проверена , и когда я пытаюсь проверить сертификат центра отметки времени, он отображается как «Недоступно» .

Вот мой метод sign в классе, который реализует SignatureInterface, он вызывает метод SignTimeStamps

public byte[] sign(java.io.InputStream content)
    {
        CMSProcessableInputStream input = new CMSProcessableInputStream(content);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        // Certificate Chain
        java.util.List certList = java.util.Arrays.asList(certificates);

        java.security.cert.CertStore certStore = null;

        try
        {
            certStore = java.security.cert.CertStore.getInstance("Collection", new java.security.cert.CollectionCertStoreParameters(certList), provider);
            gen.addSigner(privateKey, (java.security.cert.X509Certificate)certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
            gen.addCertificatesAndCRLs(certStore);
            CMSSignedData signedData = gen.generate(input, false, provider);
            if (signatureResources.TsaUrl != null)
            {
                // signedData = AddTimeStamp(signedData);
                signedData = SignTimeStamps(signedData);
            }
            return signedData.getEncoded();
        }
        catch (java.lang.Exception e)
        {
            // should be handled
            System.Console.WriteLine("error while creating pkcs7 signature");
            e.printStackTrace();
        }

        throw new java.lang.RuntimeException("problem while preparing signature");
    }

Метод SignTimeStamps вызывает метод SignTimeStamp для каждого SignerInfo

private CMSSignedData SignTimeStamps(CMSSignedData signedData)
{
        SignerInformationStore signerStore = signedData.getSignerInfos();
        java.util.Iterator iterator = ((java.util.Collection)signerStore.getSigners()).iterator();
        java.util.List newSigners = new java.util.ArrayList();

        while (iterator.hasNext())
        {
            newSigners.add(SignTimeStamp((SignerInformation)iterator.next()));
        }

        return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
}

И затем мы получаем подпись из SignerInfo, которая является отпечатком сообщения для нашего TimeStampToken, как указано в Приложении A RF C 3161

private SignerInformation SignTimeStamp(SignerInformation signer)
{
        AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
        ASN1EncodableVector vector = new ASN1EncodableVector();
        if (unsignedAttributes != null)
        {
            vector = unsignedAttributes.toASN1EncodableVector();
        }

        TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle(signatureResources.TsaUrl);
        byte[] token = tsaClient.GetTimeStampToken(signer.getSignature());
        DERObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
        ASN1Encodable signatureTimeStamp = new org.bouncycastle.asn1.cms.Attribute(oid, new DERSet(ASN1Object.fromByteArray(token)));
        vector.add(signatureTimeStamp);
        SignerInformation newSigner = SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(vector));
        if (newSigner == null) return signer;
        return newSigner;
}

Так что я не совсем уверен, что я делаю не так, что я могу сделать так, чтобы отметка времени могла быть проверена, и центр отметки времени отображался как доступный?

РЕДАКТИРОВАТЬ Вот ссылка на файл PDF, который я подписал https://www.dropbox.com/s/al3h8lorqisi34q/dummy_signed.pdf?dl=1. TSA - это бесплатный TSA (http://time.certum.pl/)

РЕДАКТИРОВАТЬ 2

Вот мой файл TSAClientBouncyCastle.cs

using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Text;
using System.util;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Tsp;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Asn1.Tsp;
using Org.BouncyCastle.Crypto;

namespace ProofOfConcept {
public class TSAClientBouncyCastle : ITSAClient {

    /** The Logger instance. */
    private static readonly ILogger LOGGER = LoggerFactory.GetLogger(typeof(TSAClientBouncyCastle));

    /** URL of the Time Stamp Authority */
    protected internal String tsaURL;
    /** TSA Username */
    protected internal String tsaUsername;
    /** TSA password */
    protected internal String tsaPassword;
    /** An interface that allows you to inspect the timestamp info. */
    protected ITSAInfoBouncyCastle tsaInfo;
    /** The default value for the hash algorithm */
    public const int DEFAULTTOKENSIZE = 4096;
    
    /** Estimate of the received time stamp token */
    protected internal int tokenSizeEstimate;
    
    /** The default value for the hash algorithm */
    public const String DEFAULTHASHALGORITHM = "SHA-256";
    
    /** Hash algorithm */
    protected internal String digestAlgorithm;
    /**
    * Creates an instance of a TSAClient that will use BouncyCastle.
    * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
    */
    public TSAClientBouncyCastle(String url) 
        : this(url, null, null, DEFAULTTOKENSIZE, DEFAULTHASHALGORITHM) {
    }
    
    /**
    * Creates an instance of a TSAClient that will use BouncyCastle.
    * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
    * @param username String - user(account) name
    * @param password String - password
    */
    public TSAClientBouncyCastle(String url, String username, String password) 
        : this(url, username, password, DEFAULTTOKENSIZE, DEFAULTHASHALGORITHM) {
    }
    
    /**
    * Constructor.
    * Note the token size estimate is updated by each call, as the token
    * size is not likely to change (as long as we call the same TSA using
    * the same imprint length).
    * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
    * @param username String - user(account) name
    * @param password String - password
    * @param tokSzEstimate int - estimated size of received time stamp token (DER encoded)
    */
    public TSAClientBouncyCastle(String url, String username, String password, int tokSzEstimate, String digestAlgorithm) {
        this.tsaURL       = url;
        this.tsaUsername  = username;
        this.tsaPassword  = password;
        this.tokenSizeEstimate = tokSzEstimate;
        this.digestAlgorithm = digestAlgorithm;
    }
    
    /**
     * @param tsaInfo the tsaInfo to set
     */
    public void SetTSAInfo(ITSAInfoBouncyCastle tsaInfo) {
        this.tsaInfo = tsaInfo;
    }

    /**
    * Get the token size estimate.
    * Returned value reflects the result of the last succesfull call, padded
    * @return an estimate of the token size
    */
    public virtual int GetTokenSizeEstimate() {
        return tokenSizeEstimate;
    }
    
    /**
     * Gets the MessageDigest to digest the data imprint
     * @return the digest algorithm name
     */
    public IDigest GetMessageDigest() {
        return DigestAlgorithms.GetMessageDigest(digestAlgorithm);
    }

    /**
     * Get RFC 3161 timeStampToken.
     * Method may return null indicating that timestamp should be skipped.
     * @param imprint data imprint to be time-stamped
     * @return encoded, TSA signed data of the timeStampToken
     */
    public virtual byte[] GetTimeStampToken(byte[] imprint) {
        byte[] respBytes = null;
        // Setup the time stamp request
        TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
        tsqGenerator.SetCertReq(true);
        // tsqGenerator.setReqPolicy("1.3.6.1.4.1.601.10.3.1");
        BigInteger nonce = BigInteger.ValueOf(DateTime.Now.Ticks + Environment.TickCount);
        TimeStampRequest request = tsqGenerator.Generate(DigestAlgorithms.GetAllowedDigests(digestAlgorithm), imprint, nonce);
        byte[] requestBytes = request.GetEncoded();
        
        // Call the communications layer
        respBytes = GetTSAResponse(requestBytes);           
        
        // Handle the TSA response
        TimeStampResponse response = new TimeStampResponse(respBytes);
        
        // validate communication level attributes (RFC 3161 PKIStatus)
        response.Validate(request);
        PkiFailureInfo failure = response.GetFailInfo();
        int value = (failure == null) ? 0 : failure.IntValue;
        if (value != 0) {
            // @todo: Translate value of 15 error codes defined by PKIFailureInfo to string
            throw new IOException(MessageLocalization.GetComposedMessage("invalid.tsa.1.response.code.2", tsaURL, value));
        }
        // @todo: validate the time stap certificate chain (if we want
        //        assure we do not sign using an invalid timestamp).
        
        // extract just the time stamp token (removes communication status info)
        TimeStampToken tsToken = response.TimeStampToken;
        if (tsToken == null) {
            throw new IOException(MessageLocalization.GetComposedMessage("tsa.1.failed.to.return.time.stamp.token.2", tsaURL, response.GetStatusString()));
        }
        TimeStampTokenInfo tsTokenInfo = tsToken.TimeStampInfo; // to view details
        byte[] encoded = tsToken.GetEncoded();
        
        LOGGER.Info("Timestamp generated: " + tsTokenInfo.GenTime);
        if (tsaInfo != null) {
            tsaInfo.InspectTimeStampTokenInfo(tsTokenInfo);
        }
        // Update our token size estimate for the next call (padded to be safe)
        this.tokenSizeEstimate = encoded.Length + 32;
        return encoded;
    }
    
    /**
    * Get timestamp token - communications layer
    * @return - byte[] - TSA response, raw bytes (RFC 3161 encoded)
    */
    protected internal virtual byte[] GetTSAResponse(byte[] requestBytes) {
        HttpWebRequest con = (HttpWebRequest)WebRequest.Create(tsaURL);
        con.ContentLength = requestBytes.Length;
        con.ContentType = "application/timestamp-query";
        con.Method = "POST";
        if ((tsaUsername != null) && !tsaUsername.Equals("") ) {
            string authInfo = tsaUsername + ":" + tsaPassword;
            authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo), Base64FormattingOptions.None);
            con.Headers["Authorization"] = "Basic " + authInfo;
        }
        Stream outp = con.GetRequestStream();
        outp.Write(requestBytes, 0, requestBytes.Length);
        outp.Close();
        HttpWebResponse response = (HttpWebResponse)con.GetResponse();
        if (response.StatusCode != HttpStatusCode.OK)
            throw new IOException(MessageLocalization.GetComposedMessage("invalid.http.response.1", (int)response.StatusCode));
        Stream inp = response.GetResponseStream();

        MemoryStream baos = new MemoryStream();
        byte[] buffer = new byte[1024];
        int bytesRead = 0;
        while ((bytesRead = inp.Read(buffer, 0, buffer.Length)) > 0) {
            baos.Write(buffer, 0, bytesRead);
        }
        inp.Close();
        response.Close();
        byte[] respBytes = baos.ToArray();            
        
        String encoding = response.ContentEncoding;
        if (encoding != null && Util.EqualsIgnoreCase(encoding, "base64")) {
            respBytes = Convert.FromBase64String(Encoding.ASCII.GetString(respBytes));
        }
        return respBytes;
    }    
}

РЕДАКТИРОВАТЬ ОКОНЧАТЕЛЬНОЕ РЕШЕНИЕ (РЕШЕНИЕ)

Спасибо всем за то, что помогли мне, как указал Тилман Хаушерр, я просто не хешировал подпись. Я заменил

byte[] token = tsaClient.GetTimeStampToken(signer.getSignature());

в моем методе SignTimeStamp на

java.security.MessageDigest mda = java.security.MessageDigest.getInstance("SHA-256");
byte[] digest = mda.digest(signer.getSignature());
byte[] token = tsaClient.GetTimeStampToken(digest);

И теперь я могу видеть полномочия по установке временных меток. Еще раз большое спасибо!

...