{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}

module Network.AWS.CloudFront.SignedCookies.Crypto
  (
  -- * Reading the private key
    readPrivateKeyPemFile

  -- * Generating signatures
  , sign

  -- * Types
  , PrivateKey
  , ByteString

  ) where

import Network.AWS.CloudFront.SignedCookies.Crypto.Internal
import Network.AWS.CloudFront.SignedCookies.Types

-- asn1-encoding
import Data.ASN1.BinaryEncoding (DER (DER))
import Data.ASN1.Encoding (decodeASN1')
import Data.ASN1.Error (ASN1Error)

-- asn1-types
import Data.ASN1.Types (ASN1)

-- bytestring
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS

-- cryptonite
import Crypto.PubKey.RSA (PrivateKey)
import qualified Crypto.PubKey.RSA.PKCS15 as RSA
import Crypto.Hash.Algorithms (SHA1 (SHA1))

-- pem
import qualified Data.PEM as PEM

-- text
import qualified Data.Text as Text

-- | Construct the signature that will go into the
--   @CloudFront-Signature@ cookie.

sign
  :: PrivateKey     -- ^ The RSA private key that you read from the @.pem@ file
  -> ByteString     -- ^ The JSON representation of the 'Policy'
                    --   (see "Network.AWS.CloudFront.SignedCookies.Policy")
  -> IO ByteString

sign key bs =
  RSA.signSafer (Just SHA1) key bs >>= either (fail . show) pure

-- | Read an RSA private key from a @.pem@ file you downloaded from AWS.

readPrivateKeyPemFile
  :: PemFilePath    -- ^ The filesystem path of the @.pem@ file
  -> IO PrivateKey

readPrivateKeyPemFile (PemFilePath path) = do

  lbs <- BS.readFile (Text.unpack path)

  pemSections <- either fail pure (PEM.pemParseBS lbs)

  pemBs <- PEM.pemContent <$> case pemSections of
    [x] -> pure x
    xs ->
      let msg = "Expected exactly 1 PEM section but found " ++
                show @Int (length xs)
      in  fail msg

  asn1s :: [ASN1] <- either (fail . show) pure (decodeASN1' DER pemBs)

  either fail pure (rsaPrivateKeyFromASN1 asn1s)