Mostrar cadena certificados sin usar el «certificate store» de Windows (en c#)

ElevenPaths    18 septiembre, 2013

Java mantiene su propia «store» de certificados, independiente a la de Windows. Esto implica que si se visualiza de forma «nativa» en Windows un certificado extraído de un fichero APK o JAR, puede que no encuentre el certificado raíz en el «store» del sistema y por tanto no podrá validarlo. Habría que usar el propio store de Java para poder «verlo».


¿Pero y si este mismo certificado se «ve» en otro entorno? En la imagen de abajo se muestra un ejemplo de lo que ocurre si Windows no integra en su store (como sí lo hace Java) el certificado del emisor intermedio (en la imagen «Thawte Code Signing CA») y el que a su vez firme este. Esta pantalla se consigue o bien «ejecutando» un fichero en formato DER o bien con X509Certificate2UI.DisplayCertificate() que hereda de System.Security.Cryptography.X509Certificates.
  

Para mostrarlo de esta forma, validando toda la cadena, se pueden pensar diferentes
posibilidades: 
  • Instalar todos los certificados root de
    Java en la store de Windows: No es la opción óptima, además de pedir la
    confirmación uno a uno.
  • Instalar temporalmente el certificado root
    del apk o jar en cuestión y borrarlo tras la comprobación. Sigue pidiendo
    confirmación y no resulta buena idea tocar la raíz de confianza del usuario.
  • Extraer toda la cadena de certificados
    del apk o jar y hacer la comprobación manualmente. Obviamente la mejor.

La primera aproximación suele ser intentar
construir un X509Chain que proporciona Microsoft para comprobar una cadena de
certificados hacia arriba. El comportamiento de X509Chain es altamente
cofigurable y permite cambiar las distintas políticas de comprobación así como
añadir distintos certificados a la cadena manualmente. Una vez definidos
ChainPolicy y ChainElements se llama al método Build() de X509Chain que
devuelve un booleano con la validez o no de la cadena… y eso es todo. Es
decir, nos devuelve un booleano pero nada de gráficos para mostrar por
pantalla.

Aunque Windows no mostrara los
certificados porque no confiase en ellos, estos sí que están presentes en la
estructura PKCS#7 que se ha extraído del apk o jar, por tanto es necesario
bajar un poco el nivel.
Lo ideal sería un «store» propio de certificados en
memoria para no alterar los del equipo (CurrentUser y LocalMachine).
Este store se le puede pasar a  la
función «CryptUIDlgViewCertificate» que está presente en
«Cryptui.dll» y que no es más que el cuadro de diálogo que muestra
Windows asociado a los .cer, .crt, etc. De esta manera podría comprobar la
cadena y mostrar los certificados aunque Windows no confiara en ellos de forma
nativa.
La manera de crear un «store
virtual» de certificados es mediante «CertOpenStore«. Para usarlo en C# hay que importar la 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);

Y al llamarlo se debe indicar que nuestro
storeProvider será de tipo CERT_STORE_PROV_PKCS7 siendo «pvPara» un
puntero a los datos.

Los
archivos apk y jar guardan la estructura PKCS en un formato RSA o DSA y por tanto es necesario descifrar el PKCS7
contenido en el archivo antes de acceder a los certificados que contiene.
Para conseguirlo se puede utilizar «WinCrypt», especifícamente CryptQueryObject de la siguiente manera:
  
if (!WinCrypt.CryptQueryObject(
    WinCrypt.CERT_QUERY_OBJECT_FILE,
    Marshal.StringToHGlobalUni(@"X:RutaFichero.RSA"),
    WinCrypt.CERT_QUERY_CONTENT_FLAG_ALL,
    WinCrypt.CERT_QUERY_FORMAT_FLAG_ALL,
    0,
    out encodingType,
    out contentType,
    out formatType,
    ref certStore,         //Contiene el "Store" con los certificados.
    ref cryptMsg,        //Contiene la estructura PKCS7
    ref context))

Una vez tenemos «certStore» de
tipo «IntPtr» lo podemos pasar a
«CryptUIDlgViewCertificate» como «Extra Store» contra el
que comprobará el certificado principal y realizará la validación hacia arriba
hasta el certificado principal…  y lo
mostrará en pantalla. Aquí el código:


//en myCert tendremos el certificado principal
X509Certificate2 myCert = new X509Certificate2(@"X:RutaFichero.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
CRYPTUI_VIEWCERTIFICATE_STRUCT certViewInfo = new CRYPTUI_VIEWCERTIFICATE_STRUCT();
 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();
 MessageBox.Show(error.ToString());
}
Finalmente se consigue que se muestre el resultado deseado, validando gráficamente la cadena.

Tero de la Rosa

Deja una respuesta

Tu dirección de correo electrónico no será publicada.