module Data.X509.Ext
( Extension(..)
, ExtBasicConstraints(..)
, ExtKeyUsage(..)
, ExtKeyUsageFlag(..)
, ExtExtendedKeyUsage(..)
, ExtKeyUsagePurpose(..)
, ExtSubjectKeyId(..)
, ExtSubjectAltName(..)
, ExtAuthorityKeyId(..)
, ExtCrlDistributionPoints(..)
, AltName(..)
, DistributionPoint(..)
, ReasonFlag(..)
, extensionGet
, extensionDecode
, extensionEncode
) where
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import Data.ASN1.Types
import Data.ASN1.Parse
import Data.ASN1.BitArray
import Data.List (find)
import Data.X509.ExtensionRaw
import Data.X509.DistinguishedName
import Control.Applicative
import Control.Monad.Error
data ExtKeyUsageFlag =
KeyUsage_digitalSignature
| KeyUsage_nonRepudiation
| KeyUsage_keyEncipherment
| KeyUsage_dataEncipherment
| KeyUsage_keyAgreement
| KeyUsage_keyCertSign
| KeyUsage_cRLSign
| KeyUsage_encipherOnly
| KeyUsage_decipherOnly
deriving (Show,Eq,Ord,Enum)
class Extension a where
extOID :: a -> OID
extEncode :: a -> [ASN1]
extDecode :: [ASN1] -> Either String a
extensionGet :: Extension a => Extensions -> Maybe a
extensionGet (Extensions Nothing) = Nothing
extensionGet (Extensions (Just l)) = findExt l
where findExt [] = Nothing
findExt (x:xs) = case extensionDecode x of
Just (Right e) -> Just e
_ -> findExt xs
extensionDecode :: Extension a => ExtensionRaw -> Maybe (Either String a)
extensionDecode = doDecode undefined
where doDecode :: Extension a => a -> ExtensionRaw -> Maybe (Either String a)
doDecode dummy (ExtensionRaw oid _ asn1)
| extOID dummy == oid = Just (extDecode asn1)
| otherwise = Nothing
extensionEncode :: Extension a => Bool -> a -> ExtensionRaw
extensionEncode critical ext = ExtensionRaw (extOID ext) critical (extEncode ext)
data ExtBasicConstraints = ExtBasicConstraints Bool (Maybe Integer)
deriving (Show,Eq)
instance Extension ExtBasicConstraints where
extOID = const [2,5,29,19]
extEncode (ExtBasicConstraints b Nothing) = [Start Sequence,Boolean b,End Sequence]
extEncode (ExtBasicConstraints b (Just i)) = [Start Sequence,Boolean b,IntVal i,End Sequence]
extDecode [Start Sequence,Boolean b,IntVal v,End Sequence]
| v >= 0 = Right (ExtBasicConstraints b (Just v))
| otherwise = Left "invalid pathlen"
extDecode [Start Sequence,Boolean b,End Sequence] = Right (ExtBasicConstraints b Nothing)
extDecode [Start Sequence,End Sequence] = Right (ExtBasicConstraints False Nothing)
extDecode _ = Left "unknown sequence"
data ExtKeyUsage = ExtKeyUsage [ExtKeyUsageFlag]
deriving (Show,Eq)
instance Extension ExtKeyUsage where
extOID = const [2,5,29,15]
extEncode (ExtKeyUsage flags) = [BitString $ flagsToBits flags]
extDecode [BitString bits] = Right $ ExtKeyUsage $ bitsToFlags bits
extDecode _ = Left "unknown sequence"
data ExtKeyUsagePurpose =
KeyUsagePurpose_ServerAuth
| KeyUsagePurpose_ClientAuth
| KeyUsagePurpose_CodeSigning
| KeyUsagePurpose_EmailProtection
| KeyUsagePurpose_TimeStamping
| KeyUsagePurpose_OCSPSigning
| KeyUsagePurpose_Unknown OID
deriving (Show,Eq,Ord)
extKeyUsagePurposedOID :: [(OID, ExtKeyUsagePurpose)]
extKeyUsagePurposedOID =
[(keyUsagePurposePrefix 1, KeyUsagePurpose_ServerAuth)
,(keyUsagePurposePrefix 2, KeyUsagePurpose_ClientAuth)
,(keyUsagePurposePrefix 3, KeyUsagePurpose_CodeSigning)
,(keyUsagePurposePrefix 4, KeyUsagePurpose_EmailProtection)
,(keyUsagePurposePrefix 8, KeyUsagePurpose_TimeStamping)
,(keyUsagePurposePrefix 9, KeyUsagePurpose_OCSPSigning)]
where keyUsagePurposePrefix r = [1,3,6,1,5,5,7,3,r]
data ExtExtendedKeyUsage = ExtExtendedKeyUsage [ExtKeyUsagePurpose]
deriving (Show,Eq)
instance Extension ExtExtendedKeyUsage where
extOID = const [2,5,29,37]
extEncode (ExtExtendedKeyUsage purposes) =
[Start Sequence] ++ map (OID . lookupRev) purposes ++ [End Sequence]
where lookupRev (KeyUsagePurpose_Unknown oid) = oid
lookupRev kup = maybe (error "unknown key usage purpose") fst $ find ((==) kup . snd) extKeyUsagePurposedOID
extDecode l = ExtExtendedKeyUsage `fmap` (flip runParseASN1 l $ onNextContainer Sequence $ getMany $ do
n <- getNext
case n of
OID o -> return $ maybe (KeyUsagePurpose_Unknown o) id $ lookup o extKeyUsagePurposedOID
_ -> error "invalid content in extended key usage")
data ExtSubjectKeyId = ExtSubjectKeyId B.ByteString
deriving (Show,Eq)
instance Extension ExtSubjectKeyId where
extOID = const [2,5,29,14]
extEncode (ExtSubjectKeyId o) = [OctetString o]
extDecode [OctetString o] = Right $ ExtSubjectKeyId o
extDecode _ = Left "unknown sequence"
data AltName =
AltNameRFC822 String
| AltNameDNS String
| AltNameURI String
| AltNameIP B.ByteString
deriving (Show,Eq,Ord)
data ExtSubjectAltName = ExtSubjectAltName [AltName]
deriving (Show,Eq,Ord)
instance Extension ExtSubjectAltName where
extOID = const [2,5,29,17]
extEncode (ExtSubjectAltName names) = encodeGeneralNames names
extDecode l = runParseASN1 (ExtSubjectAltName <$> parseGeneralNames) l
data ExtAuthorityKeyId = ExtAuthorityKeyId B.ByteString
deriving (Show,Eq)
instance Extension ExtAuthorityKeyId where
extOID _ = [2,5,29,35]
extEncode (ExtAuthorityKeyId keyid) =
[Start Sequence,Other Context 0 keyid,End Sequence]
extDecode [Start Sequence,Other Context 0 keyid,End Sequence] =
Right $ ExtAuthorityKeyId keyid
extDecode _ = Left "unknown sequence"
data ExtCrlDistributionPoints = ExtCrlDistributionPoints [DistributionPoint]
deriving (Show,Eq)
data ReasonFlag =
Reason_Unused
| Reason_KeyCompromise
| Reason_CACompromise
| Reason_AffiliationChanged
| Reason_Superseded
| Reason_CessationOfOperation
| Reason_CertificateHold
| Reason_PrivilegeWithdrawn
| Reason_AACompromise
deriving (Show,Eq,Ord,Enum)
data DistributionPoint =
DistributionPointFullName [AltName]
| DistributionNameRelative DistinguishedName
deriving (Show,Eq)
instance Extension ExtCrlDistributionPoints where
extOID _ = [2,5,29,31]
extEncode = error "extEncode ExtCrlDistributionPoints unimplemented"
extDecode = error "extDecode ExtCrlDistributionPoints unimplemented"
parseGeneralNames :: ParseASN1 [AltName]
parseGeneralNames = do
c <- getNextContainer Sequence
r <- sequence $ map toStringy c
return r
where
toStringy (Other Context 1 b) = return $ AltNameRFC822 $ BC.unpack b
toStringy (Other Context 2 b) = return $ AltNameDNS $ BC.unpack b
toStringy (Other Context 6 b) = return $ AltNameURI $ BC.unpack b
toStringy (Other Context 7 b) = return $ AltNameIP b
toStringy b = throwError ("GeneralNames: not coping with anything else " ++ show b)
encodeGeneralNames :: [AltName] -> [ASN1]
encodeGeneralNames names =
[Start Sequence]
++ map encodeAltName names
++ [End Sequence]
where encodeAltName (AltNameRFC822 n) = Other Context 1 $ BC.pack n
encodeAltName (AltNameDNS n) = Other Context 2 $ BC.pack n
encodeAltName (AltNameURI n) = Other Context 6 $ BC.pack n
encodeAltName (AltNameIP n) = Other Context 7 $ n
bitsToFlags :: Enum a => BitArray -> [a]
bitsToFlags bits = concat $ flip map [0..(bitArrayLength bits-1)] $ \i -> do
let isSet = bitArrayGetBit bits i
if isSet then [toEnum $ fromIntegral i] else []
flagsToBits :: Enum a => [a] -> BitArray
flagsToBits flags = foldl bitArraySetBit bitArrayEmpty $ map (fromIntegral . fromEnum) flags
where bitArrayEmpty = toBitArray (B.pack [0,0]) 7