A little oversight I had in the development of MungeTLS was that in all my testing, I'd basically hard-coded a certificate for the server to use. Heh, at early points, I had literally hard-coded it in the CPP file as a huge byte array tucked away at the bottom. It later progressed to calling the Windows cert store APIs to find a certificate and grab its encoded representation.
I didn't really touch it again, since it was working pretty well, but now that I've gotten most other crypto stuff working, it's time to make it a little more useful. One important thing I've always had to do in SSL client library testing is handling of different certificate chains. Different bitness of certificate keys, different "usage" OIDs allowed, out of order certificate chains, and so on. As a start, MungeTLS needs to support sending more than a measly one certificate in the Certificate
handshake message.
This effort wasn't really that complicated or time consuming. It came down to switching all my PCCERT_CONTEXT
variables to PCCERT_CHAIN_CONTEXT
. The chain context structure gives you access to an array of PCCERT_CONTEXT
structures, which is exactly what I need.
Correspondingly, in an unprecedented show of convenience, there is simply a method to grab a whole cert chain, given a PCCERT_CONTEXT
: CertGetCertificateChain
. It's beautiful! As far as parameters go, the only strange one that doesn't have a good default value is a PCERT_CHAIN_PARA
structure. And of this, the only required parameter is RequestedUsage
, which is nearly optional. Here's what I use to initialize it:
CERT_CHAIN_PARA chainPara = {0};
chainPara.cbSize = sizeof(chainPara);
// indicates not to use this member
chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
if (!CertGetCertificateChain(
NULL,
pCertContext,
NULL,
NULL,
&chainPara,
0,
NULL,
&pCertChain))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto error;
}
Interacting with the resultant chain structure is also easy. Here's where I get access to the PCCERT_CONTEXT
that I was previously being passed directly:
spPubKeyCipherer.reset(new WindowsPublicKeyCipherer());
hr = spPubKeyCipherer->Initialize(
(*CertChain())->rgpChain[0]->rgpElement[0]->pCertContext);
There's only one small hiccup in this structure: notice that I have to traverse two levels of arrays. I think of a certificate chain as a simple array, where element 0 is the leaf and element size-1 is the self-signed root certificate. But apparently Windows gives us a list of chains. I don't know what this means, exactly. In the simple example I'm using--three certificates in a chain--Windows returns me only one list. I wonder what circumstances produce more than one. I bet it's some weird smartcard thing.
And here's the code where I create a Certificate
handshake message with multiple certificates in the chain:
PCCERT_CHAIN_CONTEXT pCertChain = *ConnParams()->CertChain();
// what does it mean to have 2 simple chains? no support for now
assert(pCertChain->cChain == 1);
PCERT_SIMPLE_CHAIN pSimpleChain = pCertChain->rgpChain[0];
for (DWORD i = 0; i < pSimpleChain->cElement; i++)
{
hr = certificate.AddCertificateFromMemory(
pSimpleChain->rgpElement[i]->pCertContext->pbCertEncoded,
pSimpleChain->rgpElement[i]->pCertContext->cbCertEncoded);
if (hr != S_OK)
{
goto error;
}
}
At first I was manually just mashing all of the encoded representations together in a byte vector and sending those raw over the wire. That's not actually correct according to the RFC, though. Here's the relevant section:
opaque ASN.1Cert<1..2^24-1>;
struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;
You have to kind of know the notation in the RFC doc to really grok this, but angle brackets indicate a "vector", which is always preceded by a length field just large enough to represent the range indicated. So by mashing together all the certificates, I was not correctly setting the length of the individual certificates in the blob. An illustration:
Wrong:
Certificate length,
certificate_list[0],
certificate_list[1],
and certificate_list[2]
00 00 0f 01 02 03 04 11 12 13 14 15 21 22 23 24 25 26
Right:
Certificate length,
certificate_list[0].length,
certificate_list[0],
certificate_list[1].length,
certificate_list[1],
certificate_list[2].length,
and certificate_list[2]
00 00 0f 00 00 04 01 02 03 04 00 00 05 11 12 13 14 15 00 00 06 21 22 23 24 25 26
Not so subtle once you look at it side by side, right? In the first case you have no idea how long each individual certificate is. That was probably an overblown, unnecessary example. But we're at the end! This all works. See?
A thought on platform agnosticism
It's worth pointing out that this is one area of the code I haven't yet made platform-agnostic. Ideally the caller would pass in a list of X.509 certificates in their byte representation, the leaf certificate's private key, and the leaf certificate's public key. Maybe it's possible for me to parse the public key out of the certificate, but that's going to require understanding the X.509 format... I'm not sure I signed up for that!
Perhaps a better method is to make two initialization possibilities for the Windows public key encryptor object: one that takes the certificate store parameters to look it up in the Windows cert store and one that takes some well known format, like PFX. Still, the goal is for someone to be able to rapidly do testing using the tools they already have, without requiring a lot of weird conversions. This effort needs some more thinking.
0 comments:
Post a Comment