Search code examples
c#.net-corebouncycastlepkcs#7

How to create a PKCS#7 detached signature with .Net Core?


I want to create PKCS#7 detached signature with .Net Core (2.0).

I read all answers here more or less relevant to my issue and found this and this answers. All other were helpless. The first example do exactly what I need but it relies on .NetFramework.
The second one use Bouncy Castle library and do little different but similar thing. I found Portable.BouncyCastle project worked on .Net Core. As I can understand it is the only option for me.

This is the code from the first example with some modifications:

    string s = "data string";
    byte[] data = Encoding.UTF8.GetBytes(s);        
    X509Certificate2 certificate = null;
    X509Store my = new X509Store(StoreName.My,StoreLocation.CurrentUser);
    my.Open(OpenFlags.ReadOnly);
    certificate = my.Certificates.Find(X509FindType.FindByThumbprint, "my thumbprint", false)[0];
    if (certificate == null) throw new Exception("No certificates found.");

    ContentInfo content = new ContentInfo(new Oid("1.2.840.113549.1.7.1"),data);
    SignedCms signedCms = new SignedCms(content, true);

    CmsSigner signer = new CmsSigner(certificate);
    signer.DigestAlgorithm = new Oid("SHA256"); 

    // create the signature
    signedCms.ComputeSignature(signer);
    return signedCms.Encode();

It works fine in my case. signedCms.Encode() returns 1835 bytes and this value pass validation.

But if I use BounceCastle I get another result. This is the code:

                X509Certificate2 certificate = null;
        X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        my.Open(OpenFlags.ReadOnly);

        certificate = my.Certificates.Find(X509FindType.FindByThumbprint, "my thumbprint", false)[0];
        var privKey = DotNetUtilities.GetRsaKeyPair(certificate.GetRSAPrivateKey()).Private;
        var cert = DotNetUtilities.FromX509Certificate(certificate);

        var content = new CmsProcessableByteArray(data);

        var generator = new CmsSignedDataGenerator();

        generator.AddSigner(privKey, cert, CmsSignedGenerator.EncryptionRsa, CmsSignedGenerator.DigestSha256);

        var signedContent = generator.Generate(content, false);
        return signedContent.GetEncoded();

signedContent.GetEncoded() returns 502 bytes and this result can't be validated. I understand that I'm doing wrong something but I don't know what.

How should I modify the sample with Bouncy Castle that it get me the same result as the code above?


Solution

  • I found an another discussion that gave me a clue. There is a link to a GitHub repo with an example application. I modified it slightly and now it works as expected. Here is the code:

                X509Certificate2 certificate = null;
            X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            my.Open(OpenFlags.ReadOnly);
    
            certificate = my.Certificates.Find(X509FindType.FindByThumbprint, "thumbprint", false)[0];
            var privKey = DotNetUtilities.GetRsaKeyPair(certificate.GetRSAPrivateKey()).Private;
            var cert = DotNetUtilities.FromX509Certificate(certificate);
    
            var content = new CmsProcessableByteArray(data);
    
            var generator = new CmsSignedDataGenerator();
    
            generator.AddSigner(privKey, cert, CmsSignedGenerator.EncryptionRsa, CmsSignedGenerator.DigestSha256);
    
            var signedContent = generator.Generate(content, false);
    
            string hashOid = OID.SHA256;
    
            var si = signedContent.GetSignerInfos();
            var signer = si.GetSigners().Cast<SignerInformation>().First();
    
            SignerInfo signerInfo = signer.ToSignerInfo();
    
            Asn1EncodableVector digestAlgorithmsVector = new Asn1EncodableVector();
            digestAlgorithmsVector.Add(
                new AlgorithmIdentifier(
                    algorithm: new DerObjectIdentifier(hashOid),
                    parameters: DerNull.Instance));
    
            // Construct SignedData.encapContentInfo
            ContentInfo encapContentInfo = new ContentInfo(
                contentType: new DerObjectIdentifier(OID.PKCS7IdData),
                content: null);
    
            Asn1EncodableVector certificatesVector = new Asn1EncodableVector();
            certificatesVector.Add(X509CertificateStructure.GetInstance(Asn1Object.FromByteArray(cert.GetEncoded())));
    
            // Construct SignedData.signerInfos
            Asn1EncodableVector signerInfosVector = new Asn1EncodableVector();
            signerInfosVector.Add(signerInfo.ToAsn1Object());
    
            // Construct SignedData
            SignedData signedData = new SignedData(
                digestAlgorithms: new DerSet(digestAlgorithmsVector),
                contentInfo: encapContentInfo,
                certificates: new BerSet(certificatesVector),
                crls: null,
                signerInfos: new DerSet(signerInfosVector));
    
            ContentInfo contentInfo = new ContentInfo(
                contentType: new DerObjectIdentifier(OID.PKCS7IdSignedData),
                content: signedData);
    
            return contentInfo.GetDerEncoded();