Reverse-Engineering CryptoAPI's Certificate Registry Blobs
Every so often, I’m doing Namecoin-related development research (in this case, making TLS work properly) and I run across some really interesting information that no one else seems to have documented. While this post isn’t solely Namecoin-related (it’s probably useful to anyone curious about tinkering with TLS), I hope you find it interesting regardless.
A note on the focus here: while this research was done for the purpose of engineering specific things, I’m writing it from more of a “basic research” point of view. My dad’s career was in basic research, and I firmly believe that learning cool stuff for the sake of learning it is a worthwhile endeavor, regardless of what the practical applications are (and indeed, usually when basic research turns out to have applications, which is commonplace, the initial researchers didn’t know what those applications would be). Since I’m an engineer, there will be a bit of application-related commentary here, but don’t read this expecting it to be a summary of the next Namecoin software release’s feature set or use cases.
In Windows-based OS’s, most applications handle certificates via the CryptoAPI. CryptoAPI serves a somewhat similar role in Windows certificate verification as OpenSSL does on GNU/Linux-based systems. Notably, Mozilla-based applications like Firefox and Thunderbird don’t use CryptoAPI (nor OpenSSL); they use the Mozilla library NSS (on both Windows and GNU/Linux). However, except for Mozilla applications, and a few applications ported from GNU/Linux (e.g. Python) which use OpenSSL, just about everything on Windows uses CryptoAPI for its certificate needs. CryptoAPI is a quite old Microsoft technology; it dates back at least to Windows NT 4. (It might be even older, but I’ve never touched nor read about any of the earlier incarnations of Windows NT, so I wouldn’t know.) Like any other codebase that’s been around for over 2 decades, its design is somewhat convoluted, and my guess is that if it were being designed from scratch today, it would look very different.
CryptoAPI maintains a bunch of different stores for certificates. These stores are designated according to the certificate’s intended usage (e.g. a website cert, an intermediate CA, a root CA, a personal cert, and a bunch of other use cases that I don’t understand because I’ve never managed any kind of certificate infrastructure for an enterprise), the method by which the certificate was loaded (e.g. by a web browser cache, by group policy, and a bunch of other methods that, again, I don’t understand because I don’t do enterprise infrastructure), which users have permission to use the certs (roaming profiles have special handling), and even which applications are expected to consider them valid (Cortana, Edge, and Windows Store all have their own certificate stores, for reasons that I don’t understand in the slightest, although I do wonder whether adding an intercepting proxy to Cortana’s cert store would be useful in an attempt to wiretap Cortana and see what data Microsoft collects on its users). You can see a subset of the certificate stores’ contents via
certmgr.msc, and there’s a command-line tool included with Windows called
certutil which can edit or dump this data as well. Neither of these tools actually shows all of the stores, e.g. Cortana, Edge, and Windows Store are secret and invisible. Also, don’t confuse the CryptoAPI
certutil with the Mozilla command-line tool also called
certutil, which is similar but is for NSS stores and has an entirely different syntax.
Incidentally, CryptoAPI has some interesting behavior when it comes to root CA’s. If you add a self-signed website cert to a root CA store, that self-signed website cert becomes fully trusted (HSTS and HPKP even work, which implies that it doesn’t get reported as an override). Of course, this is usually a dangerous idea, since that self-signed website could then sign other websites’ certs – you did tell Windows to treat it as a root CA, after all. But Windows actually does respect the CA and CertUsage flags in this case: if you construct a cert that is not valid as a CA, Windows will happily let you add it to a root CA store, will accept it as a website cert, but will refuse to trust any other cert signed by that cert. Namecoin lead security engineer Ryan Castellucci told me on IRC that he’s not sure if this behavior is even defined in a spec, but in my testing, NSS seems to exhibit identical behavior (no idea about OpenSSL). Regardless of specs, Microsoft has a fanatical obsession with not changing behavior of any public-facing API that might impact backwards compatibility (to Microsoft, the original implementation is the spec), so I think it’s probably pretty safe to rely on this behavior, even when someone as thoroughly knowledgeable as Ryan has never encountered anything in the wild that does this. Of course, that’s just my assessment – I take no responsibility if this burns you. As they say on Brainiac: Science Abuse, “we do these experiments so you don’t have to – do not try this at home – no really, don’t.”
You might wonder: why the heck isn’t there a permission system for this? Coming from a culture that loves the concept (if not implementation) of things like AppArmor and SELinux, that was certainly my thought. But alas, I was unable to find any Microsoft documentation that suggested a way to delegate access to a specific cert store to some other user. (Of course, Microsoft’s documentation is a train wreck, so maybe they did address this use case and I just couldn’t find any mention of it.) However, I did learn something interesting by Googling. While OpenSSL cert stores are just a filesystem folder, and NSS cert stores are a database file (whose database backend is either BerkeleyDB or SQLite), CryptoAPI mostly uses… the Windows Registry. Remember, this is Microsoft, they dump their garbage in the registry with as little hesitation as petroleum companies dump their garbage in Latin-American rainforests. (Personal certificates that are part of roaming profiles are instead placed in a user’s profile folder, apparently ever since Windows 2000 came out. But almost everything else is in the registry.) Since the registry does have a permission system, this looks like the perfect solution.
It was relatively easy to figure out where these certificates are located in the dense, uncharted jungle that is the registry. Indeed, you can search your registry for keys titled
Root and you’ll find all the root CA stores (the other types of stores are in sibling keys). Each certificate is located in its own subkey (the subkey is named based on the certificate’s SHA-1 fingerprint). Actually, let me digress for a moment. Why the hell is Microsoft using SHA-1 hashes as the names of registry keys, even in Windows 10? Yes, I know SHA-1 was not known to be weak when Microsoft designed CryptoAPI, but tying the name of something to a specific hash algorithm seems like a massively stupid idea in terms of design and safety. (And no, it’s not a good idea to drive drunk just because your crazy git uncle Linus does it every New Year’s Eve and hasn’t died yet.) Anyway, inside that subkey is a single value, called
Blob, which contains binary data encoding the certificate. Not too complicated, right?
Oh, wait. We’re talking about Microsoft. Everything is complicated, usually for no discernable reason whatsoever. Also, the most complicated things usually have the least documentation. I know people who have long-ago adopted a policy of getting their Windows documentation from the ReactOS source code instead of the Microsoft website, because a small, minimally funded project that’s reverse-engineering everything writes more accurate documentation than the wildly successful company who actually engineered the system and wrote the original source code. Anyway, I looked at the contents of the binary blob in the registry, and noticed that it didn’t look right. More specifically, it wasn’t a DER-encoded x.509 structure, nor was it even PEM-encoded. Actually, there was a substring that did correspond to the DER-encoded x.509 structure, but there was a crapload of extra data too. For reference, it looked like this (in
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates\trust\Certificates\BBC2FAE0B710372FC293E092904F7E628D3D4546] "Blob"=hex:04,00,00,00,01,00,00,00,10,00,00,00,97,30,35,47,95,5b,3b,e4,05,71,\ d4,5c,6c,cd,7e,21,0f,00,00,00,01,00,00,00,20,00,00,00,95,6e,94,6c,93,46,fd,\ c8,b5,02,66,9b,c9,1b,be,5c,19,df,97,f0,b4,8d,fa,f2,57,28,77,a1,7a,37,bc,bc,\ 14,00,00,00,01,00,00,00,14,00,00,00,33,c6,3b,84,aa,7a,15,b1,23,a5,4c,7e,38,\ 23,25,bc,e8,7f,cb,eb,19,00,00,00,01,00,00,00,10,00,00,00,73,1a,dd,da,db,51,\ b3,34,87,0f,15,1e,03,c0,b0,11,5c,00,00,00,01,00,00,00,04,00,00,00,00,01,00,\ 00,03,00,00,00,01,00,00,00,14,00,00,00,bb,c2,fa,e0,b7,10,37,2f,c2,93,e0,92,\ 90,4f,7e,62,8d,3d,45,46,20,00,00,00,01,00,00,00,d3,01,00,00,30,82,01,cf,30,\ 82,01,76,a0,03,02,01,02,02,14,00,f5,9d,9e,8e,09,5d,3f,54,a9,02,2a,89,09,62,\ 41,df,f1,fa,e1,30,0a,06,08,2a,86,48,ce,3d,04,03,02,30,3e,31,19,30,17,06,03,\ 55,04,03,13,10,74,65,73,74,2e,76,65,63,6c,61,62,73,2e,62,69,74,31,21,30,1f,\ 06,03,55,04,05,13,18,4e,61,6d,65,63,6f,69,6e,20,54,4c,53,20,43,65,72,74,69,\ 66,69,63,61,74,65,30,1e,17,0d,31,37,30,31,30,31,30,30,30,30,30,30,5a,17,0d,\ 31,38,30,31,30,31,30,30,30,30,30,30,5a,30,3e,31,19,30,17,06,03,55,04,03,13,\ 10,74,65,73,74,2e,76,65,63,6c,61,62,73,2e,62,69,74,31,21,30,1f,06,03,55,04,\ 05,13,18,4e,61,6d,65,63,6f,69,6e,20,54,4c,53,20,43,65,72,74,69,66,69,63,61,\ 74,65,30,59,30,13,06,07,2a,86,48,ce,3d,02,01,06,08,2a,86,48,ce,3d,03,01,07,\ 03,42,00,04,fe,1c,b5,b7,88,c1,d7,8a,e8,9f,1e,a7,d6,6f,15,42,1d,36,8d,b9,51,\ 7e,ed,9c,57,7f,cb,73,2a,26,7d,59,63,ca,95,10,30,68,e6,bb,15,8d,6c,f2,34,6b,\ 77,05,ea,68,8d,3a,28,d2,0a,eb,6a,4d,97,5b,ed,32,ef,8a,a3,52,30,50,30,0e,06,\ 03,55,1d,0f,01,01,ff,04,04,03,02,07,80,30,13,06,03,55,1d,25,04,0c,30,0a,06,\ 08,2b,06,01,05,05,07,03,01,30,0c,06,03,55,1d,13,01,01,ff,04,02,30,00,30,1b,\ 06,03,55,1d,11,04,14,30,12,82,10,74,65,73,74,2e,76,65,63,6c,61,62,73,2e,62,\ 69,74,30,0a,06,08,2a,86,48,ce,3d,04,03,02,03,47,00,30,44,02,20,65,d7,93,a8,\ 18,c7,de,6f,42,89,27,47,08,90,e1,ed,bb,23,e0,d7,51,69,04,f0,be,9d,98,bc,00,\ 88,69,dc,02,20,5b,b4,45,f5,9e,76,48,37,1d,58,1b,34,f9,17,1f,12,c6,98,cb,c0,\ d0,d3,50,19,2a,db,63,69,6b,31,cb,20
Just to see what would happen, I took a raw DER-encoded x.509 certificate and shoved it into the registry to see if CryptoAPI would accept it, but of course it didn’t, so I needed to figure out what that extra data was. Now, as a middle school student, as a high school student, and as an undergraduate student (though sadly not as a graduate student), I did a lot of reverse-engineering of various binary formats (making a Mega Drive Zero Wing ROM include custom dialogue between “Cracker” and “Admin” about how “All your 802.11b are belong to us” and that “You are on the way to DDoS microsoft.com” was a few days of work in 7th grade). So I was quite ready to go that route. However, I learned long ago that it’s always better to spend a few hours on Google to see if someone else has already done your dirty work for you, because usually someone has. So I did that.
The first result I found was a Microsoft mailing list thread from 2002 where Mitch Gallant and Rebecca Bartlett both inquired about this format. Microsoft’s response was to refuse to provide any documentation, and generally be rude and unhelpful, on the grounds that this use case was unsupported. Now, hang on, because I need to point something out. I’ve asked a lot of software vendors for obscure technical information about their products, nearly all of which were for unsupported use cases. By far my favorite vendor to work with in this area was the robotics hardware company Charmed Labs (they are incredibly nice and helpful, even happily giving me proprietary source code that was protected by patents, for me to do whatever I wanted with, as long as I didn’t distribute or use commercially). But generally speaking, the “good” companies’ responses to such requests follow this kind of formulation:
- What you’re trying to do is not something that we officially support.
- We think what you’re doing is a bad idea for these reasons.
- There may be other reasons that it’s a bad idea, which we haven’t thought of.
- We think the “right” way is something else, and we’re happy to help you with that method if you like.
- Regardless, here are all the answers to your questions.
- If you have any other questions about this unsupported use case, we’ll try to answer, as long as it doesn’t become a time sink for us. Don’t expect super-quick replies, because we’re looking up answers in our spare time.
- If any of the info we’re providing turns out to be incomplete or wrong, or you end up getting burned in any other way for doing this, we’re not responsible.
Whenever I’ve gotten a reply like this (and it’s happened reasonably often), I was a happy camper, because I was able to make an informed decision. Sometimes I decided to abandon my quest; other times I disregarded the warning and pressed on anyway. In the latter case, I knew full well what I was getting into, because the vendor had given me sufficient information and context for me to make up my mind. Usually, I was satisfied with my decision at the end of the day. In fact, I cannot remember a case where I did something I seriously regretted after being warned against it like that. I’m sure it could have happened, had the quantum noise been different, but if that had come to pass, I’m confident that I wouldn’t have blamed the vendor for giving me information coupled with advice that I ignored. There were several times where my disregard for the warning resulted in some lost development time or temporary confusion, but seriously, who could possibly be angry for the chance to gain practical experience in an unfamiliar area, particularly given that when I did decide to change course, I now had both the vendor’s expert recommendations and my new practical experience to inform my decision. What more could anyone want? My point is, the good software vendors treat their users like real, sentient people when they ask for information, while the bad software vendors (Microsoft included) treat their users the way that the owners of Number 4 Privet Drive treated their nephew up until mid-1991: Don’t ask questions!
Technically, Microsoft did provide parts (1) through (4) of the above form, but they don’t even qualify for partial credit here, because the reason they gave for (2) is atrocious on its face: once in 2 decades, they moved a store from the registry to the filesystem to handle roaming profiles, and anyone using the registry directly would have had to update their software (with a really minor change) once Windows 2000 came out. Frankly, if you’re unwilling to update your software once in 2 decades, I don’t know why you’re in this field, and you should go get a Ph.D. in Latin Literature so that you don’t need to ever learn anything new in order to stay at the top of your field for your whole life. Anyway, reading that thread was entirely unhelpful, except for the fact that it told me I had made a great choice never interviewing at Microsoft, because if I ever become the kind of tech support robot who shows up in that thread, someone please kill me.
So, I continued to look through Google for a while. And I did find two people who had actually tried to reverse-engineer the blob format. Tim Jacobs had posted some information in 2008, and Willem Jan Hengeveld had posted some other info in 2003. Interestingly, both Tim and Willem had been looking into dealing with bugs in mobile versions of Windows that made it hard to import a certificate any other way. (See, basic research has diverse, and often non-obvious, use cases.) Tim’s documentation wasn’t particularly helpful for me, because while it explained how to solve a specific problem (which wasn’t the problem I had), it didn’t really explain why that solution worked, nor how he figured it out (actually, I’m very skeptical of why his solution even works, and based on my research below, I suspect that he simply got lucky and that his method will spectacularly break in many real-world scenarios). However, Willem’s documentation was very helpful. According to Willem, a cert blob is a sequence of records, each of which consists of a 4-byte
propid (which I gather means “property ID”), a 4-byte
unknown value (which I assume is reserved by Microsoft for future expansion, since everything I encountered used exactly the same value), a 4-byte size, and then the raw data for that property (whose size in bytes was specified by the size field). Willem also listed the common property ID’s that show up.
There was just one problem: the blob I was looking at had a bunch of property ID’s that weren’t in Willem’s list. So, of course, I Googled for one of the property ID’s that was in Willem’s list, and I ended up finding this page and this page on the Microsoft website, which had a (mostly) complete list. Except… those references had descriptions (which, admittedly, is nice) but not any info on what numerical values the property ID’s were. However, they did include a reference to
Wincrypt.h. Ah ha, I thought, I’ve done this before! So I went and looked up that header file in the MinGW source code, and was treated to a complete list of the numerical values of all the property ID’s.
From there, I started gathering a list of which property ID’s Windows seemed to be using, so that I could generate the appropriate information while inserting a cert, given only its DER x.509 encoding. Unfortunately, quite a lot of the property ID’s were for things that looked quite annoying to calculate. After trying to figure out a way to calculate a “signature hash” of an x.509 cert in Golang, and not having any fun whatsoever (mind you, I did find ways to do it, I just knew I would despise the process of coding it, and of ever looking at the horrible code that was bound to result), a thought crossed my mind: What does CryptoAPI do if some of the properties are missing from the blob, as long as the record format is correct? So, I took the blob that Windows had generated, and I wiped everything except for the x.509 cert itself and the 12-byte header for that property. I inserted it into the registry, and visited the corresponding website in Chrome. The website loaded just fine! Then I went back to the registry editor, and refreshed, and was quite surprised to see that the moment that CryptoAPI had validated the cert, it had re-calculated all of the other missing fields, and inserted them into the registry.
So, basically, all of those other properties are, as best I can tell, just an elaborate caching mechanism, completely superfluous for proper operation. Microsoft made CryptoAPI substantially more complex, added at least 4 public-facing API functions (those are just the ones I accidentally ran across), and invented a custom, undocumented binary blob format, all so that they could avoid doing a couple of extra hash operations when verifying a chain that included a previously seen certificate. (Remember, hash operations are fast, while RSA and ECDSA, which aren’t cached here and are still needed to verify cert chains, are slow.)
Typical Microsoft. slow clap
Thanks goes to ncdns developer Hugo Landau and Monero developer Riccardo Spagni for keeping me company on IRC while I figured all of the above out. What does this have to do with Namecoin? You’ll find out in my next post.