packages feed

certificate 0.2.1 → 0.3

raw patch · 4 files changed

+167/−64 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Certificate.PEM: parsePEM :: ByteString -> ByteString -> ByteString -> Either String ByteString
+ Data.Certificate.PEM: parsePEMCertReq :: ByteString -> Maybe ByteString
+ Data.Certificate.PEM: parsePEMKeyDSA :: ByteString -> Maybe ByteString
+ Data.Certificate.PEM: parsePEMKeyRSA :: ByteString -> Maybe ByteString
+ Data.Certificate.PEM: parsePEMs :: ByteString -> [PEM]
+ Data.Certificate.X509: instance Eq CertKeyUsage
+ Data.Certificate.X509: instance Eq CertificateExts
+ Data.Certificate.X509: instance Show CertKeyUsage
+ Data.Certificate.X509: instance Show CertificateExts
- Data.Certificate.PEM: parsePEMCert :: ByteString -> Either String ByteString
+ Data.Certificate.PEM: parsePEMCert :: ByteString -> Maybe ByteString
- Data.Certificate.PEM: parsePEMKey :: ByteString -> Either String ByteString
+ Data.Certificate.PEM: parsePEMKey :: ByteString -> Maybe ByteString
- Data.Certificate.X509: Certificate :: Int -> Integer -> SignatureALG -> CertificateDN -> CertificateDN -> (Time, Time) -> PubKey -> Maybe [ASN1] -> Maybe (SignatureALG, [Word8]) -> [ASN1] -> Certificate
+ Data.Certificate.X509: Certificate :: Int -> Integer -> SignatureALG -> CertificateDN -> CertificateDN -> (Time, Time) -> PubKey -> Maybe CertificateExts -> Maybe (SignatureALG, [Word8]) -> [ASN1] -> Certificate
- Data.Certificate.X509: PubKeyDSA :: (ByteString, Integer, Integer, Integer) -> PubKeyDesc
+ Data.Certificate.X509: PubKeyDSA :: (Integer, Integer, Integer, Integer) -> PubKeyDesc
- Data.Certificate.X509: certExtensions :: Certificate -> Maybe [ASN1]
+ Data.Certificate.X509: certExtensions :: Certificate -> Maybe CertificateExts

Files

