x509-1.4.2: Data/X509/Signed.hs
-- |
-- 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 . fst) . 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
}
(Left err, _) -> Left $ ("signed object error: " ++ show err)
_ -> Left $ "signed object structure error"
onContainer ((Start _, _) : l) f =
case reverse l of
((End _, _) : l2) -> f $ reverse l2
_ -> f []
onContainer _ f = f []