packages feed

jose-jwt-0.2: tests/Tests/JweSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}

module Tests.JweSpec where

import Control.Applicative
import Data.Aeson (eitherDecode, decodeStrict')
import Data.Bits (xor)
import Data.Either.Combinators
import qualified Data.ByteString as B
import Test.Hspec
import Test.HUnit hiding (Test)
import Test.QuickCheck

import qualified Crypto.PubKey.RSA as RSA
import Crypto.PubKey.RSA.Prim (dp)
import Crypto.PubKey.MaskGenFunction
import Crypto.PubKey.HashDescr
import Crypto.Cipher.Types (AuthTag(..))
import Crypto.Random (CPRG(..))
import Jose.Jwt
import qualified Jose.Jwe as Jwe
import Jose.Jwa
import qualified Jose.Jwk as Jwk
import Jose.Internal.Crypto
import qualified Jose.Internal.Base64 as B64

--------------------------------------------------------------------------------
-- JWE Appendix Data Tests (plus quickcheck)
--------------------------------------------------------------------------------

spec :: Spec
spec =
  describe "JWE encoding and decoding" $ do
    context "when using JWE Appendix 1 data" $ do
      let a1Header = defJweHdr {jweAlg = RSA_OAEP, jweEnc = A256GCM}

      it "generates the expected IV and CMK from the RNG" $ do
        let g = RNG $ B.append a1cek a1iv
        generateCmkAndIV g A256GCM @?= (a1cek, a1iv, RNG "")

      it "generates the expected RSA-encrypted content key" $ do
        let g = RNG $ a1oaepSeed
        rsaEncrypt g RSA_OAEP a1PubKey a1cek @?= (a1jweKey, RNG "")

      it "encrypts the payload to the expected ciphertext and authentication tag" $ do
        let aad = B64.encode . encodeHeader $ a1Header
        encryptPayload A256GCM a1cek a1iv aad a1Payload @?= (a1Ciphertext, AuthTag a1Tag)

      it "encodes the payload to the expected JWT, leaving the RNG empty" $ do
        let g = RNG $ B.concat [a1cek, a1iv, a1oaepSeed]
        Jwe.rsaEncode g RSA_OAEP A256GCM a1PubKey a1Payload @?= (a1, RNG "")

      it "decodes the JWT to the expected header and payload" $ do
        (fst $ Jwe.rsaDecode blinderRNG a1PrivKey a1) @?= Right (a1Header, a1Payload)

      it "decodes the JWK to the correct RSA key values" $ do
        let Right (Jwk.RsaPrivateJwk (RSA.PrivateKey pubKey d 0 0 0 0 0) _ _ _) = eitherDecode a1jwk
        RSA.public_n pubKey  @?= a1RsaModulus
        RSA.public_e pubKey  @?= rsaExponent
        d                    @?= a1RsaPrivateExponent

      it "decodes the JWT using the JWK" $ do
        let Right k1 = eitherDecode a1jwk
            Just  k2 = decodeStrict' a2jwk
        (fst $ decode blinderRNG [k2, k1] a1) @?= (Right $ Jwe (a1Header, a1Payload))

    context "when using JWE Appendix 2 data" $ do
      let a2Header = defJweHdr {jweAlg = RSA1_5, jweEnc = A128CBC_HS256}
      let aad = B64.encode . encodeHeader $ a2Header

      it "generates the expected RSA-encrypted content key" $ do
        let g = RNG $ a2seed
        rsaEncrypt g RSA1_5 a2PubKey a2cek @?= (a2jweKey, RNG "")

      it "encrypts the payload to the expected ciphertext and authentication tag" $ do
        encryptPayload A128CBC_HS256 a2cek a2iv aad a2Payload @?= (a2Ciphertext, AuthTag a2Tag)

      it "encodes the payload to the expected JWT" $ do
        let g = RNG $ B.concat [a2cek, a2iv, a2seed]
        Jwe.rsaEncode g RSA1_5 A128CBC_HS256 a2PubKey a2Payload @?= (a2, RNG "")

      it "decrypts the ciphertext to the correct payload" $ do
        decryptPayload A128CBC_HS256 a2cek a2iv aad a2Tag a2Ciphertext @?= Right a2Payload

      it "decodes the JWT to the expected header and payload" $ do
        (fst $ Jwe.rsaDecode blinderRNG a2PrivKey a2) @?= Right (a2Header, a2Payload)

    context "when used with quickcheck" $ do
      it "padded msg is always a multiple of 16" $ property $
        \bs -> B.length (pad bs) `mod` 16 == 0
      it "unpad is the inverse of pad" $ property $
        \bs -> (fromRight' . unpad . pad) bs == bs
      it "jwe decode/decode returns the original payload" $ property $ jweRoundTrip

-- verboseQuickCheckWith quickCheckWith stdArgs {maxSuccess=10000}  jweRoundTrip
jweRoundTrip :: RNG -> JWEAlgs -> B.ByteString -> Bool
jweRoundTrip g (JWEAlgs a e) msg = encodeDecode == Right (defJweHdr {jweAlg = a, jweEnc = e}, msg)
  where
    encodeDecode = fst $ Jwe.rsaDecode blinderRNG a2PrivKey $ fst $ Jwe.rsaEncode g a e a2PubKey msg

-- A decidedly non-random, random number generator which allows specific
-- sequences of bytes to be supplied which match the JWE test data.
data RNG = RNG B.ByteString deriving (Eq, Show)

genBytes :: Int -> RNG -> (B.ByteString, RNG)
genBytes 0 g = (B.empty, g)
genBytes n (RNG bs) = (bytes, RNG next)
  where
    (bytes, next) = if B.null bs
                      then error "RNG is empty"
                      else B.splitAt n bs

instance CPRG RNG where
    cprgCreate   = undefined
    cprgSetReseedThreshold = undefined
    cprgGenerate = genBytes
    cprgGenerateWithEntropy = undefined
    cprgFork = undefined

blinderRNG = RNG $ B.replicate 2000 255

--------------------------------------------------------------------------------
-- JWE Appendix 1 Test Data
--------------------------------------------------------------------------------

a1 = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ"

a1Payload = "The true sign of intelligence is not knowledge but imagination."

a1cek = B.pack [177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252]

a1iv  = B.pack [227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]

a1aad = B.pack [101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 116, 84, 48, 70, 70, 85, 67, 73, 115, 73, 109, 86, 117, 89, 121, 73, 54, 73, 107, 69, 121, 78, 84, 90, 72, 81, 48, 48, 105, 102, 81]

a1Ciphertext = B.pack [229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192]

a1Tag = B.pack [92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145]

Right a1jweKey = B64.decode "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg"

a1jwk = "{\"kty\":\"RSA\", \"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\", \"e\":\"AQAB\", \"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\" }"

a1RsaModulus = 20407373051396142380600281265251892119308905183562582378265551916401741797298132714477564366125574073854325621181754666299468042787718090965019045494120492365709229334674806858420600185271825023335981142192553851711447185679749878133484409202142610505370119489349112667599681596271324052456163162582257897587607185901342235063647947816589525124013368466111231306949063172170503467209564034546753006291531308789606255762727496010190006847721118463557533668762287451483156476421856126198680670740028037673487624895510756370816101325723975021588898704953504010419555312457504338174094966173304768490140232017447246019099

rsaExponent = 65537 :: Integer

a1RsaPrivateExponent = 18268766796654718362565236454995853620820821188251417451980738596264305499270399136757621249007756005599271096771478165267306874014871487538744562309757162619646837295513011635819128008143685281506609665247035139326775637222412463191989209202137797209813686014322033219332678022668756745556718137625135245640638710814390273901357613670762406363679831247433360271391936119294533419667412739496199381069233394069901435128732415071218792819358792459421008659625326677236263304891550388749907992141902573512326268421915766834378108391128385175130554819679860804655689526143903449732010240859012168194104458903308465660105

a1oaepSeed = extractOaepSeed a1PrivKey a1jweKey

(a1PubKey, a1PrivKey) = createKeyPair a1RsaModulus a1RsaPrivateExponent


--------------------------------------------------------------------------------
-- JWE Appendix 2 Test Data
--------------------------------------------------------------------------------

a2Payload = "Live long and prosper."

a2cek = B.pack [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207]

--a2cek = B.pack [203, 165, 180, 113, 62, 195, 22, 98, 91, 153, 210, 38, 112, 35, 230, 236]

--a2cik = B.pack [218, 24, 160, 17, 160, 50, 235, 35, 216, 209, 100, 174, 155, 163, 10, 117, 180, 111, 172, 200, 127, 201, 206, 173, 40, 45, 58, 170, 35, 93, 9, 60]

a2iv = B.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]

a2Ciphertext = B.pack [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102]

a2Tag = B.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]