Certificate.hs view
@@ -11,10 +11,10 @@ import Data.ASN1.DER  readcert :: FilePath -> IO (Either String Certificate)-readcert file = B.readFile file >>= return . either Left (decodeCertificate . L.fromChunks . (:[])) . parsePEMCert+readcert file = B.readFile file >>= return . maybe (Left "no valid certificate found") (decodeCertificate . L.fromChunks . (:[])) . parsePEMCert  readprivate :: FilePath -> IO (Either String PrivateKey)-readprivate file = B.readFile file >>= return . either Left (decodePrivateKey . L.fromChunks . (:[])) . parsePEMKey+readprivate file = B.readFile file >>= return . maybe (Left "no valid private RSA key found") (decodePrivateKey . L.fromChunks . (:[])) . parsePEMKeyRSA  showCert :: Certificate -> String showCert cert =
Data/Certificate/PEM.hs view
@@ -9,35 +9,58 @@ -- -- Read PEM files ---module Data.Certificate.PEM (-	parsePEM,-	parsePEMCert,-	parsePEMKey+module Data.Certificate.PEM+	( parsePEMCert+	, parsePEMCertReq+	, parsePEMKey+	, parsePEMKeyRSA+	, parsePEMKeyDSA+	, parsePEMs 	) where  import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC import Data.ByteString.Base64-import Data.Either+import Data.List -mapTill :: (a -> Bool) -> (a -> b) -> [a] -> [b]-mapTill _    _ []     = []-mapTill endp f (x:xs) = if endp x then [] else f x : mapTill endp f xs+type PEM = (String, ByteString) -{- | parse a PEM content that is delimited by the begin string and the end string,-   and returns the base64-decoded bytestring on success or a string on error. -}-parsePEM :: ByteString -> ByteString -> ByteString -> Either String ByteString-parsePEM begin end content =-	concatErrOrContent $ mapTill ((==) end) (decode) $ tail $ dropWhile ((/=) begin) ls-	where-		ls     = BC.split '\n' content-		concatErrOrContent x =-			let (l, r) = partitionEithers x in-			if l == [] then Right $ B.concat r else Left $ head l+takeTillEnd :: [ByteString] -> ([ByteString], [ByteString])+takeTillEnd ls = break (BC.isPrefixOf "-----END ") ls -parsePEMCert :: ByteString -> Either String ByteString-parsePEMCert = parsePEM "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"+findSectionName :: ByteString -> String+findSectionName s = BC.unpack $ B.take (B.length x - 5) x+	where x = B.drop 11 s -parsePEMKey :: ByteString -> Either String ByteString-parsePEMKey = parsePEM "-----BEGIN RSA PRIVATE KEY-----" "-----END RSA PRIVATE KEY-----"+parsePEMSections :: [ByteString] -> [PEM]+parsePEMSections []     = []+parsePEMSections (x:xs)+	| "-----BEGIN " `B.isPrefixOf` x =+		let (content, rest) = takeTillEnd xs in+		case decode $ B.concat content of+			Left _  -> parsePEMSections rest+			Right y -> (findSectionName x, y) : parsePEMSections rest+	| otherwise                      = parsePEMSections xs++parsePEMs :: ByteString -> [PEM]+parsePEMs content = parsePEMSections $ BC.lines content++findPEM :: String -> [PEM] -> Maybe ByteString+findPEM name = maybe Nothing (Just . snd) . find ((==) name . fst)++parsePEMCert :: ByteString -> Maybe ByteString+parsePEMCert = findPEM "CERTIFICATE" . parsePEMs++parsePEMCertReq :: ByteString -> Maybe ByteString+parsePEMCertReq = findPEM "CERTIFICATE REQUEST" . parsePEMs++parsePEMKeyRSA :: ByteString -> Maybe ByteString+parsePEMKeyRSA = findPEM "RSA PRIVATE KEY" . parsePEMs++parsePEMKeyDSA :: ByteString -> Maybe ByteString+parsePEMKeyDSA = findPEM "DSA PRIVATE KEY" . parsePEMs++{-# DEPRECATED parsePEMKey "use parsePEMKeyRSA now" #-}+parsePEMKey :: ByteString -> Maybe ByteString+parsePEMKey = parsePEMKeyRSA
Data/Certificate/X509.hs view
@@ -73,9 +73,9 @@ 	deriving (Show, Eq)  data PubKeyDesc =-	  PubKeyRSA (Int, Integer, Integer)                   -- ^ RSA format with (len modulus, modulus, e)-	| PubKeyDSA (L.ByteString, Integer, Integer, Integer) -- ^ DSA format with (pub, p, q, g)-	| PubKeyUnknown [Word8]                               -- ^ unrecognized format+	  PubKeyRSA (Int, Integer, Integer)              -- ^ RSA format with (len modulus, modulus, e)+	| PubKeyDSA (Integer, Integer, Integer, Integer) -- ^ DSA format with (pub, p, q, g)+	| PubKeyUnknown [Word8]                          -- ^ unrecognized format 	deriving (Show,Eq)  data PubKey = PubKey SignatureALG PubKeyDesc -- OID RSA|DSA|rawdata@@ -92,6 +92,26 @@ -- FIXME use a proper standard type for representing time. type Time = (Int, Int, Int, Int, Int, Int, Bool) +data CertKeyUsage =+	  CertKeyUsageDigitalSignature+	| CertKeyUsageNonRepudiation+	| CertKeyUsageKeyEncipherment+	| CertKeyUsageDataEncipherment+	| CertKeyUsageKeyAgreement+	| CertKeyUsageKeyCertSign+	| CertKeyUsageCRLSign+	| CertKeyUsageEncipherOnly+	| CertKeyUsageDecipherOnly+	deriving (Show, Eq)++data CertificateExts = CertificateExts+	{ certExtKeyUsage             :: Maybe (Bool, [CertKeyUsage])+	, certExtBasicConstraints     :: Maybe (Bool, Bool)+	, certExtSubjectKeyIdentifier :: Maybe (Bool, [Word8])+	, certExtPolicies             :: Maybe (Bool)+	, certExtOthers               :: [ (OID, Bool, ASN1) ]+	} deriving (Show,Eq)+ data Certificate = Certificate 	{ certVersion      :: Int                           -- ^ Certificate Version 	, certSerial       :: Integer                       -- ^ Certificate Serial number@@ -100,7 +120,7 @@ 	, certSubjectDN    :: CertificateDN                 -- ^ Certificate Subject DN 	, certValidity     :: (Time, Time)                  -- ^ Certificate Validity period 	, certPubKey       :: PubKey                        -- ^ Certificate Public key-	, certExtensions   :: Maybe [ASN1]                  -- ^ Certificate Extensions (format will change)+	, certExtensions   :: Maybe CertificateExts         -- ^ Certificate Extensions 	, certSignature    :: Maybe (SignatureALG, [Word8]) -- ^ Certificate Signature Algorithm and Signature 	, certOthers       :: [ASN1]                        -- ^ any others fields not parsed 	} deriving (Show,Eq)@@ -188,29 +208,32 @@ 	n <- getNext 	case n of 		Sequence [ OID oid, Null ] -> return $ oidSig oid-		_                          -> throwError "algorithm ID bad format"+		Sequence [ OID oid ]       -> return $ oidSig oid+		_                          -> throwError ("algorithm ID bad format " ++ show n) -stringOfASN1PrintString :: ASN1 -> String-stringOfASN1PrintString (PrintableString x) = map (toEnum.fromEnum) $ L.unpack x-stringOfASN1PrintString (UTF8String x)      = map (toEnum.fromEnum) $ L.unpack x-stringOfASN1PrintString x                   = error ("not a print string " ++ show x)+stringOfASN1String :: ASN1 -> String+stringOfASN1String (PrintableString x) = map (toEnum.fromEnum) $ L.unpack x+stringOfASN1String (UTF8String x)      = map (toEnum.fromEnum) $ L.unpack x+stringOfASN1String (T61String x)       = map (toEnum.fromEnum) $ L.unpack x+stringOfASN1String (UniversalString x) = map (toEnum.fromEnum) $ L.unpack x+stringOfASN1String (BMPString x)       = map (toEnum.fromEnum) $ L.unpack x+stringOfASN1String x                   = error ("not a print string " ++ show x)  parseCertHeaderDNHelper :: [ASN1] -> State CertificateDN () parseCertHeaderDNHelper l = do-	forM_ l $ (\e -> do-		case e of-			Set [ Sequence [ OID [2,5,4,3], val ] ] ->-				modify (\s -> s { cdnCommonName = Just $ stringOfASN1PrintString val })-			Set [ Sequence [ OID [2,5,4,6], val ] ] ->-				modify (\s -> s { cdnCountry = Just $ stringOfASN1PrintString val })-			Set [ Sequence [ OID [2,5,4,10], val ] ] ->-				modify (\s -> s { cdnOrganization = Just $ stringOfASN1PrintString val })-			Set [ Sequence [ OID [2,5,4,11], val ] ] ->-				modify (\s -> s { cdnOrganizationUnit = Just $ stringOfASN1PrintString val })-			Set [ Sequence [ OID oid, val ] ] ->-				modify (\s -> s { cdnOthers = (oid, show val) : cdnOthers s })-			_      ->-				return ()+	forM_ l $ (\e -> case e of+		Set [ Sequence [ OID [2,5,4,3], val ] ] ->+			modify (\s -> s { cdnCommonName = Just $ stringOfASN1String val })+		Set [ Sequence [ OID [2,5,4,6], val ] ] ->+			modify (\s -> s { cdnCountry = Just $ stringOfASN1String val })+		Set [ Sequence [ OID [2,5,4,10], val ] ] ->+			modify (\s -> s { cdnOrganization = Just $ stringOfASN1String val })+		Set [ Sequence [ OID [2,5,4,11], val ] ] ->+			modify (\s -> s { cdnOrganizationUnit = Just $ stringOfASN1String val })+		Set [ Sequence [ OID oid, val ] ] ->+			modify (\s -> s { cdnOthers = (oid, show val) : cdnOthers s })+		_      ->+			return () 		)  parseCertHeaderDN :: ParseCert CertificateDN@@ -218,15 +241,14 @@ 	n <- getNext 	case n of 		Sequence l -> do-			let defdn = CertificateDN {-				cdnCommonName       = Nothing,-				cdnCountry          = Nothing,-				cdnOrganization     = Nothing,-				cdnOrganizationUnit = Nothing,-				cdnOthers           = []+			let defdn = CertificateDN+				{ cdnCommonName       = Nothing+				, cdnCountry          = Nothing+				, cdnOrganization     = Nothing+				, cdnOrganizationUnit = Nothing+				, cdnOthers           = [] 				}-			let dn = execState (parseCertHeaderDNHelper l) defdn -			return dn+			return $ execState (parseCertHeaderDNHelper l) defdn  		_          -> throwError "Distinguished name bad format"  parseCertHeaderValidity :: ParseCert (Time, Time)@@ -249,21 +271,77 @@ 				SignatureALG_rsa                   -> parse_RSA bits 				_                                  -> PubKeyUnknown $ L.unpack bits 			return $ PubKey sig desc-		Sequence [ Sequence [ OID pkalg, Sequence [ IntVal dsaP, IntVal dsaQ, IntVal dsaG ]], BitString _ dsapub ] ->+		Sequence [ Sequence [ OID pkalg, Sequence [ IntVal dsaP, IntVal dsaQ, IntVal dsaG ]], BitString _ dsapubenc ] -> 			let sig = oidSig pkalg in-			return $ PubKey sig (PubKeyDSA (dsapub, dsaP, dsaQ, dsaG))+			case decodeASN1 dsapubenc of+				Right (IntVal dsapub) -> return $ PubKey sig (PubKeyDSA (dsapub, dsaP, dsaQ, dsaG))+				_                     -> throwError "unrecognized DSA pub format" 		_ -> 			throwError ("subject public key bad format : " ++ show n) -parseCertExtensions :: ParseCert (Maybe [ASN1])+-- RFC 5280+parseCertExtensionHelper :: [ASN1] -> State CertificateExts ()+parseCertExtensionHelper l = do+	forM_ (mapMaybe extractStruct l) $ \e -> case e of+		([2,5,29,14], critical, Right (OctetString x)) ->+			modify (\s -> s { certExtSubjectKeyIdentifier = Just (critical, L.unpack x) })+		{-+		([2,5,29,15], critical, Right (BitString _ _)) ->+			all the flags:+			digitalSignature        (0),+			nonRepudiation          (1), -- recent editions of X.509 have renamed this bit to contentCommitment+			keyEncipherment         (2),+			dataEncipherment        (3),+			keyAgreement            (4),+			keyCertSign             (5),+			cRLSign                 (6),+			encipherOnly            (7),+			decipherOnly            (8) }++			return ()+		-}+		([2,5,29,19], critical, Right (Sequence [Boolean True])) ->+			modify (\s -> s { certExtBasicConstraints = Just (critical, True) })+		{-+		([2,5,29,31], critical, obj) -> -- distributions points+			return ()+		([2,5,29,32], critical, obj) -> -- policies+			return ()+		([2,5,29,33], critical, obj) -> -- policies mapping+			return ()+		([2,5,29,35], critical, obj) -> -- authority key identifer+			return ()+		-}+		(oid, critical, Right obj)    ->+			modify (\s -> s { certExtOthers = (oid, critical, obj) : certExtOthers s })+		(_, True, Left _)             -> fail "critical extension not understood"+		(_, False, Left _)            -> return ()+	where+		extractStruct (Sequence [ OID oid, Boolean True, OctetString obj ]) = Just (oid, True, decodeASN1 obj)+		extractStruct (Sequence [ OID oid, OctetString obj ])               = Just (oid, False, decodeASN1 obj)+		extractStruct _                                                     = Nothing++parseCertExtensions :: ParseCert (Maybe CertificateExts) parseCertExtensions = do 	h <- hasNext 	if h 		then do 			n <- lookNext 			case n of-				Other Context 3 (Right l) -> getNext >> return (Just l)-				_                         -> return Nothing+				Other Context 3 (Right [Sequence l]) -> do+					_ <- getNext+					let def = CertificateExts+						{ certExtKeyUsage             = Nothing+						, certExtBasicConstraints     = Nothing+						, certExtSubjectKeyIdentifier = Nothing+						, certExtPolicies             = Nothing+						, certExtOthers               = []+						}+					return $ Just $ execState (parseCertExtensionHelper l) def+				Other Context 3 _                    ->+					throwError "certificate header bad format"+				_                                    ->+					return Nothing 		else return Nothing  {- | parse header structure of a x509 certificate. it contains@@ -339,8 +417,10 @@ encodePK (PubKey sig (PubKeyRSA (_, modulus, e))) = Sequence [ Sequence [ OID $ sigOID sig, Null ], BitString 0 bits ] 	where bits = encodeASN1 $ Sequence [ IntVal modulus, IntVal e ] -encodePK (PubKey sig (PubKeyDSA (bits, p, q, g))) = Sequence [ Sequence [ OID $ sigOID sig, dsaseq ], BitString 0 bits ]-	where dsaseq = Sequence [ IntVal p, IntVal q, IntVal g ]+encodePK (PubKey sig (PubKeyDSA (pub, p, q, g)))  = Sequence [ Sequence [ OID $ sigOID sig, dsaseq ], BitString 0 bits ]+	where+		dsaseq = Sequence [ IntVal p, IntVal q, IntVal g ]+		bits   = encodeASN1 $ IntVal pub  encodePK (PubKey sig (PubKeyUnknown l))           = Sequence [ Sequence [ OID $ sigOID sig, Null ], BitString 0 (L.pack l) ] @@ -348,7 +428,7 @@ encodeCertificateHeader cert = 	[ eVer, eSerial, eAlgId, eIssuer, eValidity, eSubject, epkinfo ] ++ others 	where-		eVer      = Other Application 0 (Right [ IntVal (fromIntegral $ certVersion cert) ])+		eVer      = Other Context 0 (Right [ IntVal (fromIntegral $ certVersion cert) ]) 		eSerial   = IntVal $ certSerial cert 		eAlgId    = Sequence [ OID (sigOID $ certSignatureAlg cert), Null ] 		eIssuer   = encodeDN $ certIssuerDN cert
certificate.cabal view
@@ -1,5 +1,5 @@ Name:                certificate-Version:             0.2.1+Version:             0.3 Description:     Certificates and Key reader/writer     .