packages feed

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 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\"]"