content: -- |Tor defines a form of hybrid crypto, in which data is either just encrypted
  -- with a public key, or, encrypted using a hybrid of RSA and AES. This module
  -- implements this technique.
  module Tor.HybridCrypto(
           hybridEncrypt
         , hybridDecrypt
         )
   where
  
  import Crypto.Cipher.AES
  import Crypto.Cipher.Types
  import Crypto.Error
  import Crypto.Hash.Algorithms
  import Crypto.PubKey.MaskGenFunction
  import Crypto.PubKey.RSA.OAEP
  import Crypto.PubKey.RSA.Types
  import Crypto.Random
  import Data.ByteString
  import Prelude hiding (length, splitAt)
  
  -- |Encrypt a piece of data using the given public key, optionally forcing the
  -- routine to use hybrid encryption even if the size of the data doesn't warrant
  -- it.
  hybridEncrypt :: MonadRandom m =>
                   Bool -> PublicKey -> ByteString ->
                   m ByteString
  hybridEncrypt force pubkey m
    | not force && (length m < (128 - 42)) = -- PK_ENC_LEN - PK_PAD_LEN
        failLeft (encrypt oaepParams pubkey m)
    | otherwise =
        do -- Generate a KEY_LEN byte random key K;
           kbs <- getRandomBytes 16
           -- let M1 = the first PK_ENC_LEN - PK_PAD_LEN - KEY_LEN bytes of M
           -- and let M2 = the rest of M.
           let (m1, m2) = splitAt (128 - 42 - 16) m
           -- pad and encrypt K|M1 with PK
           ekm1 <- failLeft (encrypt oaepParams pubkey (kbs `append` m1))
           -- encrypt M2 with our stream cipher, using the key K
           let key = throwCryptoError (cipherInit kbs) :: AES128
               em2 = ctrCombine key nullIV m2
           return (ekm1 `append` em2)
  
  -- |Decrypt a piece of data using the given private key.
  hybridDecrypt :: MonadRandom m =>
                   PrivateKey -> ByteString ->
                   m ByteString
  hybridDecrypt privKey em
    | length em <= fromIntegral (private_size privKey) =
        failLeft (decryptSafer oaepParams privKey em)
    | otherwise =
        do let (ekm1, em2) = splitAt (fromIntegral (private_size privKey)) em
           km1 <- failLeft (decryptSafer oaepParams privKey ekm1)
           let (kbs, m1) = splitAt 16 km1
               key       = throwCryptoError (cipherInit kbs) :: AES128
               m2        = ctrCombine key nullIV em2
           return (m1 `append` m2)
  
  oaepParams :: OAEPParams SHA1 ByteString ByteString
  oaepParams = OAEPParams SHA1 (mgf1 SHA1) Nothing
  
  failLeft :: (Show a, Monad m) => m (Either a b) -> m b
  failLeft action =
    do v <- action
       case v of
         Left err ->
           fail ("Received unexpected left value (HybridCrypto): " ++ show err)
         Right x  -> return x
  