Visualizza il feed RSS

sspintux

WCF e verifica della revoca di un certificato

Valuta questo inserimento
di pubblicato il 20-07-2012 alle 16:48 (4697 Visite)
Qualche appunto sulle possibili soluzioni all'immancabile "rogna" in vista delle ferie
e di cui non ricorderei nulla se dovesse procrastinarsi a dopo.

Scenario

Verifica della revoca di un certificato le cui CA intermedie hanno punti di distribuzione
della CRL non raggiungibili.

Soluzione (da testare) :
implementare un CustomCertificateValidator in cui la verifica viene eseguita
tramite X509Chain e relativa X509ChainPolicy con FlagVerifica =OnLine e
FlagVerification=EndCertificate

... e fin qui, va beh! ...ma le rogne non sembrano finite :
pare proprio che le CryptoApi, che sono quelle usate dietro le quinte, nonostante
la richiesta di verifica OnLine, vadano comunque a consultare prima l'esistenza
delle CRL in cache .... e se esistono (e non sono scadute) OnLine non ci vanno !

Soluzione 1 (da testare) :
usare l'utility certutil per cancellare la CRL dalla cache ( e relativo Delta)
nota1: con certutil è possibile cancellare in modo selettivo ed anche forzare la scadenza della cache.
nota2: la cache è per User, per Process .. o cosa ?

- pro: sembra facile
- contro : prestazioni ed il fatto di dover lanciare un processo esterno da un WCF

Soluzione 2 (da testare) :
- scaricare autonomamente la CRL (e relativo Delta) ed usare direttamente le CryptoAPI per verificare se il Serial Number del certificato è presente.

- pro: sembra fico
- contro : non sembra una cosa tanto banale a farsi

Soluzione 3 (auspicabile) :
la rogna da risolvere se la prende un altro ed io me ne vado in ferie
- pro: mi piace assai
- contro : assolutamente nessuno


Soluzione 4 (tutto da vedere , di riserva) :
Bouncy Castle per .NET

*** ...............***

... purtroppo la mia soluzione preferita (la 3) sembra sempre meno probabile

... cominciamo a mettere su qualche pezzo che potrebbe tornare utile :

Funzione per ricavare la lista delle CRL di un certificato
codice:
private static IList<string> GetListUrlCRLDistributionPoint(X509Certificate2 certificate)
{
    IList<string> items = null;

    var CrlDpExt = certificate.Extensions["2.5.29.31"];
    if (CrlDpExt != null)
    {
        var ear1 = CrlDpExt.Format(true).Split(Environment.NewLine.ToArray());
        if (ear1 != null)
        {
            var ear2 = ear1.Where(e => e.Trim().StartsWith("URL=")).ToList();
            if (ear2 != null)
                items = ear2.Select(e => e.Trim().Replace("URL=", "")).ToList();
        }
    }

    return items;
}
Nota : da tenere presente anche KeyInfoX509Data che ha una proprietà CRL

*** ...............***

Azz!, la questione sembra complicarsi ulteriormente

perché spulciando in rete c'è chi afferma che un processo si crea una sua cache delle CRL anche in memoria e sulla quale non ha sembra avere effetto una delete/refresh cache eseguita tramite certutil fino a che il processo non si ravvia;

... e questo *potrebbe* essere il mio caso dal momento che la verifica del certificato
la eseguo da un servizio WCF ospitato in IIS di cui difficilmente potrò eseguire un riciclo dell'application pool.

Considerato che non trovo fonti certe ed inequivocabili di informazione e che non posso attualmente
fare test con certificati revocati, dovrò prevedere un log per cercare di capire cosa succede in realtà ed aggiustare il tiro in seguito.

Nota : la soluzione 2 potrebbe saltare a piè pari questo eventuale problema;
nel caso in questione, ed indipendentemente dal tipo di soluzione perseguita,
è comunque da tenere presente che non sembra sensato interrompere l'erogazione
del servizio qualora il server di pubblicazione delle CRL fosse *momentaneamente *
irraggiungibile.

Considerato - sempre nel caso in questione - che le CRL vengono aggiornate nominalmente a cadenza settimanale non sembra giustificato neanche verificare la revoca del certificato ad ogni richiesta (...le prestazioni calano).

Buon link da studiarsi per soluzione 2 :
How to get information from a CRL (.NET) - Decrypt my World - Site Home - MSDN Blogs

Ovviamente è gradito qualunque suggerimento/commento da parte di eventuali lettori
(il sistema operativo è Windows server 2008 R2 ed utilizzo il .NET FW 4.0).


*** ...............***

Ho deciso di intraprendere la strada della custom cache (Soluzione 2), ovvero :
  • Prima eseguo la verifica "normalmente" tramite X509Chain.Chain.
    Ignoro eventuali errori dovuti alla non raggiungibilità della CRL per non interrompere l'erogazione del servizio.
  • Scarico, se possibile ed ad intervalli di tempo configurabili , la CRL di base letta dal certificato e la salvo su disco. Per ora ignoro eventuali CRL fornite tramite query ldap e considero solo quelle esposte tramite protocollo http/ftp.
    Anche qui ignoro eventuali errori dovuti alla non raggiungibilità della CRL.
  • Verifico se il certificato è stato revocato nella CRL di base appena scaricata o comunque nell'ultima salvata su disco.
  • Leggo l'URL della Delta CRL dalla CRL di base precedente e la salvo su disco per poi verificare se il certificato fosse stato revocato nella Delta CRL appena scaricata o comunque nell' ultima salvata su disco.

