module Jose.Internal.Crypto
( hmacSign
, hmacVerify
, rsaSign
, rsaVerify
, rsaEncrypt
, rsaDecrypt
, encryptPayload
, decryptPayload
, generateCmkAndIV
, pad
, unpad
)
where
import Crypto.Cipher.Types (AuthTag(..))
import qualified Crypto.PubKey.RSA as RSA
import qualified Crypto.PubKey.RSA.PKCS15 as PKCS15
import qualified Crypto.PubKey.RSA.OAEP as OAEP
import Crypto.Random (CPRG, cprgGenerate)
import qualified Crypto.Cipher.AES as AES
import Crypto.PubKey.HashDescr
import Crypto.MAC.HMAC (hmac)
import Data.Byteable (constEqBytes)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Maybe (fromMaybe)
import qualified Data.Serialize as Serialize
import Data.Word (Word64, Word8)
import Jose.Jwa
import Jose.Types (JwtError(..))
hmacSign :: JwsAlg
-> ByteString
-> ByteString
-> ByteString
hmacSign a k m = hmac (hashFunction hash) 64 k m
where
hash = fromMaybe (error $ "Not an HMAC alg: " ++ show a) $ lookup a hmacHashes
hmacVerify :: JwsAlg
-> ByteString
-> ByteString
-> ByteString
-> Bool
hmacVerify a key msg sig = case lookup a hmacHashes of
Just _ -> constEqBytes (hmacSign a key msg) sig
Nothing -> False
rsaSign :: JwsAlg
-> RSA.PrivateKey
-> ByteString
-> ByteString
rsaSign a key = either (error "Signing failed") id . PKCS15.sign Nothing hash key
where
hash = fromMaybe (error $ "Not an RSA Algorithm " ++ show a) $ lookupRSAHash a
rsaVerify :: JwsAlg
-> RSA.PublicKey
-> ByteString
-> ByteString
-> Bool
rsaVerify a key msg sig = case lookupRSAHash a of
Just hash -> PKCS15.verify hash key msg sig
Nothing -> False
hmacHashes :: [(JwsAlg, HashDescr)]
hmacHashes = [(HS256, hashDescrSHA256), (HS384, hashDescrSHA384), (HS512, hashDescrSHA512)]
lookupRSAHash :: JwsAlg -> Maybe HashDescr
lookupRSAHash alg = case alg of
RS256 -> Just hashDescrSHA256
RS384 -> Just hashDescrSHA384
RS512 -> Just hashDescrSHA512
_ -> Nothing
generateCmkAndIV :: CPRG g
=> g
-> Enc
-> (B.ByteString, B.ByteString, g)
generateCmkAndIV g e = (cmk, iv, g'')
where
(cmk, g') = cprgGenerate (keySize e) g
(iv, g'') = cprgGenerate (ivSize e) g'
keySize :: Enc -> Int
keySize A128GCM = 16
keySize A256GCM = 32
keySize A128CBC_HS256 = 32
keySize A256CBC_HS512 = 64
ivSize :: Enc -> Int
ivSize A128GCM = 12
ivSize A256GCM = 12
ivSize _ = 16
rsaEncrypt :: CPRG g
=> g
-> JweAlg
-> RSA.PublicKey
-> B.ByteString
-> (B.ByteString, g)
rsaEncrypt gen a pubKey content = (ct, g')
where
encrypt = case a of
RSA1_5 -> PKCS15.encrypt gen
RSA_OAEP -> OAEP.encrypt gen oaepParams
(Right ct, g') = encrypt pubKey content
rsaDecrypt :: JweAlg
-> RSA.PrivateKey
-> B.ByteString
-> Either JwtError B.ByteString
rsaDecrypt a rsaKey jweKey = do
decrypt <- decryptAlg
either (\_ -> Left BadCrypto) Right $ decrypt rsaKey jweKey
where
decryptAlg = case a of
RSA1_5 -> Right $ PKCS15.decrypt Nothing
RSA_OAEP -> Right $ OAEP.decrypt Nothing oaepParams
oaepParams :: OAEP.OAEPParams
oaepParams = OAEP.defaultOAEPParams (hashFunction hashDescrSHA1)
decryptPayload :: Enc
-> ByteString
-> ByteString
-> ByteString
-> ByteString
-> ByteString
-> Either JwtError ByteString
decryptPayload e cek iv aad sig ct = do
(plaintext, tag) <- case e of
A128GCM -> decryptedGCM
A256GCM -> decryptedGCM
_ -> decryptedCBC
if tag == AuthTag sig
then return plaintext
else Left BadSignature
where
decryptedGCM = Right $ AES.decryptGCM (AES.initAES cek) iv aad ct
decryptedCBC = do
let (macKey, encKey) = B.splitAt (B.length cek `div` 2) cek
let al = fromIntegral (B.length aad) * 8 :: Word64
plaintext <- unpad $ AES.decryptCBC (AES.initAES encKey) iv ct
let mac = authTag e macKey $ B.concat [aad, iv, ct, Serialize.encode al]
return (plaintext, mac)
encryptPayload :: Enc
-> ByteString
-> ByteString
-> ByteString
-> ByteString
-> (ByteString, AuthTag)
encryptPayload e cek iv aad msg = case e of
A128GCM -> aesgcm
A256GCM -> aesgcm
_ -> (aescbc, sig)
where
aesgcm = AES.encryptGCM (AES.initAES cek) iv aad msg
(macKey, encKey) = B.splitAt (B.length cek `div` 2) cek
aescbc = AES.encryptCBC (AES.initAES encKey) iv (pad msg)
al = fromIntegral (B.length aad) * 8 :: Word64
sig = authTag e macKey $ B.concat [aad, iv, aescbc, Serialize.encode al]
authTag :: Enc -> ByteString -> ByteString -> AuthTag
authTag e k m = AuthTag $ B.take tLen $ hmacSign a k m
where
(tLen, a) = case e of
A128CBC_HS256 -> (16, HS256)
A256CBC_HS512 -> (32, HS512)
_ -> error "TODO"
unpad :: ByteString -> Either JwtError ByteString
unpad bs
| padLen > 16 || padLen /= B.length padding = Left BadCrypto
| B.any (/= padByte) padding = Left BadCrypto
| otherwise = Right pt
where
len = B.length bs
padByte = B.last bs
padLen = fromIntegral padByte
(pt, padding) = B.splitAt (len padLen) bs
pad :: ByteString -> ByteString
pad bs = B.append bs $ padding
where
lastBlockSize = B.length bs `mod` 16
padByte = fromIntegral $ 16 lastBlockSize :: Word8
padding = B.replicate (fromIntegral padByte) padByte