c ++ - Hvad er den rigtige måde at verificere et SSL-certifikat i Win32 på?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg vil gerne bekræfte et SSL-certifikat i Win32 ved hjælp af C ++. Jeg tror, ​​jeg vil bruge Cert * API'en, så jeg kan få gavn af Windows-certifikatbutikken. Dette er hvad jeg har kommet med.



  • Er det korrekt?

  • Er der en bedre måde at gøre dette på?

  • Gør jeg noget galt?



bool IsValidSSLCertificate( PCCERT\_CONTEXT certificate, LPWSTR serverName )
{
    LPTSTR usages[] = { szOID\_PKIX\_KP\_SERVER\_AUTH };

    CERT\_CHAIN\_PARA params                           = { sizeof( params ) };
    params.RequestedUsage.dwType                     = USAGE\_MATCH\_TYPE\_AND;
    params.RequestedUsage.Usage.cUsageIdentifier     = \_countof( usages );
    params.RequestedUsage.Usage.rgpszUsageIdentifier = usages;

    PCCERT\_CHAIN\_CONTEXT chainContext = 0;

    if ( !CertGetCertificateChain( NULL,
                                   certificate,
                                   NULL,
                                   NULL,
                                   &params,
                                   CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN,
                                   NULL,
                                   &chainContext ) )
    {
        return false;
    }

    SSL\_EXTRA\_CERT\_CHAIN\_POLICY\_PARA sslPolicy = { sizeof( sslPolicy ) };
    sslPolicy.dwAuthType                       = AUTHTYPE\_SERVER;
    sslPolicy.pwszServerName                   = serverName;

    CERT\_CHAIN\_POLICY\_PARA policy = { sizeof( policy ) };
    policy.pvExtraPolicyPara      = &sslPolicy;

    CERT\_CHAIN\_POLICY\_STATUS status = { sizeof( status ) };

    BOOL verified = CertVerifyCertificateChainPolicy( CERT\_CHAIN\_POLICY\_SSL,
                                                      chainContext,
                                                      &policy,
                                                      &status );

    CertFreeCertificateChain( chainContext );
    return verified && status.dwError == 0;
}

Bedste reference


Du skal være opmærksom på RFC3280 afsnit 6.1 og RFC5280 afsnit 6.1. Begge beskriver algoritmer til validering af certifikatbaner. Selvom Win32 API tager sig af nogle ting for dig, kan det stadig være værdifuldt at vide om processen generelt. [18] [19]


Også her er en (efter min mening) ret troværdig reference: Kromcertifikatverifikationskode. [20]


Alt i alt tror jeg, at din kode ikke er forkert. Men her er et par ting, jeg ville se på/ændre, hvis jeg var dig:


1. Separat Fælles Navn Validering



Chrom validerer certifikat-fællesnavn separat fra kæden. Tilsyneladende har de bemærket nogle problemer med det. Se kommentarerne til deres begrundelse:


cert\_verify\_proc.win.cc:731 // Certificate name validation happens separately, later, using an internal
cert\_verify\_proc.win.cc:732 // routine that has better support for RFC 6125 name matching.


2. Brug CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT



Chrom bruger også flag CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT i stedet for CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN. Jeg begyndte faktisk at undersøge dette, før jeg fandt deres kode, og det forstærkede min tro på, at du skulle bruge CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT.


Selvom begge ovennævnte RFC'er angiver, at et selvsigneret tillidsanker ikke betragtes som en del af en kæde, er dokumentationen for CertGetCertificateChain (http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v= vs.85) .aspx) siger, at den opbygger en kæde op til, hvis det er muligt, et tillid til rootcertifikat. Et pålideligt rodcertifikat er defineret (på samme side) som et betroet selvtegnet certifikat. [21]


