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

miércoles, 18 de septiembre de 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:\Ruta\Fichero.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:\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
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

No hay comentarios:

Publicar un comentario