OCSP (Online Certificate Status Protocol) is generally used to obtain revocation certificate status from certification authority (CA) as alternative to CRL (Certificate Revocation List). OCSP request is sent to server as HTTP POST request with 2 specific header values “application/ocsp-request” as Content-Type and “application/ocsp-response” as Accept. The example of OCSP request I got from Wireshark looks like:
POST / HTTP/1.1\r\n Content-Type: application/ocsp-request\r\n Accept: application/ocsp-response\r\n Host: ocsps.ssl.com\r\n Content-Length: 83\r\n Expect: 100-continue\r\n Connection: Keep-Alive\r\n \r\n Online Certificate Status Protocol tbsRequest requestList: 1 item Request reqCert hashAlgorithm (SHA-1) Algorithm Id: 1.3.14.3.2.26 (SHA-1) issuerNameHash: d49294be2b4a19852331fe698267be94a9d8d4c5 issuerKeyHash: 26147ee0dcd7a6f7e2d40427df61f1c2ece732ca serialNumber: 0x6aaefb5046e7425ace83e2fa1e145105 |
In binary presentation OCSP part is:
0000 30 4b 30 49 30 09 06 05 2b 0e 03 02 1a 05 00 0 0K0I0…+……. 0010 14 d4 92 94 be 2b 4a 19 85 23 31 fe 69 82 67 be …..+J..#1.i.g. 0020 94 a9 d8 d4 c5 04 14 26 14 7e e0 dc d7 a6 f7 e2 …….&.~…… 0030 d4 04 27 df 61 f1 c2 ec e7 32 ca 02 10 6a ae fb ..’.a….2…j.. 0040 50 46 e7 42 5a ce 83 e2 fa 1e 14 51 05 PF.BZ……Q. |
The code:
using System; using System.Net; using System.Collections; using System.IO; using System.Collections.Generic; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using Org.BouncyCastle.Ocsp; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X509; namespace OCSPRequest { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Please specify ULR as argument!"); return; } Console.WriteLine("Server: {0}", args[0]); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback += OnValidateCertificate; try { HttpWebRequest httpReq = (HttpWebRequest)HttpWebRequest.Create(args[0]); HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse(); httpResp.Close(); } catch (Exception ex) { Console.WriteLine("Exception: {0}", ex.Message); } } public static bool OnValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { Listurls = GetOcspUrl(certificate); if (urls.Count > 0) { Console.WriteLine("OCSP URL: {0}", urls[0]); byte[] ocspRequest = CreateOCSPRequest(certificate); byte[] ocspResponse = PostOCSPRequest(urls[0], ocspRequest); ValidateOCSPResponse(ocspResponse); } return (errors == SslPolicyErrors.None); } private static List GetOcspUrl(X509Certificate cert) { List ocspUrls = new List (); try { // Get OCSP extension System.Security.Cryptography.X509Certificates.X509Extension ocspExtension = null; X509Certificate2 cert2 = new X509Certificate2(cert); for (int index = 0; index < cert2.Extensions.Count; index++) { if (cert2.Extensions[index].Oid.Value == X509Extensions.AuthorityInfoAccess.Id) { ocspExtension = cert2.Extensions[index]; break; } } if (ocspExtension == null) { return null; } Asn1InputStream asn1InputStream = new Asn1InputStream(ocspExtension.RawData); Asn1Object obj = asn1InputStream.ReadObject(); if (obj == null) { return null; } Asn1Sequence s = (Asn1Sequence)obj; IEnumerator elements = s.GetEnumerator(); while (elements.MoveNext()) { Asn1Sequence element = (Asn1Sequence)elements.Current; DerObjectIdentifier oid = (DerObjectIdentifier)element[0]; if (oid.Id.Equals("1.3.6.1.5.5.7.48.1")) // Is Ocsp URL there? { Asn1TaggedObject taggedObject = (Asn1TaggedObject)element[1]; GeneralName gn = (GeneralName)GeneralName.GetInstance(taggedObject); ocspUrls.Add(((DerIA5String)DerIA5String.GetInstance(gn.Name)).GetString()); } } } catch (Exception e) { throw new Exception("Error parsing.", e); } return ocspUrls; } private static byte[] CreateOCSPRequest(X509Certificate cert) { OcspReqGenerator gen = new OcspReqGenerator(); Org.BouncyCastle.X509.X509Certificate bouncyCert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(cert); X509Chain chain = new X509Chain(); chain.Build(new X509Certificate2(cert)); List certList = new List (); foreach (X509ChainElement chainElem in chain.ChainElements) { Org.BouncyCastle.X509.X509Certificate bouncyCertFromChain = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(chainElem.Certificate); if (bouncyCertFromChain.GetTbsCertificate().Length != 0) { if (bouncyCert.SerialNumber.CompareTo(bouncyCertFromChain.SerialNumber) != 0) { certList.Add(bouncyCertFromChain); } } } try { CertificateID certId = new CertificateID(CertificateID.HashSha1, certList[0], bouncyCert.SerialNumber); byte[] IssuerKeyHash = certId.GetIssuerKeyHash(); byte[] IssuerNameHash = certId.GetIssuerNameHash(); byte[] serialNumber = bouncyCert.SerialNumber.ToByteArrayUnsigned(); Console.WriteLine("OCSP request:"); Console.WriteLine("Algorith ID: {0}", CertificateID.HashSha1); Console.WriteLine("User Name Hash: {0}", BitConverter.ToString(IssuerNameHash).Replace("-", "")); Console.WriteLine("User Key Hash: {0}", BitConverter.ToString(IssuerKeyHash).Replace("-", "")); Console.WriteLine("Serial Number: 0x{0}", BitConverter.ToString(serialNumber).Replace("-", "")); gen.AddRequest(certId); OcspReq ocspReq = gen.Generate(); return ocspReq.GetEncoded(); } catch (OcspException e) { Console.WriteLine(e.StackTrace); } catch (IOException e) { Console.WriteLine(e.StackTrace); } return null; } private static byte[] PostOCSPRequest(string url, byte[] data) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/ocsp-request"; request.ContentLength = data.Length; request.Accept = "application/ocsp-response"; Stream stream = request.GetRequestStream(); stream.Write(data, 0, data.Length); stream.Close(); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Console.WriteLine("HTTP response code {0} (POST OCSP request to {1})", (int)response.StatusCode, url); Stream responseStream = response.GetResponseStream(); byte[] buffer = new byte[4096 * 8]; MemoryStream ms = new MemoryStream(); int read = 0; while ((read = responseStream.Read(buffer, 0, buffer.Length)) > 0) { ms.Write(buffer, 0, read); } responseStream.Close(); return ms.ToArray(); } private static int ValidateOCSPResponse(byte[] byteResponse) { OcspResp ocspResponse = new OcspResp(byteResponse); int status = -1; switch (ocspResponse.Status) { case OcspRespStatus.Successful: BasicOcspResp or = (BasicOcspResp)ocspResponse.GetResponseObject(); if (or.Responses.Length == 1) { SingleResp resp = or.Responses[0]; Object certificateStatus = resp.GetCertStatus(); if (certificateStatus == null || certificateStatus == Org.BouncyCastle.Ocsp.CertificateStatus.Good) { status = 0; Console.WriteLine("OCSP Response: Good"); } else if (certificateStatus is Org.BouncyCastle.Ocsp.RevokedStatus) { status = 1; Console.WriteLine("OCSP Response: Revoked"); } else if (certificateStatus is Org.BouncyCastle.Ocsp.UnknownStatus) { status = 999; Console.WriteLine("OCSP Response: Unkown"); } } break; default: Console.WriteLine("Unknow status {0}." + ocspResponse.Status); break; } return status; } } }
Execution examples.
Revoked certificate:
D:\Projects>OCSPRequest.exe https://revoked-rsa-dv.ssl.com Server: https://revoked-rsa-dv.ssl.com OCSP URL http://ocsps.ssl.com OCSP request: Algorith ID: 1.3.14.3.2.26 User Name Hash: D49294BE2B4A19852331FE698267BE94A9D8D4C5 User Key Hash: 26147EE0DCD7A6F7E2D40427DF61F1C2ECE732CA Serial Namber: 0x6AAEFB5046E7425ACE83E2FA1E145105 HTTP response code 200 (POST OCSP request to http://ocsps.ssl.com) OCSP Response: Revoked |
Not revoked certificate:
D:\Projects>OCSPRequest.exe https://ladydebug.com Server: https://ladydebug.com OCSP URL http://ocsp.comodoca.com OCSP request: Algorith ID: 1.3.14.3.2.26 User Name Hash: 93B9FA878A7AEE4BF3FD5A2D574A3451CE84CB7C User Key Hash: 7E035A65416BA77E0AE1B89D08EA1D8E1D6AC765 Serial Namber: 0x364B7FE938E8C35DA613182931919E90 HTTP response code 200 (POST OCSP request to http://ocsp.comodoca.com) OCSP Response: Good |