Dette eliminerer muligheden for, at * EXCLUDE\_ROOT kan hoppe over tilbagekaldskontrol af et non-root-trustanker (Win32 kræver faktisk, at tillidsankre skal være selvsignerede, selvom det ikke kræves af RFC'er. Selv om dette ikke er officielt dokumenteret).


Nu, da et root CA-certifikat ikke kan tilbagekalde sig selv (CRL ikke kunne underskrives/verificeret), forekommer det mig, at disse to flag er identiske.


Jeg gjorde nogle googling og snuble over dette forum post: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum=windowssecurity. Et medlem af .NET Product Group (angiveligt) hævder, at flagene i praksis virker ens, hvis roten er selvsigneret (i teorien ville ENTIRE\_CHAIN-flagmet kontrollere rootcertifikatet for tilbagekaldelse, hvis det indeholdt en CDP-udvidelse, men det kan ikke ske). [22]


Han anbefaler også at bruge flagmet * EXCLUDE\_ROOT, fordi det andet flag kan forårsage en unødvendig netværksanmodning, hvis den selvtegnede root CA indeholder CDP-udvidelsen.


Uheldigvis:



  • Jeg kan ikke finde nogen officielt dokumenteret forklaring på forskellene mellem de to flag.

  • Selv om det er sandsynligt, at den linkede diskussion gælder for de samme Win32 API-flag under hooden til .NET, er det ikke garanteret.



For at være helt sikker på, at det er ok at bruge CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT, googled jeg lidt mere og fandt den Chromium SSL certifikat verifikationskode jeg linkede til øverst i mit svar.


Som en ekstra bonus indeholder Chrom cert\_verify\_proc\_win.cc filen følgende tips om IE-verifikationskode:


618: // IE passes a non-NULL pTime argument that specifies the current system
619: // time.  IE passes CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT as the
620: // chain\_flags argument.


Ikke sikker på, hvordan de ville vide dette, men på dette tidspunkt vil jeg føle mig trygge ved at bruge CERT\_CHAIN\_REVOCATION\_CHECK\_EXCLUDE\_ROOT.


3. Forskellige godkendte certifikatbrug



Jeg bemærkede, at Chrom også angiver 3 certifikatbrug i stedet for 1:


szOID\_PKIX\_KP\_SERVER\_AUTH,
szOID\_SERVER\_GATED\_CRYPTO,
szOID\_SGC\_NETSCAPE


Fra hvad jeg kan indsamle via Google, kan de andre anvendelser kræves af ældre webbrowsere, ellers kan de undlade at oprette en sikker forbindelse.


Hvis Chrom anser det for hensigtsmæssigt at inkludere disse anvendelser, vil jeg følge med.


Bemærk, at hvis du ændrer din kode, skal du også indstille params.RequestedUsage.dwType til USAGE\_MATCH\_TYPE\_OR i stedet for USAGE\_MATCH\_TYPE\_AND.


-



Jeg kan ikke tænke på andre kommentarer for øjeblikket. Men hvis jeg var dig, ville jeg tjekke Chromium source selv (og måske også Firefox) - bare for at være sikker på, at jeg ikke har gået glip af noget.

Andre referencer 1


Jeg tror, ​​at det bedste svar afhænger af, hvad du præcist forsøger at gøre.


Jeg vil advare dig om, at SSL er baseret på antagelsen om, at begge endepunkter har en sikker forbindelse. Hvis hver endepunkt ikke er interesseret i at opretholde sikkerheden, er der ingen.


Det er en trivial indsats for at sætte bytekoder i din distribuerede kode, der simpelthen vender tilbage til denne funktion. Derfor flyttede Windows meget validering ind i kernen. Men de forventer ikke, at folk kører vinduer på virtuel hardware, hvilket gør omgåelse af OS næsten lige så trivielt.


Overvej nu, at du forventer at blive forsynet med en cert fra en kilde, men at foregive at den pågældende kilde ikke kunne få de samme oplysninger fra en pålidelig kilde. Og så aflever den til dig. Så du kan ikke stole på certifikater for at 'bevise' nogen er nogen især.


Den eneste beskyttelse, der opnås ved certifikater, er at forhindre udenforstående, ikke endepunkter, fra at krænke fortroligheden af ​​den besked, der transporteres.


Enhver anden brug er dømt til at mislykkes, og det vil i sidste ende mislykkes med potentielt katastrofale resultater.


Undskyld for det store indlæg. Kommentar sektionen har en ordgrænse.

Andre referencer 2


Funktionerne CertGetCertificateChain og CertVerifyCertificatePolicy går sammen. Denne del er korrekt.


For CertGetCertificateChain kan flagget indstilles til en af ​​de følgende tre, hvis du vil tjekke for tilbagekaldelse:



  • CERT\_CHAIN\_REVOCATION\_CHECK\_END\_CERT

  • CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN ​​

  • CERT\_CHAIN\_REVOCATION\_CHECK\_CHAIN\_EXCLUDE\_ROOT.



Kun en af ​​dem kan bruges, disse tre muligheder kan ikke være ORed. Ved siden af ​​et af disse flag kan man overveje, hvordan kæden skal skabes; bruger local cache eller bare CRL eller OCSP. For disse overvejelser læs dette link. [23]


Fejl ved at udføre funktionen eller mere simpelthen, hvis returværdien er 0, betyder det ikke, at certifikatet er ugyldigt, men du kunne ikke udføre operationen. For fejloplysninger brug GetLastError(). Så din logik om at returnere falsk er forkert, det er mere tilfældet med at smide fejlen og lade klientkode beslutte, om du vil prøve igen eller fortsætte med at lave andre ting.


I dette link er der en sektion kaldet 'klassificere fejlen', læs venligst det. I grund og grund skal du tjekke certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT\_TRUST\_STATUS msdn reference. Så her kan du få din forretningslogik. Hvis du f.eks. Finder fejlstanden for værdien (CERT\_TRUST\_REVOCATION\_STATUS\_UNKNOWN | CERT\_TRUST\_IS\_OFFLINE\_REVOCATION), kan denne bekræftelse af certifikatet ikke udføres, du har mulighed for at bestemme, hvad du vil have (lad certifikatet gå eller markere det som ugyldigt). [24]


Så inden du ringer til CertVerifyCertificatePolicy har du mulighed for at afvise eller allerede markere en valideringsfejl.


Hvis du vælger at komme til CertVerifyCertificatePolicy, er kromkoden en vidunderlig reference til, hvordan du maper policy\_status.dwError til din fejlklasse/enum.