-- |
-- Module      : Data.X509.Signed
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Exposes helpers for X509 certificate and revocation list, signed structures.
--
-- Signed structures are of the form:
--      Sequence {
--          object              a
--          signatureAlgorithm  AlgorithmIdentifier
--          signatureValue      BitString
--      }
--
-- Unfortunately as lots of signed objects published have been signed on an
-- arbitrary BER ASN1 encoding (instead of using the unique DER encoding) or in
-- a non-valid DER implementation, we need to keep the raw data being signed,
-- as we can't recompute the bytestring used to sign for non compliant cases.
--
-- Signed represent the pure data type for compliant cases, and SignedExact
-- the real world situation of having to deal with compliant and non-compliant cases.
--
module Data.X509.Signed
    (
    -- * Types
      Signed(..)
    , SignedExact
    -- * SignedExact to Signed
    , getSigned
    , getSignedData
    -- * Marshalling function
    , encodeSignedObject
    , decodeSignedObject
    -- * Object to Signed and SignedExact
    , objectToSignedExact
    , objectToSigned
    , signedToExact
    ) where

import Control.Arrow (first)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.X509.AlgorithmIdentifier
import Data.ASN1.Types
import Data.ASN1.Encoding
import Data.ASN1.BinaryEncoding
import Data.ASN1.Stream
import Data.ASN1.BitArray
import qualified Data.ASN1.BinaryEncoding.Raw as Raw (toByteString)

-- | Represent a signed object using a traditional X509 structure.
--
-- When dealing with external certificate, use the SignedExact structure
-- not this one.
data (Show a, Eq a, ASN1Object a) => Signed a = Signed
    { signedObject    :: a            -- ^ Object to sign
    , signedAlg       :: SignatureALG -- ^ Signature Algorithm used
    , signedSignature :: B.ByteString -- ^ Signature as bytes
    } deriving (Show, Eq)

-- | Represent the signed object plus the raw data that we need to
-- keep around for non compliant case to be able to verify signature.
data (Show a, Eq a, ASN1Object a) => SignedExact a = SignedExact
    { getSigned          :: Signed a     -- ^ get the decoded Signed data
    , exactObjectRaw     :: B.ByteString -- ^ The raw representation of the object a
                                         -- TODO: in later version, replace with offset in exactRaw
    , encodeSignedObject :: B.ByteString -- ^ The raw representation of the whole signed structure
    } deriving (Show, Eq)

-- | Get the signed data for the signature
getSignedData :: (Show a, Eq a, ASN1Object a)
              => SignedExact a
              -> B.ByteString
getSignedData = exactObjectRaw

-- | make a 'SignedExact' copy of a 'Signed' object
--
-- As the signature is already generated, expect the
-- encoded object to have been made on a compliant DER ASN1 implementation.
--
-- It's better to use 'objectToSignedExact' instead of this.
signedToExact :: (Show a, Eq a, ASN1Object a)
              => Signed a
              -> SignedExact a
signedToExact signed = sExact
  where (sExact, ())      = objectToSignedExact fakeSigFunction (signedObject signed)
        fakeSigFunction _ = (signedSignature signed, signedAlg signed, ())

-- | Transform an object into a 'SignedExact' object
objectToSignedExact :: (Show a, Eq a, ASN1Object a)
                    => (ByteString -> (ByteString, SignatureALG, r)) -- ^ signature function
                    -> a                                             -- ^ object to sign
                    -> (SignedExact a, r)
objectToSignedExact signatureFunction object = (SignedExact signed objRaw signedRaw, r)
  where signed     = Signed { signedObject    = object
                            , signedAlg       = sigAlg
                            , signedSignature = sigBits
                            }
        signedRaw  = encodeASN1' DER signedASN1
        signedASN1 = Start Sequence
                       : objASN1
                       (toASN1 sigAlg
                       (BitString (toBitArray sigBits 0)
                   : End Sequence
                   : []))
        objASN1            = \xs -> Start Sequence : toASN1 object (End Sequence : xs)
        objRaw             = encodeASN1' DER (objASN1 [])
        (sigBits,sigAlg,r) = signatureFunction objRaw

-- | Transform an object into a 'Signed' object.
--
-- It's recommended to use the SignedExact object instead of Signed.
objectToSigned :: (Show a, Eq a, ASN1Object a)
               => (ByteString
               -> (ByteString, SignatureALG, r))
               -> a
               -> (Signed a, r)
objectToSigned signatureFunction object = first getSigned $ objectToSignedExact signatureFunction object

-- | Try to parse a bytestring that use the typical X509 signed structure format
decodeSignedObject :: (Show a, Eq a, ASN1Object a)
                   => ByteString
                   -> Either String (SignedExact a)
decodeSignedObject b = either (Left . show) parseSigned $ decodeASN1Repr' BER b
  where -- the following implementation is very inefficient.
        -- uses reverse and containing, move to a better solution eventually
        parseSigned l = onContainer (fst $ getConstructedEndRepr l) $ \l2 ->
            let (objRepr,rem1)   = getConstructedEndRepr l2
                (sigAlgSeq,rem2) = getConstructedEndRepr rem1
                (sigSeq,_)       = getConstructedEndRepr rem2
                obj              = onContainer objRepr (either Left Right . fromASN1 . map fst)
             in case (obj, map fst sigSeq) of
                    (Right (o,[]), [BitString signature]) ->
                        let rawObj = Raw.toByteString $ concatMap snd objRepr
                         in case fromASN1 $ map fst sigAlgSeq of
                                Left s           -> Left ("signed object error sigalg: " ++ s)
                                Right (sigAlg,_) ->
                                    let signed = Signed
                                                    { signedObject    = o
                                                    , signedAlg       = sigAlg
                                                    , signedSignature = bitArrayGetData signature
                                                    }
                                     in Right $ SignedExact
                                                { getSigned          = signed
                                                , exactObjectRaw     = rawObj
                                                , encodeSignedObject = b
                                                }
                    (Right (_,remObj), _) ->
                        Left $ ("signed object error: remaining stream in object: " ++ show remObj)
                    (Left err, _) -> Left $ ("signed object error: " ++ show err)
        onContainer ((Start _, _) : l) f =
            case reverse l of
                ((End _, _) : l2) -> f $ reverse l2
                _                 -> f []
        onContainer _ f = f []