Showing certificate chain without validating with Windows "certificate store" (C#)

jueves, 19 de septiembre de 2013

Java has its own independent certificate store. If you wish to view natively in Windows a certificate extracted from an APK or JAR file Windows may not find the root certificate and thus won’t be able to “verify trust” and validate it. We would have to use Java’s dialog to view the certificate correctly.

What if we visualize the same certificate in Windows? In this second screen capture we can see the given error when the Intermediate CA and Root CA are not found in our local Windows certificate store (“Thawte Code Signing CA” in our example). This dialog is shown by default when executing files with .DER extension or by calling X509Certificate2UI.DisplayCertificate() in code which inherits System.Security.Cryptography.X509Certificates.

To display the certificate and validate the chain correctly we have different possibilities: 
  • Installing all Java’s “Root CA” certificates: Inefficient, It requires user confirmation for every certificate.
  • Install temporarily the root certificate referenced by the APK/JAR file and delete it after the validation process. This option also needs user confirmation and it’s usually not a good idea to modify the user’s trusted certificate list.
  • Extract the entire certification chain of the file and doing the validation manually. Obviously the best option.
The first option to think of is using Microsoft’s own X509Chain to validate the certificate chain. The behavior of X509Chain is highly configurable and allows us to change the various chain verification policies and adding chain elements manually. Once we have defined “ChainPolicy” and “ChainElements” we use the X509Chain.Build() method that returns us a Boolean value either validating or not the certificate chain. And that’s it, we have a Boolean value but no graphic information.
Furthermore, if we don’t have the root certificate installed in our certificate store, we’re unable to import the intermediate certificate as a X509ChainElement which is necessary to build the chain correctly.

However, even if Windows doesn’t trust these certificates, they’re still present in PKCS#7 structure that we have extracted from the APK/JAR file. We need to dig deeper and call lower level functions.

The ideal scenario is to create our own certificate store in memory and leave the CurrentUser and LocalMachine stores unmodified. The store is then passed to CryptUIDlgViewCertificate which is imported from “Cryptui.dll" and is the same dialog that is associated in Windows to file with extensions such as .cer .crt… This way, Windows validates the chain against the store that we have created and the chain is displayed correctly even though the root certificate is not trusted by Windows natively.

The way to create our “virtual store” in memory is using "CertOpenStore". To use it in C# we need to import the DLL:

[DllImport("CRYPT32", EntryPoint
= "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr CertOpenStore(
            int storeProvider, int encodingType,
            int hcryptProv, int flags, string pvPara);

When calling the function we need to indicate that our storeProvider is of type CERT_STORE_PROV_PKCS7 and "pvPara" will point to the data.

Apk and Jar files store the PKCS#7 structure in a RSA or DSA format. Therefore, we need to extract the PKCS#7 structure first to work with the data contained inside.

We can do this using WinCrypt and more specifically CryptQueryObject in the following way:
if (!WinCrypt.CryptQueryObject(
    out encodingType,
    out contentType,
    out formatType,
    ref certStore,         //Contiene el "Store" con los certificados.
    ref cryptMsg,        //Contiene la estructura PKCS7
    ref context))

cryptMsg contains the PKCS#7 structure that we can work with but WinCrypt kindly offers us certStore of type IntPtr that already contains the certificates, CRL (certificate revocation list) and CTL (certificate trust list) which saves us time. We can then pass certStore as an extra store to CryptUIDlgViewCertificate which will validate the main certificate against the extra store and show the result in its own window. Here is the code:

//en myCert tendremos el certificado principal
X509Certificate2 myCert = new X509Certificate2(@"X:\Ruta\Fichero.RSA");

//los extra stores que queramos usar deben pasarse como puntero al array que los contiene
var extraStoreArray = new[] { certStore };       
var extraStoreArrayHandle = GCHandle.Alloc(extraStoreArray, GCHandleType.Pinned);
var extraStorePointer = extraStoreArrayHandle.AddrOfPinnedObject();

//rellenamos la estructura con los parámetros
 certViewInfo.dwSize = Marshal.SizeOf(certViewInfo);
 certViewInfo.pCertContext = myCert.Handle;
 certViewInfo.szTitle = "Certificate Info";
 certViewInfo.dwFlags = CRYPTUI_DISABLE_ADDTOSTORE;                         
 certViewInfo.nStartPage = 0;
 certViewInfo.cStores = 1;
 certViewInfo.rghStores = extraStorePointer;
 bool fPropertiesChanged = false;

if (!CryptUIDlgViewCertificate(ref certViewInfo, ref fPropertiesChanged))
 int error = Marshal.GetLastWin32Error();

Finally we obtain the desired result and a valid certificate chain.

Tero de la Rosa

No hay comentarios:

Publicar un comentario en la entrada