Signatures from third party service

I am working on signing pdf documents, I am using Aplose 18,9 but i am willing to look at update to that if that solves my problem.

We are trying to sign pdf documents using a third party signing service, when the service is called and the hash digest is sent to them we get back after the user agrees to it, a pkcs1 signature and the certificate, without the private key, we need to create a pkcs7 signature with a timestamp (we have access to a tsa service) so the signature resulting is a PAdES signature preferably Level B-LTA. Is aspose capable to do this? Currently we are using Itext but Adobe does not validate that signature so that it has the error “Document has been alter…” error, so using that framework is not an option and since we are using Aspose for lots of other document manipulations and Aspose does have a signature function i we wondering if this was a possiblillity.

thank you

@johannonesystems

A simple and basic way to sign a PDF document with PKCS7 and Timestamp, you can use below code snippet and share with us if it fulfills your requirements:

using (Document document = new Document(dataDir + "Invoice No TimeStamp.pdf"))
{
    using (Facades.PdfFileSignature signature = new Facades.PdfFileSignature(document))
    {
        PKCS7 pkcs = new PKCS7(dataDir + "mykey2.pfx", "aa");
        TimestampSettings timestampSettings = new TimestampSettings("https://freetsa.org/tsr", string.Empty); // User/Password can be omitted
        pkcs.TimestampSettings = timestampSettings;
        System.Drawing.Rectangle rect = new System.Drawing.Rectangle(100, 100, 200, 100);
        // Create any of the three signature types
        signature.Sign(1, "Signature Reason", "Contact", "Location", true, rect, pkcs);
        // Save output PDF file
        signature.Save(dataDir + "output.pdf");
    }
}

I have tried Itext to do this, There i use a IExternalSingatureContainer interface class that uses our class to contact the signature service and get the byte[] of a pkcs1 signature, in some cases we can get PKCS7 and if we simply return that byte array from the signature container and then add a document timestamp, the the signature is fine, but in some cases we can only get PKCS1 signature, and the Certs without private key. when we use that to create a pkcs7 class then adobe shows an error “document has been alterd…”.
Our Itext code:
public byte[] Sign(Stream data)
{
String hashAlgorithm = DigestAlgorithms.SHA256;
byte[] hash = DigestAlgorithms.Digest(data, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
string hashStr = Convert.ToBase64String(hash);

        // Create and add the 'SignedProperties' element (you need to adapt this to follow the AdES standard)
        PdfDictionary vri = new PdfDictionary();
        vri.Put(PdfName.Contents, new PdfString("SignedPropertiesPlaceholder"));
        sigDic.Put(PdfName.VRI, vri);

        // this class singns the hash using a third party PKCS1 singature service where the customer confirms the singature using his mobile phone
        SignService signService = new SignService(this.Propperties, this.KtOrPhone, ConfirmString);
        string authId = signService.InitSign(hashStr);
        signService.GetSignatureResult(authId);

        var user = signService.UserInfo;
        SignerFullName = user.name;
        SignerIdentifier = user.nationalRegisterId;
        Propperties.SignerIDNumber = SignerIdentifier;
        Propperties.SignerName = SignerFullName;
        // if this paramerter si returnd here the ti singature is corrrect but has no tsa, we requer tsa, that is added below.
        Signature = System.Convert.FromBase64String(signService.Signature);

//if we return here Singature and the signature is pkcs7 then the singature is fine, but we need to ask for pkcs1 because pkcs7 is not always available.
byte[] certBytes = Convert.FromBase64String(signService.SigningCert);
X509Certificate2 certificate = new X509Certificate2(certBytes);

        X509Chain certificateChain = new X509Chain();
        certificateChain.ChainPolicy = new X509ChainPolicy
        {
            RevocationMode = X509RevocationMode.Offline, // or X509RevocationMode.Offline if you have an OCSP responder
            RevocationFlag = X509RevocationFlag.EntireChain,
        };
        bool chainBuilt = certificateChain.Build(certificate);
        UsedCerts = new X509Certificate2Collection(certificateChain.ChainElements.Cast<X509ChainElement>().Select(e => e.Certificate).ToArray());

        SigningCertSubject = certificate.Subject;
        SignerCertExpier = certificate.NotAfter.ToShortDateString();
        try
        {
            string[] arr = SigningCertSubject.Split(',');
            string name = arr.FirstOrDefault(x => x.Trim().StartsWith("CN"));
            string serial = arr.FirstOrDefault(x => x.Trim().StartsWith("SERIALNUMBER"));
            name = name.Contains('=') ? name.Split('=')[1] : name;
            serial = serial.Contains('=') ? serial.Split('=')[1] : serial;
        }
        catch (InvalidOperationException e)
        {
            throw new Exception("Undirritunin er ólögleg", e);
        }

        // Create an RSACryptoServiceProvider
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)UsedCerts[0].PublicKey.Key;
        RSAParameters rsaParams = UsedCerts[0].GetRSAPublicKey().ExportParameters(false);
        rsa.ImportParameters(rsaParams);

        if (!rsa.VerifyData(ReadStreamToByteArray(data), Signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
        {
            throw new Exception("Singature data Illegal");
        }

        if (!rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), Signature))
        {
            throw new Exception("Singature hash Illegal");
        }

        X509CertificateParser parser = new X509CertificateParser();
        Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[UsedCerts.Count];
        for (int i = 0; i < UsedCerts.Count; i++)
        {
            chain[i] = parser.ReadCertificate(UsedCerts[i].GetRawCertData());
        }
        ITSAClient tsaClient = new TSAClientBouncyCastle(Propperties.TimeServicePropperties.TimeServerURL);

        OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
        Collection<byte[]> ocsp = new Collection<byte[]>();
        Collection<byte[]> crl = new Collection<byte[]>();
        for (var i = 0; i < chain.Length - 1; i++)
        {
            byte[] encoded = ocspClient.GetEncoded((Org.BouncyCastle.X509.X509Certificate)chain[i], (Org.BouncyCastle.X509.X509Certificate)chain[i + 1], null);// Propperties.OcspURL
            if (encoded != null)
                ocsp.Add(encoded);

        }
        
        PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, chain, hashAlgorithm, false);
        pdfPKCS7.SetExternalDigest(Signature, null, "RSA");
        byte[] sh = pdfPKCS7.GetAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, ocsp, null);
        byte[] result = pdfPKCS7.GetEncodedPKCS7(sh, PdfSigner.CryptoStandard.CADES, tsaClient, ocsp, null);

        return result;
}

in line pdfPKCS7.GetEncodedPKCS7 we have tried as first parameter sh and hash with same result.
is this something Aspose can handle to result in a legal pkcs7 signature?

@johannonesystems

We need to check and investigate the feasibility to meet your requirements. Therefore, we have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): PDFNET-55824

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.