i have a large file that needs to be signed in order to be archived. It's a CSV file that is generated once in a month and has a size of > 2Gb (don't ask me what this is good for :) ).
Anyway.. The signing of the file is not the problem, there is a lib for that, i generate the hash and i get my signature.
What i want to do now, is to verify the signature.
tutorials want me to do something like this:
byte[] sig = ReadFile(signatureFile);
SignedCms cms = new SignedCms(new ContentInfo(ReadFile(dataFile)), true);
cms.Decode(sig);
cms.CheckSignature(false);
this actually works.. but as you can imagine, i don't feel like loading the entire content of a 2 GB file into memory.
the msdn article about the ContentInfo class tells me, that i can choose the oid for the content, i want my contentInfo initialized with.
the oid for hashed data would be "1.2.840.113549.1.7.5"
so here i am:
byte[] sig = ReadFile(signatureFile);
SignedCms cms = new SignedCms(new ContentInfo(new byte[]{}), true);
cms.Decode(sig);
SignerInfo inf = cms.SignerInfos[0];
string algorithm = inf.DigestAlgorithm.FriendlyName;
cms = new SignedCms(new ContentInfo(new Oid("1.2.840.113549.1.7.5"), GetHash(algorithm, dataFile)), true);
cms.Decode(sig);
cms.CheckSignature(false);
now CheckSignature throws me an exception saying that the hash is incorrect.
the following would work:
byte[] sig = ReadFile(signatureFile);
SignedCms cms = new SignedCms(new ContentInfo(new byte[]{}), true);
cms.Decode(sig)
byte[] compareableHash = GetHash(algorithm, dataFile);
foreach (var t in inf.SignedAttributes)
{
if (t.Oid.Value == "1.2.840.113549.1.9.4")
{
Pkcs9MessageDigest digest = (Pkcs9MessageDigest)t.Values[0];
if (CompareableHash(compareableHash, digest.MessageDigest)) // this compares the hashes bytewise
{
Console.WriteLine("hash is correct!");
}
}
}
can i trust this method to be 100% reliable or is there a better way?
Related
I try to sign a pdf file using my smartcard (USB token) but encounter "Document has been altered or corrupted since it was signed" error when I open the signed pdf file in Adobe. The error is not so descriptive and I'm not sure where to look at because the code seems good to me but apparently it's not..
The code that I use is:
var signer = smartCardManager.getSigner("myTokenPassword");
var toBeSignedHash = GetHashOfPdf(File.ReadAllBytes(#"xxx\pdf.pdf"), cert.asX509Certificate2().RawData, "dsa", null, false);
var signature = signer.sign(toBeSignedHash);
var signedPdf = EmbedSignature(cert.getBytes(), signature);
File.WriteAllBytes(#"xxx\signedpdf.pdf", signedPdf);
public byte[] GetHashOfPdf(byte[] unsignedFile, byte[] userCertificate, string signatureFieldName, List<float> location, bool append)
{
byte[] result = null;
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(userCertificate))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
using (PdfReader reader = new PdfReader(unsignedFile))
{
using (var os = new MemoryStream())
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0', null, append);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(0,0,0,0), 1, signatureFieldName);
appearance.Certificate = certificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
this.hash = hash;
this.os = os.ToArray();
File.WriteAllBytes(#"xxx\temp.pdf", this.os);
}
}
return result;
}
public byte[] EmbedSignature(byte[] publicCert, byte[] sign)
{
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(publicCert))
};
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
using (var reader = new PdfReader(this.os))
{
using (var os2 = new MemoryStream())
{
signatureContainer.SetExternalDigest(sign, null, "RSA");
byte[] encodedSignature = signatureContainer.GetEncodedPKCS7(this.hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "dsa", os2, external);
return os2.ToArray();
}
}
}
The pdf file that I try to sign is this.
Temp pdf file that is created after adding signature fields is this.
Signed pdf file is this.
Base64 format of the hash that is signed is: klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
Base64 format of the signature is: Uam/J6W0YX99rVP4M9mL9Lg9l6YzC2yiR4OtJ18AH1PtBVaNPteT3oPS7SUc+6ak2LfijgJ6j1RgdLamodDPKl/0E90kbBenry+/g1Ttd1bpO8lqTn1PWJU2TxeGHwyRyaFBOUga2AxpErIHrwxfuKCBcodB7wvAqRjso0jovnyP/4DluyOPm97QWh4na0S+rtUWOdqVmKGOuGJ3sBXuk019ewpvFcqWBX4Mvz7IKV56wcxQVQuJLCiyXsMXoazwyDCvdteaDz05K25IVwgEEjwLrppnc/7Ue9a9KVadFFzXWXfia7ndmUCgyd70r/Z+Oviu9MIAZL8GuTpkD7fJeA==
I use hex encoding of byte arrays here. Your base64 encoded hash
klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
in hex encoding is equal to
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
###In short
Your code hashes the signed attributes twice. Simply don't hash the bytes returned by signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS) in GetHashOfPdf but instead use the authenticated attribute bytes themselves as return value.
###In detail
Analyzing the signature in your example PDF it turns out that
indeed the hash of the signed attributes is
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
but the hash in the RSA encrypted DigestInfo object of the signature is
1DC7CAA50D88243327A9D928D5FB4F1A61CBEFF9E947D393DDA705BD61B67F25
which turns out to be the hash of the before mentioned hash of the signed attributes.
Thus, your
var signature = signer.sign(toBeSignedHash);
call appears to hash the toBeSignedHash value again.
The most simple fix would be to replace
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
by
result = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
in GetHashOfPdf to have only signer.sign do the hashing.
###Analyzing such issues
In a comment you asked
how did you figure all this out :)?
Well, yours is not the first question with a customized iText signing process resulting in errors or at least unwanted profiles.
In the course of the analysis of those questions the first step usually is to extract the embedded signature container and inspect it in an ASN.1 viewer.
In case of your PDF the main result of that inspection was that the signature as such looked ok and that your signed attributes don't contain any variable data.
If there had been some variable data (e.g. a signing time attribute) in them, a candidate for the cause of the issue would have been that you build the signed attributes twice, once explicitly in GetHashOfPdf, once implicitly in EmbedSignature, with different values for the variable data. But as mentioned above, this was not the case.
The next step here was to actually check the hashes involved. Checking the document hash is simple, one calculates the signed byte range hash and compares with the value of the MessageDigest signed attribute, cf. the ExtractHash test testSotnSignedpdf (in Java).
The result for your PDF turned out to be ok.
The following step was to inspect the signature container more thoroughly. In this context I once started to write some checks but did not get very far, cf. the SignatureAnalyzer class. I extended it a bit for the test of the hash of the signed attributes making use of the signature algorithm you used, the old RSASSA-PKCS1-v1_5: In contrast to many other signature algorithms, this one allows to trivially extract the signed hash.
Here the result for your PDF turned out not to be ok, the hash of the signed attributes differed from the signed hash.
There are two often seen causes for a mismatch here,
either the signed attributes are signed with a wrong encoding (it must be the regular DER encoding, not some arbitrary BER encoding and in particular not an encoding with the implicit tag the value stored in the signature has --- even larger players do this wrong sometimes, e.g. Docusign, cf. DSS-1343)
or the hash was somehow transformed during signing (e.g. the hash is base64 encoded or hashed again).
As it turned out, the latter was the case here, the hash was hashed again.
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
I need to use this signature to sign pdf via IText deferred signing because the web service works asynchronously.
All the IText examples uses PKCS#7 message format but I am not sure what should I do for XML signature.
Code That Adds Empty Signature Field and Gets Signable Bytes of Pdf
public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Code That Opens Temp Pdf And Embeds The Received Signature
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, string signature)
{
byte[] signedBytes = Convert.FromBase64String(signature);
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
The returned XML Signature from web service:
<SignatureValue Id="Signature-xxx-SIG">
RMFbYIigsrjYQEc4PCoHMMg8vwz/hYrCjj0IR+835BZZ/TsTMHZhMVnu2KQZi1UL
dWMeuhTxagBmjdBSzGiy1OEdH5r0FM77Of4Zz99o/aAhYqr3qpOETGgNn9GHlphL
5FSPYbNsq2rDHA8FsNdqNdb6qJUZYsfYvuhJaUMstBXeL/dLIT46t7ySCQ7CGjE5
mpD1AG8M+TVWf4ut5tucZuZV9PMQB3tyoarQD5RoUv872RzB5IorcIhLnf+O6E6o
EF0HuGitRhN9bbPgdLaUma5MNjKCaeQTpCXp3KXwi8VoQGd5fpUBZbAKtMpKeCts
RAOk1O4uk/4poic4IGPhDw==
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
AI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7mUU0i2A9wJTeiktS
IeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDACVedVkBUNkIgoXBy
g4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141zRHindfq86QrDTC7q
bHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOgGPY918G1HjBtlajR
nyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XPFhxzLx/ugBS/8GHt
iVYeJOlfHXRl5w2k2Vv/ssU=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate>
MIIGpTCCBY2gAwIBAgIRAJvlsG1D+P++OcU8W8D8FnkwDQYJKoZIhvcNAQELBQAw
bzELMAkGA1UEBhMCVFIxKDAmBgNVBAoMH0VsZWt0cm9uaWsgQmlsZ2kgR3V2ZW5s
aWdpIEEuUy4xNjA0BgNVBAMMLVRFU1QgVHVya2NlbGwgTW9iaWwgTkVTIEhpem1l
dCBTYWdsYXlpY2lzaSBTMjAeFw0xNzA4MjUxMjQ3MjFaFw0xODA4MjUxMjQ3MjFa
MEoxCzAJBgNVBAYTAlRSMREwDwYDVQQKDAhGaXJlIExMVDEUMBIGA1UEBRMLNzY1
NDM0NTY3NjUxEjAQBgNVBAMMCU1lcnQgSW5hbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7
mUU0i2A9wJTeiktSIeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDA
CVedVkBUNkIgoXByg4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141z
RHindfq86QrDTC7qbHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOg
GPY918G1HjBtlajRnyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XP
FhxzLx/ugBS/8GHtiVYeJOlfHXRl5w2k2Vv/ssUCAwEAAaOCA18wggNbMEIGCCsG
AQUFBwEBBDYwNDAyBggrBgEFBQcwAYYmaHR0cDovL3Rlc3RvY3NwMi5lLWd1dmVu
LmNvbS9vY3NwLnh1ZGEwHwYDVR0jBBgwFoAUT9gSazAfQrnZruIq9+dJFZ7E9OUw
ggFyBgNVHSAEggFpMIIBZTCBsQYGYIYYAwABMIGmMDYGCCsGAQUFBwIBFipodHRw
Oi8vd3d3LmUtZ3V2ZW4uY29tL2RvY3VtZW50cy9ORVNVRS5wZGYwbAYIKwYBBQUH
AgIwYBpeQnUgc2VydGlmaWthLCA1MDcwIHNhecSxbMSxIEVsZWt0cm9uaWsgxLBt
emEgS2FudW51bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWth
ZMSxcjCBrgYJYIYYAwABAQEDMIGgMDcGCCsGAQUFBwIBFitodHRwOi8vd3d3LmUt
Z3V2ZW4uY29tL2RvY3VtZW50cy9NS05FU0kucGRmMGUGCCsGAQUFBwICMFkaV0J1
IHNlcnRpZmlrYSwgTUtORVNJIGthcHNhbcSxbmRhIHlhecSxbmxhbm3EscWfIGJp
ciBuaXRlbGlrbGkgZWxla3Ryb25payBzZXJ0aWZpa2FkxLFyLjBdBgNVHR8EVjBU
MFKgUKBOhkxodHRwOi8vdGVzdHNpbC5lLWd1dmVuLmNvbS9FbGVrdHJvbmlrQmls
Z2lHdXZlbmxpZ2lBU01LTkVTSS1TMi9MYXRlc3RDUkwuY3JsMA4GA1UdDwEB/wQE
AwIGwDCBgwYIKwYBBQUHAQMEdzB1MAgGBgQAjkYBATBpBgtghhgBPQABp04BAQxa
QnUgc2VydGlmaWthLCA1MDcwIHNheWlsaSBFbGVrdHJvbmlrIEltemEgS2FudW51
bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWthZGlyMFAGA1Ud
CQRJMEcwFAYIKwYBBQUHCQIxCAQGQW5rYXJhMB0GCCsGAQUFBwkBMREYDzE5Nzgx
MjMxMjAwMDAwWjAQBggrBgEFBQcJBDEEBAJUUjAYBgNVHREEETAPgQ1maXJlQGZp
cmUuY29tMB0GA1UdDgQWBBTYUEJX62lhEzkZLSrTdSIdE9n8KzANBgkqhkiG9w0B
AQsFAAOCAQEAV/MY/Tp7n7eG7/tWJW+XlDgIPQK1nAFgvZHm+DodDJ8Bn7CPWmv8
EBmcNxx5PYMoG7y54E6+KzVyjdPBu5o0YtWyKlLKnH1St+EtptOmt29VFAODjalb
Q0An9361DxIDRHbMnQOaJiRTwMlIhED5VDa3OTUhBr7bNlVkp3N5Vs2RuoSdNypR
fq4D/cCpakVugsFyPk4zhWkGhTS5DlL7c5KYqCnU4ugpC33IRJGB05PSdjICeX7Y
N0oAdhzF+5QZEwePjgrDb+ROVpXYaGVMWICA8N2tOXuqdDYoGAzj1YemnPqrSn8a
2iwqbWnFujwep5w5C/TCFVaQWa4NGncMOw==
</X509Certificate>
</X509Data>
</KeyInfo>
When I use the returned PKCS#1 signature with above code, the signature verification fails with "Error encountered while BER decoding".
Since the BlankSignatureContainer created with ADBE_PKCS7_DETACHED subFilter, I think this is normal. However, I am not sure what filter and subFilter I should use for PKCS#1? Or Should / Can I create PKCS#7 message from PKCS#1 and user's certificate and use this format instead?
EDIT 1:
I can also retrieve the end users certificate before requesting the signature.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<ns1:MSS_ProfileQueryResponse xmlns:ns1="/mobilesignature/validation/soap">
<MSS_ProfileResp MinorVersion="1" MajorVersion="1" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:ns5="http://uri.etsi.org/TS102204/v1.1.2#">
<ns5:AP_Info Instant="2017-09-16T04:54:43.260Z" AP_PWD="turkcell123" AP_TransID="_1371012883260" AP_ID="http://turkcell.com.tr"/>
<ns5:MSSP_Info Instant="2017-09-16T13:33:36.316+02:00">
<ns5:MSSP_ID/>
</ns5:MSSP_Info>
<ns5:SignatureProfile>
<ns5:mssURI>http://MobileSignature/profile2</ns5:mssURI>
<ns5:CertIssuerDN> Mobil NES Hizmet Saglayicisi S2</ns5:CertIssuerDN>
<ns5:CertSerialNumber>71408272140747005781907792964830346324</ns5:CertSerialNumber>
<ns5:CertHash>
<ns5:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ns5:DigestValue>e1yKlaPIU95phxxYvrUsmSQDpKqKU60/b+a8BLw1wNM=</ns5:DigestValue>
</ns5:CertHash>
<ns5:msspUrl>http://localhost</ns5:msspUrl>
<ns5:certIssuerDN-DER>MG8xCzAJBgNVBAYTAlRSMSgwJgYDVQQKDB9FbGVrdHJvbmlrIEJpbGdpIEd1dmVubGlnaSBBLlMuMTYwNAYDVQQDDC1URVNUIFR1cmtjZWxsIE1vYmlsIE5FUyBIaXptZXQgU2FnbGF5aWNpc2kgUzI=</ns5:certIssuerDN-DER>
</ns5:SignatureProfile>
<ns5:Status>
<ns5:StatusCode Value="100"/>
<ns5:StatusMessage>REQUEST_OK</ns5:StatusMessage>
</ns5:Status>
</MSS_ProfileResp>
</ns1:MSS_ProfileQueryResponse>
</soap:Body>
</soap:Envelope>
EDIT 2:
I have updated my signing code to construct a CMS. However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(x509Signature))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
appearance.Certificate = chain[0];
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signature = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
_signature = signature;
_apperance = appearance;
_hash = hash;
_signatureHash = signatureHash;
return signatureHash;
}
}
}
public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
_signature.SetExternalDigest(signedBytes, null, "RSA");
byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
(This answer summarizes the evolution of working code in the course of comments and edits to the question.)
Which option does PDF support
The PDF format (the commonly supported ISO 32000-1:2008) specified embedded signatures using either naked PKCS#1 signatures or full PKCS#7/CMS signature containers, cf. section 12.8.3 "Signature Interoperability", in particular
section 12.8.3.2 "PKCS#1 Signatures" and
section 12.8.3.3 "PKCS#7 Signatures as used in ISO 32000".
Newer standards, like the ETSI PAdES standards and the new ISO 32000-2:2017, forbid or deprecate the former option. For a new solution that shall not be outdated already on the day it is published, therefore, one should go for the latter choice.
Knowing the certificate beforehand
I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.
If that were all the functionality of the web service, there'd be a problem: Both when embedding a naked PKCS#1 signature and when constructing a PKCS#7 signature container, one needs to know the end entity certificate before calling the service to create a signature for a hash because that certificate or a reference to it must be embedded in the signed PDF data or the signed CMS attributes.
(Strictly speaking very basic CMS signature container do not require this but all signature profiles to take seriously do.)
Fortunately it turned out (edit 1) that one
can access to another service "This service enables Application Providers to request end user’s certificate serial number and certificate hash which can be used in constructing the data to be signed."
The code
The OP found iText/Java code for implementing the functionality to sign a PDF with an embedded PKCS#7/CMS signature container based on a signing service as described above (edit 2).
However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.
As it turned out, though, those 77 bytes were not the hash of the signed attributes:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
[...]
byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
[...]
return signatureHash;
}
They were the signed attributes bytes. Thus, these bytes merely needed to be hashed to produce the hash to send to the signing service for creating a signature.
Trying to validate PDF signature isn't working. The PDF were signed by Adobe Acrobat and then trying to verify it with the public key of the client certificate.
So I get the public key of the client certificate, hash the PDF and verify if the hash is equal to the pdf signature, but it fails.
HttpClientCertificate cert = request.ClientCertificate;
X509Certificate2 cert2 = new X509Certificate2(cert.Certificate);
PdfReader pdfreader = new PdfReader("path_to_file");
AcroFields fields = pdfreader.AcroFields;
AcroFields.Item item = fields.GetFieldItem("Signature1");
List<string> names = fields.GetSignatureNames();
foreach (string name in names){
PdfDictionary dict = fields.GetSignatureDictionary(name);
PdfPKCS7 pkcs7 = fields.VerifySignature(name);
Org.BouncyCastle.X509.X509Certificate pdfSign = pkcs7.SigningCertificate;
// Get its associated CSP and public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert2.PublicKey.Key;
// Hash the data
SHA256 sha256 = new SHA256Managed();
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
// Verify the signature with the hash
bool ok = csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), pdfSing.GetSignature());
}
First, to verify whether the signature correctly you can simply use the PdfPKCS7 object you already retrieved, more exactly its Verify method:
/**
* Verify the digest.
* #throws SignatureException on error
* #return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise
*/
virtual public bool Verify()
Thus, you can simply call
bool ok = pkcs7.Verify();
and ok is true only if the document hash matches the hash in the signature.
Concerning your attempt to calculate the document hash like this
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
This indeed gives you the hash value of the complete PDF.
For document types with integrated signatures like PDFs, though, this is not the hash of interest because the complete PDF obviously includes the integrated signature!
Thus, you have to find the space reserved for the signature in the PDF and ignore it during hash calculation, cf. this answer on Information Security Stack Exchange, in particular this image:
In case of multiple signatures you furthermore have to consider that the earlier signatures only sign a former revision of the PDF, so the hash is to be calculated only for a starting segment of the file, cf. this image from the answer referenced above:
The iText(Sharp) method PdfPKCS7.Verify() takes all this into account.
I have a set of questions:
What does a .p7s file contain? I read that it usually is an encrypted message sent to an e-mail, but in my case I have a file and a .p7s file on my hdd. The file is much larger than the .p7s, so I am wondering what is in it (and of course how to later use it).
2.this question occurs mostly because I have no naswer for 1. - If I have my public key in the form of a byte array, how can I verify the signature in C#? I found this on the internet, but again, it uses some text gotten from an e-mail and I honestly don't know what is happening:
public static bool Verify(byte[] signature, string text)
{
X509Certificate2 certificate = new X509Certificate2(#"D:\My-CV.docx.p7s");
if (signature == null)
throw new ArgumentNullException("signature");
if (certificate == null)
throw new ArgumentNullException("certificate");
//hash the text
// Methode 3 for Hashing
System.Security.Cryptography.SHA1 hash3 = System.Security.Cryptography.SHA1.Create();
System.Text.UnicodeEncoding encoder = new System.Text.UnicodeEncoding();
byte[] combined = encoder.GetBytes(text);
byte[] hash3byte = hash3.ComputeHash(combined);
//Adding the text from the email, to a contentInfo
ContentInfo content = new ContentInfo(hash3byte);
// decode the signature
SignedCms verifyCms = new SignedCms(content, true);
verifyCms.Decode(signature);
// verify it
try
{
verifyCms.CheckSignature(new X509Certificate2Collection(certificate), false);
return true;
}
catch (CryptographicException)
{
return false;
}
}
Can someone help me with this?
The .p7s file is containing the signature of your document. The content is not encrypted, but it's look like.
The document file is much larger, because it contains the date, the .p7s the signature.
To check the signature, you need the public part of the certificate with which the document was signed.
You can check this two posts to check the signature :
Check signature for x509 certificate
In C#, sign an xml with a x.509 certificate and check the signature
The last thing is to load the signature data, from the p7s file.
I make some search, and come back to you.
I've got the following code sample in Java, and I need to re-enact it in C#:
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pkcs8PrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);
Signature sign = Signature.getInstance("MD5withRSA");
sign.initSign(privKey);
sign.update(data);
byte[] signature = sign.sign();
Is it possible with the standard .Net Crypto API, or should I use BouncyCastle?
Thanks,
b.
Another way is to use CNG (Cryptography Next Generation), along with the Security.Cryptography DLL from CodePlex
Then you can write:
byte[] dataToSign = Encoding.UTF8.GetBytes("Data to sign");
using (CngKey signingKey = CngKey.Import(pkcs8PrivateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsa = new RSACng(signingKey))
{
rsa.SignatureHashAlgorithm = CngAlgorithm.MD5;
return rsa.SignData(dataToSign);
}
Updated thanks to Simon Mourier: with .Net 4.6, you no longer need a separate library
I am running into a very similar problem trying to create a native C# tool for packing Chrome extensions (using SHA1, not MD5, but that's not a big difference). I believe I have tried literally every possible solution for .Net: System.Security.Cryptography, BouncyCastle, OpenSSL.Net and Chilkat RSA.
The best solution is probably Chilkat; their interface is the cleanest and most straightforward, it's well-supported and well-documented, and there are a million examples. For instance, here's some code using their library that does something very close to what you want: http://www.example-code.com/csharp/rsa_signPkcs8.asp. However, it's not free (though $150 is not unreasonable, seeing as I have burned 2 days trying to figure this out, and I make a bit more than $75 a day!).
As a free alternative, JavaScience offers up a number of crypto utilities in source form for multiple languages (including C#/.Net) at http://www.jensign.com/JavaScience/cryptoutils/index.html. The one that's most salient to what you are trying to do is opensslkey (http://www.jensign.com/opensslkey/index.html), which will let you generate a RSACryptoServiceProvider from a .pem file. You can then use that provider to sign your code:
string pemContents = new StreamReader("pkcs8privatekey.pem").ReadToEnd();
var der = opensslkey.DecodePkcs8PrivateKey(pemContents);
RSACryptoServiceProvider rsa = opensslkey.DecodePrivateKeyInfo(der);
signature = rsa.SignData(data, new MD5CryptoServiceProvider());
You can use this code . At the first you should download "BouncyCastle.Crypto.dll" from http://www.bouncycastle.org/csharp/ .
/// <summary>
/// MD5withRSA Signing
/// https://www.vrast.cn
/// keyle_xiao 2017.1.12
/// </summary>
public class MD5withRSASigning
{
public Encoding encoding = Encoding.UTF8;
public string SignerSymbol = "MD5withRSA";
public MD5withRSASigning() { }
public MD5withRSASigning(Encoding e, string s)
{
encoding = e;
SignerSymbol = s;
}
private AsymmetricKeyParameter CreateKEY(bool isPrivate, string key)
{
byte[] keyInfoByte = Convert.FromBase64String(key);
if (isPrivate)
return PrivateKeyFactory.CreateKey(keyInfoByte);
else
return PublicKeyFactory.CreateKey(keyInfoByte);
}
public string Sign(string content, string privatekey)
{
ISigner sig = SignerUtilities.GetSigner(SignerSymbol);
sig.Init(true, CreateKEY(true, privatekey));
var bytes = encoding.GetBytes(content);
sig.BlockUpdate(bytes, 0, bytes.Length);
byte[] signature = sig.GenerateSignature();
/* Base 64 encode the sig so its 8-bit clean */
var signedString = Convert.ToBase64String(signature);
return signedString;
}
public bool Verify(string content, string signData, string publickey)
{
ISigner signer = SignerUtilities.GetSigner(SignerSymbol);
signer.Init(false, CreateKEY(false, publickey));
var expectedSig = Convert.FromBase64String(signData);
/* Get the bytes to be signed from the string */
var msgBytes = encoding.GetBytes(content);
/* Calculate the signature and see if it matches */
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(expectedSig);
}
}
This SO question answers the PKCS#8 part of your code. The rest of the .NET RSA classes are a bizarre jumble of partially overlapping classes that are very difficult to fathom. It certainly appears that signature support is in either of the RSACryptoServiceProvider and/or RSAPKCS1SignatureFormatter classes.
Disclaimer: I know Java and cryptography, but my knowledge of C# and .NET is very limited. I am writing here only under the influence of my Google-fu skills.
Assuming that you could decode a PKCS#8-encoded RSA private key, then, from what I read on MSDN, the rest of the code should look like this:
byte[] hv = MD5.Create().ComputeHash(data);
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider();
RSAParameters rsp = new RSAParameters();
// here fill rsp fields by decoding pkcs8PrivateKey
rsp.ImportParameters(key);
RSAPKCS1SignatureFormatter rf = new RSAPKCS1SignatureFormatter(rsp);
rf.SetHashAlgorithm("MD5");
byte[] signature = rf.CreateSignature(hv);
The relevant classes are in the System.Security.Cryptography namespace.
As for the PKCS#8 key blob decoding (i.e. filling in the rsp fields), I found this page which describes a command-line utility in C# which can perform that job. The source code is provided and is a single C# file. From what I read in it, that code decodes the PKCS#8 file "manually"; indirectly, this should mean that raw .NET (2.0) does not have facilities for PKCS#8 key file decoding (otherwise the author of that tool would not have went to the trouble of implementing such decoding). For your task at hand, you could scavenge from that source file the parts that you need, skipping anything about PEM and symmetric encryption; your entry point would be the DecodePrivateKeyInfo() function, which apparently expects a DER-encoded unencrypted PKCS#8 file, just like Java's PKCS8EncodedKeySpec.