module Data.Certificate.X509Cert
(
SignatureALG(..)
, PubKeyALG(..)
, PubKey(..)
, ASN1StringType(..)
, ASN1String
, Certificate(..)
, CertificateExts(..)
, oidCommonName
, oidCountry
, oidOrganization
, oidOrganizationUnit
, oidSig
, sigOID
, parseCertificate
, encodeCertificateHeader
) where
import Data.Word
import Data.List (find)
import Data.ASN1.DER
import Data.Maybe
import Data.Time.Calendar
import Data.Time.Clock (DiffTime, secondsToDiffTime)
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L
import Control.Monad.State
import Control.Monad.Error
import Data.Certificate.X509Internal
type OID = [Integer]
data SignatureALG =
SignatureALG_md5WithRSAEncryption
| SignatureALG_md2WithRSAEncryption
| SignatureALG_sha1WithRSAEncryption
| SignatureALG_dsaWithSHA1
| SignatureALG_ecdsaWithSHA384
| SignatureALG_Unknown OID
deriving (Show, Eq)
data PubKeyALG =
PubKeyALG_RSA
| PubKeyALG_DSA
| PubKeyALG_ECDSA
| PubKeyALG_DH
| PubKeyALG_Unknown OID
deriving (Show,Eq)
data PubKey =
PubKeyRSA (Int,Integer,Integer)
| PubKeyDSA (Integer,Integer,Integer,Integer)
| PubKeyDH (Integer,Integer,Integer,Maybe Integer,([Word8], Integer))
| PubKeyECDSA [ASN1]
| PubKeyUnknown OID [Word8]
deriving (Show,Eq)
type Time = (Day, DiffTime, Bool)
data CertKeyUsage =
CertKeyUsageDigitalSignature
| CertKeyUsageNonRepudiation
| CertKeyUsageKeyEncipherment
| CertKeyUsageDataEncipherment
| CertKeyUsageKeyAgreement
| CertKeyUsageKeyCertSign
| CertKeyUsageCRLSign
| CertKeyUsageEncipherOnly
| CertKeyUsageDecipherOnly
deriving (Show, Eq)
data ASN1StringType = UTF8 | Printable | Univ | BMP | IA5 | T61 deriving (Show,Eq)
type ASN1String = (ASN1StringType, String)
data Certificate = Certificate
{ certVersion :: Int
, certSerial :: Integer
, certSignatureAlg :: SignatureALG
, certIssuerDN :: [ (OID, ASN1String) ]
, certSubjectDN :: [ (OID, ASN1String) ]
, certValidity :: (Time, Time)
, certPubKey :: PubKey
, certExtensions :: Maybe CertificateExts
} 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)
oidCommonName, oidCountry, oidOrganization, oidOrganizationUnit :: OID
oidCommonName = [2,5,4,3]
oidCountry = [2,5,4,6]
oidOrganization = [2,5,4,10]
oidOrganizationUnit = [2,5,4,11]
parse_RSA :: ByteString -> ParseASN1 PubKey
parse_RSA bits =
case decodeASN1Stream $ bits of
Right [Start Sequence, IntVal modulus, IntVal pubexp, End Sequence] ->
return $ PubKeyRSA (calculate_modulus modulus 1, modulus, pubexp)
_ ->
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 -> ParseASN1 PubKey
parse_ECDSA bits =
case decodeASN1Stream bits of
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
n <- getNext
case n of
IntVal v -> return $ fromIntegral v
_ -> throwError "unexpected type for version"
return $ maybe 1 id v
parseCertHeaderSerial :: ParseASN1 Integer
parseCertHeaderSerial = do
n <- getNext
case n of
IntVal v -> return v
_ -> throwError ("missing serial" ++ show n)
sig_table :: [ (OID, SignatureALG) ]
sig_table =
[ ([1,2,840,113549,1,1,5], SignatureALG_sha1WithRSAEncryption)
, ([1,2,840,113549,1,1,4], SignatureALG_md5WithRSAEncryption)
, ([1,2,840,113549,1,1,2], SignatureALG_md2WithRSAEncryption)
, ([1,2,840,10040,4,3], SignatureALG_dsaWithSHA1)
, ([1,2,840,10045,4,3,3], SignatureALG_ecdsaWithSHA384)
]
pk_table :: [ (OID, PubKeyALG) ]
pk_table =
[ ([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
oidSig oid = maybe (SignatureALG_Unknown oid) snd $ find ((==) oid . fst) sig_table
oidPubKey :: OID -> PubKeyALG
oidPubKey oid = maybe (PubKeyALG_Unknown oid) snd $ find ((==) oid . fst) pk_table
sigOID :: SignatureALG -> OID
sigOID (SignatureALG_Unknown oid) = oid
sigOID sig = maybe [] fst $ find ((==) sig . snd) sig_table
pubkeyalgOID :: PubKeyALG -> OID
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
case n of
[ OID oid, Null ] -> return $ oidSig oid
[ OID oid ] -> return $ oidSig oid
_ -> throwError ("algorithm ID bad format " ++ show n)
asn1String :: ASN1 -> ASN1String
asn1String (PrintableString x) = (Printable, x)
asn1String (UTF8String x) = (UTF8, x)
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
encodeAsn1String (Printable, x) = PrintableString x
encodeAsn1String (UTF8, x) = UTF8String x
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
onNextContainer Sequence getDNs
where
getDNs = do
n <- hasNext
if n
then do
dn <- parseDNOne
liftM (dn :) getDNs
else return []
parseDNOne = onNextContainer Set $ do
s <- getNextContainer Sequence
case s of
[OID oid, val] -> return (oid, asn1String val)
_ -> throwError "expecting sequence"
parseCertHeaderValidity :: ParseASN1 (Time, Time)
parseCertHeaderValidity = do
n <- getNextContainer Sequence
case n of
[ UTCTime t1, UTCTime t2 ] -> return (convertTime t1, convertTime t2)
_ -> throwError "bad validity format"
where convertTime (y,m,d,h,mi,s,u) =
let day = fromGregorian (fromIntegral y) m d in
let dtime = secondsToDiffTime (fromIntegral h * 3600 + fromIntegral mi * 60 + fromIntegral s) in
(day, dtime, u)
parseCertHeaderSubjectPK :: ParseASN1 PubKey
parseCertHeaderSubjectPK = onNextContainer Sequence $ do
l <- getNextContainer Sequence
bits <- getNextBitString
case l of
[OID pkalg,Null] -> do
let sig = oidPubKey pkalg
case sig of
PubKeyALG_RSA -> parse_RSA bits
_ -> return $ PubKeyUnknown pkalg $ L.unpack bits
[OID pkalg,OID _] -> do
let sig = oidPubKey pkalg
case sig of
PubKeyALG_ECDSA -> parse_ECDSA bits
_ -> 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 $ PubKeyDSA (dsapub, p, q, g)
_ -> return $ PubKeyUnknown pkalg $ L.unpack bits
n ->
throwError ("subject public key bad format : " ++ show n)
where getNextBitString = getNext >>= \bs -> case bs of
BitString _ bits -> return bits
_ -> throwError "expecting bitstring"
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,19], critical, Right [Start Sequence, Boolean True, End Sequence]) ->
modify (\s -> s { certExtBasicConstraints = Just (critical, True) })
(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 [OID oid,Boolean True,OctetString obj] = Just (oid, True, decodeASN1Stream obj)
extractStruct [OID oid,OctetString obj] = Just (oid, False, decodeASN1Stream obj)
extractStruct _ = Nothing
parseCertExtensions :: ParseASN1 (Maybe CertificateExts)
parseCertExtensions = do
onNextContainerMaybe (Container Context 3) $ do
let def = CertificateExts
{ certExtKeyUsage = Nothing
, certExtBasicConstraints = Nothing
, certExtSubjectKeyIdentifier = Nothing
, certExtPolicies = Nothing
, certExtOthers = []
}
l <- getNextContainer Sequence
return $ execState (parseCertExtensionHelper $ makeASN1Sequence l) def
parseCertificate :: ParseASN1 Certificate
parseCertificate = do
version <- parseCertHeaderVersion
serial <- parseCertHeaderSerial
sigalg <- parseCertHeaderAlgorithmID
issuer <- parseCertHeaderDN
validity <- parseCertHeaderValidity
subject <- parseCertHeaderDN
pk <- parseCertHeaderSubjectPK
exts <- parseCertExtensions
hnext <- hasNext
when hnext $ throwError "expecting End Of Data."
return $ Certificate
{ certVersion = version
, certSerial = serial
, certSignatureAlg = sigalg
, certIssuerDN = issuer
, certSubjectDN = subject
, certValidity = validity
, certPubKey = pk
, certExtensions = exts
}
encodeDN :: [ (OID, ASN1String) ] -> [ASN1]
encodeDN dn = asn1Container Sequence $ concatMap dnSet dn
where
dnSet (oid, stringy) = asn1Container Set (asn1Container Sequence [OID oid, encodeAsn1String stringy])
encodePK :: PubKey -> [ASN1]
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 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 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 =
eVer ++ eSerial ++ eAlgId ++ eIssuer ++ eValidity ++ eSubject ++ epkinfo ++ eexts
where
eVer = asn1Container (Container Context 0) [IntVal (fromIntegral $ certVersion cert)]
eSerial = [IntVal $ certSerial cert]
eAlgId = asn1Container Sequence [OID (sigOID $ certSignatureAlg cert), Null]
eIssuer = encodeDN $ certIssuerDN cert
(t1, t2) = certValidity cert
eValidity = asn1Container Sequence [UTCTime $ unconvertTime t1, UTCTime $ unconvertTime t2]
eSubject = encodeDN $ certSubjectDN cert
epkinfo = encodePK $ certPubKey cert
eexts = []
unconvertTime (day, difftime, z) =
let (y, m, d) = toGregorian day in
let seconds = floor $ toRational difftime in
let h = seconds `div` 3600 in
let mi = (seconds `div` 60) `mod` 60 in
let s = seconds `mod` 60 in
(fromIntegral y,m,d,h,mi,s,z)