diff --git a/Changelog.md b/Changelog.md
new file mode 100644
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,4 @@
+### 0.1.0.0
+
+- Initial version.
+
diff --git a/Data/X509/AIA.hs b/Data/X509/AIA.hs
new file mode 100644
--- /dev/null
+++ b/Data/X509/AIA.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE RecordWildCards, PatternSynonyms #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Data.X509.AIA
+-- Copyright   :  (c) Alexey Radkov 2024
+-- License     :  BSD-style
+--
+-- Maintainer  :  alexey.radkov@gmail.com
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- Encode and decode X509 Authority Information Access extension.
+--
+-- This module complies with /rfc5280/.
+-----------------------------------------------------------------------------
+
+module Data.X509.AIA (ExtAuthorityInfoAccess (..)
+                     ,AuthorityInfoAccess (..)
+                     ,AIAMethod (..)
+                     ) where
+
+import Data.X509
+import Data.ASN1.Types
+import Data.ByteString (ByteString)
+
+pattern OidAIA :: [Integer]
+pattern OidAIA = [1, 3, 6, 1, 5, 5, 7, 1, 1]
+
+pattern OidOCSP :: [Integer]
+pattern OidOCSP = [1, 3, 6, 1, 5, 5, 7, 48, 1]
+
+pattern OidCAIssuers :: [Integer]
+pattern OidCAIssuers = [1, 3, 6, 1, 5, 5, 7, 48, 2]
+
+-- | Authority Info Access extension.
+newtype ExtAuthorityInfoAccess = ExtAuthorityInfoAccess [AuthorityInfoAccess]
+    deriving (Show, Eq)
+
+-- | Authority Info Access description.
+data AuthorityInfoAccess = AuthorityInfoAccess { aiaMethod :: AIAMethod
+                                               , aiaLocation :: ByteString
+                                               } deriving (Show, Eq)
+
+-- | Method of Authority Info Access (/OCSP/ or /CA issuers/).
+data AIAMethod = OCSP | CAIssuers deriving (Show, Eq)
+
+instance OIDable AIAMethod where
+    getObjectID OCSP = OidOCSP
+    getObjectID CAIssuers = OidCAIssuers
+
+instance OIDNameable AIAMethod where
+    fromObjectID OidOCSP = Just OCSP
+    fromObjectID OidCAIssuers = Just CAIssuers
+    fromObjectID _ = Nothing
+
+data DecState = DecStart | DecMethod | DecLocation | DecEnd
+
+instance Extension ExtAuthorityInfoAccess where
+    extOID = const OidAIA
+    extHasNestedASN1 = const True
+    extEncode (ExtAuthorityInfoAccess aia) =
+        Start Sequence
+        : concatMap (\AuthorityInfoAccess {..} ->
+                        [ Start Sequence
+                        , OID $ getObjectID aiaMethod
+                        , Other Context 6 aiaLocation
+                        , End Sequence
+                        ]
+                    ) aia
+        ++ [End Sequence]
+    extDecode [Start Sequence, End Sequence] =
+        Right $ ExtAuthorityInfoAccess []
+    extDecode (Start Sequence : encAia) =
+        go DecStart Nothing encAia []
+        where go DecStart Nothing (Start Sequence : next) =
+                  go DecMethod Nothing next
+              go DecMethod Nothing (OID v : next) =
+                  go DecLocation (Just v) next
+              go DecLocation (Just v) (Other Context 6 s : next) =
+                  case fromObjectID v of
+                      Nothing -> const $ Left "bad AIA method"
+                      Just v' -> go DecEnd Nothing next
+                                        . (AuthorityInfoAccess v' s :)
+              go DecEnd Nothing (End Sequence : next@(Start Sequence : _)) =
+                  go DecStart Nothing next
+              go DecEnd Nothing [End Sequence, End Sequence] =
+                  Right . ExtAuthorityInfoAccess . reverse
+              go _ _ _ = const $ Left "bad or incompatible AIA sequence"
+    extDecode _ =
+        Left "bad AIA sequence"
+
diff --git a/Data/X509/OCSP.hs b/Data/X509/OCSP.hs
new file mode 100644
--- /dev/null
+++ b/Data/X509/OCSP.hs
@@ -0,0 +1,241 @@
+{-# LANGUAGE LambdaCase, ViewPatterns, PatternSynonyms #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Data.X509.OCSP
+-- Copyright   :  (c) Alexey Radkov 2024
+-- License     :  BSD-style
+--
+-- Maintainer  :  alexey.radkov@gmail.com
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- Encode and decode X509 OCSP requests and responses.
+--
+-- This module complies with /rfc6960/.
+-----------------------------------------------------------------------------
+
+module Data.X509.OCSP (CertId (..)
+                      ,encodeOCSPRequestASN1
+                      ,encodeOCSPRequest
+                      ,OCSPResponse (..)
+                      ,OCSPResponseStatus (..)
+                      ,OCSPResponsePayload (..)
+                      ,OCSPResponseCertData (..)
+                      ,OCSPResponseCertStatus (..)
+                      ,decodeOCSPResponse
+                      ) where
+
+import Data.X509
+import Data.ASN1.Types
+import Data.ASN1.Encoding
+import Data.ASN1.BinaryEncoding
+import Data.ASN1.Stream
+import Data.ASN1.Error
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as L
+import Data.Int
+import Data.Word
+import Data.Bits
+import Crypto.Hash.SHA1
+import Control.Arrow
+
+pattern OidAlgorithmSHA1 :: [Integer]
+pattern OidAlgorithmSHA1 = [1, 3, 14, 3, 2, 26]
+
+pattern OidBasicOCSPResponse :: [Integer]
+pattern OidBasicOCSPResponse = [1, 3, 6, 1, 5, 5, 7, 48, 1, 1]
+
+derLWidth :: Word8 -> Int64
+derLWidth x | testBit x 7 = succ $ fromIntegral $ x .&. 0x7f
+            | otherwise = 1
+
+issuerDNHash :: Certificate -> ByteString
+issuerDNHash cert = hashlazy $ encodeASN1 DER dn
+    where dn = toASN1 (certIssuerDN cert) []
+
+pubKeyHash :: Certificate -> ByteString
+pubKeyHash cert = hashlazy $ L.drop (succ $ derLWidth $ L.head pk) pk
+    where pk = case toASN1 (certPubKey cert) [] of
+                   Start Sequence
+                     : Start Sequence
+                     : OID _
+                     : _
+                     : End Sequence
+                     : v@(BitString _)
+                     : _ -> L.drop 1 $ encodeASN1 DER $ pure v
+                   _ -> error "bad pubkey sequence"
+
+-- | Certificate Id.
+--
+-- This data is used when building OCSP requests and parsing OCSP responses.
+data CertId = CertId { certIdIssuerNameHash :: ByteString
+                       -- ^ Value of /issuerNameHash/ as defined in /rfc6960/
+                     , certIdIssuerKeyHash :: ByteString
+                       -- ^ Value of /issuerKeyHash/ as defined in /rfc6960/
+                     , certIdSerialNumber :: Integer
+                       -- ^ Serial number of checked certificate
+                     } deriving (Show, Eq)
+
+-- | Build and encode OCSP request in ASN1 format.
+--
+-- The returned value contains the encoded request and an object of type
+-- 'CertId' with hashes calculated by /SHA1/ algorithm.
+encodeOCSPRequestASN1
+    :: Certificate              -- ^ Issuer certificate
+    -> Certificate              -- ^ Checked certificate
+    -> ([ASN1], CertId)
+encodeOCSPRequestASN1 issuerCert cert =
+    let h1 = issuerDNHash cert
+        h2 = pubKeyHash issuerCert
+        sn = certSerial cert
+    in ( [ Start Sequence
+         , Start Sequence
+         , Start Sequence
+         , Start Sequence
+         , Start Sequence
+         , Start Sequence
+         , OID OidAlgorithmSHA1
+         , Null
+         , End Sequence
+         , OctetString h1
+         , OctetString h2
+         , IntVal sn
+         , End Sequence
+         , End Sequence
+         , End Sequence
+         , End Sequence
+         , End Sequence
+         ]
+       , CertId h1 h2 sn
+       )
+
+-- | Build and encode OCSP request in ASN1 DER format.
+--
+-- The returned value contains the encoded request and an object of type
+-- 'CertId' with hashes calculated by /SHA1/ algorithm.
+encodeOCSPRequest
+    :: Certificate              -- ^ Issuer certificate
+    -> Certificate              -- ^ Checked certificate
+    -> (L.ByteString, CertId)
+encodeOCSPRequest = (first (encodeASN1 DER) .) . encodeOCSPRequestASN1
+
+-- | OCSP response data.
+data OCSPResponse =
+    OCSPResponse { ocspRespStatus :: OCSPResponseStatus
+                   -- ^ Response status
+                 , ocspRespPayload :: Maybe OCSPResponsePayload
+                   -- ^ Response payload data
+                 } deriving (Show, Eq)
+
+-- | Status of OCSP response as defined in /rfc6960/.
+data OCSPResponseStatus = OCSPRespSuccessful
+                        | OCSPRespMalformedRequest
+                        | OCSPRespInternalError
+                        | OCSPRespUnused1
+                        | OCSPRespTryLater
+                        | OCSPRespSigRequired
+                        | OCSPRespUnauthorized
+                        deriving (Show, Eq, Bounded, Enum)
+
+-- | OCSP response payload data.
+data OCSPResponsePayload =
+    OCSPResponsePayload { ocspRespCertData :: OCSPResponseCertData
+                          -- ^ Checked certificate data
+                        , ocspRespData :: [ASN1]
+                          -- ^ Whole response payload
+                        } deriving (Show, Eq)
+
+-- | OCSP response certificate data.
+data OCSPResponseCertData =
+    OCSPResponseCertData { ocspRespCertStatus :: OCSPResponseCertStatus
+                           -- ^ Status of checked certificate
+                         , ocspRespCertThisUpdate :: ASN1
+                           -- ^ Value of /thisUpdate/ as defined in /rfc6960/
+                         , ocspRespCertNextUpdate :: Maybe ASN1
+                           -- ^ Value of /nextUpdate/ as defined in /rfc6960/
+                         } deriving (Show, Eq)
+
+-- | Status of the checked certificate as defined in /rfc6960/.
+data OCSPResponseCertStatus = OCSPRespCertGood
+                            | OCSPRespCertRevoked
+                            | OCSPRespCertUnknown
+                            deriving (Show, Eq, Bounded, Enum)
+
+-- | Decode OCSP response.
+--
+-- Value of the /certificate id/ is expected to be equal to what was returned
+-- by 'encodeOCSPRequest': it is used to check the correctness of the response.
+--
+-- /Left/ value gets returned on parse errors detected by 'decodeASN1'.
+-- /Right/ value with /Nothing/ gets returned on unexpected ASN1 contents.
+decodeOCSPResponse
+    :: CertId                   -- ^ Certificate Id
+    -> L.ByteString             -- ^ OCSP response
+    -> Either ASN1Error (Maybe OCSPResponse)
+decodeOCSPResponse certId resp = decodeASN1 DER resp >>= \case
+    [ Start Sequence
+      , Enumerated (toEnum . fromIntegral -> v)
+      , End Sequence
+      ] -> return $ Just $ OCSPResponse v Nothing
+    [ Start Sequence
+      , Enumerated (toEnum . fromIntegral -> v)
+      , Start (Container Context 0)
+      , Start Sequence
+      , OID OidBasicOCSPResponse
+      , OctetString resp'
+      , End Sequence
+      , End (Container Context 0)
+      , End Sequence
+      ] -> do
+          pl <- decodeASN1 DER $ L.fromStrict resp'
+          return $
+              case pl of
+                  Start Sequence
+                    : Start Sequence
+                    : Start (Container Context ctx)
+                    : c1 | ctx `elem` [0..2] ->
+                        let skipVersion =
+                                if ctx == 0
+                                    then drop 1 . skipCurrentContainer
+                                    else id
+                        in Just $ getCurrentContainerContents $
+                               drop 2 $ skipCurrentContainer $ skipVersion c1
+                  _ -> Nothing
+              >>= \case
+                      Start Sequence
+                        : Start Sequence
+                        : Start Sequence
+                        : OID _
+                        : _
+                        : End Sequence
+                        : OctetString h1
+                        : OctetString h2
+                        : IntVal sn
+                        : End Sequence
+                        : c2 | CertId h1 h2 sn == certId ->
+                            case c2 of
+                                Other Context (toEnum -> n) _
+                                  : c3 -> Just (n, c3)
+                                Start (Container Context (toEnum -> n))
+                                  : c3 -> Just (n, skipCurrentContainer c3)
+                                _ -> Nothing
+                      _ -> Nothing
+              >>= \(n, tc1) -> case tc1 of
+                                   tu@(ASN1Time TimeGeneralized _ _)
+                                     : c4 -> Just (n, tu, c4)
+                                   _ -> Nothing
+              >>= \(st, tu, tc2) ->
+                  let nu = case tc2 of
+                               Start (Container Context 0)
+                                 : t@(ASN1Time TimeGeneralized _ _)
+                                 : End (Container Context 0)
+                                 : _ -> Just t
+                               _ -> Nothing
+                  in Just $ OCSPResponse v $
+                         Just $ OCSPResponsePayload
+                             (OCSPResponseCertData st tu nu) pl
+    _ -> return Nothing
+    where getCurrentContainerContents = fst . getConstructedEnd 0
+          skipCurrentContainer = snd . getConstructedEnd 0
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2024, Alexey Radkov. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,89 @@
+Basic X509 OCSP implementation in Haskell
+=========================================
+
+[![Build Status](https://github.com/lyokha/x509-ocsp/workflows/CI/badge.svg)](https://github.com/lyokha/x509-ocsp/actions?query=workflow%3ACI)
+[![Hackage](https://img.shields.io/hackage/v/x509-ocsp.svg?label=hackage%20%7C%20x509-ocsp&logo=haskell&logoColor=%239580D1)](https://hackage.haskell.org/package/x509-ocsp)
+
+This module helps building OCSP requests and parse OCSP responses in Haskell.
+
+There are many scenarios of OCSP use. One of the simplest practical use cases
+for clients is sending OCSP requests to the OCSP responder upon validation of
+the server certificate during the TLS handshake. See the basic implementation
+of this scenario in directory [*client-ocsp*](test/client-ocsp). To test this
+scenario, run
+
+```ShellSession
+$ cd test/client-ocsp
+$ cabal build
+$ nginx -c /path/to/x509-ocsp/test/client-ocsp/nginx.conf
+```
+
+You may need to make the root certificate trusted by the system before running
+Nginx. Below is how to do this in *Fedora*.
+
+```ShellSession
+$ sudo trust anchor --store ../data/certs/root/rootCA.crt
+$ sudo update-ca-trust
+```
+
+Now let's create a database file *index.txt* (its path must be equal to what
+written in clause *database* in file *../data/certs/openssl.cnf*),
+
+```ShellSession
+$ touch ../data/index.txt
+```
+
+and put there the server certificate.
+
+```ShellSession
+$ openssl ca -valid ../data/certs/server/server.crt -keyfile ../data/certs/root/rootCA.key -cert ../data/certs/root/rootCA.crt -config ../data/certs/openssl.cnf
+```
+
+The certificate has *good* status because we used option *-valid*. Run OpenSSL
+OCSP responder in a separate terminal window to see what it will print out.
+
+```ShellSession
+$ openssl ocsp -index ../data/index.txt -port 8081 -rsigner ../data/certs/root/rootCA.crt -rkey ../data/certs/root/rootCA.key -CA ../data/certs/root/rootCA.crt -text
+```
+
+Run the test. It should print *Response: In backend 8010*.
+
+```ShellSession
+$ cabal run
+Response: In backend 8010
+```
+
+The output of the OCSP responder must contain details of the request and the
+response.
+
+Let's revoke the certificate.
+
+```ShellSession
+$ openssl ca -revoke ../data/certs/server/server.crt -keyfile ../data/certs/root/rootCA.key -cert ../data/certs/root/rootCA.crt -config ../data/certs/openssl.cnf
+```
+
+Restart the OCSP responder and look at what *cabal run* will print (you may also
+run *cabal run* twice instead of restarting the responder).
+
+```ShellSession
+$ cabal run
+client-ocsp: HttpExceptionRequest Request {
+  host                 = "localhost"
+  port                 = 8010
+  secure               = True
+  requestHeaders       = []
+  path                 = "/"
+  queryString          = ""
+  method               = "GET"
+  proxy                = Nothing
+  rawBody              = False
+  redirectCount        = 10
+  responseTimeout      = ResponseTimeoutDefault
+  requestVersion       = HTTP/1.1
+  proxySecureMode      = ProxySecureWithConnect
+}
+ (InternalException (HandshakeFailed (Error_Protocol "certificate rejected: [CacheSaysNo \"OCSP: bad certificate status OCSPRespCertRevoked\"]" CertificateUnknown)))
+```
+
+ The certificate has been revoked and the handshake fails.
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+import Distribution.Simple
+main = defaultMain
+
diff --git a/test/data/certs/root/rootCA.crt b/test/data/certs/root/rootCA.crt
new file mode 100644
--- /dev/null
+++ b/test/data/certs/root/rootCA.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEETCCAvmgAwIBAgIUQEy4pp/hZGsF+9Yi5Nkz7A6IOxswDQYJKoZIhvcNAQEL
+BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MRIwEAYDVQQLDAlP
+Q1NQIFRlc3QxGDAWBgNVBAMMD015IENvbXBhbnkgUm9vdDEYMBYGCgmSJomT8ixk
+AQEMCHRlc3RPQ1NQMB4XDTI0MDIwNTExMDU0OVoXDTI1MDIwNDExMDU0OVowgZcx
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g
+RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MRIwEAYDVQQLDAlPQ1NQIFRl
+c3QxGDAWBgNVBAMMD015IENvbXBhbnkgUm9vdDEYMBYGCgmSJomT8ixkAQEMCHRl
+c3RPQ1NQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5xat2GzvwJsD
+xI4wjKocsTjemx6wLR5bc9AWmOOiMcj/5b2OQp/0ng82GiluAsMECxBh1Rs2SPTY
+GvCWdBQRF4ohKhKafmZAbjy/wIHEn/xC9dN6HxNRaOqB2Mt9WQKz8j/e2JxlQb9Q
+JBpDWNMBToqleEv3TCajNat8Fal2DA5nXgenFhSj9WhJHHVfFeXwH3OAMsVLPmCb
+fq3H9sSqCAJU4TRsgB/Wwt/OGSm2p74A5QlRuNkgviedmEuS4RfqtLS/gzQTObNg
+kjT0xSN374sloKAYlTiQAYKuWtjpnJjkxmQJcDGEAzLDesYwmy0bC85MGov3ebfj
+hYc2GTClawIDAQABo1MwUTAdBgNVHQ4EFgQUloJQHAxQscJA5gjKYv4f/gPGkxow
+HwYDVR0jBBgwFoAUloJQHAxQscJA5gjKYv4f/gPGkxowDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAATDeVc18pvx0OLv+hItQ21izepga63zOi/B2
+9Cb+UrZvmJWajlWXqy6azntl9MXei3SSH6ojjlpbaZkKk4oVXVyyj2R9UT3GYlYM
+olTD43+DSWMXWfbwOqnxgORYCnp8LF4Wx8QbcMhyuqceuvaV6/GyyjCjWCbU7m8a
+13Je1+PnuG9d/iqhIilvVVNnyF2zo0jrsCeVL26xiQo8AEWLw9MN2m7hlAGLS4Ht
+2wlJWP1Lr3+Ib5S4WGE3jYL1onQUhiP/eklCnAE9ynb44sxSwq9OsQ0KCNGO11ZP
+i+pHOm0GnHYDZNvlsj4ODn/RfvnnzafYHldr2QKACqQRM/fzBA==
+-----END CERTIFICATE-----
diff --git a/test/data/certs/server-chain.crt b/test/data/certs/server-chain.crt
new file mode 100644
--- /dev/null
+++ b/test/data/certs/server-chain.crt
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIESjCCAzKgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMCVVMx
+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzAR
+BgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAsMCU9DU1AgVGVzdDEYMBYGA1UEAwwP
+TXkgQ29tcGFueSBSb290MRgwFgYKCZImiZPyLGQBAQwIdGVzdE9DU1AwHhcNMjQw
+MjA1MTEwNzA2WhcNMjUwMjA0MTEwNzA2WjCBkTELMAkGA1UEBhMCVVMxEzARBgNV
+BAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoM
+Ck15IENvbXBhbnkxEjAQBgNVBAsMCU9DU1AgVGVzdDESMBAGA1UEAwwJbG9jYWxo
+b3N0MRgwFgYKCZImiZPyLGQBAQwIdGVzdE9DU1AwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCK4oJ61Uflu4i9GjO51NG6RFFZl8Ckcq9guQcyGJqCv5hU
+ab4cArdH05VifDdHNydMbuGmkShKfju8gKfr/zr7V05WufhORv7MgX6UNtHsLz69
+BIcC64biygYI0W7Jn+87W1aHb7I0mYcP3aEgez0CfZePDB34WCA25DwSdd5Qw81a
+ATrVFHsQnK040Ykbahd6a9Wiiq+SiTE+A3u4ctStyidxGa+acGI/vPfB27nQaJXo
+Qz93tIP3z9v4aqAN/15izYA82NXlSH+59H1WBclKP5yz2sWUTYXmPRKC6oWgaziN
+cnCsWhDf5TSns9z4TwC8svhJjSMFPEf6h/4mONfdAgMBAAGjgaQwgaEwHwYDVR0j
+BBgwFoAUloJQHAxQscJA5gjKYv4f/gPGkxowCQYDVR0TBAIwADALBgNVHQ8EBAMC
+BPAwMQYIKwYBBQUHAQEEJTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0
+OjgwODEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQ+gjDVz1BTpKDW
+qm4mWOpMIFCneTANBgkqhkiG9w0BAQsFAAOCAQEALM7G5YUK+8KqBL6iSKmHi0LM
+tpTCqHSKy/B2VkAyo0/gI1tBgO+gO0kVnBMggpbU0DxUUWeTZg1zOxkCmvMB0fmu
+a4MpSc7jw6hot1gAhpGw4ZF4opDAA/qyEQpuWQwrBEw2RMl/01+nDFkSfliqOlGd
+A7ELipZc8uvl4i64Pzpw/QS6EyukVE/I4r4GGwHH0xHWDnFm0+x1vp5sGB9v0Dzb
+/119xBn/CYAMXY4lMt0TaF240KDwi4WO6/V0bAzmW/esWVthFSm5f0TSWBUbcxpO
+8geg4XDYGO6YaJB4Vv3odcTy3nCYwhT+60LNr8gTaB41nDjk3ZJtM16Upn4OXQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEETCCAvmgAwIBAgIUQEy4pp/hZGsF+9Yi5Nkz7A6IOxswDQYJKoZIhvcNAQEL
+BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MRIwEAYDVQQLDAlP
+Q1NQIFRlc3QxGDAWBgNVBAMMD015IENvbXBhbnkgUm9vdDEYMBYGCgmSJomT8ixk
+AQEMCHRlc3RPQ1NQMB4XDTI0MDIwNTExMDU0OVoXDTI1MDIwNDExMDU0OVowgZcx
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g
+RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MRIwEAYDVQQLDAlPQ1NQIFRl
+c3QxGDAWBgNVBAMMD015IENvbXBhbnkgUm9vdDEYMBYGCgmSJomT8ixkAQEMCHRl
+c3RPQ1NQMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5xat2GzvwJsD
+xI4wjKocsTjemx6wLR5bc9AWmOOiMcj/5b2OQp/0ng82GiluAsMECxBh1Rs2SPTY
+GvCWdBQRF4ohKhKafmZAbjy/wIHEn/xC9dN6HxNRaOqB2Mt9WQKz8j/e2JxlQb9Q
+JBpDWNMBToqleEv3TCajNat8Fal2DA5nXgenFhSj9WhJHHVfFeXwH3OAMsVLPmCb
+fq3H9sSqCAJU4TRsgB/Wwt/OGSm2p74A5QlRuNkgviedmEuS4RfqtLS/gzQTObNg
+kjT0xSN374sloKAYlTiQAYKuWtjpnJjkxmQJcDGEAzLDesYwmy0bC85MGov3ebfj
+hYc2GTClawIDAQABo1MwUTAdBgNVHQ4EFgQUloJQHAxQscJA5gjKYv4f/gPGkxow
+HwYDVR0jBBgwFoAUloJQHAxQscJA5gjKYv4f/gPGkxowDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAATDeVc18pvx0OLv+hItQ21izepga63zOi/B2
+9Cb+UrZvmJWajlWXqy6azntl9MXei3SSH6ojjlpbaZkKk4oVXVyyj2R9UT3GYlYM
+olTD43+DSWMXWfbwOqnxgORYCnp8LF4Wx8QbcMhyuqceuvaV6/GyyjCjWCbU7m8a
+13Je1+PnuG9d/iqhIilvVVNnyF2zo0jrsCeVL26xiQo8AEWLw9MN2m7hlAGLS4Ht
+2wlJWP1Lr3+Ib5S4WGE3jYL1onQUhiP/eklCnAE9ynb44sxSwq9OsQ0KCNGO11ZP
+i+pHOm0GnHYDZNvlsj4ODn/RfvnnzafYHldr2QKACqQRM/fzBA==
+-----END CERTIFICATE-----
diff --git a/test/data/certs/server/server.crt b/test/data/certs/server/server.crt
new file mode 100644
--- /dev/null
+++ b/test/data/certs/server/server.crt
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIESjCCAzKgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMCVVMx
+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzAR
+BgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAsMCU9DU1AgVGVzdDEYMBYGA1UEAwwP
+TXkgQ29tcGFueSBSb290MRgwFgYKCZImiZPyLGQBAQwIdGVzdE9DU1AwHhcNMjQw
+MjA1MTEwNzA2WhcNMjUwMjA0MTEwNzA2WjCBkTELMAkGA1UEBhMCVVMxEzARBgNV
+BAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoM
+Ck15IENvbXBhbnkxEjAQBgNVBAsMCU9DU1AgVGVzdDESMBAGA1UEAwwJbG9jYWxo
+b3N0MRgwFgYKCZImiZPyLGQBAQwIdGVzdE9DU1AwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCK4oJ61Uflu4i9GjO51NG6RFFZl8Ckcq9guQcyGJqCv5hU
+ab4cArdH05VifDdHNydMbuGmkShKfju8gKfr/zr7V05WufhORv7MgX6UNtHsLz69
+BIcC64biygYI0W7Jn+87W1aHb7I0mYcP3aEgez0CfZePDB34WCA25DwSdd5Qw81a
+ATrVFHsQnK040Ykbahd6a9Wiiq+SiTE+A3u4ctStyidxGa+acGI/vPfB27nQaJXo
+Qz93tIP3z9v4aqAN/15izYA82NXlSH+59H1WBclKP5yz2sWUTYXmPRKC6oWgaziN
+cnCsWhDf5TSns9z4TwC8svhJjSMFPEf6h/4mONfdAgMBAAGjgaQwgaEwHwYDVR0j
+BBgwFoAUloJQHAxQscJA5gjKYv4f/gPGkxowCQYDVR0TBAIwADALBgNVHQ8EBAMC
+BPAwMQYIKwYBBQUHAQEEJTAjMCEGCCsGAQUFBzABhhVodHRwOi8vbG9jYWxob3N0
+OjgwODEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQ+gjDVz1BTpKDW
+qm4mWOpMIFCneTANBgkqhkiG9w0BAQsFAAOCAQEALM7G5YUK+8KqBL6iSKmHi0LM
+tpTCqHSKy/B2VkAyo0/gI1tBgO+gO0kVnBMggpbU0DxUUWeTZg1zOxkCmvMB0fmu
+a4MpSc7jw6hot1gAhpGw4ZF4opDAA/qyEQpuWQwrBEw2RMl/01+nDFkSfliqOlGd
+A7ELipZc8uvl4i64Pzpw/QS6EyukVE/I4r4GGwHH0xHWDnFm0+x1vp5sGB9v0Dzb
+/119xBn/CYAMXY4lMt0TaF240KDwi4WO6/V0bAzmW/esWVthFSm5f0TSWBUbcxpO
+8geg4XDYGO6YaJB4Vv3odcTy3nCYwhT+60LNr8gTaB41nDjk3ZJtM16Upn4OXQ==
+-----END CERTIFICATE-----
diff --git a/test/data/req.der b/test/data/req.der
new file mode 100644
Binary files /dev/null and b/test/data/req.der differ
diff --git a/test/data/resp.der b/test/data/resp.der
new file mode 100644
Binary files /dev/null and b/test/data/resp.der differ
diff --git a/test/test-ocsp.hs b/test/test-ocsp.hs
new file mode 100644
--- /dev/null
+++ b/test/test-ocsp.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Data.X509.AIA
+import Data.X509.OCSP
+import Data.X509
+import Data.PEM
+import Test.HUnit
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import Data.ASN1.Types
+import Data.ASN1.Encoding
+import Data.ASN1.BinaryEncoding
+import Data.List (uncons)
+import Control.Monad
+ 
+toCertificate :: ByteString -> Certificate
+toCertificate cert = either error getCertificate $ do
+    pem <- pemParseBS cert >>= maybe (Left "no pems") (pure . fst) . uncons
+    decodeSignedObject $ pemContent pem
+ 
+testAIA :: Certificate -> Test
+testAIA cert = TestCase $ extensionGet (certExtensions cert) @?= expected
+    where expected = Just $
+              ExtAuthorityInfoAccess
+                  [AuthorityInfoAccess { aiaMethod = OCSP
+                                       , aiaLocation = "http://localhost:8081"
+                                       }
+                  ]
+ 
+testOCSPRequestASN1 :: Certificate -> Certificate -> [ASN1] -> Test
+testOCSPRequestASN1 issuerCert cert = TestCase . (buildRequest @?=)
+    where buildRequest = fst $ encodeOCSPRequestASN1 issuerCert cert
+ 
+testOCSPRequest :: Certificate -> Certificate -> ByteString -> Test
+testOCSPRequest issuerCert cert = TestCase . (buildRequest @?=) . L.fromStrict
+    where buildRequest = fst $ encodeOCSPRequest issuerCert cert
+ 
+testOCSPResponse :: CertId -> ByteString -> Test
+testOCSPResponse certId resp =
+    TestCase $ getRespStatus @?= Just OCSPRespCertGood
+    where getRespStatus = either (const Nothing)
+              (fmap ocspRespPayload >=>
+                  fmap (ocspRespCertStatus . ocspRespCertData)
+              ) $ decodeOCSPResponse certId $ L.fromStrict resp
+
+main :: IO ()
+main = do
+    certS <- toCertificate <$> B.readFile "test/data/certs/server/server.crt"
+    certR <- toCertificate <$> B.readFile "test/data/certs/root/rootCA.crt"
+
+    let certId = snd $ encodeOCSPRequestASN1 certR certS
+
+    reqDer <- B.readFile "test/data/req.der"
+    let req = either (error . show) id $ decodeASN1 DER $ L.fromStrict reqDer
+
+    respDer <- B.readFile "test/data/resp.der"
+
+    runTestTTAndExit $ TestList
+        [TestLabel "testAIA"             $ testAIA certS
+        ,TestLabel "testOCSPRequestASN1" $ testOCSPRequestASN1 certR certS req
+        ,TestLabel "testOCSPRequest"     $ testOCSPRequest certR certS reqDer
+        ,TestLabel "testOCSPResponse"    $ testOCSPResponse certId respDer
+        ]
+
diff --git a/x509-ocsp.cabal b/x509-ocsp.cabal
new file mode 100644
--- /dev/null
+++ b/x509-ocsp.cabal
@@ -0,0 +1,54 @@
+cabal-version:          2.4
+
+name:                   x509-ocsp
+version:                0.1.0.0
+synopsis:               Basic X509 OCSP implementation
+description:            Build X509 OCSP requests and parse responses
+homepage:               https://github.com/lyokha/x509-ocsp
+license:                BSD-3-Clause
+license-file:           LICENSE
+extra-doc-files:        README.md, Changelog.md
+data-files:             test/data/**/*.crt test/data/*.der
+author:                 Alexey Radkov <alexey.radkov@gmail.com>
+maintainer:             Alexey Radkov <alexey.radkov@gmail.com>
+stability:              experimental
+copyright:              2024 Alexey Radkov
+category:               Data
+build-type:             Simple
+
+source-repository head
+  type:                 git
+  location:             https://github.com/lyokha/x509-ocsp.git
+
+library
+  default-language:     Haskell2010
+  build-depends:        base >= 4.8 && < 5
+                      , bytestring
+                      , crypton-x509
+                      , asn1-encoding
+                      , asn1-types
+                      , cryptohash-sha1
+
+  exposed-modules:      Data.X509.AIA
+                      , Data.X509.OCSP
+
+  ghc-options:         -Wall
+
+test-suite test-ocsp
+  default-language:     Haskell2010
+  build-depends:        base >= 4.8 && < 5
+                      , HUnit >= 1.6.1.0
+                      , bytestring
+                      , crypton-x509
+                      , asn1-encoding
+                      , asn1-types
+                      , pem
+                      , x509-ocsp
+
+  type:                 exitcode-stdio-1.0
+
+  main-is:              test-ocsp.hs
+  hs-source-dirs:       test
+
+  ghc-options:         -Wall
+
