certificate 0.6.0 → 0.7.0
raw patch · 5 files changed
+230/−64 lines, 5 filesdep +cryptocipherdep +cryptohashdep ~asn1-dataPVP ok
version bump matches the API change (PVP)
Dependencies added: cryptocipher, cryptohash
Dependency ranges changed: asn1-data
API changes (from Hackage documentation)
- Data.Certificate.X509: PubKey :: PubKeyALG -> PubKeyDesc -> PubKey
- Data.Certificate.X509: data PubKeyDesc
- Data.Certificate.X509Cert: PubKey :: PubKeyALG -> PubKeyDesc -> PubKey
- Data.Certificate.X509Cert: data PubKeyDesc
- Data.Certificate.X509Cert: instance Eq PubKeyDesc
- Data.Certificate.X509Cert: instance Show PubKeyDesc
+ Data.Certificate.X509: PubKeyALG_DH :: PubKeyALG
+ Data.Certificate.X509: PubKeyDH :: (Integer, Integer, Integer, Maybe Integer, ([Word8], Integer)) -> PubKey
+ Data.Certificate.X509: T61 :: ASN1StringType
+ Data.Certificate.X509: getSigningData :: X509 -> ByteString
+ Data.Certificate.X509Cert: PubKeyALG_DH :: PubKeyALG
+ Data.Certificate.X509Cert: PubKeyDH :: (Integer, Integer, Integer, Maybe Integer, ([Word8], Integer)) -> PubKey
+ Data.Certificate.X509Cert: T61 :: ASN1StringType
+ System.Certificate.X509: findCertificate :: (X509 -> Bool) -> IO (Maybe X509)
+ System.Certificate.X509: getSystemPath :: IO FilePath
+ System.Certificate.X509: instance Eq ReadErr
+ System.Certificate.X509: instance Show ReadErr
+ System.Certificate.X509: readAll :: IO [Either ReadErr X509]
- Data.Certificate.X509: PubKeyDSA :: (Integer, Integer, Integer, Integer) -> PubKeyDesc
+ Data.Certificate.X509: PubKeyDSA :: (Integer, Integer, Integer, Integer) -> PubKey
- Data.Certificate.X509: PubKeyECDSA :: [ASN1] -> PubKeyDesc
+ Data.Certificate.X509: PubKeyECDSA :: [ASN1] -> PubKey
- Data.Certificate.X509: PubKeyRSA :: (Int, Integer, Integer) -> PubKeyDesc
+ Data.Certificate.X509: PubKeyRSA :: (Int, Integer, Integer) -> PubKey
- Data.Certificate.X509: PubKeyUnknown :: [Word8] -> PubKeyDesc
+ Data.Certificate.X509: PubKeyUnknown :: OID -> [Word8] -> PubKey
- Data.Certificate.X509Cert: PubKeyDSA :: (Integer, Integer, Integer, Integer) -> PubKeyDesc
+ Data.Certificate.X509Cert: PubKeyDSA :: (Integer, Integer, Integer, Integer) -> PubKey
- Data.Certificate.X509Cert: PubKeyECDSA :: [ASN1] -> PubKeyDesc
+ Data.Certificate.X509Cert: PubKeyECDSA :: [ASN1] -> PubKey
- Data.Certificate.X509Cert: PubKeyRSA :: (Int, Integer, Integer) -> PubKeyDesc
+ Data.Certificate.X509Cert: PubKeyRSA :: (Int, Integer, Integer) -> PubKey
- Data.Certificate.X509Cert: PubKeyUnknown :: [Word8] -> PubKeyDesc
+ Data.Certificate.X509Cert: PubKeyUnknown :: OID -> [Word8] -> PubKey
Files
- Certificate.hs +80/−24
- Data/Certificate/X509.hs +12/−1
- Data/Certificate/X509Cert.hs +43/−31
- System/Certificate/X509.hs +81/−0
- certificate.cabal +14/−8
Certificate.hs view
@@ -5,7 +5,7 @@ import qualified Data.ByteString as B import qualified Data.Text.Lazy as T import Data.Text.Lazy.Encoding (decodeUtf8)-import Data.Certificate.X509+import qualified Data.Certificate.X509 as X509 import Data.Certificate.KeyRSA as KeyRSA import Data.Certificate.KeyDSA as KeyDSA import Data.Certificate.PEM@@ -14,7 +14,15 @@ import Control.Applicative ((<$>)) import Data.Maybe import System.Exit+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+ import Data.ASN1.DER (decodeASN1Stream, ASN1(..), ASN1ConstructionType(..)) import Numeric @@ -28,19 +36,19 @@ showExts e = putStrLn $ show e -showCert :: X509 -> IO ()-showCert (X509 cert _ sigalg sigbits) = do- putStrLn ("version: " ++ show (certVersion cert))- putStrLn ("serial: " ++ show (certSerial cert))- putStrLn ("sigalg: " ++ show (certSignatureAlg cert))+showCert :: X509.X509 -> IO ()+showCert (X509.X509 cert _ sigalg sigbits) = do+ putStrLn ("version: " ++ show (X509.certVersion cert))+ putStrLn ("serial: " ++ show (X509.certSerial cert))+ putStrLn ("sigalg: " ++ show (X509.certSignatureAlg cert)) putStrLn "issuer:"- showDN $ certIssuerDN cert+ showDN $ X509.certIssuerDN cert putStrLn "subject:"- showDN $ certSubjectDN cert- putStrLn ("valid: " ++ show (certValidity cert))- putStrLn ("pk: " ++ show (certPubKey cert))+ showDN $ X509.certSubjectDN cert+ putStrLn ("valid: " ++ show (X509.certValidity cert))+ putStrLn ("pk: " ++ show (X509.certPubKey cert)) putStrLn "exts:"- showExts $ certExtensions cert+ showExts $ X509.certExtensions cert putStrLn ("sigAlg: " ++ show sigalg) putStrLn ("sig: " ++ show sigbits) @@ -109,17 +117,63 @@ p (Other tc tn x) = putStr "other" doMain :: CertMainOpts -> IO ()-doMain opts@(X509Opt _ _ _ _) = do+doMain opts@(X509 _ _ _ _ _) = do cert <- maybe (error "cannot read PEM certificate") (id) . parsePEMCert <$> B.readFile (head $ files opts) when (raw opts) $ putStrLn $ hexdump $ L.fromChunks [cert] when (asn1 opts) $ case decodeASN1Stream $ L.fromChunks [cert] of Left err -> error ("decoding ASN1 failed: " ++ show err) Right asn1 -> showASN1 asn1- when (text opts || not (or [asn1 opts,raw opts])) $ case decodeCertificate $ L.fromChunks [cert] of++ let x509o = X509.decodeCertificate $ L.fromChunks [cert]+ let x509 = case x509o of Left err -> error ("decoding certificate failed: " ++ show err)- Right c -> showCert c+ Right c -> c+ when (text opts || not (or [asn1 opts,raw opts])) $ showCert x509+ when (verify opts) $ verifyCert x509 exitSuccess+ where+ verifyCert x509@(X509.X509 cert _ sigalg sig) = do+ sysx509 <- SysCert.findCertificate (matchsysX509 cert)+ case sysx509 of+ Nothing -> putStrLn "couldn't find signing certificate"+ Just (X509.X509 syscert _ _ _) -> do+ verifyAlg (B.concat $ L.toChunks $ X509.getSigningData x509)+ (B.pack sig)+ sigalg+ (X509.certPubKey syscert)++ rsaVerify h hdesc pk a b = either (Left . show) (Right) $ RSA.verify h hdesc pk a b++ verifyF X509.SignatureALG_md2WithRSAEncryption (X509.PubKeyRSA rsakey) =+ rsaVerify MD2.hash (B.pack [0x30,0x20,0x30,0x0c,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x05,0x05,0x00,0x04,0x10]) (mkRSA rsakey)++ verifyF X509.SignatureALG_md5WithRSAEncryption (X509.PubKeyRSA rsakey) =+ rsaVerify MD5.hash (B.pack [0x30,0x20,0x30,0x0c,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x05,0x05,0x00,0x04,0x10]) (mkRSA rsakey)++ verifyF X509.SignatureALG_sha1WithRSAEncryption (X509.PubKeyRSA rsakey) =+ rsaVerify SHA1.hash (B.pack [0x30,0x20,0x30,0x0c,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x05,0x05,0x00,0x04,0x10]) (mkRSA rsakey)++ verifyF X509.SignatureALG_dsaWithSHA1 (X509.PubKeyDSA (pub,p,q,g)) =+ (\_ _ -> Left "unimplemented DSA checking")++ verifyF _ _ =+ (\_ _ -> Left "unexpected/wrong signature")++ mkRSA (lenmodulus, modulus, e) =+ RSA.PublicKey { RSA.public_sz = lenmodulus, RSA.public_n = modulus, RSA.public_e = e }++ verifyAlg toSign expectedSig sigalg pk =+ let f = verifyF sigalg pk in+ case f toSign expectedSig of+ Left err -> putStrLn ("certificate couldn't be verified: something happened: " ++ show err)+ Right True -> putStrLn "certificate verified"+ Right False -> putStrLn "certificate not verified"++ matchsysX509 cert (X509.X509 syscert _ _ _) = do+ let x = X509.certSubjectDN syscert+ let y = X509.certIssuerDN cert+ x == y doMain (Key files) = do content <- B.readFile $ head files@@ -141,22 +195,24 @@ putStrLn "no recognized private key found" data CertMainOpts =- X509Opt- { files :: [FilePath]- , asn1 :: Bool- , text :: Bool- , raw :: Bool+ X509+ { files :: [FilePath]+ , asn1 :: Bool+ , text :: Bool+ , raw :: Bool+ , verify :: Bool } | Key { files :: [FilePath] } deriving (Show,Data,Typeable) -x509Opts = X509Opt- { files = def &= args &= typFile- , asn1 = def- , text = def- , raw = def+x509Opts = X509+ { files = def &= args &= typFile+ , asn1 = def+ , text = def+ , raw = def+ , verify = def } &= help "x509 certificate related commands" keyOpts = Key
Data/Certificate/X509.hs view
@@ -15,13 +15,15 @@ -- * Data Structure (reexported from X509Cert) , SignatureALG(..) , PubKeyALG(..)- , PubKeyDesc(..) , PubKey(..) , ASN1StringType(..) , ASN1String , Certificate(..) , CertificateExts(..) + -- * helper for signing/veryfing certificate+ , getSigningData+ -- * serialization from ASN1 bytestring , decodeCertificate , encodeCertificate@@ -38,6 +40,15 @@ data X509 = X509 Certificate (Maybe L.ByteString) SignatureALG [Word8] deriving (Show,Eq)++{- | get signing data related to a X509 message,+ - which is either the cached data or the encoded certificate -}+getSigningData :: X509 -> L.ByteString+getSigningData (X509 _ (Just e) _ _) = e+getSigningData (X509 cert Nothing _ _) = e+ where+ (Right e) = encodeASN1Stream header+ header = asn1Container Sequence $ encodeCertificateHeader cert {- | decode an X509 from a bytestring - the structure is the following:
Data/Certificate/X509Cert.hs view
@@ -3,7 +3,6 @@ -- * Data Structure SignatureALG(..) , PubKeyALG(..)- , PubKeyDesc(..) , PubKey(..) , ASN1StringType(..) , ASN1String@@ -51,17 +50,17 @@ PubKeyALG_RSA | PubKeyALG_DSA | PubKeyALG_ECDSA+ | PubKeyALG_DH | PubKeyALG_Unknown OID deriving (Show,Eq) -data PubKeyDesc =- PubKeyRSA (Int, Integer, Integer) -- ^ RSA format with (len modulus, modulus, e)- | PubKeyDSA (Integer, Integer, Integer, Integer) -- ^ DSA format with (pub, p, q, g)+data PubKey =+ PubKeyRSA (Int,Integer,Integer) -- ^ RSA format with (len modulus, modulus, e)+ | PubKeyDSA (Integer,Integer,Integer,Integer) -- ^ DSA format with (pub, p, q, g)+ | PubKeyDH (Integer,Integer,Integer,Maybe Integer,([Word8], Integer))+ -- ^ DH format with (p,g,q,j,(seed,pgenCounter)) | PubKeyECDSA [ASN1] -- ^ ECDSA format not done yet FIXME- | PubKeyUnknown [Word8] -- ^ unrecognized format- deriving (Show,Eq)--data PubKey = PubKey PubKeyALG PubKeyDesc -- OID RSA|DSA|rawdata+ | PubKeyUnknown OID [Word8] -- ^ unrecognized format deriving (Show,Eq) -- FIXME use a proper standard type for representing time.@@ -79,7 +78,7 @@ | CertKeyUsageDecipherOnly deriving (Show, Eq) -data ASN1StringType = UTF8 | Printable | Univ | BMP | IA5 deriving (Show,Eq)+data ASN1StringType = UTF8 | Printable | Univ | BMP | IA5 | T61 deriving (Show,Eq) type ASN1String = (ASN1StringType, Text) data Certificate = Certificate@@ -109,21 +108,22 @@ {- | parse a RSA pubkeys from ASN1 encoded bits. - return PubKeyRSA (len-modulus, modulus, e) if successful -}-parse_RSA :: ByteString -> PubKeyDesc+parse_RSA :: ByteString -> ParseASN1 PubKey parse_RSA bits = case decodeASN1Stream $ bits of Right [Start Sequence, IntVal modulus, IntVal pubexp, End Sequence] ->- PubKeyRSA (calculate_modulus modulus 1, modulus, pubexp)+ return $ PubKeyRSA (calculate_modulus modulus 1, modulus, pubexp) _ ->- PubKeyUnknown $ L.unpack bits+ throwError ("bad RSA format") where calculate_modulus n i = if (2 ^ (i * 8)) > n then i else calculate_modulus n (i+1) -parse_ECDSA :: ByteString -> PubKeyDesc+parse_ECDSA :: ByteString -> ParseASN1 PubKey parse_ECDSA bits = case decodeASN1Stream bits of- Right l -> PubKeyECDSA l- Left x -> PubKeyUnknown $ map (fromIntegral . fromEnum) $ show x+ Right l -> return $ PubKeyECDSA l+ Left _ -> return $ PubKeyUnknown (pubkeyalgOID PubKeyALG_ECDSA) (L.unpack bits)+ parseCertHeaderVersion :: ParseASN1 Int parseCertHeaderVersion = do v <- onNextContainerMaybe (Container Context 0) $ do@@ -154,6 +154,7 @@ [ ([1,2,840,113549,1,1,1], PubKeyALG_RSA) , ([1,2,840,10040,4,1], PubKeyALG_DSA) , ([1,2,840,10045,2,1], PubKeyALG_ECDSA)+ , ([1,2,840,10046,2,1], PubKeyALG_DH) ] oidSig :: OID -> SignatureALG@@ -170,6 +171,13 @@ pubkeyalgOID (PubKeyALG_Unknown oid) = oid pubkeyalgOID sig = maybe [] fst $ find ((==) sig . snd) pk_table +pubkeyToAlg :: PubKey -> PubKeyALG+pubkeyToAlg (PubKeyRSA _) = PubKeyALG_RSA+pubkeyToAlg (PubKeyDSA _) = PubKeyALG_DSA+pubkeyToAlg (PubKeyDH _) = PubKeyALG_DH+pubkeyToAlg (PubKeyECDSA _) = PubKeyALG_ECDSA+pubkeyToAlg (PubKeyUnknown oid _) = PubKeyALG_Unknown oid+ parseCertHeaderAlgorithmID :: ParseASN1 SignatureALG parseCertHeaderAlgorithmID = do n <- getNextContainer Sequence@@ -184,6 +192,7 @@ asn1String (UniversalString x) = (Univ, x) asn1String (BMPString x) = (BMP, x) asn1String (IA5String x) = (IA5, x)+asn1String (T61String x) = (IA5, x) asn1String x = error ("not a print string " ++ show x) encodeAsn1String :: ASN1String -> ASN1@@ -192,6 +201,7 @@ encodeAsn1String (Univ, x) = UniversalString x encodeAsn1String (BMP, x) = BMPString x encodeAsn1String (IA5, x) = IA5String x+encodeAsn1String (T61, x) = T61String x parseCertHeaderDN :: ParseASN1 [ (OID, ASN1String) ] parseCertHeaderDN = do@@ -222,23 +232,21 @@ l <- getNextContainer Sequence bits <- getNextBitString case l of- [OID pkalg,Null] -> do+ [OID pkalg,Null] -> do let sig = oidPubKey pkalg- let desc = case sig of+ case sig of PubKeyALG_RSA -> parse_RSA bits- _ -> PubKeyUnknown $ L.unpack bits- return $ PubKey sig desc- [OID pkalg,OID pkalg2] -> do+ _ -> return $ PubKeyUnknown pkalg $ L.unpack bits+ [OID pkalg,OID _] -> do let sig = oidPubKey pkalg- let desc = case sig of+ case sig of PubKeyALG_ECDSA -> parse_ECDSA bits- _ -> PubKeyUnknown $ L.unpack bits- return $ PubKey sig desc+ _ -> return $ PubKeyUnknown pkalg $ L.unpack bits [OID pkalg,Start Sequence,IntVal p,IntVal q,IntVal g,End Sequence] -> do let sig = oidPubKey pkalg case decodeASN1Stream bits of- Right [IntVal dsapub] -> return $ PubKey sig (PubKeyDSA (dsapub, p, q, g))- _ -> throwError "unrecognized DSA pub format"+ Right [IntVal dsapub] -> return $ PubKeyDSA (dsapub, p, q, g)+ _ -> return $ PubKeyUnknown pkalg $ L.unpack bits n -> throwError ("subject public key bad format : " ++ show n) @@ -347,19 +355,23 @@ dnSet (oid, stringy) = asn1Container Set (asn1Container Sequence [OID oid, encodeAsn1String stringy]) encodePK :: PubKey -> [ASN1]-encodePK (PubKey sig (PubKeyRSA (_, modulus, e))) =- asn1Container Sequence (asn1Container Sequence [ OID $ pubkeyalgOID sig, Null ] ++ [BitString 0 bits])+encodePK k@(PubKeyRSA (_, modulus, e)) =+ asn1Container Sequence (asn1Container Sequence [pkalg,Null] ++ [BitString 0 bits]) where+ pkalg = OID $ pubkeyalgOID $ pubkeyToAlg k (Right bits) = encodeASN1Stream $ asn1Container Sequence [IntVal modulus, IntVal e] -encodePK (PubKey sig (PubKeyDSA (pub, p, q, g))) =- asn1Container Sequence (asn1Container Sequence ([OID $ pubkeyalgOID sig] ++ dsaseq) ++ [BitString 0 bits])+encodePK k@(PubKeyDSA (pub, p, q, g)) =+ asn1Container Sequence (asn1Container Sequence ([pkalg] ++ dsaseq) ++ [BitString 0 bits]) where+ pkalg = OID $ pubkeyalgOID $ pubkeyToAlg k dsaseq = asn1Container Sequence [IntVal p,IntVal q,IntVal g] (Right bits) = encodeASN1Stream [IntVal pub] -encodePK (PubKey sig (PubKeyUnknown l)) = - asn1Container Sequence (asn1Container Sequence [OID $ pubkeyalgOID sig, Null] ++ [BitString 0 $ L.pack l])+encodePK k@(PubKeyUnknown _ l) =+ asn1Container Sequence (asn1Container Sequence [pkalg,Null] ++ [BitString 0 $ L.pack l])+ where+ pkalg = OID $ pubkeyalgOID $ pubkeyToAlg k encodeCertificateHeader :: Certificate -> [ASN1] encodeCertificateHeader cert =
+ System/Certificate/X509.hs view
@@ -0,0 +1,81 @@+-- |+-- Module : System.Certificate.X509+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : linux only+--+-- this module is portable to unix system where there is usually+-- a /etc/ssl/certs with system X509 certificates.+--+-- the path can be dynamically override using the environment variable+-- defined by envPathOverride in the module, which by+-- default is SYSTEM_CERTIFICATE_PATH+--+module System.Certificate.X509+ ( getSystemPath+ , readAll+ , findCertificate+ ) where++import System.Directory (getDirectoryContents)+import System.Environment (getEnv)+import Data.List (isPrefixOf)++import Data.Either+import Data.Certificate.X509+import Data.Certificate.PEM+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L++import Control.Applicative ((<$>))+import Control.Exception+import Control.Monad++import Prelude hiding (catch)++defaultSystemPath :: FilePath+defaultSystemPath = "/etc/ssl/certs/"++envPathOverride :: String+envPathOverride = "SYSTEM_CERTIFICATE_PATH"++getSystemPath :: IO FilePath+getSystemPath = catch (getEnv envPathOverride) inDefault+ where+ inDefault :: IOException -> IO FilePath+ inDefault _ = return defaultSystemPath++data ReadErr =+ Exception IOException+ | CertError String+ deriving (Show,Eq)++readCertificate :: FilePath -> IO (Either ReadErr X509)+readCertificate file = do+ rawdata <- try $ B.readFile file :: IO (Either IOException B.ByteString)+ either (return . Left . Exception) parseCert $ rawdata+ where+ parseCert pemdata = case parsePEMCert pemdata of+ Nothing -> return $ Left $ CertError "certificate not in PEM format"+ Just certdata -> do+ return $ either (Left . CertError) Right $ decodeCertificate $ L.fromChunks [certdata]++readAll :: IO [Either ReadErr X509]+readAll = do+ path <- getSystemPath+ certfiles <- filter (not . isPrefixOf ".") <$> getDirectoryContents path+ forM certfiles $ \certfile -> readCertificate (path ++ certfile)++findCertificate :: (X509 -> Bool) -> IO (Maybe X509)+findCertificate f = do+ path <- getSystemPath+ certfiles <- filter (not . isPrefixOf ".") <$> getDirectoryContents path+ loop $ map (path ++) certfiles+ where+ loop [] = return Nothing+ loop (x:xs) = do+ ox509 <- readCertificate x+ case ox509 of+ Left _ -> loop xs+ Right x509 -> if f x509 then return $ Just x509 else loop xs
certificate.cabal view
@@ -1,5 +1,5 @@ Name: certificate-Version: 0.6.0+Version: 0.7.0 Description: Certificates and Key reader/writer .@@ -26,17 +26,19 @@ Default: False Library- Build-Depends: base >= 3 && < 7,- bytestring,- text >= 0.11,- mtl,- asn1-data >= 0.4.5 && < 0.5,- base64-bytestring+ Build-Depends: base >= 3 && < 5+ , bytestring+ , text >= 0.11+ , mtl+ , asn1-data >= 0.4.6 && < 0.5+ , base64-bytestring+ , directory Exposed-modules: Data.Certificate.X509, Data.Certificate.X509Cert, Data.Certificate.PEM, Data.Certificate.KeyDSA Data.Certificate.KeyRSA+ System.Certificate.X509 Other-modules: Data.Certificate.X509Internal ghc-options: -Wall @@ -44,7 +46,11 @@ Main-Is: Certificate.hs if flag(executable) Buildable: True- Build-depends: cmdargs, text >= 0.11+ Build-depends: cmdargs+ , text >= 0.11+ , cryptohash+ , cryptocipher >= 0.2.4+ , directory else Buildable: False