packages feed

x509-ocsp 0.2.0.0 → 0.3.0.0

raw patch · 6 files changed

+200/−21 lines, 6 filesdep +crypton-x509-validationPVP ok

version bump matches the API change (PVP)

Dependencies added: crypton-x509-validation

API changes (from Hackage documentation)

- Data.X509.OCSP: [ocspRespData] :: OCSPResponsePayload -> [ASN1]
+ Data.X509.OCSP: OCSPResponseVerificationData :: ByteString -> SignatureALG -> ByteString -> [Certificate] -> OCSPResponseVerificationData
+ Data.X509.OCSP: [ocspRespASN1] :: OCSPResponsePayload -> [ASN1]
+ Data.X509.OCSP: [ocspRespCerts] :: OCSPResponseVerificationData -> [Certificate]
+ Data.X509.OCSP: [ocspRespDer] :: OCSPResponseVerificationData -> ByteString
+ Data.X509.OCSP: [ocspRespSignatureAlg] :: OCSPResponseVerificationData -> SignatureALG
+ Data.X509.OCSP: [ocspRespSignature] :: OCSPResponseVerificationData -> ByteString
+ Data.X509.OCSP: data OCSPResponseVerificationData
+ Data.X509.OCSP: getOCSPResponseVerificationData :: OCSPResponse -> Maybe OCSPResponseVerificationData
+ Data.X509.OCSP: instance GHC.Classes.Eq Data.X509.OCSP.OCSPResponseVerificationData
+ Data.X509.OCSP: instance GHC.Show.Show Data.X509.OCSP.OCSPResponseVerificationData

Files