Right a2jweKey = B64.decode "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"

a2jwk = "{\"kty\":\"RSA\", \"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\", \"e\":\"AQAB\", \"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\" }"

a2RsaModulus =  22402924734748322419583087865046136971812964522608965289668050862528140628890468829261358173206844190609885548664216273129288787509446229835492005268681636400878070687042995563617837593077316848511917526886594334868053765054121327206058496913599608196082088434862911200952954663261204130886151917541465131565772711448256433529200865576041706962504490609565420543616528240562874975930318078653328569211055310553145904641192292907110395318778917935975962359665382660933281263049927785938817901532807037136641587608303638483543899849101763615990006657357057710971983052920787558713523025279998057051825799400286243909447

a2RsaPrivateExponent = 10643756465292254988457796463889735064030094089452909840615134957452106668931481879498770304395097541282329162591478128330968231330113176654221501869950411410564116254672288216799191435916328405513154035654178369543717138143188973636496077305930253145572851787483810154020967535132278148578697716656066036003388130625459567907864689911133288140117207430454310073863484450086676106606775792171446149215594844607410066899028283290532626577379520547350399030663657813726123700613989625283009134539244470878688076926304079342487789922656366430636978871435674556143884272163840709196449089335092169596187792960067104244313

a2 = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.9hH0vgRfYgPnAHOd8stkvw"

