hs-tls/Network/TLS/Extra/Certificate.hs
2011-03-31 21:47:07 +01:00

118 lines
4.9 KiB
Haskell

{-# LANGUAGE OverloadedStrings, CPP #-}
-- |
-- Module : Network.TLS.Extra.Certificate
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
module Network.TLS.Extra.Certificate
( certificateVerifyChain
, certificateVerifyAgainst
) where
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.Certificate.X509
import System.Certificate.X509 as SysCert
-- for signing/verifying certificate
import qualified Crypto.Hash.SHA1 as SHA1
import qualified Crypto.Hash.MD2 as MD2
import qualified Crypto.Hash.MD5 as MD5
import qualified Crypto.Cipher.RSA as RSA
import qualified Crypto.Cipher.DSA as DSA
#if defined(NOCERTVERIFY)
# warning "********certificate verify chain doesn't yet work on your platform *************"
# warning "********please consider contributing to the certificate to fix this issue *************"
# warning "********getting trusted system certificate is platform dependant *************"
{- on windows and OSX, the trusted certificates are not yet accessible,
- for now, print a big fat warning (better than nothing) and returns true -}
certificateVerifyChain :: [X509] -> IO Bool
certificateVerifyChain _ = do
putStrLn "****************** certificate verify chain doesn't yet work on your platform **********************"
putStrLn "please consider contributing to the certificate package to fix this issue"
return True
#else
-- | verify a certificates chain using the system certificates available.
--
-- each certificate of the list is verified against the next certificate, until
-- it can be verified against a system certificate (system certificates are assumed as trusted)
--
-- This helper only check that the chain of certificate is valid, which means that each items
-- received are signed by the next one, or by a system certificate. Some extra checks need to
-- be done at the user level so that the certificate chain received make sense in the context.
--
-- for example for HTTP, the user should typically verify the certificate subject match the URL
-- of connection.
--
-- TODO: verify validity, check revocation list if any, add optional user output to know
-- the rejection reason.
certificateVerifyChain :: [X509] -> IO Bool
certificateVerifyChain l
| l == [] = return False
| otherwise = do
-- find a matching certificate that we trust (== installed on the system)
found <- SysCert.findCertificate (matchsysX509 $ head l)
case found of
Just sysx509 -> certificateVerifyAgainst (head l) sysx509
Nothing -> do
validChain <- certificateVerifyAgainst (head l) (head $ tail l)
if validChain
then certificateVerifyChain $ tail l
else return False
where
matchsysX509 (X509 cert _ _ _) (X509 syscert _ _ _) = do
let x = certSubjectDN syscert
let y = certIssuerDN cert
x == y
#endif
-- | verify a certificate against another one.
-- the first certificate need to be signed by the second one for this function to succeed.
certificateVerifyAgainst :: X509 -> X509 -> IO Bool
certificateVerifyAgainst ux509@(X509 _ _ sigalg sig) (X509 scert _ _ _) = do
let f = verifyF sigalg pk
case f udata esig of
Right True -> return True
_ -> return False
where
udata = B.concat $ L.toChunks $ getSigningData ux509
esig = B.pack sig
pk = certPubKey scert
verifyF :: SignatureALG -> PubKey -> B.ByteString -> B.ByteString -> Either String Bool
-- md[245]WithRSAEncryption:
--
-- pkcs-1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) US(840) rsadsi(113549) pkcs(1) 1 }
-- rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
-- md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
-- md4WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 3 }
-- md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
verifyF SignatureALG_md2WithRSAEncryption (PubKeyRSA rsak) = rsaVerify MD2.hash asn1 (mkRSA rsak)
where asn1 = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x02\x10"
verifyF SignatureALG_md5WithRSAEncryption (PubKeyRSA rsak) = rsaVerify MD5.hash asn1 (mkRSA rsak)
where asn1 = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10"
verifyF SignatureALG_sha1WithRSAEncryption (PubKeyRSA rsak) = rsaVerify SHA1.hash asn1 (mkRSA rsak)
where asn1 = "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14"
verifyF SignatureALG_dsaWithSHA1 (PubKeyDSA (pub,p,q,g)) = dsaSHA1Verify pk
where
pk = DSA.PublicKey { DSA.public_params = (p,g,q), DSA.public_y = pub }
verifyF _ _ = (\_ _ -> Left "unexpected/wrong signature")
dsaSHA1Verify pk a b = either (Left . show) (Right) $ DSA.verify asig SHA1.hash pk b
where asig = (0,0) {- FIXME : need to work out how to get R/S from the bytestring a -}
rsaVerify h hdesc pk a b = either (Left . show) (Right) $ RSA.verify h hdesc pk a b
mkRSA (lenmodulus, modulus, e) =
RSA.PublicKey { RSA.public_sz = lenmodulus, RSA.public_n = modulus, RSA.public_e = e }