Per implementare gli ultimi 2 punti è necessario ricorrere alle CrypoAPI perchè il .NET Framework non mette a disposizione ( .. o almeno io non le trovo) classi di alto livello per facilitarci la gestione delle CRL.
Buona parte delle dichiarazioni per il .NET possono essere reperite dal blog già citato prima di Alejandro Campos Magencio

*** ...............***

Mi appunto le dichiarazioni delle CryptoAPI per .NET (alle quali ne ho aggiunto di mancanti per i miei scopi) e qualche altra funzione di utilità;
faccio presente che il tutto è ancora in fase di sviluppo e che quindi il codice non è testato a dovere.

[ATTACH=CONFIG]Win32.cs - Dichiarazioni CryptoAPI per .NET [/ATTACH]

codice:
//Funzione per ricavare l'elenco delle Delta CRL a partire dalla CRL di Base.
//Anche se ritorna una lista, almeno nel mio caso, mi aspetto che sia una sola.
 private static IList<string> GetListUrlDeltaCrl(Win32.CRL_INFO stCrlInfo)
{
    X509Extension deltaCrlExtension = null;

    IntPtr rgFoundExtension = Win32.CertFindExtension(Win32.szOID_CRL_CRL_EXTENSION, stCrlInfo.cExtension, stCrlInfo.rgExtension);

    if (rgFoundExtension != IntPtr.Zero)
    {
        Win32.CERT_EXTENSION stCrlExtx = (Win32.CERT_EXTENSION)Marshal.PtrToStructure(rgFoundExtension, typeof(Win32.CERT_EXTENSION));

      if (stCrlExtx.Value.pbData != IntPtr.Zero)
      {
          byte[] rawData = new byte[stCrlExtx.Value.cbData];
          Marshal.Copy(stCrlExtx.Value.pbData, rawData, 0, rawData.Length);
          deltaCrlExtension = new X509Extension(stCrlExtx.pszObjId, rawData, stCrlExtx.fCritical);
       }
    }

    if (deltaCrlExtension == null)
    {
     return null;
   }
   return GetCrlUrlFromExtension(deltaCrlExtension);
 }

/*--------------------------------------------------------------------------------------*/

//Ritorna elenco URL a partire dall'estensione CRL
 private static string GetCrlUrlFromExtension(X509Extension CrlDpExt)
 {
    IList<string> items = null;

    if (CrlDpExt != null)
    {
        var ear1 = CrlDpExt.Format(true).Split(Environment.NewLine.ToArray());
        if (ear1 != null)
        {
            var ear2 = ear1.Where(e => e.Trim().StartsWith("URL=")).ToList();
            if (ear2 != null)
                items = ear2.Select(e => e.Trim().Replace("URL=", "")).ToList();
        }
    }

    return items;
  }  
}

/*--------------------------------------------------------------------------------------*/

//Verifica se il certificato è presente nella CRL.
//In alternativa si può usare la funzione reperibile dal  blog di Alejandro Campos Magencio
public static bool CheckCertificateRevocation(X509Certificate2 cert, byte[] crl)
{
    IntPtr crlcontext = Win32.CertCreateCRLContext(Win32.X509_OR_PKCS_7_ASN_ENCODING,crl, crl.Length);
    byte[] bb = cert.GetRawCertData();
    IntPtr certContext = Win32.CertCreateCertificateContext(Win32.X509_OR_PKCS_7_ASN_ENCODING, bb, bb.Length);

    IntPtr pCrlEntry = new IntPtr();
    if (!Win32.CertIsValidCRLForCertificate(certContext, crlcontext, 0, 0))
         return false;
    bool res = Win32.CertFindCertificateInCRL(certContext, crlcontext, 0, IntPtr.Zero, ref pCrlEntry);

    Win32.CertFreeCertificateContext(certContext);
    Win32.CertFreeCRLContext(crlcontext);
    if (!res)
         throw new Exception("Failed to check the CRL");

    if (pCrlEntry != IntPtr.Zero)
       return true; // Certificato revocato ? da testare!
    else
      return false;
 }

*** ...............***

Altro snippet di codice utile che mancava è come ricavare una Win32.CRL_INFO a partire da un array di byte (che è l'array scaricato dall'url della CRL);
codice:
//Snippet di codice indicativo da completare
byte[] crl;
crl = DownloadCrl(UrlCrl); //ToDo : per esempio con WebClient

IntPtr pvContext = Win32.CertCreateCRLContext(Win32.X509_OR_PKCS_7_ASN_ENCODING, crl, crl.Length);

Win32.CRL_CONTEXT stCrlContext = Win32.CRL_CONTEXT)Marshal.PtrToStructure(pvContext, typeof(Win32.CRL_CONTEXT));

Win32.CRL_INFO stCrlInfo = (Win32.CRL_INFO)Marshal.PtrToStructure(stCrlContext.pCrlInfo, typeof(Win32.CRL_INFO));

// ..... lavoro da eseguire

//alla fine bisogna rilasciare il context creato
Win32.CertFreeCRLContext(pvContext);
Con questo finisce ( ... spero ) l' argomento.

Direi che con un minimo di refactoring sulle funzioni presentate, ed a meno della logica di gestione della Custom Cache che non ho qui riportato , si può mettere su qualcosa di accettabile.

Ho fatto qualche test e sembra funzionare ...ma mi manca la controprova;
ovvero non ho al momento un certificato revocato con una CRL (esposta con protocollo http ) che sia raggiungibile per verificare che venga effettivamente riconosciuto come revocato;
... anzi se qualcuno volesse fornirmelo avrebbe tutta la mia gratitudine.

Anteprime allegati File allegati

aggiornamento da 04-08-2012 a 09:02 di sspintux

Categorie
Programmazione , Microsoft , Tecnologia

Commenti