(a2PubKey, a2PrivKey) = createKeyPair a2RsaModulus a2RsaPrivateExponent

a2seed = extractPKCS15Seed a2PrivKey a2jweKey


--------------------------------------------------------------------------------
-- Quickcheck Stuff
--------------------------------------------------------------------------------

-- Valid JWE Alg/Enc combinations
data JWEAlgs = JWEAlgs JweAlg Enc deriving Show

instance Arbitrary B.ByteString where
    arbitrary = B.pack <$> arbitrary

instance Arbitrary Enc where
    arbitrary = elements [A128CBC_HS256, A256CBC_HS512, A128GCM, A256GCM]

instance Arbitrary JWEAlgs where
  arbitrary = do
    a <- elements [RSA1_5, RSA_OAEP]
    e <- elements [A128CBC_HS256, A256CBC_HS512, A128GCM, A256GCM]
    return $ JWEAlgs a e

instance Arbitrary RNG where
  arbitrary = (RNG . B.pack) <$> vector 600



--------------------------------------------------------------------------------
-- Utility Functions
--------------------------------------------------------------------------------

createKeyPair n d = (pubKey, privKey)
  where
    privKey = RSA.PrivateKey
                { RSA.private_pub = pubKey
                , RSA.private_d = d
                , RSA.private_q = 0
                , RSA.private_p = 0
                , RSA.private_dP = 0
                , RSA.private_dQ = 0
                , RSA.private_qinv = 0
                }
    pubKey = RSA.PublicKey
                { RSA.public_size = 256
                , RSA.public_n = n
                , RSA.public_e = rsaExponent
                }

 -- Extracts the random padding bytes from the decrypted content key
 -- allowing them to be used in the test RNG
extractOaepSeed :: RSA.PrivateKey -> B.ByteString -> B.ByteString
extractOaepSeed key ct = B.pack $ B.zipWith xor maskedSeed seedMask
  where
    em       = dp Nothing key ct
    sha1     = hashFunction hashDescrSHA1
    hashLen  = B.length $ sha1 B.empty
    em0      = B.tail em
    (maskedSeed, maskedDB) = B.splitAt hashLen em0
    seedMask = mgf1 sha1 maskedDB hashLen

 -- Decrypt, drop the 02 at the start and take the bytes up to the next 0
extractPKCS15Seed :: RSA.PrivateKey -> B.ByteString -> B.ByteString
extractPKCS15Seed key ct = B.takeWhile (/= 0) . B.drop 2 $ dp Nothing key ct