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 +5/−0
- Data/X509/OCSP.hs +102/−10
- test/data/certs4096/rootCA.crt +35/−0
- test/data/certs4096/server.crt +31/−0
- test/test-ocsp.hs +25/−10
- x509-ocsp.cabal +2/−1
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