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