Changelog.md view
@@ -1,3 +1,8 @@+### 0.3.0.0++- Add function *getOCSPResponseVerificationData* to help verify the signature of+  the OCSP response. See how it can be used in the *client-ocsp* example.+ ### 0.2.0.0  - **Breaking changes**: flip the order of arguments in *encodeOCSPRequestASN1*
Data/X509/OCSP.hs view
@@ -15,21 +15,29 @@ -- This module complies with /rfc6960/. ----------------------------------------------------------------------------- -module Data.X509.OCSP (CertId (..)+module Data.X509.OCSP (+    -- * Shared data+                       CertId (..)+    -- * OCSP request                       ,encodeOCSPRequestASN1                       ,encodeOCSPRequest+    -- * OCSP response                       ,OCSPResponse (..)                       ,OCSPResponseStatus (..)                       ,OCSPResponsePayload (..)                       ,OCSPResponseCertData (..)                       ,OCSPResponseCertStatus (..)                       ,decodeOCSPResponse+    -- * OCSP response verification+                      ,OCSPResponseVerificationData (..)+                      ,getOCSPResponseVerificationData                       ) where  import Data.X509 import Data.ASN1.Types import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding+import Data.ASN1.BitArray import Data.ASN1.Stream import Data.ASN1.Error import Data.ByteString (ByteString)@@ -62,7 +70,7 @@                      : OID _                      : _                      : End Sequence-                     : v@(BitString _)+                     : v@BitString {}                      : _ -> L.drop 1 $ encodeASN1 DER $ pure v                    _ -> error "bad pubkey sequence" @@ -77,10 +85,10 @@                        -- ^ Certificate serial number                      } deriving (Show, Eq) --- | Build and encode OCSP request in ASN1 format.+-- | Build and encode OCSP request in ASN.1 format. -- -- The returned value contains the encoded request and an object of type--- 'CertId' with hashes calculated by /SHA1/ algorithm.+-- 'CertId' with hashes calculated by the SHA1 algorithm. encodeOCSPRequestASN1     :: Certificate              -- ^ Certificate     -> Certificate              -- ^ Issuer certificate@@ -110,10 +118,10 @@        , CertId h1 h2 sn        ) --- | Build and encode OCSP request in ASN1 DER format.+-- | Build and encode OCSP request in ASN.1\/DER format. -- -- The returned value contains the encoded request and an object of type--- 'CertId' with hashes calculated by /SHA1/ algorithm.+-- 'CertId' with hashes calculated by the SHA1 algorithm. encodeOCSPRequest     :: Certificate              -- ^ Certificate     -> Certificate              -- ^ Issuer certificate@@ -142,7 +150,7 @@ data OCSPResponsePayload =     OCSPResponsePayload { ocspRespCertData :: OCSPResponseCertData                           -- ^ Selected certificate data-                        , ocspRespData :: [ASN1]+                        , ocspRespASN1 :: [ASN1]                           -- ^ Whole response payload                         } deriving (Show, Eq) @@ -189,7 +197,7 @@       , End (Container Context 0)       , End Sequence       ] -> do-          pl <- decodeASN1 DER $ L.fromStrict resp'+          pl <- decodeASN1' DER resp'           Right $               case pl of                   Start Sequence@@ -237,6 +245,90 @@                       Just $ OCSPResponsePayload                           (OCSPResponseCertData st tu nu) pl     _ -> Right Nothing-    where getCurrentContainerContents = fst . getConstructedEnd 0-          skipCurrentContainer = snd . getConstructedEnd 0++-- | Verification data from OCSP response payload.+--+-- This data can be used to verify the signature of the OCSP response with+-- 'Data.X509.Validation.verifySignature'. The response is signed with+-- signature /ocspRespSignature/. Binary data /ocspRespDer/ and algorthm+-- /ocspRespSignatureAlg/ are what was used to sign the response. The+-- verification process may require the public key of the issuer certificate+-- if it's not attached in /ocspRespCerts/.+--+-- See details of signing and verification of OCSP responses in /rfc6960/.+--+-- Below is a simple implementation of the OCSP response signature verification.+--+-- @+-- {-# LANGUAGE RecordWildCards #-}+--+-- -- ...+--+-- verifySignature\' :: 'OCSPResponse' -> 'Certificate' -> 'Data.X509.Validation.SignatureVerification'+-- verifySignature\' resp v'Certificate' {..}+--     | Just __/OCSPResponseVerificationData/__ {..} <-+--         'getOCSPResponseVerificationData' resp =+--             'Data.X509.Validation.verifySignature' __/ocspRespSignatureAlg/__ 'certPubKey' __/ocspRespDer/__+--                 __/ocspRespSignature/__+--     | otherwise = 'Data.X509.Validation.SignatureFailed' 'Data.X509.Validation.SignatureInvalid'+-- @+--+-- Note that the issuer certificate gets passed to /verifySignature\'/ rather+-- than looked up in /ocspRespCerts/. The OCSP Signature Authority Delegation+-- is not checked in the function.+data OCSPResponseVerificationData =+    OCSPResponseVerificationData { ocspRespDer :: ByteString+                                   -- ^ Response data (DER-encoded)+                                 , ocspRespSignatureAlg :: SignatureALG+                                   -- ^ Signature algorithm+                                 , ocspRespSignature :: ByteString+                                   -- ^ Signature+                                 , ocspRespCerts :: [Certificate]+                                   -- ^ Certificates+                                 } deriving (Show, Eq)++-- | Get verification data from OCSP response payload.+--+-- The function returns /Nothing/ on unexpected ASN.1 contents.+getOCSPResponseVerificationData :: OCSPResponse ->+    Maybe OCSPResponseVerificationData+getOCSPResponseVerificationData resp+    | Just resp' <- ocspRespPayload resp+    , Start Sequence : Start Sequence : c1 <- ocspRespASN1 resp' = do+        let (wrapInSequence -> resp'', next) = getConstructedEnd 0 c1+            der = encodeASN1' DER resp''+        case next of+            Start Sequence : c2 -> do+                let (wrapInSequence -> alg, next') = getConstructedEnd 0 c2+                (alg', _) <- either (const Nothing) Just $ fromASN1 alg+                case next' of+                    BitString (BitArray _ sig) : c3+                        | c3 == [End Sequence] ->+                            Just $ OCSPResponseVerificationData+                                der alg' sig []+                        | Start (Container Context 0)+                            : Start Sequence+                            : certs <- getCurrentContainerContents c3 -> do+                                certs' <- reverse <$> collectCerts certs []+                                Just $ OCSPResponseVerificationData+                                    der alg' sig certs'+                    _ -> Nothing+            _ -> Nothing+    where collectCerts (Start Sequence : c1) certs+              | (Start Sequence : cert, c2) <- getConstructedEnd 0 c1 =+                  case fromASN1 (getCurrentContainerContents cert) of+                      Right (cert', _) -> collectCerts c2 $ cert' : certs+                      _ -> Nothing+          collectCerts [End Sequence, End (Container Context 0)] certs =+              Just certs+          collectCerts _ _ =+              Nothing+          wrapInSequence = (Start Sequence :) . (++ [End Sequence])+getOCSPResponseVerificationData _ = Nothing++getCurrentContainerContents :: [ASN1] -> [ASN1]+getCurrentContainerContents = fst . getConstructedEnd 0++skipCurrentContainer :: [ASN1] -> [ASN1]+skipCurrentContainer = snd . getConstructedEnd 0 
+ test/data/certs4096/rootCA.crt view
@@ -0,0 +1,35 @@+-----BEGIN CERTIFICATE-----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+-----END CERTIFICATE-----
+ test/data/certs4096/server.crt view
@@ -0,0 +1,31 @@+-----BEGIN CERTIFICATE-----+MIIFUjCCAzqgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBmzELMAkGA1UEBhMCVVMx+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzAR+BgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAsMCU9DU1AgVGVzdDEYMBYGA1UEAwwP+TXkgQ29tcGFueSBSb290MRwwGgYKCZImiZPyLGQBAQwMdGVzdE9DU1A0MDk2MB4X+DTI0MDQyNTE3MTc0NVoXDTI1MDQyNTE3MTc0NVowgZUxCzAJBgNVBAYTAlVTMRMw+EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYD+VQQKDApNeSBDb21wYW55MRIwEAYDVQQLDAlPQ1NQIFRlc3QxEjAQBgNVBAMMCWxv+Y2FsaG9zdDEcMBoGCgmSJomT8ixkAQEMDHRlc3RPQ1NQNDA5NjCCASIwDQYJKoZI+hvcNAQEBBQADggEPADCCAQoCggEBALziwuedckgzrd6QTZ+78YC9hFkWaguVAW3P+Nql9m1iga4UAMOi6zg6hoLHp8BPCTTZ5UhppxXCovWaj3aYmsuAL5mZ4mUSVDvmw+jRy5NiE4jPYuWuvwAwopAEsh5bo1X8zvhIEAlKwyuddb+du4zsoY57slBxB04fgz+0k682XoEDAsnxrOcvSGUXuZImDoIhITGfKNbAWYh7FEfWIW7pCciLyB1V9TB4y/z+L8AVaANZ6zgpHv7bo0By640S3SPKRyJrzxpGV7DbyFXxSozpiYN9BGjv0eYuWTNz+aAENsGc2qu0HxXPSwLZ0j7LmmUhNKxsi9vwukg7BYS6j+P/wp4sCAwEAAaOBpDCB+oTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAxBggrBgEFBQcBAQQlMCMwIQYIKwYB+BQUHMAGGFWh0dHA6Ly9sb2NhbGhvc3Q6ODA4MTAfBgNVHSMEGDAWgBS4sdTmNJdg+prJ/jUndr9tj8Cz67DAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFBip+NnDfLbUMY/KEyHcaxT9IgSnsMA0GCSqGSIb3DQEBCwUAA4ICAQAhyrY8h10/dwri+y8+zjvRHdnAso4R6IlGH7Z7CHoDjj/Q4NJcyJLjXNVItRUTi1XsXKBYarOA0/yFf+1CNlSLXsIRbsrY6u2IRKKxg5umkMGaClEryNK8msa+AKRod60308NbnuIeCxvyYN+7IvZtaBl56J9m9aQFRbkm5SLiWBCljbmfzKadUQanxVOS3AVs1+0ICAewbuaLcwZ+/LQcfAV0vgHnd19ZHyX9RyW5k2Fu3lkZxO6aTJ1Dv1Clyc1QUOTxwtZb213Nk5Yc+ca+9Ys3egYhaqYs09iLuGzb/oa+7/ueNnbv9oeF0AxMe7yOuZqVqyn4RfHB5Db2/+ikFZascdSGHNAHHK0zOszyhqPNq3MSAW8Bf2hJZpwHQJ6y1L5oTB05BcQV+R7UVW+0libcu40DHkxyoViczlgTpv5W5/NZgs//lbFb0C0on2kgT1jgs/EjltuLcEp0jWI+4PGf066/xX9dpozhK45VNzw+Mcu/n2ukjCJ4iMQOkDr7vAMg5UGGGccRhlj/nrp9+lzlMOItT13kA9Cymk/f3yd8oDMonQStZIWK5DkgFF2NNJjIuyp8wVatZPU5FxKtL+MF6Jcew9/5RhW9QztiCBoRd0wCiTe2ebh/MK3dhcjjVRnLCtqsh9Be6VAgbWs8Fu+cBMpqSbsDPRAXbCNjqMiiEbl4QAFRA==+-----END CERTIFICATE-----
test/test-ocsp.hs view
@@ -1,9 +1,10 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards, OverloadedStrings #-}  module Main where  import Data.X509.AIA import Data.X509.OCSP+import Data.X509.Validation import Data.X509 import Data.PEM import Test.HUnit@@ -14,7 +15,6 @@ import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding import Data.List (uncons)-import Control.Monad   toCertificate :: ByteString -> Certificate toCertificate cert = either error getCertificate $ do@@ -38,14 +38,25 @@ testOCSPRequest cert issuerCert = TestCase . (buildRequest @?=) . L.fromStrict     where buildRequest = fst $ encodeOCSPRequest cert issuerCert  -testOCSPResponse :: CertId -> ByteString -> Test-testOCSPResponse certId resp =+testOCSPResponse :: Maybe OCSPResponse -> Test+testOCSPResponse resp =     TestCase $ getRespStatus @?= Just OCSPRespCertGood-    where getRespStatus = either (const Nothing)-              (fmap ocspRespPayload >=>-                  fmap (ocspRespCertStatus . ocspRespCertData)-              ) $ decodeOCSPResponse certId $ L.fromStrict resp+    where getRespStatus = ocspRespCertStatus . ocspRespCertData <$>+              (resp >>= ocspRespPayload)+ +verifyOCSPResponse :: Certificate -> Maybe OCSPResponse -> Test+verifyOCSPResponse issuerCert resp =+    TestCase $ getVerificationStatus @?= Just SignaturePass+    where getVerificationStatus = (`verifySignature'` issuerCert) <$> resp +verifySignature' :: OCSPResponse -> Certificate -> SignatureVerification+verifySignature' resp Certificate {..}+    | Just OCSPResponseVerificationData {..} <-+        getOCSPResponseVerificationData resp =+            verifySignature ocspRespSignatureAlg certPubKey ocspRespDer+                ocspRespSignature+    | otherwise = SignatureFailed SignatureInvalid+ main :: IO () main = do     certS <- toCertificate <$> B.readFile "test/data/certs/server/server.crt"@@ -54,14 +65,18 @@     let certId = snd $ encodeOCSPRequestASN1 certS certI      reqDer <- B.readFile "test/data/req.der"-    let req = either (error . show) id $ decodeASN1 DER $ L.fromStrict reqDer+    let req = showError $ decodeASN1' DER reqDer      respDer <- B.readFile "test/data/resp.der"+    let resp = showError $ decodeOCSPResponse certId $ L.fromStrict respDer      runTestTTAndExit $ TestList         [TestLabel "testAIA"             $ testAIA certS         ,TestLabel "testOCSPRequestASN1" $ testOCSPRequestASN1 certS certI req         ,TestLabel "testOCSPRequest"     $ testOCSPRequest certS certI reqDer-        ,TestLabel "testOCSPResponse"    $ testOCSPResponse certId respDer+        ,TestLabel "testOCSPResponse"    $ testOCSPResponse resp+        ,TestLabel "verifyOCSPResponse"  $ verifyOCSPResponse certI resp         ]++    where showError = either (error . show) id 
x509-ocsp.cabal view
@@ -1,7 +1,7 @@ cabal-version:          2.4  name:                   x509-ocsp-version:                0.2.0.0+version:                0.3.0.0 synopsis:               Basic X509 OCSP implementation description:            Build X509 OCSP requests and parse responses homepage:               https://github.com/lyokha/x509-ocsp@@ -40,6 +40,7 @@                       , HUnit >= 1.6.1.0                       , bytestring                       , crypton-x509+                      , crypton-x509-validation                       , asn1-encoding                       , asn1-types                       , pem