packages feed

jose-0.13: test/JWS.hs

-- Copyright (C) 2013, 2014, 2015, 2016  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 #-}

module JWS
  ( spec
  ) where

import Data.Maybe

import Control.Lens hiding ((.=))
import Control.Lens.Extras (is)
import Control.Lens.Cons.Extras (recons)
import Data.Aeson
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64.URL as B64U
import qualified Data.Set as S
import Test.Hspec

import Crypto.JOSE.Compact
import Crypto.JOSE.JWA.JWK
import Crypto.JOSE.JWK
import Crypto.JOSE.JWS
import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
import qualified Crypto.JOSE.Types as Types


drg :: ChaChaDRG
drg = drgNewTest (1,2,3,4,5)

spec :: Spec
spec = do
  headerSpec
  appendixA1Spec
  appendixA2Spec
  appendixA3Spec
  appendixA5Spec
  appendixA6Spec
  cfrgSpec
  base64urlSpec
  reserialiseSpec


-- Extension of JWSHeader to test "crit" behaviour
--
newtype JWSHeader' p = JWSHeader' { unJWSHeader' :: JWSHeader p }
  deriving (Eq, Show)
_JWSHeader' :: Iso' (JWSHeader' p) (JWSHeader p)
_JWSHeader' = iso unJWSHeader' JWSHeader'
instance HasJWSHeader JWSHeader' where
  jwsHeader = _JWSHeader'
instance HasParams JWSHeader' where
  parseParamsFor proxy hp hu = JWSHeader' <$> parseParamsFor proxy hp hu
  params (JWSHeader' h) = params h
  extensions = const ["foo"]

-- More elaborate extension of JWSHeader to test parsing behaviour
--
data ACMEHeader p = ACMEHeader
  { _acmeJwsHeader :: JWSHeader p
  , _acmeNonce :: Types.Base64Octets
  } deriving (Show)
acmeJwsHeader :: Lens' (ACMEHeader p) (JWSHeader p)
acmeJwsHeader f s@ACMEHeader{ _acmeJwsHeader = a} =
  fmap (\a' -> s { _acmeJwsHeader = a'}) (f a)
acmeNonce :: Lens' (ACMEHeader p) Types.Base64Octets
acmeNonce f s@ACMEHeader{ _acmeNonce = a} =
  fmap (\a' -> s { _acmeNonce = a'}) (f a)
instance HasJWSHeader ACMEHeader where
  jwsHeader = acmeJwsHeader
instance HasParams ACMEHeader where
  parseParamsFor proxy hp hu = ACMEHeader
    <$> parseParamsFor proxy hp hu
    <*> headerRequiredProtected "nonce" hp hu
  params h =
    (True, "nonce" .= view acmeNonce h)
    : params (view acmeJwsHeader h)
  extensions = const ["nonce"]


headerSpec :: Spec
headerSpec = describe "JWS Header" $ do
  it "parses signature correctly" $ do
    let
      sigJSON =
        "{\"protected\":\"eyJhbGciOiJSUzI1NiJ9\",\
        \ \"header\":{\"kid\":\"2010-12-29\"},\
        \ \"signature\":\"\"}"
      h = newJWSHeader (Protected, JWA.JWS.RS256)
        & kid .~ Just (HeaderParam Unprotected "2010-12-29")
      sig = eitherDecode sigJSON
    sig ^? _Right . header `shouldBe` Just h
    sig ^? _Right . signature `shouldBe` Just ("" :: BS.ByteString)

  it "rejects duplicate headers" $
    let
      -- protected header: {"kid":""}
      s = "{\"protected\":\"eyJraWQiOiIifQ\",\"header\":{\"alg\":\"none\",\"kid\":\"\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
        `shouldSatisfy` is _Left

  it "rejects reserved crit parameters" $
    let
      -- protected header: {"crit":["kid"],"kid":""}
      s = "{\"protected\":\"eyJjcml0IjpbImtpZCJdLCJraWQiOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
        `shouldSatisfy` is _Left

  it "rejects unknown crit parameters" $
    let
      -- protected header: {"crit":["foo"],"foo":""}
      s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdLCJmb28iOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
        `shouldSatisfy` is _Left

  it "accepts known crit parameter in protected header" $
    let
      -- protected header: {"crit":["foo"],"foo":""}
      s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdLCJmb28iOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
        `shouldSatisfy` is _Right

  it "accepts known crit parameter in unprotected header" $
    let
      -- protected header: {"crit":["foo"]}
      s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdfQ\",\"header\":{\"alg\":\"none\",\"foo\":\"\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
        `shouldSatisfy` is _Right

  it "rejects known crit parameter that does not appear in JOSE header" $
    let
      -- protected header: {"crit":["foo"]}
      s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdfQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
        `shouldSatisfy` is _Left

  it "rejects unprotected crit parameters" $
    let
      s = "{\"header\":{\"alg\":\"none\",\"crit\":[\"foo\"],\"foo\":\"\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
        `shouldSatisfy` is _Left

  it "rejects empty crit parameters" $
    let
      -- protected header: {"crit":[]}
      s = "{\"protected\":\"eyJjcml0IjpbXX0\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
        `shouldSatisfy` is _Left

  it "parses required protected header when present in protected header" $
    let
      -- protected header: {"crit":["nonce"],"nonce":"bm9uY2U"}
      s = "{\"protected\":\"eyJjcml0IjpbIm5vbmNlIl0sIm5vbmNlIjoiYm05dVkyVSJ9\""
          <> ",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection ACMEHeader))
        `shouldSatisfy` is _Right

  it "rejects required protected header when present in unprotected header" $
    let
      s = "{\"header\":{\"alg\":\"none\"},\"nonce\":\"bm9uY2U\",\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection ACMEHeader))
        `shouldSatisfy` is _Left

  it "accepts unprotected \"alg\" param with 'OptionalProtection' protection indicator" $
    let
      s = "{\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
        `shouldSatisfy` is _Right

  it "rejects unprotected \"alg\" param with 'RequiredProtection' protection indicator" $
    let
      s = "{\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
    in
      (eitherDecode s :: Either String (Signature RequiredProtection JWSHeader))
        `shouldSatisfy` is _Left


examplePayloadBytes :: BS.ByteString
examplePayloadBytes = "\
  \{\"iss\":\"joe\",\r\n\
  \ \"exp\":1300819380,\r\n\
  \ \"http://example.com/is_root\":true}"


appendixA1Spec :: Spec
appendixA1Spec = describe "RFC 7515 A.1.  Example JWS using HMAC SHA-256" $ do
  -- can't make aeson encode JSON to exact representation used in
  -- IETF doc, be we can go in reverse and then ensure that the
  -- round-trip checks out
  --
  it "decodes the example to the correct value" $ do
    jws ^? _Right . signatures . signature `shouldBe` Just mac
    jws ^? _Right . signatures . header `shouldBe` Just h

  it "serialises the decoded JWS back to the original data" $
    fmap encodeCompact jws `shouldBe` Right compactJWS

  it "computes the HMAC correctly" $
    fst (withDRG drg $ runJOSE $ (sign alg_ (k ^. jwkMaterial) (signingInput' ^. recons)))
      `shouldBe` (Right mac :: Either Error BS.ByteString)

  it "validates the JWS correctly" $
    (jws >>= verifyJWS defaultValidationSettings k)
    `shouldBe` Right examplePayloadBytes

  where
    signingInput' = "\
      \eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9\
      \.\
      \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
      \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
    compactJWS = signingInput' <> ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
    jws = decodeCompact compactJWS :: Either Error (CompactJWS JWSHeader)
    alg_ = JWA.JWS.HS256
    h = newJWSHeaderProtected alg_
        & typ .~ Just (newHeaderParamProtected "JWT")
    mac = view recons
      [116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173,
      187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83,
      132, 141, 121]
    k = fromOctets
      [3,35,53,75,43,15,165,188,131,126,6,101,119,123,166,143,90,179,40,
       230,240,84,201,40,169,15,132,178,210,80,46,191,211,251,90,146,
       210,6,71,239,150,138,180,195,119,98,61,34,61,46,33,114,5,46,79,8,
       192,205,154,245,103,208,128,163]


jwkRSA1024 :: JWK
jwkRSA1024 = fromJust $ decode $
  "{\"qi\":\"qYMpiKTOyFktv0Z3pQbig1RNA1xH35HMtjwISviC_bGo2zvzrYztBC_RzWsw"
  <> "3Nwsc32n65HIdpNbau1UhB3EwQ\","
  <> "\"p\":\"3ovj_M4MMJamOtjhtjswZhhwSYBiK6f2TjIEWiji-XV9SRcoyJsnp5flpeX"
  <> "VTEXS_PgLmjtUi2MLGAvXLlTtyQ\","
  <> "\"n\":\"yy1luWS19u8F-9eAdJ2iwCvuFrjOKuj1YBeNegPZpMJ9mhi8YISQLg-FTFR"
  <> "J68FzMeZM0liJq9mm-tNfPsNFxU7VM_sha2jWuJI32u3W5m7myTb8vNjHAd8acvuIRJ"
  <> "3hoJpJtSc1XBHHHIUK6lXNepHwQMjSCWtTY2wjRMKvYBU\","
  <> "\"q\":\"6bgjSNOcMzZMh64q66kIU68_U6wHwdbSFyLLtwVORsEYPhQUjWEoO7thY9j"
  <> "7m5NLRWgumoPIdDlLhOOnEf_V7Q\","
  <> "\"d\":\"Mordhkv-VCpLs8V9KAVayjFjbfWVG-mNuNTDFfpFNw5GzoGewufXMg4cW8u"
  <> "QA_zAmkYvEBiETuK6_iR8yhErlqMwFA4mdS4Yq0OqOPd9rCalUOoJf8cV1W5scsWXmL"
  <> "-xX_TmnGnIpjYcDJw6Zw0KP7hvHKPTPUriY2Zb--LLhaE\","
  <> "\"dp\":\"EgxgUglX3bzqAE3EiGXmd_E1chCSZZ36kL7nsXQtbDPGFF5ndVV38tST0E"
  <> "-Ca-whv1hSgJCdO6ytoqabLeu_WQ\","
  <> "\"e\":\"AQAB\","
  <> "\"dq\":\"3gLqkY1hvUwBGomZf85LeKLp9uNdYwZa_1swRCSoHJHkI2QTudDm1QbEFo"
  <> "LRTxF12PKEAobYbX7Xe958n550aQ\","
  <> "\"kty\":\"RSA\"}"

appendixA2Spec :: Spec
appendixA2Spec = describe "RFC 7515 A.2. Example JWS using RSASSA-PKCS-v1_5 SHA-256" $ do
  it "computes the signature correctly" $
    fst (withDRG drg $ runJOSE (sign JWA.JWS.RS256 (k ^. jwkMaterial) signingInput'))
      `shouldBe` (Right sig :: Either Error BS.ByteString)

  it "validates the signature correctly" $
    verify JWA.JWS.RS256 (k ^. jwkMaterial) signingInput' sig
      `shouldBe` (Right True :: Either Error Bool)

  it "prohibits signing with 1024-bit key" $
    fst (withDRG drg (runJOSE $
      signJWS signingInput' (Identity (newJWSHeaderProtected JWA.JWS.RS256, jwkRSA1024))))
        `shouldBe` (Left KeySizeTooSmall :: Either Error (CompactJWS JWSHeader))

  where
    signingInput' = "\
      \eyJhbGciOiJSUzI1NiJ9\
      \.\
      \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
      \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
    k = fromJust $ decode "\
      \{\"kty\":\"RSA\",\
      \ \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx\
            \HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs\
            \D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH\
            \SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV\
            \MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8\
            \NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\",\
      \ \"e\":\"AQAB\",\
      \ \"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I\
            \jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0\
            \BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn\
            \439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT\
            \CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh\
            \BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"\
      \}" :: JWK
    sig = BS.pack sigOctets
    sigOctets =
      [112, 46, 33, 137, 67, 232, 143, 209, 30, 181, 216, 45, 191, 120, 69,
      243, 65, 6, 174, 27, 129, 255, 247, 115, 17, 22, 173, 209, 113, 125,
      131, 101, 109, 66, 10, 253, 60, 150, 238, 221, 115, 162, 102, 62, 81,
      102, 104, 123, 0, 11, 135, 34, 110, 1, 135, 237, 16, 115, 249, 69,
      229, 130, 173, 252, 239, 22, 216, 90, 121, 142, 232, 198, 109, 219,
      61, 184, 151, 91, 23, 208, 148, 2, 190, 237, 213, 217, 217, 112, 7,
      16, 141, 178, 129, 96, 213, 248, 4, 12, 167, 68, 87, 98, 184, 31,
      190, 127, 249, 217, 46, 10, 231, 111, 36, 242, 91, 51, 187, 230, 244,
      74, 230, 30, 177, 4, 10, 203, 32, 4, 77, 62, 249, 18, 142, 212, 1,
      48, 121, 91, 212, 189, 59, 65, 238, 202, 208, 102, 171, 101, 25, 129,
      253, 228, 141, 247, 127, 55, 45, 195, 139, 159, 175, 221, 59, 239,
      177, 139, 93, 163, 204, 60, 46, 176, 47, 158, 58, 65, 214, 18, 202,
      173, 21, 145, 18, 115, 160, 95, 35, 185, 232, 56, 250, 175, 132, 157,
      105, 132, 41, 239, 90, 30, 136, 121, 130, 54, 195, 212, 14, 96, 69,
      34, 165, 68, 200, 242, 122, 122, 45, 184, 6, 99, 209, 108, 247, 202,
      234, 86, 222, 64, 92, 178, 33, 90, 69, 178, 194, 85, 102, 181, 90,
      193, 167, 72, 160, 112, 223, 200, 163, 42, 70, 149, 67, 208, 25, 238,
      251, 71]


appendixA3Spec :: Spec
appendixA3Spec = describe "RFC 7515 A.3.  Example JWS using ECDSA P-256 SHA-256" $
  it "validates the signature correctly" $
    verify JWA.JWS.ES256 (k ^. jwkMaterial) signingInput' sig
    `shouldBe` (Right True :: Either Error Bool)
  where
    signingInput' = "\
      \eyJhbGciOiJFUzI1NiJ9\
      \.\
      \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
      \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
    k = fromJust $ decode "\
      \{\"kty\":\"EC\",\
      \ \"crv\":\"P-256\",\
      \ \"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU\",\
      \ \"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\",\
      \ \"d\":\"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI\"\
      \}" :: JWK
    sig = BS.pack
      [14, 209, 33, 83, 121, 99, 108, 72, 60, 47, 127, 21, 88,
      7, 212, 2, 163, 178, 40, 3, 58, 249, 124, 126, 23, 129,
      154, 195, 22, 158, 166, 101,
      197, 10, 7, 211, 140, 60, 112, 229, 216, 241, 45, 175,
      8, 74, 84, 128, 166, 101, 144, 197, 242, 147, 80, 154,
      143, 63, 127, 138, 131, 163, 84, 213]

appendixA5Spec :: Spec
appendixA5Spec = describe "RFC 7515 A.5.  Example Unsecured JWS" $ do
  it "encodes the correct JWS" $
    fmap encodeCompact jws `shouldBe` Right exampleJWS

  it "decodes the correct JWS" $
    decodeCompact exampleJWS `shouldBe` jws

  it "can be verified with an empty key set" $
    (jws >>= verifyJWS conf (JWKSet []) :: Either Error BS.ByteString)
      `shouldSatisfy` is _Right

  where
    jws = fst $ withDRG drg $ runJOSE $
      signJWS examplePayloadBytes (Identity (newJWSHeaderProtected JWA.JWS.None, undefined))
      :: Either Error (CompactJWS JWSHeader)
    exampleJWS = "eyJhbGciOiJub25lIn0\
      \.\
      \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
      \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ\
      \."
    conf = set algorithms (S.singleton None) defaultValidationSettings


appendixA6Spec :: Spec
appendixA6Spec = describe "RFC 7515 A.6.  Example JWS Using General JSON Serialization" $ do
  it "decodes JWS with multiple signatures correctly" $ do
    let jws = eitherDecode exampleJWSTwoSigs :: Either String (GeneralJWS JWSHeader)
    lengthOf (_Right . signatures) jws `shouldBe` 2
    jws ^? _Right . signatures . header `shouldBe` Just h1'
    jws ^? _Right . signatures . signature `shouldBe` Just mac1
    jws ^? _Right . dropping 1 signatures . header `shouldBe` Just h2'
    jws ^? _Right . dropping 1 signatures . signature `shouldBe` Just mac2

  let
    decodeChecks jws = do
      lengthOf (_Right . signatures) jws `shouldBe` 1
      jws ^? _Right . signatures . header `shouldBe` Just h2'
      jws ^? _Right . signatures . signature `shouldBe` Just mac2

  it "decodes single-sig Generalised JWS correctly" $
    decodeChecks (eitherDecode exampleJWSOneSig :: Either String (GeneralJWS JWSHeader))

  it "fails to decode single-sig Generalised JWS to 'JWS Identity'" $
    (eitherDecode exampleJWSOneSig :: Either String (FlattenedJWS JWSHeader))
      `shouldSatisfy` is _Left

  it "decodes flattened JWS to 'JWS []' correctly" $
    decodeChecks (eitherDecode exampleJWSFlat :: Either String (GeneralJWS JWSHeader))

  it "decodes flattened JWS to 'JWS Identity' correctly" $
    decodeChecks (eitherDecode exampleJWSFlat :: Either String (FlattenedJWS JWSHeader))

  it "fails to decode flattened JWS when \"signatures\" key is present" $ do
    (eitherDecode exampleFlatJWSWithSignatures :: Either String (GeneralJWS JWSHeader))
      `shouldSatisfy` is _Left
    (eitherDecode exampleFlatJWSWithSignatures :: Either String (FlattenedJWS JWSHeader))
      `shouldSatisfy` is _Left

  where
    h1 = newJWSHeader (Protected, JWA.JWS.RS256)
    h1' = h1 & kid .~ Just (HeaderParam Unprotected "2010-12-29")
    mac1 = view recons
      [112, 46, 33, 137, 67, 232, 143, 209, 30, 181, 216, 45, 191, 120, 69,
      243, 65, 6, 174, 27, 129, 255, 247, 115, 17, 22, 173, 209, 113, 125,
      131, 101, 109, 66, 10, 253, 60, 150, 238, 221, 115, 162, 102, 62, 81,
      102, 104, 123, 0, 11, 135, 34, 110, 1, 135, 237, 16, 115, 249, 69,
      229, 130, 173, 252, 239, 22, 216, 90, 121, 142, 232, 198, 109, 219,
      61, 184, 151, 91, 23, 208, 148, 2, 190, 237, 213, 217, 217, 112, 7,
      16, 141, 178, 129, 96, 213, 248, 4, 12, 167, 68, 87, 98, 184, 31,
      190, 127, 249, 217, 46, 10, 231, 111, 36, 242, 91, 51, 187, 230, 244,
      74, 230, 30, 177, 4, 10, 203, 32, 4, 77, 62, 249, 18, 142, 212, 1,
      48, 121, 91, 212, 189, 59, 65, 238, 202, 208, 102, 171, 101, 25, 129,
      253, 228, 141, 247, 127, 55, 45, 195, 139, 159, 175, 221, 59, 239,
      177, 139, 93, 163, 204, 60, 46, 176, 47, 158, 58, 65, 214, 18, 202,
      173, 21, 145, 18, 115, 160, 95, 35, 185, 232, 56, 250, 175, 132, 157,
      105, 132, 41, 239, 90, 30, 136, 121, 130, 54, 195, 212, 14, 96, 69,
      34, 165, 68, 200, 242, 122, 122, 45, 184, 6, 99, 209, 108, 247, 202,
      234, 86, 222, 64, 92, 178, 33, 90, 69, 178, 194, 85, 102, 181, 90,
      193, 167, 72, 160, 112, 223, 200, 163, 42, 70, 149, 67, 208, 25, 238,
      251, 71] :: BS.ByteString
    h2 = newJWSHeader (Protected, JWA.JWS.ES256)
    h2' = h2 & kid .~ Just (HeaderParam Unprotected "e9bc097a-ce51-4036-9562-d2ade882db0d")
    mac2 = B64U.decodeLenient
      "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA\
      \pmWQxfKTUJqPP3-Kg6NU1Q"

    exampleJWSTwoSigs = "\
      \{\"payload\":\
      \  \"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF\
          \tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\",\
      \ \"signatures\":[\
      \   {\"protected\":\"eyJhbGciOiJSUzI1NiJ9\",\
      \    \"header\":\
      \     {\"kid\":\"2010-12-29\"},\
      \    \"signature\":\
      \     \"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZ\
             \mh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjb\
             \KBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHl\
             \b1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES\
             \c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AX\
             \LIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw\"},\
      \   {\"protected\":\"eyJhbGciOiJFUzI1NiJ9\",\
      \    \"header\":\
      \     {\"kid\":\"e9bc097a-ce51-4036-9562-d2ade882db0d\"},\
      \    \"signature\":\
      \     \"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS\
            \lSApmWQxfKTUJqPP3-Kg6NU1Q\"}]\
      \}"
    exampleJWSOneSig = "\
      \{\"payload\":\
      \  \"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF\
          \tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\",\
      \ \"signatures\":[\
      \   {\"protected\":\"eyJhbGciOiJFUzI1NiJ9\",\
      \    \"header\":\
      \     {\"kid\":\"e9bc097a-ce51-4036-9562-d2ade882db0d\"},\
      \    \"signature\":\
      \     \"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS\
            \lSApmWQxfKTUJqPP3-Kg6NU1Q\"}]\
      \}"
    exampleJWSFlat = "\
      \{\
      \ \"payload\":\
      \  \"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF\
          \tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\",\
      \ \"protected\":\"eyJhbGciOiJFUzI1NiJ9\",\
      \ \"header\":\
      \   {\"kid\":\"e9bc097a-ce51-4036-9562-d2ade882db0d\"},\
      \ \"signature\":\
      \  \"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS\
         \lSApmWQxfKTUJqPP3-Kg6NU1Q\"\
      \}"
    exampleFlatJWSWithSignatures = "\
      \{\
      \ \"payload\":\
      \  \"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF\
          \tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ\",\
      \ \"protected\":\"eyJhbGciOiJFUzI1NiJ9\",\
      \ \"header\":\
      \   {\"kid\":\"e9bc097a-ce51-4036-9562-d2ade882db0d\"},\
      \ \"signature\":\
      \  \"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS\
         \lSApmWQxfKTUJqPP3-Kg6NU1Q\",\
      \ \"signatures\":\"bogus\"\
      \}"

cfrgSpec :: Spec
cfrgSpec = describe "RFC 8037 signature/validation test vectors" $ do
  let
    k = fromJust $ decode "\
      \{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\
      \\"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\",\
      \\"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}"
    sigOctets =
      [0x86,0x0c,0x98,0xd2,0x29,0x7f,0x30,0x60,0xa3,0x3f,0x42,0x73,0x96,0x72,0xd6,0x1b
      ,0x53,0xcf,0x3a,0xde,0xfe,0xd3,0xd3,0xc6,0x72,0xf3,0x20,0xdc,0x02,0x1b,0x41,0x1e
      ,0x9d,0x59,0xb8,0x62,0x8d,0xc3,0x51,0xe2,0x48,0xb8,0x8b,0x29,0x46,0x8e,0x0e,0x41
      ,0x85,0x5b,0x0f,0xb7,0xd8,0x3b,0xb1,0x5b,0xe9,0x02,0xbf,0xcc,0xb8,0xcd,0x0a,0x02]
    sig = BS.pack sigOctets
    signingInput = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc"
  it "computes the correct signature" $
    fst (withDRG drg $ runJOSE (sign JWA.JWS.EdDSA (view jwkMaterial k) signingInput))
      `shouldBe` (Right sig :: Either Error BS.ByteString)
  it "validates signatures correctly" $
    verify JWA.JWS.EdDSA (view jwkMaterial k) signingInput sig
      `shouldBe` (Right True :: Either Error Bool)

base64urlSpec :: Spec
base64urlSpec = describe "base64url-decoding behaviour" $
  it "rejects baseurl-encoded values with padding" $
    -- from https://github.com/frasertweedale/hs-jose/issues/99
    --  and https://github.com/frasertweedale/hs-jose/issues/97
    let
      inJws = "{\"signature\":\"fake\",\"protected\":\"eyJraWQiOiI1ODJhMTdkZS0zYjg4LTQxZTgtOGM3MC0zZjBkNmYwMDNmY2QiLCJhbGciOiJSUzI1NiJ9\",\"payload\":\"eyJ1c2VybmFtZSI6Imp1c3BheSIsImdzdGluIjoiMjBhYWFhYTEyMzRhMWEyIn0=\"}"
    in
      (eitherDecode inJws :: Either String (FlattenedJWS JWSHeader))
        `shouldSatisfy` is _Left

reserialiseSpec :: Spec
reserialiseSpec = describe "reserialisation of JWS" $
  it "preserves raw protected header value" $ do
    -- https://github.com/frasertweedale/hs-jose/issues/98
    --
    -- Test two different JWKs with same protected header fields
    -- but different order.  For each input parse then reserialise,
    -- then parse again and ensure that the raw protected header
    -- string is the same for both parses.
    --
    -- The two strings with different orders are needed to test
    -- that we preserve and use the recorded raw protected header
    -- when reserialising the object.  (We don't know what order
    -- the keys will be in the aeson object hashmap, so we test
    -- with two different orders to avoid a false success).
    let
      -- {"kid":"0","alg":"RS256"}
      s1 = "{\"protected\":\"eyJraWQiOiIwIiwiYWxnIjoiUlMyNTYifQ\",\"payload\":\"\",\"signature\":\"\"}"
      -- {"alg":"RS256","kid":"0"}
      s2 = "{\"signature\":\"\",\"protected\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAifQ\",\"payload\":\"\"}"

      check s = do
        let
          jws = eitherDecode s :: Either String (FlattenedJWS JWSHeader)
          s' = encode <$> jws
          jws' = s' >>= eitherDecode :: Either String (FlattenedJWS JWSHeader)
        jws `shouldSatisfy` is _Right
        toListOf (signatures . to rawProtectedHeader) <$> jws
          `shouldBe`
          toListOf (signatures . to rawProtectedHeader) <$> jws'

    check s1
    check s2