Search code examples
c#pkcs

PartialChain error in X509Chain when building certificate chain?


I'm encountering a PartialChain error when using the X509Chain class in C# to build a certificate chain. The error message states: "Cannot build a certificate chain for a trusted root authority."

Here's the relevant code snippet:

public X509Certificate2 GenerateCertificateFromCSR(byte[] csrBytes)
{
    var csr = CertificateRequest.LoadSigningRequest(csrBytes, HashAlgorithmName.SHA256);
    
    using var issuerCertificate = new X509Certificate2(_settings.PfxPath, _settings.PfxPassword);
    var notBefore = DateTimeOffset.UtcNow;
    var notAfter = notBefore.AddYears(1);
    var serialNumber = GenerateSerialNumber();

    var issuerKey = issuerCertificate.GetRSAPrivateKey();
    var signatureGenerator = X509SignatureGenerator.CreateForRSA(issuerKey, RSASignaturePadding.Pkcs1);
    var issuerName = new X500DistinguishedName(issuerCertificate.SubjectName.Name);

    var certGenerator = csr.Create(issuerName, signatureGenerator, notBefore, notAfter, serialNumber);

    var sanExtension = csr.CertificateExtensions.FirstOrDefault(e => e.Oid.Value == "2.5.29.17");

    if (sanExtension != null)
    {
        var sanExtensionValue = sanExtension.RawData;
        certGenerator.Extensions.Add(new X509Extension(sanExtension.Oid, sanExtensionValue, sanExtension.Critical));
    }

    // Create a certificate chain and add the issuer certificate
    using var chain = new X509Chain();
    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    chain.ChainPolicy.ExtraStore.Add(issuerCertificate);

    // Build the chain
    var chainBuilt = chain.Build(certGenerator);

    // Check if the chain status has any errors
    if (!chainBuilt || chain.ChainStatus.Any(status => status.Status != X509ChainStatusFlags.NoError))
    {
        // Print out the chain status information
        foreach (var chainStatus in chain.ChainStatus)
        {
            _logger.LogError("Chain error: {Status} {StatusInformation}", chainStatus.Status, chainStatus.StatusInformation);
        }

        // Handle the error here
        throw new Exception("Chain build failed");
    }

    // Check the last element in the chain to verify that it's indeed the root CA certificate you expect
    var rootCert = chain.ChainElements[^1].Certificate;
    
    if (!rootCert.Equals(issuerCertificate))
    {
        // Handle the error here
        throw new Exception("Incorrect root certificate");
    }

    // Get the chain elements
    var chainElements = chain.ChainElements.Cast<X509ChainElement>();

    // Concatenate the certificates in the chain
    var certificateChain = new X509Certificate2Collection();
    
    foreach (var chainElement in chainElements)
    {
        certificateChain.Add(chainElement.Certificate);
    }

    // Export the certificate chain as a PFX file
    var pfxBytes = certificateChain.Export(X509ContentType.Pfx, _settings.PfxPassword);

    // Create the final certificate from the exported PFX bytes
    var finalCertificate = new X509Certificate2(pfxBytes, _settings.PfxPassword);

    return finalCertificate;
}

I have verified that the certificate is valid and trusted. However, the PartialChain error occurs consistently. I have also checked the certificate stores and ensured that all the necessary intermediate and root certificates are present.

The CA certificate is issued like this:

$caName = "RemoteMaster Internal CA"
$destDirectory = "InternalCA"
$opensslPath = "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"

# Ensure destination directory exists
if (-not (Test-Path $destDirectory)) {
    New-Item -Path $destDirectory -ItemType Directory
    Write-Host "Created directory: $destDirectory" -ForegroundColor Green
}

# Generate a private key
& $opensslPath genpkey -algorithm RSA -out "$destDirectory\$caName.key" -pkeyopt rsa_keygen_bits:4096

# Generate a CSR using the private key
& $opensslPath req -new -key "$destDirectory\$caName.key" -out "$destDirectory\$caName.csr" -subj "/CN=$caName"

# Create an OpenSSL configuration file
$config = @"
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[ req_distinguished_name ]
CN = $caName
[ v3_req ]
basicConstraints = CA:TRUE
"@
$config | Out-File "$destDirectory\openssl.cnf" -Encoding ascii

# Generate a self-signed certificate with Basic Constraints set to CA:TRUE
& $opensslPath x509 -req -days 3650 -in "$destDirectory\$caName.csr" -signkey "$destDirectory\$caName.key" -out "$destDirectory\$caName.crt" -extfile "$destDirectory\openssl.cnf" -extensions v3_req

# Convert the certificate and private key to a PFX file
& $opensslPath pkcs12 -export -out "$destDirectory\$caName.pfx" -inkey "$destDirectory\$caName.key" -in "$destDirectory\$caName.crt" -name $caName

Write-Host "Generated self-signed root CA certificate and exported to PFX." -ForegroundColor Green

# Import the certificate to the Trusted Root Certification Authorities store
Import-Certificate -FilePath "$destDirectory\$caName.crt" -CertStoreLocation "Cert:\CurrentUser\Root"
Write-Host "Added CA certificate to Trusted Root Certification Authorities store." -ForegroundColor Green

What could be causing this PartialChain error? How can I troubleshoot and resolve it?

Any insights or suggestions would be greatly appreciated. Thank you!


Solution

  • That problem's been taken care of. I should have done it like this

    public X509Certificate2 GenerateCertificateFromCSR(byte[] csrBytes)
    {
        if (csrBytes == null)
        {
            throw new ArgumentNullException(nameof(csrBytes));
        }
    
        var csr = CertificateRequest.LoadSigningRequest(csrBytes, HashAlgorithmName.SHA256);
    
        using var caCertificate = new X509Certificate2(_settings.PfxPath, _settings.PfxPassword);
        var subjectName = new X500DistinguishedName(caCertificate.SubjectName);
        var signatureGenerator = X509SignatureGenerator.CreateForRSA(caCertificate.GetRSAPrivateKey(), RSASignaturePadding.Pkcs1);
        var notBefore = DateTimeOffset.UtcNow;
        var notAfter = DateTimeOffset.UtcNow.AddYears(1);
        var serialNumber = GenerateSerialNumber();
    
        var certificate = csr.Create(subjectName, signatureGenerator, notBefore, notAfter, serialNumber);
    
        return certificate;
    }