c++certificatewincryptsubject-alternative-name

Add Subject Alternate Name to self signed certificate using wincrypt


How to add Subject Alternative Name in the certificate. Using wincrypt I have created and added the certificate to "MY" and "root" path where CN can be machine fully qualified domain name or hostname or IP. As Subject alternate name I want to add the following:DNS name=fully qualified domain name, DNS name=hostname and DNS name=IP How to do this? I do not want to use openssl.

    LPCTSTR cnName= fqdn;
    DWORD cbEncoded = 0;
if (!CertStrToName(X509_ASN_ENCODING, cnName, CERT_X500_NAME_STR, NULL, 
    pbdata, &cbData, NULL))
    {
    _tprintf(_T("CertStrToName failed 0x%x\n"), GetLastError());
    return 0;
    }
    if (!(pbdata = (BYTE *)malloc(cbData)))
    {
    _tprintf(_T("malloc Error 0x%x\n"), GetLastError());
    return 0;
    }

    if (!CertStrToName(X509_ASN_ENCODING, cnName, CERT_X500_NAME_STR, NULL, pbdata, &cbData, NULL))

{
    _tprintf(_T("CertStrToName failed 0x%x\n"), GetLastError());
    return 0;
}

CERT_NAME_BLOB IssuerBlob;
IssuerBlob.cbData = cbEncoded;
IssuerBlob.pbData = pbEncoded;

CertCreateSelfSignCertificate(NULL, &IssuerBlob, 0, &KeyProvInfo, &Alg, 0, &EndTime, 0);
OpenandAddCertificateToStore(pCertContext, L"MY");
OpenandAddCertificateToStore(pCertContext, L"Root");

This creates and adds certificate to the store without SAN in the certificate

I tried passing extension list like below:

CertCreateSelfSignCertificate(NULL, &IssuerBlob, 0, &KeyProvInfo, &Alg, 0, &EndTime, &myExtns_list);

CERT_EXTENSION myExtn;
myExtn.fCritical = TRUE;
myExtn.pszObjId = szOID_SUBJECT_ALT_NAME;
myExtn.Value = myBlobdata;

CERT_EXTENSIONS myExtns_list;
myExtns_list.cExtension = 1;
myExtns_list.rgExtension = &myExtn;

char cb[20] = { "DNS Names=abc.com" };
BYTE    *pbData = (BYTE*)cb;
CERT_NAME_BLOB myBlobdata;
myBlobdata.cbData = 20;
myBlobdata.pbData = pbData;

with this I could get the SAN as byte format in the left pane and the right pane shows my string "DNS Names=abc.com".

But my requirement is to show only the DNS Names in the SAN.


Solution

  • I'm assuming that your building of the extension data actually happened prior to your call to CertCreateSelfSignedCertificate (and, likewise, that your building of myBlobdata happened before using it).

    The main problem is that you used one SAN entry (sort of) as the entirety of the SAN extension; which meant you lost some encoding wrappers. A secondary problem is that you used szOID_SUBJECT_ALT_NAME, which is the incorrect OID for Subject Alternative Name... you actually want szOID_SUBJECT_ALT_NAME2.

    I want to add the following:DNS name=fully qualified domain name, DNS name=hostname and DNS name=IP

    Adding an IP address as a DNS name is non-standard and is supposed to result in a match failure. You'd actually want to add the IP address as an IP Address SAN entry.

    CERT_ALT_NAME_ENTRY entries[3];
    entries[0] = { CERT_ALT_NAME_DNS_NAME };
    entries[0].pwszDNSName = L"example.org";
    
    // IPv4 Address 10.12.1.130
    BYTE ip4Bytes[] = { 10, 12, 1, 130 };
    entries[1] = { CERT_ALT_NAME_IP_ADDRESS };
    entries[1].IPAddress = { sizeof(ip4Bytes), ip4Bytes };
    
    // ::1, big-endian
    BYTE ip6Bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    entries[2] = { CERT_ALT_NAME_IP_ADDRESS };
    entries[2].IPAddress = { sizeof(ip6Bytes), ip6Bytes };
    
    CERT_NAME_BLOB name = { cbEncoded, buf };
    BYTE extBuf[1024] = { 0 };
    cbEncoded = sizeof(extBuf);
    
    CERT_ALT_NAME_INFO info = { sizeof(entries) / sizeof(CERT_ALT_NAME_ENTRY), entries };
    
    if (!CryptEncodeObjectEx(
        X509_ASN_ENCODING,
        X509_ALTERNATE_NAME,
        &info,
        0,
        nullptr,
        extBuf,
        &cbEncoded))
    {
        // Whatever your error handling story is.
        //
        // Note I didn't do a 0 buffer or CRYPT_ENCODE_ALLOC; I just knew
        // that my buffer would be big enough.
    }
    
    CERT_EXTENSION extension = { 0 };
    extension.fCritical = 0;
    extension.pszObjId = szOID_SUBJECT_ALT_NAME2;
    extension.Value = { cbEncoded, extBuf };
    
    CERT_EXTENSIONS extensions = { 1, &extension };
    
    ...
    
    PCCERT_CONTEXT cert = CertCreateSelfSignCertificate(
        0,
        &name,
        0,
        &keyProvInfo,
        &sigAlg,
        0,
        &certExpirationDate,
        &extensions);
    

    In CertUI that gives me the Subject Alternative Name value I expect:

    DNS Name=example.org
    IP Address=10.12.1.130
    IP Address=0000:0000:0000:0000:0000:0000:0000:0001