diff --git a/jose.cabal b/jose.cabal
--- a/jose.cabal
+++ b/jose.cabal
@@ -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
diff --git a/src/Crypto/JOSE/JWK.hs b/src/Crypto/JOSE/JWK.hs
--- a/src/Crypto/JOSE/JWK.hs
+++ b/src/Crypto/JOSE/JWK.hs
@@ -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
   }
diff --git a/src/Crypto/JOSE/Types.hs b/src/Crypto/JOSE/Types.hs
--- a/src/Crypto/JOSE/Types.hs
+++ b/src/Crypto/JOSE/Types.hs
@@ -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
diff --git a/test/JWK.hs b/test/JWK.hs
new file mode 100644
--- /dev/null
+++ b/test/JWK.hs
@@ -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
diff --git a/test/JWS.hs b/test/JWS.hs
new file mode 100644
--- /dev/null
+++ b/test/JWS.hs
@@ -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
diff --git a/test/JWT.hs b/test/JWT.hs
new file mode 100644
--- /dev/null
+++ b/test/JWT.hs
@@ -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]
diff --git a/test/Types.hs b/test/Types.hs
new file mode 100644
--- /dev/null
+++ b/test/Types.hs
@@ -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\"]"
