lentinant
lentinant

Reputation: 832

What is difference between manual installation of certificate and installation from code?

I'm working on some certificate signing tool, and my current goal is to create method, which will generate new certificate, using existing certificate as issuer. Here is method for generating certificate:

public X509Certificate2 CreateCertificate(string subjectName, TimeSpan certificateLifespan, X509Certificate2 issuer = null)
{
    var nameFormat = "CN={0}";

    var subject = string.Format(nameFormat, subjectName);

    // create DN for subject and issuer
    var dn = new CX500DistinguishedName();
    dn.Encode(subject);

    var privateKey = new CX509PrivateKey
    {
        ProviderName = "Microsoft Strong Cryptographic Provider",
        Length = 2048,
        KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE,
        KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
                           X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG,
                MachineContext = true,
                ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG
    };

    privateKey.Create();

    var hashobj = new CObjectId();
    hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
                ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
                AlgorithmFlags.AlgorithmFlagsNone, "SHA512");

    var cert = new CX509CertificateRequestCertificate();
    cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
    cert.Subject = dn;
    if (issuer == null)
        cert.Issuer = dn;
    else
    {
        var signerCertificate = new CSignerCertificate();
        signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyNone, EncodingType.XCN_CRYPT_STRING_HEXRAW, issuer.GetRawCertDataString());
                cert.SignerCertificate = signerCertificate;
    }
    cert.NotBefore = DateTime.Now.Date;
    cert.NotAfter = cert.NotBefore + certificateLifespan;
    cert.HashAlgorithm = hashobj;
    cert.Encode();

    var enroll = new CX509Enrollment();
    enroll.InitializeFromRequest(cert);
    enroll.CertificateFriendlyName = subjectName;

    var csr = enroll.CreateRequest();
    enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
                csr, EncodingType.XCN_CRYPT_STRING_BASE64, "");

    //InstallResponse automatically installs certificate to My store. We should remove it, and manage it manually.
    using(var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadWrite);

        var certificate = store.Certificates.OfType<X509Certificate2>().FirstOrDefault(
                    c => c.Subject.StartsWith(subject, StringComparison.Ordinal));

        if (certificate != null)
            store.Remove(certificate);
        store.Close();
    }

    //Self-signed certificates are also authomatically installed to Intermediate Authority store
    if(issuer == null)
    {
        using (var store = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine))
        {
            store.Open(OpenFlags.ReadWrite);

            var certificate = store.Certificates.OfType<X509Certificate2>().FirstOrDefault(
                        c => c.Subject.StartsWith(subject, StringComparison.Ordinal));

            if (certificate != null)
                store.Remove(certificate);
            store.Close();
        }
    }

    var base64encoded = enroll.CreatePFX("", PFXExportOptions.PFXExportChainWithRoot);

    return new X509Certificate2(Convert.FromBase64String(base64encoded), "",
                X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
}

The thing is, that it works perfectly nice, but only if I install root certificate manually, or using Certificate Manager. But I'm trying to install certificate from embedded resource in code.

Here is my method to extract embedded certificate to stream:

protected Stream ExtractResourceStream(string embeddedResourcePath)
{
    var assembly = GetType().Assembly;

    var pathConverted = embeddedResourcePath.Replace("\\", ".");

    var matchingResources = assembly.GetManifestResourceNames()
            .Where(n => n.EndsWith(pathConverted, StringComparison.InvariantCultureIgnoreCase))
            .ToArray();

    var resource = matchingResources.FirstOrDefault();
    if (resource == null)
        throw new InvalidOperationException(string.Format("Resource {0} not found.", embeddedResourcePath));
    if (matchingResources.Length > 1)
        throw new InvalidOperationException(string.Format("Resource {0} found more than once.", embeddedResourcePath));

    return assembly.GetManifestResourceStream(resource);
}

Then I initialize it next way:

var bytes = new BinaryReader(stream).ReadBytes((int)stream.Length);
var root = new X509Certificate2(bytes, RootCertificatePassword);

And here is my method for certificate installation:

private void AddCertificateToStore(X509Certificate2 certificate, StoreName storeName)
{
    using (var store = new X509Store(storeName, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(certificate);
        store.Close();
    }
}

Root certificate seems to be installed properly, yet I'm getting exception "No key found" on signerCertificate.Initialize call while generating second certificate.

I'm using PFX root certificate protected by password.

So, my question is - what is difference between installing certificate manually, and installing it from stream in code? Am I missing some part while installing it in code?

Upvotes: 2

Views: 260

Answers (1)

lentinant
lentinant

Reputation: 832

Well, that was silly. Apparently, I was missing proper keyset. It works, if I initialize root certificate like this:

var root = new X509Certificate2(bytes, RootCertificatePassword, 
                X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

Upvotes: 1

Related Questions