jose 0.8.2.1 → 0.8.3
raw patch · 6 files changed
+138/−34 lines, 6 filesdep +pemdep ~aesondep ~basedep ~bytestring
Dependencies added: pem
Dependency ranges changed: aeson, base, bytestring, lens, mtl, semigroups, text
Files
- jose.cabal +24/−28
- src/Crypto/JOSE/JWA/JWK.hs +28/−4
- src/Crypto/JOSE/JWK.hs +8/−1
- src/Crypto/JOSE/Types.hs +5/−0
- test/Examples.hs +70/−0
- test/Test.hs +3/−1
jose.cabal view
@@ -1,5 +1,6 @@+cabal-version: 2.2 name: jose-version: 0.8.2.1+version: 0.8.3 synopsis: Javascript Object Signing and Encryption and JSON Web Token library description:@@ -29,7 +30,6 @@ copyright: Copyright (C) 2013-2018 Fraser Tweedale category: Cryptography build-type: Simple-cabal-version: >= 1.8 tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.1 @@ -37,7 +37,22 @@ description: Build demonstration programs default: False +common common+ default-language: Haskell2010+ ghc-options: -Wall++ build-depends:+ base >= 4.8 && < 5+ , aeson >= 0.8.0.1+ , bytestring == 0.10.*+ , lens >= 4.16+ , mtl >= 2+ , semigroups >= 0.15+ , text >= 1.1+ library+ import: common+ exposed-modules: Crypto.JOSE Crypto.JOSE.Compact@@ -61,23 +76,16 @@ Crypto.JOSE.Types.Orphans build-depends:- base >= 4.8 && < 5- , attoparsec+ attoparsec , base64-bytestring >= 1.0 && < 1.2 , concise >= 0.1 , containers >= 0.5 , cryptonite >= 0.7- , lens >= 4.16 , memory >= 0.7 , monad-time >= 0.1- , mtl >= 2- , semigroups >= 0.15 , template-haskell >= 2.4 , safe >= 0.3- , aeson >= 0.8.0.1 , unordered-containers == 0.2.*- , bytestring == 0.10.*- , text >= 1.1 , time >= 1.5 , network-uri >= 2.6 , QuickCheck >= 2@@ -88,7 +96,6 @@ if impl(ghc<8) build-depends: fail - ghc-options: -Wall hs-source-dirs: src source-repository head@@ -96,11 +103,13 @@ location: https://github.com/frasertweedale/hs-jose.git test-suite tests+ import: common type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Test.hs other-modules: AESKW+ Examples JWK JWS JWT@@ -108,26 +117,20 @@ Types build-depends:- base- , attoparsec+ attoparsec , base64-bytestring , containers , cryptonite- , lens , memory , monad-time- , mtl- , semigroups , template-haskell , safe- , aeson , unordered-containers- , bytestring- , text , time , network-uri , vector , x509+ , pem , concise , jose@@ -140,22 +143,15 @@ , quickcheck-instances executable jose-example+ import: common if !flag(demos) buildable: False hs-source-dirs: example- ghc-options: -Wall main-is: Main.hs other-modules: KeyDB JWS build-depends:- base- , aeson- , bytestring- , lens- , mtl- , semigroups- , text- , unix+ unix , jose
src/Crypto/JOSE/JWA/JWK.hs view
@@ -37,6 +37,7 @@ , curve , point , ecPrivateKey+ , ecParametersFromX509 -- * Parameters for RSA Keys , RSAPrivateKeyOthElem(..)@@ -100,6 +101,8 @@ import qualified Data.HashMap.Strict as M import Data.List.NonEmpty (NonEmpty) import qualified Data.Text as T+import Data.X509 as X509+import Data.X509.EC as X509.EC import Test.QuickCheck (Arbitrary(..), arbitrarySizedNatural, elements, oneof, vectorOf) import Crypto.JOSE.Error@@ -296,11 +299,21 @@ $ B.splitAt (B.length s `div` 2) s curve :: Crv -> ECC.Curve-curve = ECC.getCurveByName . curveName where- curveName P_256 = ECC.SEC_p256r1- curveName P_384 = ECC.SEC_p384r1- curveName P_521 = ECC.SEC_p521r1+curve = ECC.getCurveByName . review fromCurveName +-- | Conversion from known curves and back again.+fromCurveName :: Prism' ECC.CurveName Crv+fromCurveName = prism'+ (\case+ P_256 -> ECC.SEC_p256r1+ P_384 -> ECC.SEC_p384r1+ P_521 -> ECC.SEC_p521r1)+ (\case+ ECC.SEC_p256r1 -> Just P_256+ ECC.SEC_p384r1 -> Just P_384+ ECC.SEC_p521r1 -> Just P_521+ _ -> Nothing)+ point :: ECKeyParameters -> ECC.Point point k = ECC.Point (f ecX) (f ecY) where f l = case view l k of@@ -315,6 +328,17 @@ ecPrivateKey (ECKeyParameters _ _ _ (Just (Types.SizedBase64Integer _ d))) = pure d ecPrivateKey _ = throwing _KeyMismatch "Not an EC private key" +ecParametersFromX509 :: X509.PubKeyEC -> Maybe ECKeyParameters+ecParametersFromX509 pubKeyEC = do+ ecCurve <- X509.EC.ecPubKeyCurve pubKeyEC+ curveName <- X509.EC.ecPubKeyCurveName pubKeyEC+ crv <- preview fromCurveName curveName+ pt <- X509.EC.unserializePoint ecCurve (X509.pubkeyEC_pub pubKeyEC)+ (x, y) <- case pt of+ ECC.PointO -> Nothing+ ECC.Point x y ->+ pure (Types.makeSizedBase64Integer x, Types.makeSizedBase64Integer y)+ pure $ ECKeyParameters crv x y Nothing -- | Parameters for RSA Keys --
src/Crypto/JOSE/JWK.hs view
@@ -255,6 +255,12 @@ fromRSAPublic :: RSA.PublicKey -> JWK fromRSAPublic = fromKeyMaterial . RSAKeyMaterial . toRSAPublicKeyParameters +-- | Convert an EC public key into a JWK+--+fromECPublic :: X509.PubKeyEC -> Maybe JWK+fromECPublic = fmap (fromKeyMaterial . ECKeyMaterial) . ecParametersFromX509++ -- | Convert octet string into a JWK -- fromOctets :: Cons s s Word8 Word8 => s -> JWK@@ -281,7 +287,8 @@ fromX509CertificateMaybe cert = do k <- case (X509.certPubKey . X509.signedObject . X509.getSigned) cert of X509.PubKeyRSA k -> pure (fromRSAPublic k)- _ -> {- TODO EC -} Nothing+ X509.PubKeyEC k -> fromECPublic k+ _ -> Nothing pure $ k & set jwkX5cRaw (Just (pure cert))
src/Crypto/JOSE/Types.hs view
@@ -25,6 +25,7 @@ Base64Integer(..) , _Base64Integer , SizedBase64Integer(..)+ , makeSizedBase64Integer , genSizedBase64IntegerOf , checkSize , Base64Octets(..)@@ -127,6 +128,10 @@ genSizedBase64IntegerOf :: Int -> Gen SizedBase64Integer genSizedBase64IntegerOf n = SizedBase64Integer n . bsToInteger <$> genByteStringOf n++-- | Create a 'SizedBase64Integer'' from an 'Integer'.+makeSizedBase64Integer :: Integer -> SizedBase64Integer+makeSizedBase64Integer x = SizedBase64Integer (intBytes x) x instance FromJSON SizedBase64Integer where parseJSON = withText "full size base64url integer" $ parseB64Url (\bytes ->
+ test/Examples.hs view
@@ -0,0 +1,70 @@+-- | Miscellaneous end-to-end examples.+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-}+module Examples where++import Control.Lens (_Right, (&), (?~), (.~))+import Control.Lens.Extras (is)+import qualified Data.ByteString.Char8 as BC8+import qualified Data.PEM as PEM+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import qualified Data.X509 as X509++import Crypto.JOSE.JWK+import Crypto.JWT++import Test.Hspec++spec :: Spec+spec = do+ fromX509VerifySpec++fromX509VerifySpec :: Spec+fromX509VerifySpec =+ describe "Decode PEM-encoded X509 EC certificate and verify" $ do+ let certs = do+ pems <- PEM.pemParseBS pemEncodedX509+ mapM (X509.decodeSignedCertificate . PEM.pemContent) pems++ it "successfully parses the single X509 certificate" $ do+ certs `shouldSatisfy` \case Right [_] -> True; _ -> False++ case certs of+ Right [x509] -> do+ let errorOrJWK = fromX509Certificate x509 :: Either JWTError JWK+ it "successfully converts the X509 certificate to a JWK" $+ errorOrJWK `shouldSatisfy` is _Right++ it "verifies a token signed using ES256 and matches expected claims" $+ (do+ es256jwk <- errorOrJWK+ jwt <- decodeCompact es256token+ verifyClaimsAt valSettings es256jwk now jwt) `shouldBe`+ Right expectedClaims++ _ -> pure ()+ where++ es256token = "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFUzI1NiJ9.eyJuYmYiOiAxNDQ0NDc4NDAwLCAiaXNzIjogInh4eCJ9.lArczfN-pIL8oUU-7PU83u-zfXougXBZj6drFeKFsPEoVhy9WAyiZlRshYqjTSXdaw8yw2L-ovt4zTUZb2PWMg"++ pemEncodedX509 = BC8.unlines+ [ "-----BEGIN CERTIFICATE-----"+ , "MIIBcDCCARagAwIBAgIJAMZmuGSIfvgzMAoGCCqGSM49BAMCMBMxETAPBgNVBAMM"+ , "CHdoYXRldmVyMB4XDTE4MDgxMDE0Mjg1NFoXDTE4MDkwOTE0Mjg1NFowEzERMA8G"+ , "A1UEAwwId2hhdGV2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATPwn3WCEXL"+ , "mjp/bFniDwuwsfu7bASlPae2PyWhqGeWwe23Xlyx+tSqxlkXYe4pZ23BkAAscpGj"+ , "yn5gXHExyDlKo1MwUTAdBgNVHQ4EFgQUElRjSoVgKjUqY5AXz2o74cLzzS8wHwYD"+ , "VR0jBBgwFoAUElRjSoVgKjUqY5AXz2o74cLzzS8wDwYDVR0TAQH/BAUwAwEB/zAK"+ , "BggqhkjOPQQDAgNIADBFAiEA4yQ/88ZrUX68c6kOe9G11u8NUaUzd8pLOtkKhniN"+ , "OHoCIHmNX37JOqTcTzGn2u9+c8NlnvZ0uDvsd1BmKPaUmjmm"+ , "-----END CERTIFICATE-----"+ ]++ now = posixSecondsToUTCTime 1588512349++ expectedClaims = emptyClaimsSet+ & claimIss ?~ "xxx"+ & claimNbf ?~ NumericDate (posixSecondsToUTCTime 1444478400)++ valSettings = defaultJWTValidationSettings (const True)+ & jwtValidationSettingsIssuerPredicate .~ (== "xxx")
test/Test.hs view
@@ -17,6 +17,7 @@ import Test.Tasty.QuickCheck import AESKW+import Examples import JWK import JWS import JWT@@ -35,4 +36,5 @@ jwk <- testSpec "JWK" JWK.spec jws <- testSpec "JWS" JWS.spec jwt <- testSpec "JWT" JWT.spec- return $ testGroup "Unit tests" [types, jwk, jws, jwt]+ examples <- testSpec "Examples" Examples.spec+ return $ testGroup "Unit tests" [types, jwk, jws, jwt, examples]