jose 0.3.38.0 → 0.3.41.0
raw patch · 7 files changed
+862/−12 lines, 7 filesdep +x509dep −certificate
Dependencies added: x509
Dependencies removed: certificate
Files
- jose.cabal +9/−4
- src/Crypto/JOSE/JWK.hs +3/−2
- src/Crypto/JOSE/Types.hs +5/−6
- test/JWK.hs +255/−0
- test/JWS.hs +343/−0
- test/JWT.hs +87/−0
- test/Types.hs +160/−0
jose.cabal view
@@ -1,5 +1,5 @@ name: jose-version: 0.3.38.0+version: 0.3.41.0 synopsis: Javascript Object Signing and Encryption and JSON Web Token library description:@@ -31,7 +31,7 @@ README.md author: Fraser Tweedale maintainer: frase@frase.id.au-copyright: Copyright (C) 2013, 2014 Fraser Tweedale+copyright: Copyright (C) 2013, 2014, 2015 Fraser Tweedale category: Cryptography build-type: Simple cabal-version: >= 1.8@@ -82,7 +82,7 @@ , text >= 1.1 , time == 1.4.* , network-uri >= 2.6- , certificate == 1.3.*+ , x509 >= 1.4 , vector ghc-options: -Wall@@ -96,6 +96,11 @@ type: exitcode-stdio-1.0 hs-source-dirs: src, test main-is: Test.hs+ other-modules:+ JWK+ JWS+ JWT+ Types build-depends: base@@ -119,6 +124,6 @@ , text , time , network-uri- , certificate , vector+ , x509 , hspec
src/Crypto/JOSE/JWK.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014 Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015 Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -49,6 +49,7 @@ import Control.Lens hiding ((.=)) import Data.Aeson+import Data.List.NonEmpty import Crypto.JOSE.Classes import qualified Crypto.JOSE.JWA.JWE.Alg as JWA.JWE@@ -95,7 +96,7 @@ , _jwkAlg :: Maybe Alg , _jwkKid :: Maybe String , _jwkX5u :: Maybe Types.URI- , _jwkX5c :: Maybe [Types.Base64X509]+ , _jwkX5c :: Maybe (NonEmpty Types.Base64X509) , _jwkX5t :: Maybe Types.Base64SHA1 , _jwkX5tS256 :: Maybe Types.Base64SHA256 }
src/Crypto/JOSE/Types.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014 Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015 Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -37,8 +37,7 @@ import Data.Byteable import qualified Data.ByteString as B import qualified Data.ByteString.Base64.URL as B64U-import qualified Data.ByteString.Lazy as L-import Data.Certificate.X509+import Data.X509 import Network.URI (URI) import Crypto.JOSE.Types.Internal@@ -134,12 +133,12 @@ -- | A base64 encoded X.509 certificate. ---newtype Base64X509 = Base64X509 X509+newtype Base64X509 = Base64X509 SignedCertificate deriving (Eq, Show) instance FromJSON Base64X509 where parseJSON = withText "base64url X.509 certificate" $ parseB64 $- either fail (pure . Base64X509) . decodeCertificate . L.fromStrict+ either fail (pure . Base64X509) . decodeSignedCertificate instance ToJSON Base64X509 where- toJSON (Base64X509 x509) = encodeB64 $ L.toStrict $ encodeCertificate x509+ toJSON (Base64X509 x509) = encodeB64 $ encodeSignedObject x509
+ test/JWK.hs view
@@ -0,0 +1,255 @@+-- Copyright (C) 2013 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 JWK where++import Data.Aeson+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Test.Hspec++import Crypto.JOSE.JWA.JWK+import Crypto.JOSE.JWK+import qualified Crypto.JOSE.Types as Types++spec :: Spec+spec = do+ jwk3Spec+ jwkAppendixA1Spec+ jwkAppendixA2Spec+ jwkAppendixA3Spec+ jwkAppendixBSpec+ jwkAppendixC1Spec+ jwsAppendixA1Spec++jwsAppendixA1Spec :: Spec+jwsAppendixA1Spec = describe "JWS A.1.1. JWK" $ 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" $+ decode exampleJWK `shouldBe` Just jwk++ it "round-trips correctly" $+ eitherDecode (encode jwk) `shouldBe` Right jwk++ where+ exampleJWK = "\+ \{\"kty\":\"oct\",\+ \ \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75\+ \aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"\+ \}"+ jwk = JWK (OctKeyMaterial octKeyMaterial) z z z z z z z z where z = Nothing+ octKeyMaterial = OctKeyParameters Oct $ Types.Base64Octets $+ foldr B.cons B.empty+ [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]++jwk3Spec :: Spec+jwk3Spec = describe "JWK §3. Example JWK" $+ it "successfully decodes the examples" $+ lr (eitherDecode exampleJWK :: Either String JWK) `shouldBe` R+ where+ exampleJWK = "\+ \{\"kty\":\"EC\",\+ \ \"crv\":\"P-256\",\+ \ \"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU\",\+ \ \"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\",\+ \ \"kid\":\"Public key used in JWS A.3 example\"\+ \}"++jwkAppendixA1Spec :: Spec+jwkAppendixA1Spec = describe "JWK A.1. Example Public Keys" $+ it "successfully decodes the examples" $+ lr (eitherDecode exampleJWKSet :: Either String JWKSet) `shouldBe` R+ where+ exampleEC = "\+ \ {\"kty\":\"EC\",\+ \ \"crv\":\"P-256\",\+ \ \"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\+ \ \"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\+ \ \"use\":\"enc\",\+ \ \"kid\":\"1\"}"+ exampleRSA = "\+ \ {\"kty\":\"RSA\",\+ \ \"n\": \"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx\+ \4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs\+ \tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2\+ \QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI\+ \SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb\+ \w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\",\+ \ \"e\":\"AQAB\",\+ \ \"alg\":\"RS256\",\+ \ \"kid\":\"2011-04-29\"}"+ exampleJWKSet = "{\"keys\": ["+ `L.append` exampleEC `L.append` ","+ `L.append` exampleRSA `L.append` "]}"++jwkAppendixA2Spec :: Spec+jwkAppendixA2Spec = describe "JWK A.2. Example Private Keys" $+ it "successfully decodes the examples" $+ lr (eitherDecode exampleJWKSet :: Either String JWKSet) `shouldBe` R+ where+ exampleJWKSet = "\+ \{\"keys\":\+ \ [\+ \ {\"kty\":\"EC\",\+ \ \"crv\":\"P-256\",\+ \ \"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\+ \ \"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\+ \ \"d\":\"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE\",\+ \ \"use\":\"enc\",\+ \ \"kid\":\"1\"},\+ \\+ \ {\"kty\":\"RSA\",\+ \ \"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4\+ \cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst\+ \n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q\+ \vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS\+ \D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw\+ \0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\",\+ \ \"e\":\"AQAB\",\+ \ \"d\":\"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9\+ \M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij\+ \wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d\+ \_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz\+ \nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz\+ \me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q\",\+ \ \"p\":\"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV\+ \nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV\+ \WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs\",\+ \ \"q\":\"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum\+ \qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx\+ \kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk\",\+ \ \"dp\":\"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim\+ \YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu\+ \YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0\",\+ \ \"dq\":\"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU\+ \vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9\+ \GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk\",\+ \ \"qi\":\"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg\+ \UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx\+ \yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU\",\+ \ \"alg\":\"RS256\",\+ \ \"kid\":\"2011-04-29\"}\+ \ ]\+ \}"++jwkAppendixA3Spec :: Spec+jwkAppendixA3Spec = describe "JWK A.3. Example Symmetric Keys" $+ it "successfully decodes the examples" $+ lr (eitherDecode exampleJWKSet :: Either String JWKSet) `shouldBe` R+ where+ exampleJWKSet = "\+ \{\"keys\":\+ \ [\+ \ {\"kty\":\"oct\",\+ \ \"alg\":\"A128KW\",\+ \ \"k\":\"GawgguFyGrWKav7AX4VKUg\"},\+ \\+ \ {\"kty\":\"oct\",\+ \ \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75\+ \aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\",\+ \ \"kid\":\"HMAC key used in JWS A.1 example\"}\+ \ ]\+ \}"++jwkAppendixBSpec :: Spec+jwkAppendixBSpec = describe "JWK B. Example Use of \"x5c\" (X.509 Certificate Chain) Parameter" $+ it "successfully decodes the example" $+ lr (eitherDecode exampleJWK :: Either String JWK) `shouldBe` R+ where+ exampleJWK = "\+ \{\"kty\":\"RSA\",\+ \ \"use\":\"sig\",\+ \ \"kid\":\"1b94c\",\+ \ \"n\":\"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08\+ \PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q\+ \u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a\+ \YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH\+ \MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv\+ \VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ\",\+ \ \"e\":\"AQAB\",\+ \ \"x5c\":\+ \ [\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB\+ \gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD\+ \VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1\+ \wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg\+ \NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV\+ \QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w\+ \YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH\+ \YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66\+ \s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6\+ \SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn\+ \fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq\+ \PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk\+ \aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA\+ \QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL\+ \+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1\+ \zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL\+ \2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo\+ \4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq\+ \gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"]\+ \}"++jwkAppendixC1Spec :: Spec+jwkAppendixC1Spec = describe "JWK C.1. Plaintext RSA Private Key" $+ it "successfully decodes the example" $+ lr (eitherDecode exampleJWK :: Either String JWK) `shouldBe` R+ where+ exampleJWK = "\+ \{\+ \ \"kty\":\"RSA\",\+ \ \"kid\":\"juliet@capulet.lit\",\+ \ \"use\":\"enc\",\+ \ \"n\":\"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy\+ \O125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP\+ \8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0\+ \Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0X\+ \OC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1\+ \_I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q\",\+ \ \"e\":\"AQAB\",\+ \ \"d\":\"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfS\+ \NkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9U\+ \vqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnu\+ \ToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsu\+ \rY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2a\+ \hecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ\",\+ \ \"p\":\"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHf\+ \QP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8\+ \UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws\",\+ \ \"q\":\"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6I\+ \edis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYK\+ \rYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s\",\+ \ \"dp\":\"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3\+ \tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1w\+ \Y52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c\",\+ \ \"dq\":\"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9\+ \GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBy\+ \mXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots\",\+ \ \"qi\":\"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq\+ \abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o\+ \Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8\"\+ \}"++data LR = L | R deriving (Eq, Show)++lr :: Either a b -> LR+lr (Left _) = L+lr _ = R
+ test/JWS.hs view
@@ -0,0 +1,343 @@+-- Copyright (C) 2013, 2014 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 where++import Data.Maybe++import Data.Aeson+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Base64.URL as B64U+import Data.Default.Class+import Test.Hspec++import Crypto.JOSE.Classes+import Crypto.JOSE.Compact+import Crypto.JOSE.JWA.JWK+import Crypto.JOSE.JWK+import Crypto.JOSE.JWS+import Crypto.JOSE.JWS.Internal+import qualified Crypto.JOSE.JWA.JWS as JWA.JWS+import qualified Crypto.JOSE.Types as Types+import Crypto.JOSE.Types.Armour+++rng :: SystemRNG+rng = cprgCreate $ createTestEntropyPool "dummy CPRG for testing"++spec :: Spec+spec = do+ critSpec+ critSpec'+ headerSpec+ appendixA1Spec+ appendixA2Spec+ appendixA3Spec+ appendixA5Spec+ appendixA6Spec++critSpec :: Spec+critSpec = describe "JWS §4.1.10. \"crit\" Header Parameter; parsing" $+ it "parses from JSON correctly" $ do+ decode good `shouldBe`+ Just (CritParameters $ return ("exp", Number 1363284000))+ decode "{}" `shouldBe` (Nothing :: Maybe CritParameters)+ decode missingParam `shouldBe` (Nothing :: Maybe CritParameters)+ decode critNotArray `shouldBe` (Nothing :: Maybe CritParameters)+ decode critEmptyArray `shouldBe` (Nothing :: Maybe CritParameters)+ decode critValueNotString `shouldBe` (Nothing :: Maybe CritParameters)+ decode critValueNotValid `shouldBe` (Nothing :: Maybe CritParameters)+ where+ good = "{\"alg\":\"ES256\",\"crit\":[\"exp\"],\"exp\":1363284000}"+ missingParam = "{\"alg\":\"ES256\",\"crit\":[\"nope\"]}"+ critNotArray = "{\"alg\":\"ES256\",\"crit\":\"exp\"}"+ critEmptyArray = "{\"alg\":\"ES256\",\"crit\":[]}"+ critValueNotString = "{\"alg\":\"ES256\",\"crit\":[1234]}"+ critValueNotValid = "{\"alg\":\"ES256\",\"crit\":[\"crit\"]}"++critSpec' :: Spec+critSpec' = describe "JWS §4.1.10. \"crit\" Header Parameter; full example" $+ it "parses from JSON correctly" $+ decode s `shouldBe` Just ((algHeader JWA.JWS.ES256) { headerCrit = Just critValue })+ where+ s = "{\"alg\":\"ES256\",\"crit\":[\"exp\"],\"exp\":1363284000}"+ critValue = CritParameters $ return ("exp", Number 1363284000)+++headerSpec :: Spec+headerSpec = describe "(unencoded) Header" $ do+ it "parses from JSON correctly" $+ let+ headerJSON = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}"+ typValue = Just "JWT"+ in+ eitherDecode headerJSON+ `shouldBe` Right ((algHeader JWA.JWS.HS256) { headerTyp = typValue })++ it "parses signature correctly" $+ let+ sigJSON =+ "{\"protected\":\"eyJhbGciOiJSUzI1NiJ9\",\+ \ \"header\":{\"kid\":\"2010-12-29\"},\+ \ \"signature\":\"\"}"+ h = def { headerAlg = Just JWA.JWS.RS256 }+ h' = def { headerKid = Just "2010-12-29" }+ sig = Signature (Just $ Unarmoured h) (Just h') (Types.Base64Octets "")+ in+ eitherDecode sigJSON `shouldBe` Right sig+++examplePayload :: Types.Base64Octets+examplePayload = Types.Base64Octets "\+ \{\"iss\":\"joe\",\r\n\+ \ \"exp\":1300819380,\r\n\+ \ \"http://example.com/is_root\":true}"+++appendixA1Spec :: Spec+appendixA1Spec = describe "JWS 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" $+ decodeCompact compactJWS `shouldBe` Right jws++ it "round-trips correctly" $+ (encodeCompact jws >>= decodeCompact) `shouldBe` Right jws++ it "computes the HMAC correctly" $+ fst (sign alg jwk rng (L.toStrict signingInput'))+ `shouldBe` Right (BS.pack macOctets)++ it "validates the JWS correctly" $+ fmap (verifyJWS def def jwk) (decodeCompact compactJWS) `shouldBe` Right True++ where+ signingInput' = "\+ \eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9\+ \.\+ \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\+ \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"+ compactJWS = signingInput' `L.append` "\+ \.\+ \dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"+ jws = JWS examplePayload [signature]+ signature = Signature (Just $ Unarmoured h) Nothing (Types.Base64Octets mac)+ alg = JWA.JWS.HS256+ h = def { headerAlg = Just alg, headerTyp = Just "JWT" }+ mac = foldr BS.cons BS.empty macOctets+ macOctets =+ [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]+ jwk = JWK (OctKeyMaterial octKeyMaterial) z z z z z z z z where z = Nothing+ octKeyMaterial = OctKeyParameters Oct $ Types.Base64Octets $+ foldr BS.cons BS.empty+ [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]+++appendixA2Spec :: Spec+appendixA2Spec = describe "JWS A.2. Example JWS using RSASSA-PKCS-v1_5 SHA-256" $ do+ it "computes the signature correctly" $+ fst (sign JWA.JWS.RS256 jwk rng signingInput') `shouldBe` Right sig++ it "validates the signature correctly" $+ verify JWA.JWS.RS256 jwk signingInput' sig `shouldBe` Right True++ where+ signingInput' = "\+ \eyJhbGciOiJSUzI1NiJ9\+ \.\+ \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\+ \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"+ jwk = 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 "JWS A.3. Example JWS using ECDSA P-256 SHA-256" $+ it "validates the signature correctly" $+ verify JWA.JWS.ES256 jwk signingInput' sig `shouldBe` Right True+ where+ signingInput' = "\+ \eyJhbGciOiJFUzI1NiJ9\+ \.\+ \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\+ \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"+ jwk = 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 "JWS A.5. Example Plaintext JWS" $ do+ it "encodes the correct JWS" $+ (jws >>= encodeCompact) `shouldBe` Right exampleJWS++ it "decodes the correct JWS" $+ decodeCompact exampleJWS `shouldBe` jws++ where+ jws = fst $+ signJWS rng (JWS examplePayload []) (algHeader JWA.JWS.None) undefined+ exampleJWS = "eyJhbGciOiJub25lIn0\+ \.\+ \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\+ \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ\+ \."+++appendixA6Spec :: Spec+appendixA6Spec = describe "JWS A.6. Example JWS Using JWS JSON Serialization" $+ it "decodes the correct JWS" $ do+ eitherDecode exampleJWS `shouldBe` Right jws+ eitherDecode exampleJWS' `shouldBe` Right jws'+ lr (eitherDecode exampleFlatJWSWithSignatures :: Either String JWS) `shouldBe` L++ where+ jws = JWS examplePayload [sig1, sig2]+ jws' = JWS examplePayload [sig2]+ sig1 = Signature (Just $ Unarmoured h1) (Just h1') (Types.Base64Octets mac1)+ h1 = def { headerAlg = Just JWA.JWS.RS256 }+ h1' = def { headerKid = Just "2010-12-29" }+ mac1 = foldr BS.cons BS.empty+ [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]+ sig2 = Signature (Just $ Unarmoured h2) (Just h2') (Types.Base64Octets mac2)+ h2 = def { headerAlg = Just JWA.JWS.ES256 }+ h2' = def { headerKid = Just "e9bc097a-ce51-4036-9562-d2ade882db0d" }+ mac2 = B64U.decodeLenient+ "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA\+ \pmWQxfKTUJqPP3-Kg6NU1Q"++ exampleJWS = "\+ \{\"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\"}]\+ \}"+ exampleJWS' = "\+ \{\+ \ \"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\"\+ \}"++data LR = L | R deriving (Eq, Show)++lr :: Either a b -> LR+lr (Left _) = L+lr _ = R
+ test/JWT.hs view
@@ -0,0 +1,87 @@+-- Copyright (C) 2013, 2014 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 JWT where++import Data.Maybe++import Control.Lens+import Data.Aeson+import Data.Default.Class (def)+import Data.HashMap.Strict (insert)+import Data.Time+import Network.URI (parseURI)+import Safe (headMay)+import System.Locale+import Test.Hspec++import Crypto.JOSE+import Crypto.JWT+++intDate :: String -> Maybe NumericDate+intDate = fmap NumericDate . parseTime defaultTimeLocale "%F %T"++exampleClaimsSet :: ClaimsSet+exampleClaimsSet = emptyClaimsSet+ & claimIss .~ Just (fromString "joe")+ & claimExp .~ intDate "2011-03-22 18:43:00"+ & over unregisteredClaims (insert "http://example.com/is_root" (Bool True))+ & addClaim "http://example.com/is_root" (Bool True)++spec :: Spec+spec = do+ describe "JWT Claims Set" $ do+ it "parses from JSON correctly" $+ let+ claimsJSON = "\+ \{\"iss\":\"joe\",\r\n\+ \ \"exp\":1300819380,\r\n\+ \ \"http://example.com/is_root\":true}"+ in+ decode claimsJSON `shouldBe` Just exampleClaimsSet++ it "formats to a parsable and equal value" $+ decode (encode exampleClaimsSet) `shouldBe` Just exampleClaimsSet++ describe "StringOrURI" $+ it "parses from JSON correctly" $ do+ (decode "[\"foo\"]" >>= headMay >>= getString) `shouldBe` Just "foo"+ (decode "[\"http://example.com\"]" >>= headMay >>= getURI)+ `shouldBe` parseURI "http://example.com"+ decode "[\":\"]" `shouldBe` (Nothing :: Maybe [StringOrURI])+ decode "[12345]" `shouldBe` (Nothing :: Maybe [StringOrURI])++ describe "NumericDate" $+ it "parses from JSON correctly" $ do+ decode "[0]" `shouldBe` fmap (:[]) (intDate "1970-01-01 00:00:00")+ decode "[1382245921]" `shouldBe` fmap (:[]) (intDate "2013-10-20 05:12:01")+ decode "[\"notnum\"]" `shouldBe` (Nothing :: Maybe [NumericDate])++ describe "§6.1. Example Unsecured JWT" $+ it "can be decoded and validated" $+ let+ exampleJWT = "eyJhbGciOiJub25lIn0\+ \.\+ \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\+ \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ\+ \."+ jwt = decodeCompact exampleJWT+ k = fromJust $ decode "{\"kty\":\"oct\",\"k\":\"\"}"+ in do+ fmap jwtClaimsSet jwt `shouldBe` Right exampleClaimsSet+ fmap (validateJWSJWT algs def k) jwt `shouldBe` Right True+ where algs = ValidationAlgorithms [None]
+ test/Types.hs view
@@ -0,0 +1,160 @@+-- Copyright (C) 2013-2014 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 Types where++import Data.Aeson+import qualified Data.ByteString as BS+import Data.List.NonEmpty+import Network.URI (parseURI)+import Test.Hspec++import Crypto.JOSE.Types++spec :: Spec+spec = do+ base64UrlSpec+ base64OctetsSpec+ uriSpec+ base64IntegerSpec+ sizedBase64IntegerSpec+ base64X509Spec++base64UrlSpec :: Spec+base64UrlSpec = describe "Base64UrlString" $+ it "can be read from JSON" $ do+ decode "[\"QWxpY2U\"]" `shouldBe` Just [Base64UrlString "Alice"]+ decode "[\"Qm9i\"]"`shouldBe` Just [Base64UrlString "Bob"]++base64OctetsSpec :: Spec+base64OctetsSpec = describe "Base64Octets" $+ it "can be read from JSON" $ do+ decode "[\"AxY8DCtDaGlsbGljb3RoZQ\"]" `shouldBe` Just [Base64Octets iv]+ decode "[\"9hH0vgRfYgPnAHOd8stkvw\"]" `shouldBe` Just [Base64Octets tag]+ where+ iv = BS.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]+ tag = BS.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]++uriSpec :: Spec+uriSpec = describe "URI typeclasses" $ do+ it "gets parsed from JSON correctly" $ do+ decode "[\"http://example.com\"]" `shouldBe`+ fmap (fmap (:[])) parseURI "http://example.com"+ decode "[\"foo\"]" `shouldBe` (Nothing :: Maybe [URI])++ it "gets formatted to JSON correctly" $+ fmap toJSON (Network.URI.parseURI "http://example.com")+ `shouldBe` Just (String "http://example.com")++base64IntegerSpec :: Spec+base64IntegerSpec = describe "Base64Integer" $ do+ it "parses from JSON correctly" $ do+ decode "[\"AQAC\"]" `shouldBe` Just [Base64Integer 65538]+ decode "[\"????\"]" `shouldBe` (Nothing :: Maybe [Base64Integer])++ it "formats to JSON correctly" $+ toJSON (Base64Integer 65538) `shouldBe` "AQAC"++sizedBase64IntegerSpec :: Spec+sizedBase64IntegerSpec = describe "SizedBase64Integer" $ do+ it "parses from JSON correctly" $ do+ decode "[\"AQAC\"]" `shouldBe` Just [SizedBase64Integer 3 65538]+ decode "[\"????\"]" `shouldBe` (Nothing :: Maybe [SizedBase64Integer])++ it "formats to JSON correctly" $+ toJSON (SizedBase64Integer 4 1) `shouldBe` "AAAAAQ"+++-- JWS Appending B. "x5c" (X.509 Certificate Chain) Examples+--+base64X509Spec :: Spec+base64X509Spec = describe "Base64X509" $+ it "parses from JSON correctly and encodes to input string" $+ let decoded = decode x5cExample :: Maybe (NonEmpty Base64X509)+ in fmap encode decoded `shouldBe` Just x5cExample+ where+ x5cExample = "[\"\+ \MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVM\+ \xITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR2\+ \8gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExM\+ \TYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UE\+ \CBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWR\+ \keS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYW\+ \RkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlc\+ \nRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJ\+ \KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTt\+ \wY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqV\+ \Tr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aL\+ \GbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo\+ \7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgW\+ \JCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAw\+ \EAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVH\+ \SMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEA\+ \MDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWR\+ \keS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2\+ \RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVH\+ \SAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j\+ \b20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggE\+ \BANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPI\+ \UyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL\+ \5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9\+ \p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsx\+ \uxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZ\+ \EjYx8WnM25sgVjOuH0aBsXBTWVU+4=\",\"\+ \MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Z\+ \hbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIE\+ \luYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb\+ \24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8x\+ \IDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDY\+ \yMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZS\+ \BHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgM\+ \iBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN\+ \ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XC\+ \APVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux\+ \6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLO\+ \tXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWo\+ \riMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZ\+ \Eewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ\+ \4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBu\+ \zEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQK\+ \Ew5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2x\+ \pY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudm\+ \FsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CA\+ \QEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGG\+ \F2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA\+ \6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybD\+ \BLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZ\+ \mljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjAN\+ \BgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+\+ \Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgM\+ \QLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j\+ \09VZw==\",\"\+ \MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ\+ \0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNT\+ \AzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0a\+ \G9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkq\+ \hkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE\+ \5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTm\+ \V0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZ\+ \XJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQD\+ \ExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9\+ \AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5a\+ \vIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zf\+ \N1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwb\+ \P7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQU\+ \AA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQ\+ \C1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMM\+ \j4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd\"]"