dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

Unable to use SignedCms.ComputeSignature() from an asp.net core web api #28445

Closed springcomp closed 4 years ago

springcomp commented 5 years ago

Although the the following code works locally, it raises an exception when deployed to an asp.net core web app.

    public static byte[] CreateSignature(byte[] manifest, byte[] wwdra, byte[] certificat, string password, DateTime signingTime)
    {
        //setup the data to sign

        var content = new ContentInfo(manifest);
        var signedContent = new SignedCms(content, detached: true);

        var certificate = new X509Certificate2(certificat, password);

        var cmsSigner = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, certificate)
        {
            IncludeOption = X509IncludeOption.EndCertOnly
        };

        cmsSigner.Certificates.Add(new X509Certificate2(wwdra));
        cmsSigner.SignedAttributes.Add(new Pkcs9SigningTime(signingTime));

        //  Create Signature

        signedContent.ComputeSignature(cmsSigner, silent: true);

        //  Encode the CMS/PKCS dotnet/corefx#7 message.

        return signedContent.Encode();
    }

The exception is:

Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The system cannot find the file specified at Internal.Cryptography.Pal.Windows.PkcsPalWindows.GetPrivateKey[T](X509Certificate2 certificate, Boolean silent, Boolean preferNCrypt) at Internal.Cryptography.Pal.Windows.PkcsPalWindows.GetPrivateKeyForSigning[T](X509Certificate2 certificate, Boolean silent) at System.Security.Cryptography.Pkcs.CmsSignature.RSAPkcs1CmsSignature.Sign(ReadOnlySpan1 dataHash, HashAlgorithmName hashAlgorithmName, X509Certificate2 certificate, Boolean silent, Oid& signatureAlgorithm, Byte[]& signatureValue) at System.Security.Cryptography.Pkcs.CmsSignature.Sign(ReadOnlySpan1 dataHash, HashAlgorithmName hashAlgorithmName, X509Certificate2 certificate, Boolean silent, Oid& oid, ReadOnlyMemory1& signatureValue) at System.Security.Cryptography.Pkcs.CmsSigner.Sign(ReadOnlyMemory`1 data, String contentTypeOid, Boolean silent, X509Certificate2Collection& chainCerts) at System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent)

`

This seems to originate from the GetPrivateKey method, but I do not have a clue as to why it tries to open a file.

vtchalkov commented 5 years ago

I assume that you run on Windows. If this is the case then try to enable the User Profile loading in Application pool settings (the screenshot might not be exactly the same as your Application pool settings as it depends on Windows version):

image

The reason for this is that on Windows signing with a certificate (even if you load it from PFX file) needs to store the private key in temporary location that requires loaded user profile. This is not applicable if you use Network service account. More information here: https://stackoverflow.com/a/17149834

springcomp commented 5 years ago

I forgot to mention that the app is hosted on Azure. It works in .net core with Kestrel locally, it works in .net 461 with IIS Express locally.

However it fails when hosted on a Windows Azure App Service (windows OS)

vtchalkov commented 5 years ago

Then your problem is most likely the same as https://github.com/dotnet/corefx/issues/27358 I personally prefer to specify EphemeralKeySet (see https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509keystorageflags?view=netcore-2.2)

springcomp commented 5 years ago

This seems to be my exact problem. Thanks.

bartonjs commented 5 years ago

It seems like the problem is resolved, so closing.

(And, yay, now I know EphemeralKeySet has a customer other than me!)