0

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.

Rafal R
  • 3
  • 2
  • it's by design. https://security.stackexchange.com/a/51365 Trying to create RSA key with SHA1 algo should be avoided – LinkedListT Jan 23 '20 at 19:16
  • Right, I mentioned in my question that I know it's not recommended and hasn't been since a few years ago. But I found nothing in the docs that says it's specifically forbidden - if it was documented, then I would give up and move on. I found that the BouncyCastle library does allow it, so that may be my workaround. – Rafal R Jan 23 '20 at 19:51
  • It's by design, but you're right: the documentation should call that out. Once https://github.com/dotnet/dotnet-api-docs/pull/3812 is merged, it will. – bartonjs Jan 28 '20 at 16:56

1 Answers1

1

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.

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thanks, it working for creating self-signed SHA1 certificates (and even MD5 is set the magic number to "300D06092A864886F70D0101040500"). Is it possible to modify `RSASha1Pkcs1SignatureGenerator` to made signed certificates, just like if use `X509Certificate2 CertificateRequest.Create(X509Certificate2 issuerCertificate, DateTimeOffset notBefore, DateTimeOffset notAfter, byte[] serialNumber)`? – Alexander Tauenis Jun 12 '23 at 12:03
  • @AlexanderTauenis Yes, it will work in the `Create(X500DistinguishedName, X509SignatureGenerator, DateTimeOffset, DateTimeOffset, byte[])` overload (hint, you want `issuerCertificate.SubjectName` for the first parameter) – bartonjs Jun 12 '23 at 16:06
  • it results in `An error occurred during a connection to test.local. Peer's certificate has an invalid signature. (Error code: sec_error_bad_signature)` error. Strange, but the self-signed SHA1 root certificate, created also by this way, is working without problems if use it with a SHA256 child certificate. – Alexander Tauenis Jun 19 '23 at 19:09