4

Due to a bug in Windows Azure, all self-signed client certificates used with the Azure REST API from a Windows 8 application must be specified as extended validation certificates.

To provide a better user experience, I am trying to generate this self-signed certificate on a remote server. I'm using the Certificate Enrolment API, a COM library distributed as part of Windows, as described in this answer to the question How to create a self-signed certificate using C#?

The code is largely the same, just slightly modified for my own use:

public static X509Certificate2 CreateSelfSignedCertificate(
    string cname,
    string friendlyName,
    string password)
{
    // create DN for subject and issuer
    var dn = new CX500DistinguishedName();
    dn.Encode("CN=" + cname, X500NameFlags.XCN_CERT_NAME_STR_NONE);

    // create a new private key for the certificate
    CX509PrivateKey privateKey = new CX509PrivateKey();
    privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
    privateKey.MachineContext = true;
    privateKey.Length = 2048;
    privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
    privateKey.ExportPolicy 
        = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
    privateKey.Create();

    // Use the stronger SHA512 hashing algorithm
    var hashobj = new CObjectId();
    hashobj.InitializeFromAlgorithmName(
        ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
        ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY, 
        AlgorithmFlags.AlgorithmFlagsNone,
        "SHA512");

    // Create the self signing request
    var cert = new CX509CertificateRequestCertificate();
    cert.InitializeFromPrivateKey(
        X509CertificateEnrollmentContext.ContextMachine,
        privateKey,
        string.Empty);

    cert.Subject = dn;
    cert.Issuer = dn; // the issuer and the subject are the same
    cert.NotBefore = DateTime.Now;
    cert.NotAfter = DateTime.Now.AddYears(50); 
    cert.HashAlgorithm = hashobj;

    var clientAuthenticationOid = new CObjectId();
    clientAuthenticationOid.InitializeFromValue("1.3.6.1.5.5.7.3.2");

    // Set up cert to be used for Client Authentication.
    var oids = new CObjectIds();
    oids.Add(clientAuthenticationOid);

    var eku = new CX509ExtensionEnhancedKeyUsage();
    eku.InitializeEncode(oids);

    cert.X509Extensions.Add((CX509Extension)eku);

    // Add the certificate policy.
    var policy = new CCertificatePolicy();
    policy.Initialize(clientAuthenticationOid);

    // THIS IS WRONG - NEEDS A DIFFERENT QUALIFIER
    var qualifier = new CPolicyQualifier();
    qualifier.InitializeEncode(
        "c0",
         PolicyQualifierType.PolicyQualifierTypeUserNotice);

    policy.PolicyQualifiers.Add(qualifier);

    var policies = new CCertificatePolicies();
    policies.Add(policy);

    var ecp = new CX509ExtensionCertificatePolicies();
    ecp.InitializeEncode(policies);

    cert.X509Extensions.Add((CX509Extension)ecp);

    cert.Encode();

    // Do the final enrolment process
    var enroll = new CX509Enrollment();
    enroll.InitializeFromRequest(cert); // load the certificate
    enroll.CertificateFriendlyName = friendlyName;
    string csr = enroll.CreateRequest(); // Output the request in base64

    // and install it back as the response
    enroll.InstallResponse(
        InstallResponseRestrictionFlags.AllowUntrustedCertificate,
        csr,
        EncodingType.XCN_CRYPT_STRING_BASE64,
        ""); // no password

    // output a base64 encoded PKCS#12 for import to .NET
    var base64encoded = enroll.CreatePFX(
        password,
        PFXExportOptions.PFXExportChainWithRoot);

    // instantiate the target class with the PKCS#12 data
    return new X509Certificate2(
        System.Convert.FromBase64String(base64encoded),
        password,
        X509KeyStorageFlags.Exportable);
}

I've found the ICertificatePolicy interface which appears to represent the right type of structure, but I can't deduce the right IPolicyQualifier to use. In my code, the qualifier is

For clarity, this where you configure the information in the Windows 8 Certificate Manager:

where to enable extended validation

Which produces this property on the certificate:

extended validation property on a certificate

My code currently produces this property:

not quite extended validation property on a certificate

Close, but not there yet.

Is there another way to load data into the IPolicyQualifier so that it produces the expected result, perhaps using the InitialiseDecode method?

Community
  • 1
  • 1
Paul Turner
  • 38,949
  • 15
  • 102
  • 166
  • Do you have a reference to this "bug"? – EricLaw Sep 23 '14 at 18:12
  • @EricLaw: This thread is the closest thing: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/0d005703-0ec3-4466-b389-663608fff053/using-client-side-certificates-in-windows-metro?forum=winappswithcsharp – Paul Turner Sep 23 '14 at 19:59
  • The post you referenced does not appear to mention `Extended Validation` at all; it only mentions that certificates used for Client Authentication need the client Authentication OID in the EKU, which makes sense (not really a bug). `EV` certificates are a VERY different beast. – EricLaw Sep 23 '14 at 20:04
  • 1
    Apologies - it's been 18 months since I was even thinking about this problem and it was very specifically related to requiring an extended validation property which was very hard to generate. – Paul Turner Sep 23 '14 at 20:46
  • FWIW, the way EV works is that you put an OID in the certificate and then you also have to have the matching OID on the entry in the Root certificate store; that entry is metadata attached to the root entry but it isn't actually in the Root itself. – EricLaw Sep 23 '14 at 21:01

1 Answers1

1

If you want to set Client Authentication OID as "1.3.6.1.5.5.7.2" then I think you code does work as expected. How do you verify that the code does not have this value set?

I wrote the following code to save the generated certificate using your code:

X509Certificate2 xx = CreateSelfSignedCertificate("Avkash CNAME", "Test User Friendly Name", "xx");
byte[] bCertExported = xx.Export(X509ContentType.Pkcs12, "xx");
File.WriteAllBytes("c:\\Installbox\\test.pfx", bCertExported);

After I installed the certificate locally and checked it for Client Authentication OID, it does show as below:

enter image description here

AvkashChauhan
  • 20,495
  • 3
  • 34
  • 65
  • I've updated my question with a little more information. What you have here is helpful in that it confirms my code works, but it's not adding the information I'm speaking of. – Paul Turner Jan 30 '13 at 11:37