There are very many aspects and sub-questions here, both in the question text and in the comments thereunder. This answer illuminates but some of them after first presenting some backgrounds.
Some backgrounds
An integrated PDF signature implies the presence of a number of structures in a PDF:
A signature AcroForm form field. This form field can have a widget annotation (a visualization which can contain any information you care to put into it) but it does not need to have one.
A value in this signature form field. Unlike other form fields, the value of a signature field is not a mere string but a dictionary of key-value pairs. The contents differ depending on the exact type of signature. In case of the interoperable types, though, there always is a Contents entry whose value is a binary string containing the actual PKCS1/PKCS7/CMS/RFC3161 signature or time stamp which covers the whole file except this binary string.
(The sketch is a bit misleading: the '<' and '>' hex string delimiters are not part of the signed data.)
In case of type adbe.x509.rsa_sha1 the Contents entry contains a PKCS1 signature. The signature value dictionary additionally must contain a Cert entry containing the signing certificate.
In case of type ETSI.RFC3161 the Contents entry contains a RFC 3161 time stamp token.
In case of types ETSI.CAdES.detached, adbe.pkcs7.detached, and adbe.pkcs7.sha1 the Contents entry contains a CMS signature container. As the signature container can hold the signing certificate, there is no need for a Cert entry for the signing certificate.
A CMS signature container can contain a structure of "signed attributes". If it does, one of these attributes must be the hash of the signed PDF bytes (see above, everything but the Contents value) and the actual signature bytes wrapped in the container sign these signed attributes. Whether the variant without signed attributes is allowed and which attributes additionally are required, depends on the exact type of the signature.
In case of ETSI.CAdES.detached, the CMS container must contain signed attributes. Furthermore, one of the signed attributes must be an ESS signing-certificate or signing-certificate-v2 attribute referencing the signer certificate.
LTV information in this case can be added later in an incremental update to the PDF, they need not be present in the signed PDF.
In case of adbe.pkcs7.detached and adbe.pkcs7.sha1 there generically do not need to be signed attributes. Depending on the exact signing policy (prescribed by law or contract), though, signed attributes and in particular the ESS signing certificate signed attribute may be required nonetheless.
These signature types were already defined in ISO 32000-1. If one's signature policy is based on ISO 32000-1 alone, LTV information must be stored in the adbe-revocationInfoArchival attribute which must be a signed attribute.
Is the signing certificate required before signing?
In comments you reference the iText "PDF and Digital Signatures" ebook which appears to say that it suffices to retrieve the signing certificate together with the signature.
In the light of the backgrounds explained above, though, we realize that
In case of adbe.x509.rsa_sha1 signatures, the signing certificate must be in the value of the Cert entry of the signature value dictionary. As this entry is not in the Contents entry, this certificate is part of the signed data. Thus, it must be known before signing.
In case of ETSI.CAdES.detached signatures, the signed attributes must contain an ESS signing-certificate or signing-certificate-v2 attribute. This attribute references the signer certificate. Thus, it must be known before signing.
In case of adbe.pkcs7.detached and adbe.pkcs7.sha1 it depends on the actual signature policy one has to adhere to whether or not an ESS signing-certificate or signing-certificate-v2 attribute is required. Thus, it depends whether or not the signing certificate needs to be known before signing.
In case of a signature policy based on ISO 32000-1 alone, though, LTV information must be stored in a signed attribute if at all, and to retrieve LTV information, one obviously needs to know the certificates for which one attempts to retrieve them, in particular the signer certificate.
To answer the question in this topic's header, therefore: Only in context of a lax signature policy you can get away with not knowing the signer certificate before signing as long as you don't need to add LTV information.
And in case of PAdES signatures?
In a comment you mention you need to use PAdES and LTV. Does that mean you need the signer certificate before signing?
Well, it depends.
If using PAdES means using PAdES baseline profiles or extended PAdES profiles (BES/EPES), you have to create ETSI.CAdES.detached signatures. Thus, you do need the signer certificate before signing.
But if it only requires the PAdES profile for CMS digital signatures in PDF (essentially the ISO 32000-1 compatibility profile), you don't need the signer certificate before signing.
But this profile implies in particular: If present, any revocation information shall be a signed attribute of the PDF Signature. Thus, for "PAdES and LTV" you again do need the signer certificate before signing.
How to create a PDF signature without knowing the signer certificate early
So there are setups in which you shouldn't need the signer certificate before calculating the actual signature. Usually, though, security APIs require the certificate early nonetheless.
Using Bouncy Castle low-level APIs you can do that as follows. (I assume you are using SHA256withRSA.)
First prepare the PDF and determine the hash value
byte[] Hash = null;
using (PdfReader reader = new PdfReader("original.pdf"))
using (FileStream fout = new FileStream("prepared.pdf", FileMode.Create))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, fout, sp);
pdfSigner.SetFieldName("Signature");
PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageNumber(1);
int estimatedSize = 12000;
ExternalHashingSignatureContainer container = new ExternalHashingSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(container, estimatedSize);
Hash = container.Hash;
}
Now the hash of the PDF bytes to sign is in Hash
.
The ExternalHashingSignatureContainer
class used here is the following helper class:
public class ExternalHashingSignatureContainer : ExternalBlankSignatureContainer
{
public ExternalHashingSignatureContainer(PdfName filter, PdfName subFilter) : base(filter, subFilter)
{ }
public override byte[] Sign(Stream data)
{
SHA256 sha = new SHA256CryptoServiceProvider();
Hash = sha.ComputeHash(data);
return new byte[0];
}
public byte[] Hash { get; private set; }
}
For the hash calculated above in the Hash
variable you can now request a PKCS#1 signature and the signer certificate. Then you can construct the CMS container as follows:
byte[] signatureBytes = THE_RETRIEVED_SIGNATURE_BYTES;
byte[] certificateBytes = THE_RETRIEVED_CERTIFICATE_BYTES;
X509Certificate x509Certificate = new X509CertificateParser().ReadCertificate(certificateBytes);
SignerIdentifier sid = new SignerIdentifier(new IssuerAndSerialNumber(x509Certificate.IssuerDN, x509Certificate.SerialNumber));
AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256);
Attributes authenticatedAttributes = null;
AlgorithmIdentifier digEncryptionAlgorithm = new AlgorithmIdentifier(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.Sha256WithRsaEncryption);
Asn1OctetString encryptedDigest = new DerOctetString(signatureBytes);
Attributes unauthenticatedAttributes = null;
SignerInfo signerInfo = new SignerInfo(sid, digAlgorithm, authenticatedAttributes, digEncryptionAlgorithm, encryptedDigest, unauthenticatedAttributes);
Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
digestAlgs.Add(signerInfo.DigestAlgorithm);
Asn1Set digestAlgorithms = new DerSet(digestAlgs);
ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.Data, null);
Asn1EncodableVector certs = new Asn1EncodableVector();
certs.Add(x509Certificate.CertificateStructure.ToAsn1Object());
Asn1Set certificates = new DerSet(certs);
Asn1EncodableVector signerInfs = new Asn1EncodableVector();
signerInfs.Add(signerInfo);
Asn1Set signerInfos = new DerSet(signerInfs);
SignedData signedData = new SignedData(digestAlgorithms, contentInfo, certificates, null, signerInfos);
contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, signedData);
byte[] Signature = contentInfo.GetDerEncoded();
Now the CMS signature container bytes are in Signature
.
For the above please use these BouncyCastle using
s
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
You can now embed the signature container bytes into the PDF like this:
using (PdfReader reader = new PdfReader("prepared.pdf"))
using (PdfDocument document = new PdfDocument(reader))
using (FileStream fout = new FileStream("signed.pdf", FileMode.Create))
{
PdfSigner.SignDeferred(document, "Signature", fout, new ExternalPrecalculatedSignatureContainer(Signature));
}
The ExternalPrecalculatedSignatureContainer
class used here is the following helper class:
public class ExternalPrecalculatedSignatureContainer : ExternalBlankSignatureContainer
{
public ExternalPrecalculatedSignatureContainer(byte[] cms) : base(new PdfDictionary())
{
Cms = cms;
}
public override byte[] Sign(Stream data)
{
return Cms;
}
public byte[] Cms { get; private set; }
}
As mentioned above, though, this signature container is not a CAdES container. Thus, your PDF signatures won't be true PAdES signature (baseline or extended profiles) but at best ISO 32000-1 compatibility PAdES signatures.
The problem in your test code based on the above
Your Client
method createSignedData
looks like this:
public byte[] createSignedData(byte[] sh)
{
string dire = Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory()).ToString()).ToString();
string PROPERTIES = dire + "\\resources\\signkey.properties";
Properties properties = new Properties();
properties.Load(new FileStream(PROPERTIES, FileMode.Open, FileAccess.Read));
String path = properties.GetProperty("PRIVATE");
char[] pass = properties.GetProperty("PASSWORD").ToCharArray();
string alias = null;
Pkcs12Store pk12;
pk12 = new Pkcs12Store(new FileStream(path, FileMode.Open, FileAccess.Read), pass);
foreach (var a in pk12.Aliases)
{
alias = ((string)a);
if (pk12.IsKeyEntry(alias))
break;
}
ICipherParameters pk = pk12.GetKey(alias).Key;
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);
return data;
}
Unfortunately PrivateKeySignature.Sign
expects the message to sign in the sh
parameter and in particular first hashes it. In your use case on the other hand sh
already is the hash of the message to sign. Thus, you effectively hash twice where you should hash but once.
You can fix this by replacing
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
byte[] data = pks.Sign(sh);
in the code above by
StaticDigest digest = new StaticDigest();
digest.AlgorithmName = "SHA-256";
digest.Digest = sh;
RsaDigestSigner signer = new RsaDigestSigner(digest);
signer.Init(true, pk);
byte[] data = signer.GenerateSignature();
Here StaticDigest
is the following helper class:
public class StaticDigest : IDigest
{
public string AlgorithmName { get; set; }
public byte[] Digest { get; set; }
public void BlockUpdate(byte[] input, int inOff, int length)
{ }
public int DoFinal(byte[] output, int outOff)
{
Array.Copy(Digest, 0, output, outOff, Digest.Length);
return Digest.Length;
}
public int GetByteLength()
{
return 64;
}
public int GetDigestSize()
{
return Digest.Length;
}
public void Reset()
{ }
public void Update(byte input)
{ }
}
After this change your test project returns mathematically valid signatures.