Search code examples
c#.netcryptographysystem.security

ArgumentOutOfRangeException when trying to create a self signed certificate using CertificateRequest with SHA1 in .NET 4.7.2


I have a requirement to generate SHA1 hashed certificates for an old legacy system which does not support SHA256. I'm well aware that MD5 and SHA1 hashing in crypto scenarios is not recommended, but this is an external requirement.

I'm using .NET Framework 4.7.2 and its new CertificateRequest class.

Here is the relevant code snippet:

            using (var rsa = RSA.Create(2048))
            {
                var subjectName = "CN=sampleName";
                var certificateRequest = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
                var selfSignedCertificate = certificateRequest.CreateSelfSigned(DateTime.UtcNow, DateTime.UtcNow.AddYears(10));
                var certAsBytes = selfSignedCertificate.Export(X509ContentType.Pfx, "fakePassword");
                File.WriteAllBytes("cert-sha1.pfx", certAsBytes);
            }

When certificateRequest.CreateSelfSigned is called, it throws the following exception:

'SHA1' is not a known hash algorithm.
Parameter name: hashAlgorithm
Actual value was SHA1.

The relevant point in the stack trace is:

...
   at System.Security.Cryptography.X509Certificates.RSAPkcs1X509SignatureGenerator.GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm)
   at System.Security.Cryptography.X509Certificates.TbsCertificate.Encode(X509SignatureGenerator signatureGenerator, HashAlgorithmName hashAlgorithm)
   at System.Security.Cryptography.X509Certificates.TbsCertificate.Sign(X509SignatureGenerator signatureGenerator, HashAlgorithmName hashAlgorithm)
   at System.Security.Cryptography.X509Certificates.CertificateRequest.Create(X500DistinguishedName issuerName, X509SignatureGenerator generator, DateTimeOffset notBefore, DateTimeOffset notAfter, Byte[] serialNumber)
   at System.Security.Cryptography.X509Certificates.CertificateRequest.CreateSelfSigned(DateTimeOffset notBefore, DateTimeOffset notAfter)
   at Tests.Sha1Test()

From the .NET API browser for the constructor I'm using, I don't see anything why this shouldn't work, even if not recommended. Using MD5 throws a similar exception. SHA256 and above work fine.

The CertificateRequest class also does not show in the reference source (maybe because it's too new?) so I'm out of ideas.


Solution

  • Not supporting MD5 and SHA-1 was intentional, based on feedback from the feature pull request (after opening the link you have to wait a few seconds while the page loads enough code to find the anchor then re-jump).

    The documentation has now been updated to say that:

    Remarks

    This method does not support using MD5 or SHA-1 as the hash algorithm for the certificate signature. If you need an MD5 or SHA-1 based certificate signature, you need to implement a custom X509SignatureGenerator and call Create(X500DistinguishedName, X509SignatureGenerator, DateTimeOffset, DateTimeOffset, Byte[]).

    Surely creating an X509SignatureGenerator is hard, right? Well, from the same feedback link:

    private sealed class RSASha1Pkcs1SignatureGenerator : X509SignatureGenerator 
    { 
         private readonly X509SignatureGenerator _realRsaGenerator; 
    
         internal RSASha1Pkcs1SignatureGenerator(RSA rsa) 
         { 
             _realRsaGenerator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1); 
         } 
    
         protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; 
    
         public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) 
         { 
             if (hashAlgorithm == HashAlgorithmName.SHA1) 
                 return "300D06092A864886F70D0101050500".HexToByteArray(); 
    
             throw new InvalidOperationException(); 
         } 
    
         public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => 
             _realRsaGenerator.SignData(data, hashAlgorithm); 
    } 
    

    That code is also used as a test for the ability to provide a custom generator.