diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
-# jose - Javascript Object Signing and Encryption
+# jose - Javascript Object Signing and Encryption & JWT (JSON Web Token)
 
 jose is a Haskell implementation of [Javascript Object Signing and
-Encryption](https://datatracker.ietf.org/wg/jose/).
+Encryption](https://datatracker.ietf.org/wg/jose/) and [JSON Web
+Token]
+(https://datatracker.ietf.org/doc/draft-ietf-oauth-json-web-token/).
 
-The library is in its infancy.  Contributions are welcome.
+Encryption (JWE) is not supported but signing is supported.  All key
+types and algorithms are supported, but EC and symmetric key
+generation is not yet implemented.
+
+Contributions are welcome.
diff --git a/jose.cabal b/jose.cabal
--- a/jose.cabal
+++ b/jose.cabal
@@ -1,17 +1,22 @@
 name:                jose
-version:             0.1.27.0
-synopsis:            Javascript Object Signing and Encryption
+version:             0.2.31.0
+synopsis:
+  Javascript Object Signing and Encryption and JSON Web Token library
 description:
   .
   An implementation of the Javascript Object Signing and Encryption
-  (jose) formats.
+  (JOSE) and JSON Web Token (JWT) formats.
   .
-  Currently, only JSON Web Key (JWK) and JSON Web Signature (JWS)
-  are implemented, and only the RSA algorithms.
+  Web Encryption (JWE) is not yet implemented.
   .
+  All JWS algorithms (HMAC, ECDSA, RSASSA-PKCS-v1_5 and RSASSA-PSS)
+  are implemented, however, the ECDSA implementation is is
+  vulnerable to timing attacks and should therefore only be used for
+  JWS verification.
+  .
   The version number tracks the IETF jose working group draft
   revisions.  For now, expect breaking API changes on any version
-  change except for the final part being incremented.
+  change except for the final (fourth) part being incremented.
 
 homepage:            https://github.com/frasertweedale/hs-jose
 bug-reports:         https://github.com/frasertweedale/hs-jose/issues
@@ -28,16 +33,19 @@
 
 library
   exposed-modules:
+    Crypto.JOSE
     Crypto.JOSE.Classes
     Crypto.JOSE.Compact
-    Crypto.JOSE.Types
-    Crypto.JOSE.JWA.JWK
-    Crypto.JOSE.JWA.JWS
+    Crypto.JOSE.Error
     Crypto.JOSE.JWK
     Crypto.JOSE.JWS
     Crypto.JOSE.Legacy
+    Crypto.JOSE.Types
+    Crypto.JWT
 
   other-modules:
+    Crypto.JOSE.JWA.JWK
+    Crypto.JOSE.JWA.JWS
     Crypto.JOSE.JWA.JWE
     Crypto.JOSE.JWA.JWE.Alg
     Crypto.JOSE.JWS.Internal
@@ -49,7 +57,8 @@
     , attoparsec
     , base64-bytestring == 1.0.*
     , byteable == 0.1.*
-    , crypto-pubkey == 0.2.*
+    , crypto-pubkey >= 0.2.3
+    , crypto-pubkey-types >= 0.3.2
     , crypto-random == 0.0.7.*
     , cryptohash == 0.11.*
     , template-haskell >= 2.4
@@ -57,6 +66,7 @@
     , unordered-containers == 0.2.*
     , bytestring == 0.10.*
     , text == 1.1.*
+    , time == 1.4.*
     , network >= 2.4
     , certificate == 1.3.*
     , vector
@@ -79,13 +89,16 @@
     , base64-bytestring
     , byteable
     , crypto-pubkey
+    , crypto-pubkey-types
     , crypto-random
     , cryptohash
+    , old-locale
     , template-haskell
     , aeson
     , unordered-containers
     , bytestring
     , text
+    , time
     , network
     , certificate
     , vector
diff --git a/src/Crypto/JOSE.hs b/src/Crypto/JOSE.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/JOSE.hs
@@ -0,0 +1,35 @@
+-- Copyright (C) 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.
+
+{-|
+
+Prelude for the  library.
+
+-}
+module Crypto.JOSE
+  (
+    module Crypto.JOSE.Classes
+  , module Crypto.JOSE.Compact
+  , module Crypto.JOSE.Error
+  , module Crypto.JOSE.JWK
+  , module Crypto.JOSE.JWS
+  ) where
+
+import Crypto.JOSE.Classes
+import Crypto.JOSE.Compact
+import Crypto.JOSE.Error
+import Crypto.JOSE.JWK
+import Crypto.JOSE.JWS
+
+{-# ANN module ("HLint: ignore Use import/export shortcut" :: String) #-}
diff --git a/src/Crypto/JOSE/Classes.hs b/src/Crypto/JOSE/Classes.hs
--- a/src/Crypto/JOSE/Classes.hs
+++ b/src/Crypto/JOSE/Classes.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013  Fraser Tweedale
+-- 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.
@@ -12,20 +12,46 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+{-# LANGUAGE TypeFamilies #-}
+
 {-|
 
 Type classes for use with the JOSE modules.
 
 -}
-module Crypto.JOSE.Classes where
+module Crypto.JOSE.Classes
+  (
+    module Crypto.Random
+  , Key(..)
+  ) where
 
 import qualified Data.ByteString as B
 
+import Crypto.Random
+
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
+import Crypto.JOSE.Error
 
 -- | A Key that can sign messages and validate signatures according
 -- to a given 'Alg'.
 --
+-- Can fail with 'AlgorithmMismatch'
+--
 class Key k where
-  sign :: JWA.JWS.Alg -> k -> B.ByteString -> B.ByteString
-  verify :: JWA.JWS.Alg -> k -> B.ByteString -> B.ByteString -> Bool
+  type KeyGenParam k
+  type KeyContent k
+  gen :: CPRG g => KeyGenParam k -> g -> (k, g)
+  fromKeyContent :: KeyContent k -> k
+  sign
+    :: CPRG g
+    => JWA.JWS.Alg
+    -> k
+    -> g
+    -> B.ByteString
+    -> (Either Error B.ByteString, g)
+  verify
+    :: JWA.JWS.Alg
+    -> k
+    -> B.ByteString
+    -> B.ByteString
+    -> Either Error Bool
diff --git a/src/Crypto/JOSE/Compact.hs b/src/Crypto/JOSE/Compact.hs
--- a/src/Crypto/JOSE/Compact.hs
+++ b/src/Crypto/JOSE/Compact.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013  Fraser Tweedale
+-- 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.
@@ -25,24 +25,26 @@
 
 import qualified Data.ByteString.Lazy as L
 
+import Crypto.JOSE.Error
 
+
 -- | Data that can be parsed from a compact representation.
 --
 class FromCompact a where
-  fromCompact :: [L.ByteString] -> Either String a
+  fromCompact :: [L.ByteString] -> Either Error a
 
 -- | Decode a compact representation.
 --
-decodeCompact :: FromCompact a => L.ByteString -> Either String a
+decodeCompact :: FromCompact a => L.ByteString -> Either Error a
 decodeCompact = fromCompact . L.split 46
 
 
 -- | Data that can be converted to a compact representation.
 --
 class ToCompact a where
-  toCompact :: a -> Either String [L.ByteString]
+  toCompact :: a -> Either Error [L.ByteString]
 
 -- | Encode data to a compact representation.
 --
-encodeCompact :: ToCompact a => a -> Either String L.ByteString
+encodeCompact :: ToCompact a => a -> Either Error L.ByteString
 encodeCompact = fmap (L.intercalate ".") . toCompact
diff --git a/src/Crypto/JOSE/Error.hs b/src/Crypto/JOSE/Error.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/JOSE/Error.hs
@@ -0,0 +1,41 @@
+-- Copyright (C) 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.
+
+{-|
+
+JOSE error types.
+
+-}
+module Crypto.JOSE.Error
+  (
+    Error(..)
+  ) where
+
+import qualified Crypto.PubKey.RSA as RSA
+
+-- | All the errors that can occur, with the notable exception of
+--   'Data.Aeson' decoding functions.  Aeson decoding errors that
+--   occur in 'decodeCompact' are, however, lifted into this type
+--   via the 'JSONDecodeError' constructor.
+--
+data Error
+  = AlgorithmNotImplemented   -- ^ A requested algorithm is not implemented
+  | AlgorithmMismatch String  -- ^ A requested algorithm cannot be used
+  | KeyMismatch String        -- ^ Wrong type of key was given
+  | KeySizeTooSmall           -- ^ Key size is too small
+  | RSAError RSA.Error        -- ^ RSA encryption, decryption or signing error
+  | CompactEncodeError String -- ^ Cannot produce compact representation of data
+  | CompactDecodeError String -- ^ Cannot decode compact representation
+  | JSONDecodeError String    -- ^ Cannot decode JSON data
+  deriving (Eq, Show)
diff --git a/src/Crypto/JOSE/JWA/JWK.hs b/src/Crypto/JOSE/JWA/JWK.hs
--- a/src/Crypto/JOSE/JWA/JWK.hs
+++ b/src/Crypto/JOSE/JWA/JWK.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013  Fraser Tweedale
+-- 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.
@@ -15,6 +15,8 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeFamilies #-}
 
 {-|
 
@@ -33,29 +35,34 @@
   -- * Parameters for RSA Keys
   , RSAPrivateKeyOthElem(..)
   , RSAPrivateKeyOptionalParameters(..)
+  , RSAPrivateKeyParameters(..)
   , RSAKeyParameters(..)
-  , genRSAParams
-  , genRSA
 
   -- * Parameters for Symmetric Keys
   , OctKeyParameters(..)
 
+  , KeyMaterialGenParam(..)
   , KeyMaterial(..)
   ) where
 
 import Control.Applicative
 import Control.Arrow
+import Data.Maybe
 
 import Crypto.Hash
 import Crypto.PubKey.HashDescr
-import Crypto.PubKey.RSA
-import qualified Crypto.PubKey.RSA.PKCS15
+import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
+import qualified Crypto.PubKey.RSA as RSA
+import qualified Crypto.PubKey.RSA.PKCS15 as PKCS15
+import qualified Crypto.PubKey.RSA.PSS as PSS
+import qualified Crypto.Types.PubKey.ECC as ECC
 import Crypto.Random
 import Data.Aeson
 import Data.Byteable
 import qualified Data.ByteString as B
 import qualified Data.HashMap.Strict as M
 
+import Crypto.JOSE.Error
 import Crypto.JOSE.Classes
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import qualified Crypto.JOSE.TH
@@ -122,154 +129,265 @@
     , "q" .= rsaQ
     , "dp" .= rsaDp
     , "dq" .= rsaDq
-    , "dq" .= rsaQi
+    , "qi" .= rsaQi
     ] ++ maybe [] ((:[]) . ("oth" .=)) rsaOth
 
 
+-- | RSA private key parameters
+--
+data RSAPrivateKeyParameters = RSAPrivateKeyParameters
+  { rsaD :: Types.Base64Integer
+  , rsaOptionalParameters :: Maybe RSAPrivateKeyOptionalParameters
+  }
+  deriving (Eq, Show)
+
+instance FromJSON RSAPrivateKeyParameters where
+  parseJSON = withObject "RSA private key parameters" $ \o ->
+    RSAPrivateKeyParameters
+    <$> o .: "d"
+    <*> (if any (`M.member` o) ["p", "q", "dp", "dq", "qi", "oth"]
+      then Just <$> parseJSON (Object o)
+      else pure Nothing)
+
+instance ToJSON RSAPrivateKeyParameters where
+  toJSON RSAPrivateKeyParameters {..} = object $
+    ("d" .= rsaD) : maybe [] (Types.objectPairs . toJSON) rsaOptionalParameters
+
+
 -- | Parameters for Elliptic Curve Keys
 --
-data ECKeyParameters =
-  ECPrivateKeyParameters {
-    ecD :: Types.SizedBase64Integer
-    }
-  | ECPublicKeyParameters {
-    ecCrv :: Crv,
-    ecX :: Types.SizedBase64Integer,
-    ecY :: Types.SizedBase64Integer
-    }
+data ECKeyParameters = ECKeyParameters
+  {
+    ecKty :: EC
+  , ecCrv :: Crv
+  , ecX :: Types.SizedBase64Integer
+  , ecY :: Types.SizedBase64Integer
+  , ecD :: Maybe Types.SizedBase64Integer
+  }
   deriving (Eq, Show)
 
 instance FromJSON ECKeyParameters where
-  parseJSON = withObject "EC" (\o ->
-    ECPrivateKeyParameters    <$> o .: "d"
-    <|> ECPublicKeyParameters <$> o .: "crv" <*> o .: "x" <*> o .: "y")
+  parseJSON = withObject "EC" $ \o -> ECKeyParameters
+    <$> o .: "kty"
+    <*> o .: "crv"
+    <*> o .: "x"
+    <*> o .: "y"
+    <*> o .:? "d"
 
 instance ToJSON ECKeyParameters where
-  toJSON (ECPrivateKeyParameters d) = object ["d" .= d]
-  toJSON (ECPublicKeyParameters {..}) = object [
-    "crv" .= ecCrv
+  toJSON (ECKeyParameters {..}) = object $
+    [ "crv" .= ecCrv
     , "x" .= ecX
     , "y" .= ecY
-    ]
+    ] ++ fmap ("d" .=) (maybeToList ecD)
 
 instance Key ECKeyParameters where
-  sign = error "elliptic curve algorithms not implemented"
-  verify = error "elliptic curve algorithms not implemented"
+  type KeyGenParam ECKeyParameters = Crv
+  type KeyContent ECKeyParameters = ECKeyParameters
+  gen = undefined  -- TODO implement
+  fromKeyContent = id
+  sign JWA.JWS.ES256 k@(ECKeyParameters { ecCrv = P_256 }) =
+    signEC hashDescrSHA256 k
+  sign JWA.JWS.ES384 k@(ECKeyParameters { ecCrv = P_384 }) =
+    signEC hashDescrSHA384 k
+  sign JWA.JWS.ES512 k@(ECKeyParameters { ecCrv = P_521 }) =
+    signEC hashDescrSHA512 k
+  sign h _ = \g _ ->
+    (Left $ AlgorithmMismatch  $ show h ++ "cannot be used with EC key", g)
+  verify JWA.JWS.ES256 = verifyEC hashDescrSHA256
+  verify JWA.JWS.ES384 = verifyEC hashDescrSHA384
+  verify JWA.JWS.ES512 = verifyEC hashDescrSHA512
+  verify h = \_ _ _ ->
+    Left $ AlgorithmMismatch  $ show h ++ "cannot be used with EC key"
 
+signEC
+  :: CPRG g
+  => HashDescr
+  -> ECKeyParameters
+  -> g
+  -> B.ByteString
+  -> (Either Error B.ByteString, g)
+signEC h k@(ECKeyParameters {..}) g m = case ecD of
+  Just ecD' -> first (Right . sigToBS) sig where
+    sig = ECDSA.sign g privateKey (hashFunction h) m
+    sigToBS (ECDSA.Signature r s) =
+      Types.integerToBS r `B.append` Types.integerToBS s
+    privateKey = ECDSA.PrivateKey (curve k) (d ecD')
+    d (Types.SizedBase64Integer _ n) = n
+  Nothing -> (Left $ KeyMismatch "not an EC private key", g)
 
+verifyEC
+  :: HashDescr
+  -> ECKeyParameters
+  -> B.ByteString
+  -> B.ByteString
+  -> Either Error Bool
+verifyEC h k m s = Right $ ECDSA.verify (hashFunction h) pubkey sig m
+  where
+  pubkey = ECDSA.PublicKey (curve k) (point k)
+  sig = uncurry ECDSA.Signature
+    $ Types.bsToInteger *** Types.bsToInteger
+    $ B.splitAt (B.length s `div` 2) s
+
+curve :: ECKeyParameters -> ECC.Curve
+curve ECKeyParameters {..} = ECC.getCurveByName (curveName ecCrv) where
+  curveName P_256 = ECC.SEC_p256r1
+  curveName P_384 = ECC.SEC_p384r1
+  curveName P_521 = ECC.SEC_p521r1
+
+point :: ECKeyParameters -> ECC.Point
+point ECKeyParameters {..} = ECC.Point (integer ecX) (integer ecY) where
+  integer (Types.SizedBase64Integer _ n) = n
+
+
 -- | Parameters for RSA Keys
 --
-data RSAKeyParameters =
-  RSAPrivateKeyParameters {
-    rsaN :: Types.SizedBase64Integer
-    , rsaE :: Types.Base64Integer
-    , rsaD :: Types.Base64Integer
-    , rsaOptionalParameters :: Maybe RSAPrivateKeyOptionalParameters
-    }
-  | RSAPublicKeyParameters {
-    rsaN' :: Types.SizedBase64Integer
-    , rsaE' :: Types.Base64Integer
-    }
+data RSAKeyParameters = RSAKeyParameters
+  { rsaKty :: RSA
+  , rsaN :: Types.SizedBase64Integer
+  , rsaE :: Types.Base64Integer
+  , rsaPrivateKeyParameters :: Maybe RSAPrivateKeyParameters
+  }
   deriving (Eq, Show)
 
 instance FromJSON RSAKeyParameters where
-  parseJSON = withObject "RSA" (\o ->
-    RSAPrivateKeyParameters
-      <$> o .: "n"
+  parseJSON = withObject "RSA" $ \o ->
+    RSAKeyParameters
+      <$> o .: "kty"
+      <*> o .: "n"
       <*> o .: "e"
-      <*> o .: "d"
-      <*> (if any (`M.member` o) ["p", "q", "dp", "dq", "qi", "oth"]
+      <*> if M.member "d" o
         then Just <$> parseJSON (Object o)
-        else pure Nothing)
-    <|> RSAPublicKeyParameters <$> o .: "n" <*> o .: "e")
+        else pure Nothing
 
 instance ToJSON RSAKeyParameters where
-  toJSON (RSAPrivateKeyParameters {..}) = object $
-    ("n" .= rsaN)
+  toJSON RSAKeyParameters {..} = object $
+      ("kty" .= rsaKty)
+    : ("n" .= rsaN)
     : ("e" .= rsaE)
-    : ("d" .= rsaD)
-    : maybe [] (Types.objectPairs . toJSON) rsaOptionalParameters
-  toJSON (RSAPublicKeyParameters n e) = object ["n" .= n, "e" .= e]
+    : maybe [] (Types.objectPairs . toJSON) rsaPrivateKeyParameters
 
 instance Key RSAKeyParameters where
-  sign JWA.JWS.RS256 = signRSA hashDescrSHA256
-  sign JWA.JWS.RS384 = signRSA hashDescrSHA384
-  sign JWA.JWS.RS512 = signRSA hashDescrSHA512
-  sign h = error $ "alg/key mismatch: " ++ show h ++ "/RSA"
-  verify JWA.JWS.RS256 = verifyRSA hashDescrSHA256
-  verify JWA.JWS.RS384 = verifyRSA hashDescrSHA384
-  verify JWA.JWS.RS512 = verifyRSA hashDescrSHA512
-  verify h = error $ "alg/key mismatch: " ++ show h ++ "/RSA"
+  type KeyGenParam RSAKeyParameters = Int
+  type KeyContent RSAKeyParameters =
+    ( Types.SizedBase64Integer
+    , Types.Base64Integer
+    , Maybe RSAPrivateKeyParameters
+    )
+  gen size g =
+    let
+      i = Types.Base64Integer
+      ((RSA.PublicKey s n e, RSA.PrivateKey _ d p q dp dq qi), g') =
+        RSA.generate g size 65537
+    in
+      ( fromKeyContent
+        ( Types.SizedBase64Integer s n
+        , i e
+        , Just (RSAPrivateKeyParameters (i d)
+          (Just (RSAPrivateKeyOptionalParameters
+            (i p) (i q) (i dp) (i dq) (i qi) Nothing))))
+      , g')
+  fromKeyContent (n, e, p) = RSAKeyParameters RSA n e p
+  sign JWA.JWS.RS256 = signPKCS15 hashDescrSHA256
+  sign JWA.JWS.RS384 = signPKCS15 hashDescrSHA384
+  sign JWA.JWS.RS512 = signPKCS15 hashDescrSHA512
+  sign JWA.JWS.PS256 = signPSS hashDescrSHA256
+  sign JWA.JWS.PS384 = signPSS hashDescrSHA384
+  sign JWA.JWS.PS512 = signPSS hashDescrSHA512
+  sign h = \_ g -> const
+    (Left $ AlgorithmMismatch  $ show h ++ "cannot be used with RSA key", g)
+  verify JWA.JWS.RS256 = verifyPKCS15 hashDescrSHA256
+  verify JWA.JWS.RS384 = verifyPKCS15 hashDescrSHA384
+  verify JWA.JWS.RS512 = verifyPKCS15 hashDescrSHA512
+  verify JWA.JWS.PS256 = verifyPSS hashDescrSHA256
+  verify JWA.JWS.PS384 = verifyPSS hashDescrSHA384
+  verify JWA.JWS.PS512 = verifyPSS hashDescrSHA512
+  verify h = \_ _ _ ->
+    Left $ AlgorithmMismatch  $ show h ++ "cannot be used with RSA key"
 
-signRSA :: HashDescr -> RSAKeyParameters -> B.ByteString -> B.ByteString
-signRSA h k m = either (error . show) id $
-  Crypto.PubKey.RSA.PKCS15.sign Nothing h (privateKey k) m where
-    privateKey (RSAPrivateKeyParameters
-      (Types.SizedBase64Integer size n)
-      (Types.Base64Integer e)
-      (Types.Base64Integer d)
-      _) = PrivateKey (PublicKey size n e) d 0 0 0 0 0
-    privateKey _ = error "not an RSA private key"
+signPKCS15
+  :: CPRG g
+  => HashDescr
+  -> RSAKeyParameters
+  -> g
+  -> B.ByteString
+  -> (Either Error B.ByteString, g)
+signPKCS15 h k g m = case rsaPrivateKey k of
+  Left e -> (Left e, g)
+  Right k' -> first (either (Left . RSAError) Right) $
+    PKCS15.signSafer g h k' m
 
-verifyRSA
+verifyPKCS15
   :: HashDescr
   -> RSAKeyParameters
   -> B.ByteString
   -> B.ByteString
-  -> Bool
-verifyRSA h k = Crypto.PubKey.RSA.PKCS15.verify h (publicKey k) where
-  publicKey (RSAPrivateKeyParameters
-    (Types.SizedBase64Integer size n)
-    (Types.Base64Integer e)
-    _
-    _
-    ) = PublicKey size n e
-  publicKey (RSAPublicKeyParameters
-    (Types.SizedBase64Integer size n)
-    (Types.Base64Integer e)
-    ) = PublicKey size n e
+  -> Either Error Bool
+verifyPKCS15 h k m = Right . PKCS15.verify h (rsaPublicKey k) m
 
--- | Generate RSA public and private key parameters.
---
-genRSAParams :: Int -> IO (RSAKeyParameters, RSAKeyParameters)
-genRSAParams size =
-  let
-    i = Types.Base64Integer
-    si = Types.SizedBase64Integer
-  in do
-    ent <- createEntropyPool
-    ((PublicKey s n e, PrivateKey _ d p q dp dq qi), _) <-
-      return $ generate (cprgCreate ent :: SystemRNG) size 65537
-    return
-      ( RSAPublicKeyParameters (si s n) (i e)
-      , RSAPrivateKeyParameters (si s n) (i e) (i d)
-          (Just (RSAPrivateKeyOptionalParameters
-            (i p) (i q) (i dp) (i dq) (i qi) Nothing))
-      )
+signPSS
+  :: CPRG g
+  => HashDescr
+  -> RSAKeyParameters
+  -> g
+  -> B.ByteString
+  -> (Either Error B.ByteString, g)
+signPSS h k g m = case rsaPrivateKey k of
+  Left e -> (Left e, g)
+  Right k' -> first (either (Left . RSAError) Right) $
+   PSS.signSafer g (PSS.defaultPSSParams (hashFunction h)) k' m
 
--- | Generate RSA public and private key material.
---
-genRSA :: Int -> IO (KeyMaterial, KeyMaterial)
-genRSA = fmap (RSAKeyMaterial RSA *** RSAKeyMaterial RSA) . genRSAParams
+verifyPSS
+  :: HashDescr
+  -> RSAKeyParameters
+  -> B.ByteString
+  -> B.ByteString
+  -> Either Error Bool
+verifyPSS h k m = Right .
+  PSS.verify (PSS.defaultPSSParams (hashFunction h)) (rsaPublicKey k) m
 
+rsaPrivateKey :: RSAKeyParameters -> Either Error RSA.PrivateKey
+rsaPrivateKey (RSAKeyParameters _
+  (Types.SizedBase64Integer size n)
+  (Types.Base64Integer e)
+  (Just (RSAPrivateKeyParameters (Types.Base64Integer d) _)))
+  | size >= 2048 `div` 8 = Right $
+    RSA.PrivateKey (RSA.PublicKey size n e) d 0 0 0 0 0
+  | otherwise = Left KeySizeTooSmall
+rsaPrivateKey _ = Left $ KeyMismatch "not an RSA private key"
 
+rsaPublicKey :: RSAKeyParameters -> RSA.PublicKey
+rsaPublicKey (RSAKeyParameters _
+  (Types.SizedBase64Integer size n) (Types.Base64Integer e) _)
+  = RSA.PublicKey size n e
+
+
 -- | Symmetric key parameters data.
 --
-newtype OctKeyParameters = OctKeyParameters Types.Base64Octets
+data OctKeyParameters = OctKeyParameters
+  { octKty :: Oct
+  , octK :: Types.Base64Octets
+  }
   deriving (Eq, Show)
 
 instance FromJSON OctKeyParameters where
-  parseJSON = (OctKeyParameters <$>) . parseJSON
+  parseJSON = withObject "symmetric key" $ \o ->
+    OctKeyParameters <$> o .: "kty" <*> o .: "k"
 
 instance ToJSON OctKeyParameters where
-  toJSON (OctKeyParameters k) = toJSON k
+  toJSON OctKeyParameters {..} = object ["kty" .= octKty, "k" .= octK]
 
 instance Key OctKeyParameters where
-  sign JWA.JWS.HS256 = signOct SHA256
-  sign JWA.JWS.HS384 = signOct SHA384
-  sign JWA.JWS.HS512 = signOct SHA512
-  sign h = error $ "alg/key mismatch: " ++ show h ++ "/Oct"
-  verify h k m s = sign h k m == s
+  type KeyGenParam OctKeyParameters = Int
+  type KeyContent OctKeyParameters = Types.Base64Octets
+  gen = undefined  -- TODO implement
+  fromKeyContent = OctKeyParameters Oct
+  sign JWA.JWS.HS256 k g = first Right . (,g) . signOct SHA256 k
+  sign JWA.JWS.HS384 k g = first Right . (,g) . signOct SHA384 k
+  sign JWA.JWS.HS512 k g = first Right . (,g) . signOct SHA512 k
+  sign h _ g = const
+    (Left $ AlgorithmMismatch $ show h ++ "cannot be used with Oct key", g)
+  verify h k m s = fst (sign h k (undefined :: SystemRNG) m) >>= Right . (== s)
 
 signOct
   :: HashAlgorithm a
@@ -277,34 +395,46 @@
   -> OctKeyParameters
   -> B.ByteString
   -> B.ByteString
-signOct a (OctKeyParameters (Types.Base64Octets k)) m = toBytes $ hmacAlg a k m
+signOct a (OctKeyParameters _ (Types.Base64Octets k)) m
+  = toBytes $ hmacAlg a k m
 
 
 -- | Key material sum type.
 --
-data KeyMaterial =
-  ECKeyMaterial EC ECKeyParameters
-  | RSAKeyMaterial RSA RSAKeyParameters
-  | OctKeyMaterial Oct OctKeyParameters
+data KeyMaterial
+  = ECKeyMaterial ECKeyParameters
+  | RSAKeyMaterial RSAKeyParameters
+  | OctKeyMaterial OctKeyParameters
   deriving (Eq, Show)
 
 instance FromJSON KeyMaterial where
-  parseJSON = withObject "KeyMaterial" (\o ->
-    ECKeyMaterial      <$> o .: "kty" <*> parseJSON (Object o)
-    <|> RSAKeyMaterial <$> o .: "kty" <*> parseJSON (Object o)
-    <|> OctKeyMaterial <$> o .: "kty" <*> o .: "k")
+  parseJSON = withObject "KeyMaterial" $ \o ->
+    ECKeyMaterial      <$> parseJSON (Object o)
+    <|> RSAKeyMaterial <$> parseJSON (Object o)
+    <|> OctKeyMaterial <$> parseJSON (Object o)
 
 instance ToJSON KeyMaterial where
-  toJSON (ECKeyMaterial k p)  = object $ ("kty" .= k) : Types.objectPairs (toJSON p)
-  toJSON (RSAKeyMaterial k p) = object $ ("kty" .= k) : Types.objectPairs (toJSON p)
-  toJSON (OctKeyMaterial k i) = object ["kty" .= k, "k" .= i]
+  toJSON (ECKeyMaterial p)  = object $ Types.objectPairs (toJSON p)
+  toJSON (RSAKeyMaterial p) = object $ Types.objectPairs (toJSON p)
+  toJSON (OctKeyMaterial p) = object $ Types.objectPairs (toJSON p)
 
+data KeyMaterialGenParam
+  = ECGenParam Crv
+  | RSAGenParam Int
+  | OctGenParam Int
+
 instance Key KeyMaterial where
-  sign JWA.JWS.None _ = const ""
-  sign h (ECKeyMaterial _ k)  = sign h k
-  sign h (RSAKeyMaterial _ k) = sign h k
-  sign h (OctKeyMaterial _ k) = sign h k
-  verify JWA.JWS.None _ = \_ s -> s == ""
-  verify h (ECKeyMaterial _ k)  = verify h k
-  verify h (RSAKeyMaterial _ k) = verify h k
-  verify h (OctKeyMaterial _ k) = verify h k
+  type KeyGenParam KeyMaterial = KeyMaterialGenParam
+  type KeyContent KeyMaterial = KeyMaterial
+  gen (ECGenParam a) = first ECKeyMaterial . gen a
+  gen (RSAGenParam a) = first RSAKeyMaterial . gen a
+  gen (OctGenParam a) = first OctKeyMaterial . gen a
+  fromKeyContent = id
+  sign JWA.JWS.None _ = \g _ -> (Right "", g)
+  sign h (ECKeyMaterial k)  = sign h k
+  sign h (RSAKeyMaterial k) = sign h k
+  sign h (OctKeyMaterial k) = sign h k
+  verify JWA.JWS.None _ = \_ s -> Right $ s == ""
+  verify h (ECKeyMaterial k)  = verify h k
+  verify h (RSAKeyMaterial k) = verify h k
+  verify h (OctKeyMaterial k) = verify h k
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
@@ -15,6 +15,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
 
 {-|
 
@@ -27,10 +28,10 @@
 module Crypto.JOSE.JWK
   (
     JWK(..)
-  , materialJWK
-  , genRSA
 
   , JWKSet(..)
+
+  , module JWA.JWK
   ) where
 
 import Control.Applicative
@@ -41,7 +42,7 @@
 
 import Crypto.JOSE.Classes
 import qualified Crypto.JOSE.JWA.JWE.Alg as JWA.JWE
-import qualified Crypto.JOSE.JWA.JWK as JWA.JWK
+import Crypto.JOSE.JWA.JWK as JWA.JWK
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import qualified Crypto.JOSE.TH
 import qualified Crypto.JOSE.Types as Types
@@ -116,23 +117,17 @@
     ++ Types.objectPairs (toJSON jwkMaterial)
 
 instance Key JWK where
+  type KeyGenParam JWK = JWA.JWK.KeyMaterialGenParam
+  type KeyContent JWK = JWA.JWK.KeyMaterial
+  gen p = first fromKeyContent . gen p
+  fromKeyContent k = JWK k z z z z z z z z where z = Nothing
   sign h k = sign h $ jwkMaterial k
   verify h k = verify h $ jwkMaterial k
 
--- | Construct a minimal JWK from key material.
---
-materialJWK :: JWA.JWK.KeyMaterial -> JWK
-materialJWK m = JWK m n n n n n n n n where n = Nothing
 
--- | Generate a /(public, private)/ RSA keypair.
---
-genRSA :: Int -> IO (JWK, JWK)
-genRSA = fmap (materialJWK *** materialJWK) . JWA.JWK.genRSA
-
-
 -- | JWK §4.  JSON Web Key Set (JWK Set) Format
 --
-data JWKSet = JWKSet [JWK]
+newtype JWKSet = JWKSet [JWK] deriving (Eq, Show)
 
 instance FromJSON JWKSet where
   parseJSON = withObject "JWKSet" (\o -> JWKSet <$> o .: "keys")
diff --git a/src/Crypto/JOSE/JWS.hs b/src/Crypto/JOSE/JWS.hs
--- a/src/Crypto/JOSE/JWS.hs
+++ b/src/Crypto/JOSE/JWS.hs
@@ -21,7 +21,7 @@
 -}
 module Crypto.JOSE.JWS
   (
-    Header(..)
+    JWSHeader(..)
 
   , JWS(..)
   , jwsPayload
diff --git a/src/Crypto/JOSE/JWS/Internal.hs b/src/Crypto/JOSE/JWS/Internal.hs
--- a/src/Crypto/JOSE/JWS/Internal.hs
+++ b/src/Crypto/JOSE/JWS/Internal.hs
@@ -19,6 +19,7 @@
 module Crypto.JOSE.JWS.Internal where
 
 import Control.Applicative
+import Control.Arrow
 import Data.Char
 import Data.Maybe
 
@@ -26,8 +27,10 @@
 import Data.Aeson.Parser
 import Data.Aeson.Types
 import qualified Data.Attoparsec.ByteString.Lazy as A
+import Data.Byteable
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BSL
+import qualified Data.ByteString.Base64.URL as B64U
 import qualified Data.ByteString.Base64.URL.Lazy as B64UL
 import qualified Data.HashMap.Strict as M
 import qualified Data.Text as T
@@ -37,6 +40,7 @@
 
 import Crypto.JOSE.Classes
 import Crypto.JOSE.Compact
+import Crypto.JOSE.Error
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import Crypto.JOSE.JWK
 import qualified Crypto.JOSE.Types as Types
@@ -84,7 +88,7 @@
 
 
 -- | JWS Header data type.
-data Header = Header
+data JWSHeader = JWSHeader
   { headerAlg :: JWA.JWS.Alg
   , headerJku :: Maybe Types.URI  -- ^ JWK Set URL
   , headerJwk :: Maybe JWK
@@ -100,10 +104,10 @@
   }
   deriving (Show)
 
-instance Eq Header where
+instance Eq JWSHeader where
   a == b =
     let
-      ignoreRaw (Header alg jku jwk kid x5u x5c x5t x5tS256 typ cty crit _)
+      ignoreRaw (JWSHeader alg jku jwk kid x5u x5c x5t x5tS256 typ cty crit _)
         = (alg, jku, jwk, kid, x5u, x5c, x5t, x5tS256, typ, cty, crit)
     in
       ignoreRaw a == ignoreRaw b
@@ -112,8 +116,8 @@
   :: (forall a. FromJSON a => T.Text -> Parser a)
   -> (forall a. FromJSON a => T.Text -> Parser (Maybe a))
   -> Parser (Maybe CritParameters)
-  -> Parser Header
-parseHeaderWith req opt crit = Header
+  -> Parser JWSHeader
+parseHeaderWith req opt crit = JWSHeader
     <$> req "alg"
     <*> opt "jku"
     <*> opt "jwk"
@@ -132,29 +136,29 @@
   then Just <$> parseJSON (Object o)
   else pure Nothing
 
-instance FromJSON Header where
+instance FromJSON JWSHeader where
   parseJSON = withObject "JWS Header" (\o ->
     parseHeaderWith (o .:) (o .:?) (parseCrit o))
 
-instance ToJSON Header where
-  toJSON (Header alg jku jwk kid x5u x5c x5t x5tS256 typ cty crit _) = object $ catMaybes [
-    Just ("alg" .= alg)
-    , fmap ("jku" .=) jku
-    , fmap ("jwk" .=) jwk
-    , fmap ("kid" .=) kid
-    , fmap ("x5u" .=) x5u
-    , fmap ("x5c" .=) x5c
-    , fmap ("x5t" .=) x5t
-    , fmap ("x5t#S256" .=) x5tS256
-    , fmap ("typ" .=) typ
-    , fmap ("cty" .=) cty
-    ]
-    ++ Types.objectPairs (toJSON crit)
+instance ToJSON JWSHeader where
+  toJSON (JWSHeader alg jku jwk kid x5u x5c x5t x5tS256 typ cty crit _) =
+    object $ catMaybes
+      [ Just ("alg" .= alg)
+      , fmap ("jku" .=) jku
+      , fmap ("jwk" .=) jwk
+      , fmap ("kid" .=) kid
+      , fmap ("x5u" .=) x5u
+      , fmap ("x5c" .=) x5c
+      , fmap ("x5t" .=) x5t
+      , fmap ("x5t#S256" .=) x5tS256
+      , fmap ("typ" .=) typ
+      , fmap ("cty" .=) cty
+      ] ++ Types.objectPairs (toJSON crit)
 
 
 -- construct a minimal header with the given alg
-algHeader :: JWA.JWS.Alg -> Header
-algHeader alg = Header alg n n n n n n n n n n n where n = Nothing
+algHeader :: JWA.JWS.Alg -> JWSHeader
+algHeader alg = JWSHeader alg n n n n n n n n n n n where n = Nothing
 
 
 (.::) :: (FromJSON a) => Object -> Object -> T.Text -> Parser a
@@ -172,10 +176,10 @@
   _                 -> pure Nothing
 
 
-data Signature = Signature Header Types.Base64Octets
+data Signature = Signature JWSHeader Types.Base64Octets
   deriving (Eq, Show)
 
-parseHeader :: Maybe Object -> Maybe Object -> Parser Header
+parseHeader :: Maybe Object -> Maybe Object -> Parser JWSHeader
 parseHeader (Just p) (Just u) = parseHeaderWith ((.::) p u) ((.::?) p u) (parseCrit p)
 parseHeader (Just p) _ = parseHeaderWith (p .:) (p .:?) (parseCrit p)
 parseHeader _ (Just u) = parseHeaderWith (u .:) (u .:?) (if M.member "crit" u
@@ -230,28 +234,13 @@
 jwsPayload (JWS (Types.Base64Octets s) _) = BSL.fromStrict s
 
 
-encodeO :: ToJSON a => a -> BSL.ByteString
-encodeO = BSL.reverse . BSL.dropWhile (== c) . BSL.reverse
-  . B64UL.encode . encode
-  where c = fromIntegral $ ord '='
-
-decodeO :: FromJSON a => BSL.ByteString -> Either String a
-decodeO s = B64UL.decode (pad s) >>= eitherDecode
-  where
-    pad t = t `BSL.append` BSL.replicate ((4 - BSL.length t `mod` 4) `mod` 4) c
-    c = fromIntegral $ ord '='
-
-encodeS :: ToJSON a => a -> BSL.ByteString
-encodeS = BSL.init . BSL.tail . encode
-
-decodeS :: FromJSON a => BSL.ByteString -> Maybe a
-decodeS s = do
-  v <- A.maybeResult $ A.parse value $ BSL.intercalate s ["\"", "\""]
-  parseMaybe parseJSON v
-
-signingInput :: Header -> Types.Base64Octets -> BSL.ByteString
-signingInput h p = BSL.intercalate "."
-  [maybe (encodeO h) BSL.fromStrict (headerRaw h), encodeS p]
+signingInput :: JWSHeader -> Types.Base64Octets -> BS.ByteString
+signingInput h p = BS.intercalate "."
+  [ fromMaybe
+    (Types.unpad $ B64U.encode $ BSL.toStrict $ encode h)
+    (headerRaw h)
+  , toBytes p
+  ]
 
 -- Convert JWS to compact serialization.
 --
@@ -259,17 +248,33 @@
 -- signature and returns Nothing otherwise
 --
 instance ToCompact JWS where
-  toCompact (JWS p [Signature h s]) = Right [signingInput h p, encodeS s]
-  toCompact (JWS _ xs) =
-    Left $ "cannot compact serialize JWS with " ++ show (length xs) ++ " sigs"
+  toCompact (JWS p [Signature h s]) =
+    Right [BSL.fromStrict $ signingInput h p, BSL.fromStrict $ toBytes s]
+  toCompact (JWS _ xs) = Left $ CompactEncodeError $
+    "cannot compact serialize JWS with " ++ show (length xs) ++ " sigs"
 
 instance FromCompact JWS where
-  fromCompact [h, p, s] = do
-    h' <- (\h' -> h' { headerRaw = Just $ BSL.toStrict h }) <$> decodeO h
-    p' <- maybe (Left "payload decode failed") Right $ decodeS p
-    s' <- maybe (Left "sig decode failed") Right $ decodeS s
-    return $ JWS p' [Signature h' s']
-  fromCompact xs = Left $ "expected 3 parts, got " ++ show (length xs)
+  fromCompact xs = case xs of
+    [h, p, s] -> do
+      h' <- (\h' -> h' { headerRaw = Just $ BSL.toStrict h })
+        <$> decodeO "header" h
+      p' <- decodeS "payload" p
+      s' <- decodeS "signature" s
+      return $ JWS p' [Signature h' s']
+    xs' -> compactErr "compact representation"
+      $ "expected 3 parts, got " ++ show (length xs')
+    where
+      compactErr s = Left . CompactDecodeError . ((s ++ " decode failed: ") ++)
+      jsonErr = Left . JSONDecodeError
+      decodeO desc s =
+        either (compactErr desc) Right (B64UL.decode (pad s))
+        >>= either jsonErr Right . eitherDecode
+      decodeS desc s =
+        either (compactErr desc) Right
+          (A.eitherResult $ A.parse value $ BSL.intercalate s ["\"", "\""])
+        >>= either jsonErr Right . parseEither parseJSON
+      pad t = t `BSL.append` BSL.replicate ((4 - BSL.length t `mod` 4) `mod` 4) c
+      c = fromIntegral $ ord '='
 
 
 -- §5.1. Message Signing or MACing
@@ -277,13 +282,16 @@
 -- | Create a new signature on a JWS.
 --
 signJWS
-  :: JWS      -- ^ JWS to sign
-  -> Header   -- ^ Header for signature
+  :: CPRG g
+  => g        -- ^ Random number generator
+  -> JWS      -- ^ JWS to sign
+  -> JWSHeader  -- ^ Header for signature
   -> JWK      -- ^ Key with which to sign
-  -> JWS      -- ^ JWS with new signature appended
-signJWS (JWS p sigs) h k = JWS p (sig:sigs) where
-  sig = Signature h $ Types.Base64Octets $
-    sign (headerAlg h) k (BSL.toStrict $ signingInput h p)
+  -> (Either Error JWS, g) -- ^ JWS with new signature appended
+signJWS g (JWS p sigs) h k = first (either Left (Right . appendSig)) $
+  sign (headerAlg h) k g (signingInput h p)
+  where
+    appendSig sig = JWS p (Signature h (Types.Base64Octets sig):sigs)
 
 -- | Verify a JWS.
 --
@@ -291,8 +299,8 @@
 -- validated with the given 'Key'.
 --
 verifyJWS :: JWK -> JWS -> Bool
-verifyJWS k (JWS p sigs) = any (verifySig k p) sigs
+verifyJWS k (JWS p sigs) = any ((== Right True) . verifySig k p) sigs
 
-verifySig :: JWK -> Types.Base64Octets -> Signature -> Bool
+verifySig :: JWK -> Types.Base64Octets -> Signature -> Either Error Bool
 verifySig k m (Signature h (Types.Base64Octets s))
-  = verify (headerAlg h) k (BSL.toStrict $ signingInput h m) s
+  = verify (headerAlg h) k (signingInput h m) s
diff --git a/src/Crypto/JOSE/Legacy.hs b/src/Crypto/JOSE/Legacy.hs
--- a/src/Crypto/JOSE/Legacy.hs
+++ b/src/Crypto/JOSE/Legacy.hs
@@ -14,6 +14,7 @@
 
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
 
 {-|
 
@@ -24,13 +25,13 @@
 module Crypto.JOSE.Legacy
   (
     JWK'
-  , genRSA'
   ) where
 
 import Control.Applicative
 import Control.Arrow
 
 import Data.Aeson
+import Data.Aeson.Types
 
 import Crypto.JOSE.Classes
 import Crypto.JOSE.JWA.JWK
@@ -41,50 +42,33 @@
 $(Crypto.JOSE.TH.deriveJOSEType "RS" ["RS"])
 
 
-newtype RSAKeyParameters' = RSAKeyParameters' RSAKeyParameters
+newtype RSKeyParameters = RSKeyParameters RSAKeyParameters
   deriving (Eq, Show)
 
-instance FromJSON RSAKeyParameters' where
-  parseJSON = withObject "RSA" (\o ->
-    RSAKeyParameters' <$> (RSAPrivateKeyParameters
-      <$> o .: "modulus"
-      <*> o .: "exponent"
-      <*> o .: "secretExponent"
-      <*> pure Nothing)
-    <|> RSAKeyParameters' <$> (RSAPublicKeyParameters
-      <$> o .: "modulus"
-      <*> o .: "exponent")
-    )
-
-instance ToJSON RSAKeyParameters' where
-  toJSON (RSAKeyParameters' (RSAPrivateKeyParameters n e d _)) = object
-    ["modulus" .= n ,"exponent" .= e ,"secretExponent" .= d]
-  toJSON (RSAKeyParameters' (RSAPublicKeyParameters n e))
-    = object ["modulus" .= n, "exponent" .= e]
-
-instance Key RSAKeyParameters' where
-  sign h (RSAKeyParameters' k) = sign h k
-  verify h (RSAKeyParameters' k) = verify h k
-
-
-data KeyMaterial' = RSAKeyMaterial' RS RSAKeyParameters' deriving (Eq, Show)
-
-instance FromJSON KeyMaterial' where
-  parseJSON = withObject "KeyMaterial'" (\o ->
-    RSAKeyMaterial' <$> o .: "algorithm" <*> parseJSON (Object o))
+instance FromJSON RSKeyParameters where
+  parseJSON = withObject "RS" $ \o -> fmap RSKeyParameters $ RSAKeyParameters
+    <$> ((o .: "algorithm" :: Parser RS) *> pure RSA)
+    <*> o .: "modulus"
+    <*> o .: "exponent"
+    <*> (fmap (`RSAPrivateKeyParameters` Nothing) <$> (o .:? "secretExponent"))
 
-instance ToJSON KeyMaterial' where
-  toJSON (RSAKeyMaterial' a k)
-    = object $ ("algorithm" .= a) : Types.objectPairs (toJSON k)
+instance ToJSON RSKeyParameters where
+  toJSON (RSKeyParameters (RSAKeyParameters _ n e priv))
+    = object $ ["algorithm" .= RS, "modulus" .= n ,"exponent" .= e]
+      ++ maybe [] (\p -> ["secretExponent" .= rsaD p]) priv
 
-instance Key KeyMaterial' where
-  sign h (RSAKeyMaterial' _ k) = sign h k
-  verify h (RSAKeyMaterial' _ k) = verify h k
+instance Key RSKeyParameters where
+  type KeyGenParam RSKeyParameters = Int
+  type KeyContent RSKeyParameters = RSAKeyParameters
+  gen p = first fromKeyContent . gen p
+  fromKeyContent = RSKeyParameters
+  sign h (RSKeyParameters k) = sign h k
+  verify h (RSKeyParameters k) = verify h k
 
 
 -- | Legacy JSON Web Key data type.
 --
-newtype JWK' = JWK' KeyMaterial' deriving (Eq, Show)
+newtype JWK' = JWK' RSKeyParameters deriving (Eq, Show)
 
 instance FromJSON JWK' where
   parseJSON = withObject "JWK'" $ \o -> JWK' <$> parseJSON (Object o)
@@ -94,13 +78,9 @@
     "version" .= ("2012.08.15" :: String) : Types.objectPairs (toJSON k)
 
 instance Key JWK' where
+  type KeyGenParam JWK' = Int
+  type KeyContent JWK' = RSKeyParameters
+  gen p g = first JWK' $ gen p g
+  fromKeyContent = JWK'
   sign h (JWK' k) = sign h k
   verify h (JWK' k) = verify h k
-
-
--- | Generate a legacy RSA keypair.
---
-genRSA' :: Int -> IO (JWK', JWK')
-genRSA' =
-  let f = JWK' . RSAKeyMaterial' RS . RSAKeyParameters'
-  in fmap (f *** f) . genRSAParams
diff --git a/src/Crypto/JOSE/TH.hs b/src/Crypto/JOSE/TH.hs
--- a/src/Crypto/JOSE/TH.hs
+++ b/src/Crypto/JOSE/TH.hs
@@ -55,14 +55,18 @@
 endGuardPred :: ExpQ
 endGuardPred = [e| otherwise |]
 
-endGuardExp :: ExpQ
-endGuardExp = [e| fail "unrecognised value" |]
+-- | Expression for an end guard.  Arg describes type it was expecting.
+--
+endGuardExp :: String -> ExpQ
+endGuardExp s = [e| fail ("unrecognised value; expected: " ++ s) |]
 
-endGuard :: Q (Guard, Exp)
-endGuard = normalGE endGuardPred endGuardExp
+-- | Build a catch-all guard that fails.  String describes what is expected.
+--
+endGuard :: String -> Q (Guard, Exp)
+endGuard s = normalGE endGuardPred (endGuardExp s)
 
 guardedBody :: [String] -> BodyQ
-guardedBody vs = guardedB (map guard vs ++ [endGuard])
+guardedBody vs = guardedB (map guard vs ++ [endGuard (show vs)])
 
 parseJSONClauseQ :: [String] -> ClauseQ
 parseJSONClauseQ vs = clause [varP $ mkName "s"] (guardedBody vs) []
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
@@ -24,8 +24,10 @@
 import Control.Applicative
 
 import Data.Aeson
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as BSL
+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 qualified Data.Text as T
 import qualified Network.URI
@@ -55,17 +57,17 @@
 
 instance FromJSON SizedBase64Integer where
   parseJSON = withText "full size base64url integer" $ parseB64Url (\bytes ->
-    pure $ SizedBase64Integer (BS.length bytes) (bsToInteger bytes))
+    pure $ SizedBase64Integer (B.length bytes) (bsToInteger bytes))
 
 instance ToJSON SizedBase64Integer where
   toJSON (SizedBase64Integer s x) = encodeB64Url $ zeroPad $ integerToBS x
-    where zeroPad xs = BS.replicate (s - BS.length xs) 0 `BS.append` xs
+    where zeroPad xs = B.replicate (s - B.length xs) 0 `B.append` xs
 
 
 -- | A base64url encoded string.  This is used for the JWE
 -- /Agreement PartyUInfo/ and /Agreement PartyVInfo/ fields.
 --
-newtype Base64UrlString = Base64UrlString BS.ByteString
+newtype Base64UrlString = Base64UrlString B.ByteString
   deriving (Eq, Show)
 
 instance FromJSON Base64UrlString where
@@ -75,9 +77,12 @@
 -- | A base64url encoded octet sequence.  Used for payloads,
 -- signatures, symmetric keys, salts, initialisation vectors, etc.
 --
-newtype Base64Octets = Base64Octets BS.ByteString
+newtype Base64Octets = Base64Octets B.ByteString
   deriving (Eq, Show)
 
+instance Byteable Base64Octets where
+  toBytes (Base64Octets s) = unpad $ B64U.encode s
+
 instance FromJSON Base64Octets where
   parseJSON = withText "Base64Octets" $ parseB64Url (pure . Base64Octets)
 
@@ -88,12 +93,12 @@
 -- | A base64url encoded SHA-1 digest.  Used for X.509 certificate
 -- thumbprints.
 --
-newtype Base64SHA1 = Base64SHA1 BS.ByteString
+newtype Base64SHA1 = Base64SHA1 B.ByteString
   deriving (Eq, Show)
 
 instance FromJSON Base64SHA1 where
   parseJSON = withText "base64url SHA-1" $ parseB64Url (\bytes ->
-    case BS.length bytes of
+    case B.length bytes of
       20 -> pure $ Base64SHA1 bytes
       _  -> fail "incorrect number of bytes")
 
@@ -104,12 +109,12 @@
 -- | A base64url encoded SHA-256 digest.  Used for X.509 certificate
 -- thumbprints.
 --
-newtype Base64SHA256 = Base64SHA256 BS.ByteString
+newtype Base64SHA256 = Base64SHA256 B.ByteString
   deriving (Eq, Show)
 
 instance FromJSON Base64SHA256 where
   parseJSON = withText "base64url SHA-256" $ parseB64Url (\bytes ->
-    case BS.length bytes of
+    case B.length bytes of
       32 -> pure $ Base64SHA256 bytes
       _  -> fail "incorrect number of bytes")
 
@@ -124,10 +129,10 @@
 
 instance FromJSON Base64X509 where
   parseJSON = withText "base64url X.509 certificate" $ parseB64 $
-    either fail (pure . Base64X509) . decodeCertificate . BSL.fromStrict
+    either fail (pure . Base64X509) . decodeCertificate . L.fromStrict
 
 instance ToJSON Base64X509 where
-  toJSON (Base64X509 x509) = encodeB64 $ BSL.toStrict $ encodeCertificate x509
+  toJSON (Base64X509 x509) = encodeB64 $ L.toStrict $ encodeCertificate x509
 
 
 -- | A URI.  Used for X.509 certificate and JWK Set URLs.
diff --git a/src/Crypto/JOSE/Types/Internal.hs b/src/Crypto/JOSE/Types/Internal.hs
--- a/src/Crypto/JOSE/Types/Internal.hs
+++ b/src/Crypto/JOSE/Types/Internal.hs
@@ -50,20 +50,25 @@
 encodeB64 :: B.ByteString -> Value
 encodeB64 = String . E.decodeUtf8 . B64.encode
 
+-- | Add appropriate base64 '=' padding.
+--
+pad :: B.ByteString -> B.ByteString
+pad s = s `B.append` B.replicate ((4 - B.length s `mod` 4) `mod` 4) 61
+
+-- | Strip base64 '=' padding.
+--
+unpad :: B.ByteString -> B.ByteString
+unpad = B.reverse . B.dropWhile (== 61) . B.reverse
+
 -- | Produce a parser of base64url encoded text from a bytestring parser.
 --
 parseB64Url :: FromJSON a => (B.ByteString -> Parser a) -> T.Text -> Parser a
-parseB64Url f = either fail f . decodeB64Url
-  where
-    decodeB64Url = B64U.decode . E.encodeUtf8 . pad
-    pad s = s `T.append` T.replicate ((4 - T.length s `mod` 4) `mod` 4) "="
+parseB64Url f = either fail f . B64U.decode . pad . E.encodeUtf8
 
 -- | Convert a bytestring to a base64url encoded JSON 'String'
 --
 encodeB64Url :: B.ByteString -> Value
-encodeB64Url = String . unpad . E.decodeUtf8 . B64U.encode
-  where
-    unpad = T.dropWhileEnd (== '=')
+encodeB64Url = String . E.decodeUtf8 . unpad . B64U.encode
 
 -- | Convert an unsigned big endian octet sequence to the integer
 -- it represents.
diff --git a/src/Crypto/JWT.hs b/src/Crypto/JWT.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/JWT.hs
@@ -0,0 +1,231 @@
+-- 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 #-}
+
+{-|
+
+JSON Web Token implementation.
+
+-}
+module Crypto.JWT
+  (
+    JWT(..)
+  , createJWSJWT
+  , validateJWSJWT
+
+  , ClaimsSet(..)
+  , emptyClaimsSet
+
+  , Audience(..)
+
+  , StringOrURI(..)
+  , IntDate(..)
+  ) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.Monad
+import Data.Maybe
+
+import Data.Aeson
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.HashMap.Strict as M
+import qualified Data.Text as T
+import Data.Time
+import Data.Time.Clock.POSIX
+
+import Crypto.JOSE
+import Crypto.JOSE.Types
+
+
+-- §2.  Terminology
+
+-- | A JSON string value, with the additional requirement that while
+--   arbitrary string values MAY be used, any value containing a /:/
+--   character MUST be a URI.
+--
+data StringOrURI = Arbitrary T.Text | OrURI URI deriving (Eq, Show)
+
+instance FromJSON StringOrURI where
+  parseJSON = withText "StringOrURI" (\s ->
+    if T.any (== ':') s
+    then OrURI <$> parseJSON (String s)
+    else pure $ Arbitrary s)
+
+instance ToJSON StringOrURI where
+  toJSON (Arbitrary s)  = toJSON s
+  toJSON (OrURI uri)    = toJSON $ show uri
+
+
+-- | A JSON numeric value representing the number of seconds from
+--   1970-01-01T0:0:0Z UTC until the specified UTC date\/time.
+--
+newtype IntDate = IntDate UTCTime deriving (Eq, Show)
+
+instance FromJSON IntDate where
+  parseJSON = withScientific "IntDate" $
+    pure . IntDate . posixSecondsToUTCTime . fromRational . toRational
+
+instance ToJSON IntDate where
+  toJSON (IntDate t)
+    = Number $ fromRational $ toRational $ utcTimeToPOSIXSeconds t
+
+
+-- | Audience data.  In the general case, the /aud/ value is an
+-- array of case-sensitive strings, each containing a 'StringOrURI'
+-- value.  In the special case when the JWT has one audience, the
+-- /aud/ value MAY be a single case-sensitive string containing a
+-- 'StringOrURI' value.
+--
+data Audience = General [StringOrURI] | Special StringOrURI deriving (Eq, Show)
+
+instance FromJSON Audience where
+  parseJSON v = fmap General (parseJSON v) <|> fmap Special (parseJSON v)
+
+instance ToJSON Audience where
+  toJSON (General auds) = toJSON auds
+  toJSON (Special aud)  = toJSON aud
+
+
+-- | The JWT Claims Set represents a JSON object whose members are
+--   the claims conveyed by the JWT.
+--
+data ClaimsSet = ClaimsSet
+  { claimIss :: Maybe StringOrURI
+  -- ^ The issuer claim identifies the principal that issued the
+  -- JWT.  The processing of this claim is generally application
+  -- specific.
+  , claimSub :: Maybe StringOrURI
+  -- ^ The subject claim identifies the principal that is the
+  -- subject of the JWT.  The Claims in a JWT are normally
+  -- statements about the subject.  The subject value MAY be scoped
+  -- to be locally unique in the context of the issuer or MAY be
+  -- globally unique.  The processing of this claim is generally
+  -- application specific.
+  , claimAud :: Maybe Audience
+  -- ^ The audience claim identifies the recipients that the JWT is
+  -- intended for.  Each principal intended to process the JWT MUST
+  -- identify itself with a value in the audience claim.  If the
+  -- principal processing the claim does not identify itself with a
+  -- value in the /aud/ claim when this claim is present, then the
+  -- JWT MUST be rejected.
+  , claimExp :: Maybe IntDate
+  -- ^ The expiration time claim identifies the expiration time on
+  -- or after which the JWT MUST NOT be accepted for processing.
+  -- The processing of /exp/ claim requires that the current
+  -- date\/time MUST be before expiration date\/time listed in the
+  -- /exp/ claim.  Implementers MAY provide for some small leeway,
+  -- usually no more than a few minutes, to account for clock skew.
+  , claimNbf :: Maybe IntDate
+  -- ^ The not before claim identifies the time before which the JWT
+  -- MUST NOT be accepted for processing.  The processing of the
+  -- /nbf/ claim requires that the current date\/time MUST be after
+  -- or equal to the not-before date\/time listed in the /nbf/
+  -- claim.  Implementers MAY provide for some small leeway, usually
+  -- no more than a few minutes, to account for clock skew.
+  , claimIat :: Maybe IntDate
+  -- ^ The issued at claim identifies the time at which the JWT was
+  -- issued.  This claim can be used to determine the age of the
+  -- JWT.
+  , claimJti :: Maybe T.Text
+  -- ^ The JWT ID claim provides a unique identifier for the JWT.
+  -- The identifier value MUST be assigned in a manner that ensures
+  -- that there is a negligible probability that the same value will
+  -- be accidentally assigned to a different data object.  The /jti/
+  -- claim can be used to prevent the JWT from being replayed.  The
+  -- /jti/ value is a case-sensitive string.
+  , unregisteredClaims :: M.HashMap T.Text Value
+  -- ^ Claim Names can be defined at will by those using JWTs.
+  }
+  deriving (Eq, Show)
+
+-- | Return an empty claims set.
+--
+emptyClaimsSet :: ClaimsSet
+emptyClaimsSet = ClaimsSet n n n n n n n M.empty where n = Nothing
+
+filterUnregistered :: M.HashMap T.Text Value -> M.HashMap T.Text Value
+filterUnregistered = M.filterWithKey (\k _ -> k `notElem` registered) where
+  registered = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]
+
+instance FromJSON ClaimsSet where
+  parseJSON = withObject "JWT Claims Set" (\o -> ClaimsSet
+    <$> o .:? "iss"
+    <*> o .:? "sub"
+    <*> o .:? "aud"
+    <*> o .:? "exp"
+    <*> o .:? "nbf"
+    <*> o .:? "iat"
+    <*> o .:? "jti"
+    <*> pure (filterUnregistered o))
+
+instance ToJSON ClaimsSet where
+  toJSON (ClaimsSet iss sub aud exp' nbf iat jti o) = object $ catMaybes [
+    fmap ("iss" .=) iss
+    , fmap ("sub" .=) sub
+    , fmap ("aud" .=) aud
+    , fmap ("exp" .=) exp'
+    , fmap ("nbf" .=) nbf
+    , fmap ("iat" .=) iat
+    , fmap ("jti" .=) jti
+    ] ++ M.toList (filterUnregistered o)
+
+
+-- | Data representing the JOSE aspects of a JWT.
+--
+data JWTCrypto = JWTJWS JWS deriving (Eq, Show)
+
+instance FromCompact JWTCrypto where
+  fromCompact = fmap JWTJWS . fromCompact
+
+instance ToCompact JWTCrypto where
+  toCompact (JWTJWS jws) = toCompact jws
+
+
+-- | JSON Web Token data.
+--
+data JWT = JWT
+  { jwtCrypto     :: JWTCrypto  -- ^ JOSE aspect of the JWT.
+  , jwtClaimsSet  :: ClaimsSet  -- ^ Claims of the JWT.
+  } deriving (Eq, Show)
+
+instance FromCompact JWT where
+  fromCompact = fromCompact >=> toJWT where
+    toJWT (JWTJWS jws) =
+      either (Left . CompactDecodeError) (Right . JWT (JWTJWS jws))
+        $ eitherDecode $ jwsPayload jws
+
+instance ToCompact JWT where
+  toCompact = toCompact . jwtCrypto
+
+
+-- | Validate a JWT as a JWS (JSON Web Signature).
+--
+validateJWSJWT :: JWK -> JWT -> Bool
+validateJWSJWT k (JWT (JWTJWS jws) _) = verifyJWS k jws
+
+-- | Create a JWT that is a JWS.
+--
+createJWSJWT
+  :: CPRG g
+  => g
+  -> JWK
+  -> JWSHeader
+  -> ClaimsSet
+  -> (Either Error JWT, g)
+createJWSJWT g k h c = first (fmap $ \jws -> JWT (JWTJWS jws) c) $
+  signJWS g (JWS payload []) h k
+  where
+    payload = Base64Octets $ BSL.toStrict $ encode c
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013  Fraser Tweedale
+-- 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.
@@ -16,10 +16,13 @@
 
 import JWK
 import JWS
+import JWT
 import Types
 
 
+main :: IO ()
 main = hspec $ do
   Types.spec
   JWK.spec
   JWS.spec
+  JWT.spec
