content: -- Copyright (C) 2013  Fraser Tweedale
  --
  -- Licensed under the Apache License, Version 2.0 (the "License");
  -- you may not use this file except in compliance with the License.
  -- You may obtain a copy of the License at
  --
  --      http://www.apache.org/licenses/LICENSE-2.0
  --
  -- Unless required by applicable law or agreed to in writing, software
  -- distributed under the License is distributed on an "AS IS" BASIS,
  -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  -- See the License for the specific language governing permissions and
  -- limitations under the License.
  
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE TemplateHaskell #-}
  
  {-|
  
  JSON Web Encryption data types specified under JSON Web Algorithms.
  
  -}
  module Crypto.JOSE.JWA.JWE
    ( Enc(..)
    , AlgWithParams(..)
    , AESGCMParameters(AESGCMParameters)
    , ECDHParameters(ECDHParameters)
    , PBES2Parameters(PBES2Parameters)
    ) where
  
  import Data.Maybe (catMaybes)
  
  import Crypto.JOSE.JWK
  import Crypto.JOSE.TH
  import Crypto.JOSE.Types
  import Crypto.JOSE.Types.Internal (insertToObject)
  
  import Data.Aeson
  import qualified Data.Aeson.KeyMap as M
  
  
  -- | RFC 7518 §4.  Cryptographic Algorithms for Key Management
  --
  data AlgWithParams
    = RSA1_5
    | RSA_OAEP
    | RSA_OAEP_256
    | A128KW
    | A192KW
    | A256KW
    | Dir
    | ECDH_ES ECDHParameters
    | ECDH_ES_A128KW ECDHParameters
    | ECDH_ES_A192KW ECDHParameters
    | ECDH_ES_A256KW ECDHParameters
    | A128GCMKW AESGCMParameters
    | A192GCMKW AESGCMParameters
    | A256GCMKW AESGCMParameters
    | PBES2_HS256_A128KW PBES2Parameters
    | PBES2_HS384_A192KW PBES2Parameters
    | PBES2_HS512_A256KW PBES2Parameters
    deriving (Eq, Show)
  
  instance FromJSON AlgWithParams where
    parseJSON = withObject "Encryption alg and params" $ \o ->
      case M.lookup "alg" o of
        Nothing -> fail "\"alg\" parameter is required"
        Just "RSA1_5"             -> pure RSA1_5
        Just "RSA-OAEP"           -> pure RSA_OAEP
        Just "RSA-OAEP-256"       -> pure RSA_OAEP_256
        Just "A128KW"             -> pure A128KW
        Just "A192KW"             -> pure A192KW
        Just "A256KW"             -> pure A256KW
        Just "dir"                -> pure Dir
        Just "ECDH-ES"            -> ECDH_ES            <$> parseJSON (Object o)
        Just "ECDH-ES+A128KW"     -> ECDH_ES_A128KW     <$> parseJSON (Object o)
        Just "ECDH-ES+A192KW"     -> ECDH_ES_A192KW     <$> parseJSON (Object o)
        Just "ECDH-ES+A256KW"     -> ECDH_ES_A256KW     <$> parseJSON (Object o)
        Just "A128GCMKW"          -> A128GCMKW          <$> parseJSON (Object o)
        Just "A192GCMKW"          -> A192GCMKW          <$> parseJSON (Object o)
        Just "A256GCMKW"          -> A256GCMKW          <$> parseJSON (Object o)
        Just "PBES2-HS256+A128KW" -> PBES2_HS256_A128KW <$> parseJSON (Object o)
        Just "PBES2-HS384+A192KW" -> PBES2_HS384_A192KW <$> parseJSON (Object o)
        Just "PBES2-HS512+A256KW" -> PBES2_HS512_A256KW <$> parseJSON (Object o)
        _ -> fail $ "unrecognised value; expected: "
           ++ "[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"A128KW\",\"A192KW\",\"A256KW\",\"dir\",\"ECDH-ES\",\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128GCMKW\",\"A192GCMKW\",\"A256GCMKW\",\"PBES2-HS256+A128KW\",\"PBES2-HS384+A128KW\",\"PBES2-HS512+A128KW\"]"
  
  algObject :: Value -> Value
  algObject s = object [("alg", s)]
  
  algWithParamsObject :: ToJSON a => a -> Value -> Value
  algWithParamsObject a s = insertToObject "alg" s (toJSON a)
  
  instance ToJSON AlgWithParams where
    toJSON RSA1_5       = algObject "RSA1_5"
    toJSON RSA_OAEP     = algObject "RSA-OAEP"
    toJSON RSA_OAEP_256 = algObject "RSA-OAEP-256"
    toJSON A128KW       = algObject "A128KW"
    toJSON A192KW       = algObject "A192KW"
    toJSON A256KW       = algObject "A256KW"
    toJSON Dir          = algObject "Dir"
    toJSON (ECDH_ES params)             = algWithParamsObject params "ECDH-ES"
    toJSON (ECDH_ES_A128KW params)      = algWithParamsObject params "ECDH-ES+A128KW"
    toJSON (ECDH_ES_A192KW params)      = algWithParamsObject params "ECDH-ES+A192KW"
    toJSON (ECDH_ES_A256KW params)      = algWithParamsObject params "ECDH-ES+A256KW"
    toJSON (A128GCMKW params)           = algWithParamsObject params "A128GCMKW"
    toJSON (A192GCMKW params)           = algWithParamsObject params "A192GCMKW"
    toJSON (A256GCMKW params)           = algWithParamsObject params "A256GCMKW"
    toJSON (PBES2_HS256_A128KW params)  = algWithParamsObject params "PBES2-HS256+A128KW"
    toJSON (PBES2_HS384_A192KW params)  = algWithParamsObject params "PBES2-HS384+A192KW"
    toJSON (PBES2_HS512_A256KW params)  = algWithParamsObject params "PBES2-HS512+A256KW"
  
  
  -- | RFC 7518 §4.6.1.  Header Parameters Used for ECDH Key Agreement
  --
  data ECDHParameters = ECDHParameters
    { _epk :: JWK                 -- ^ Ephemeral Public Key ; a JWK PUBLIC key
    , _apu :: Maybe Base64Octets  -- ^ Agreement PartyUInfo
    , _apv :: Maybe Base64Octets  -- ^ Agreement PartyVInfo
    } deriving (Eq, Show)
  
  instance FromJSON ECDHParameters where
    parseJSON = withObject "ECDH Parameters" $ \o -> ECDHParameters
      <$> o .: "epk"
      <*> o .:? "apu"
      <*> o .:? "apv"
  
  instance ToJSON ECDHParameters where
    toJSON (ECDHParameters epk apu apv) = object $ catMaybes
      [ Just ("epk" .= epk)
      , fmap ("apu" .=) apu
      , fmap ("apu" .=) apv
      ]
  
  
  -- | RFC 7518 §4.7.1.  Header Parameters Used for AES GCM Key Encryption
  --
  data AESGCMParameters = AESGCMParameters
    { _iv :: Base64Octets  -- ^ Initialization Vector  (must be 96 bits?)
    , _tag :: Base64Octets -- ^ Authentication Tag (must be 128 bits?)
    } deriving (Eq, Show)
  
  instance FromJSON AESGCMParameters where
    parseJSON = withObject "AES-GCM Parameters" $ \o -> AESGCMParameters
      <$> o .: "iv"
      <*> o .: "tag"
  
  instance ToJSON AESGCMParameters where
    toJSON (AESGCMParameters iv tag) = object ["iv" .= iv, "tag" .= tag]
  
  
  -- | RFC 7518 §4.8.1.  Header Parameters Used for PBES2 Key Encryption
  --
  data PBES2Parameters =  PBES2Parameters
    { _p2s :: Base64Octets   -- ^ PBKDF2 salt input
    , _p2c :: Int            -- ^ PBKDF2 iteration count ; POSITIVE integer
    } deriving (Eq, Show)
  
  instance FromJSON PBES2Parameters where
    parseJSON = withObject "AES-GCM Parameters" $ \o -> PBES2Parameters
      <$> o .: "p2s"  -- TODO salt input value must be >= 8 octets
      <*> o .: "p2c"
  
  instance ToJSON PBES2Parameters where
    toJSON (PBES2Parameters p2s p2c) = object ["p2s" .= p2s, "p2c" .= p2c]
  
  
  -- | RFC 7518 §5  Cryptographic Algorithms for Content Encryption
  --
  $(deriveJOSEType "Enc" [
    "A128CBC-HS256"   -- AES HMAC SHA authenticated encryption  Required
    , "A192CBC-HS384" -- AES HMAC SHA authenticated encryption  Optional
    , "A256CBC-HS512" -- AES HMAC SHA authenticated encryption  Required
    , "A128GCM"       -- AES in Galois/Counter Mode             Recommended
    , "A192GCM"       -- AES in Galois/Counter Mode             Optional
    , "A256GCM"       -- AES in Galois/Counter Mode             Recommended
    ])
  