diff --git a/PaymentServer.cabal b/PaymentServer.cabal index 56d7c013c6bb0ec8b54d3506d5d9e0c64ddb7347..5910ce73ba57d00482ba7095a36ddf70fd4cc5bf 100644 --- a/PaymentServer.cabal +++ b/PaymentServer.cabal @@ -16,6 +16,7 @@ cabal-version: >=1.10 library hs-source-dirs: src exposed-modules: PaymentServer.Processors.Stripe + , PaymentServer.Issuer , PaymentServer.Persistence , PaymentServer.Redemption , PaymentServer.Server diff --git a/src/PaymentServer/Issuer.hs b/src/PaymentServer/Issuer.hs new file mode 100644 index 0000000000000000000000000000000000000000..0f47c47a2807523663269cab63db9183bd2c5f72 --- /dev/null +++ b/src/PaymentServer/Issuer.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- | This module can issue signatures of blinded tokens which can be used to +-- construct passes. +module PaymentServer.Issuer + ( PublicKey + , Signature + , BlindedToken + , Proof + , ChallengeBypass(ChallengeBypass) + , Issuer + , trivialIssue + ) where + +import Data.Text + ( Text + ) + +-- | A public key corresponding to our private key. +type PublicKey = Text + +-- | A cryptographic signature of a blinded token created using our private +-- key. +type Signature = Text + +-- | This is the blinded token for which we create signatures. +type BlindedToken = Text + +-- | A zero-knowledge proof that signatures were created of the corresponding +-- blinded tokens using the corresponding public key's private key. +type Proof = Text + +-- | This bundles up all of the values needed to verify the privacy-respecting +-- operation of the issuer and then construct passes. +data ChallengeBypass = + ChallengeBypass PublicKey [Signature] Proof + +-- | An issuer accepts a list of blinded tokens and returns signatures of +-- those tokens along with proof that it used a particular key to construct +-- the signatures. +type Issuer = [BlindedToken] -> ChallengeBypass + +-- | trivialIssue makes up and returns some nonsense values that satisfy the +-- structural requirements but not the semantic ones. +trivialIssue :: Issuer +trivialIssue tokens = + ChallengeBypass + "fake-public-key" + (replicate (length tokens) "fake-signature") + "fake-proof" diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs index 23dfbc849b6699f61d1be229d85e3f7ae3535766..c2cbe06f077bb5682c7a36139ab82921d0f92330 100644 --- a/src/PaymentServer/Redemption.hs +++ b/src/PaymentServer/Redemption.hs @@ -7,7 +7,6 @@ -- signatures. module PaymentServer.Redemption ( RedemptionAPI - , BlindedToken , Redeem(Redeem) , Result(Failed, Succeeded) , redemptionServer @@ -20,8 +19,7 @@ import Control.Monad.IO.Class ( liftIO ) import Data.Text - ( Text - , pack + ( pack ) import Data.Text.Encoding ( encodeUtf8 @@ -59,27 +57,20 @@ import PaymentServer.Persistence , Fingerprint , Voucher ) - --- | A cryptographic signature of a blinded token created using our private --- key. -type Signature = Text - --- | A public key corresponding to our private key. -type PublicKey = Text - --- | A zero-knowledge proof that signatures were created of the corresponding --- blinded tokens using the corresponding public key's private key. -type Proof = Text +import PaymentServer.Issuer + ( Signature + , PublicKey + , Proof + , BlindedToken + , ChallengeBypass(ChallengeBypass) + , Issuer + ) data Result = Failed | Succeeded PublicKey [Signature] Proof deriving (Show, Eq) --- | A blinded token is presented along with a voucher to be signed and the --- signatures returned to the caller. -type BlindedToken = Text - -- | A complete redemption attempt which can be presented at the redemption -- endpoint. data Redeem @@ -120,19 +111,23 @@ jsonErr400 = err400 , errHeaders = [ ("Content-Type", "application/json;charset=utf-8") ] } -redemptionServer :: VoucherDatabase d => d -> Server RedemptionAPI +redemptionServer :: VoucherDatabase d => Issuer -> d -> Server RedemptionAPI redemptionServer = redeem -- | Handler for redemption requests. Use the database to try to redeem the -- voucher and return signatures. Return a failure if this is not possible -- (eg because the voucher was already redeemed). -redeem :: VoucherDatabase d => d -> Redeem -> Handler Result -redeem database (Redeem voucher tokens) = do +redeem :: VoucherDatabase d => Issuer -> d -> Redeem -> Handler Result +redeem issue database (Redeem voucher tokens) = do let fingerprint = fingerprintFromTokens tokens result <- liftIO $ PaymentServer.Persistence.redeemVoucher database voucher fingerprint case result of Left err -> throwError jsonErr400 - Right () -> return $ Succeeded "" [] "" + Right () -> + let + (ChallengeBypass key signatures proof) = issue tokens + in + return $ Succeeded key signatures proof -- | Compute a cryptographic hash (fingerprint) of a list of tokens which can -- be used as an identifier for this exact sequence of tokens. diff --git a/src/PaymentServer/Server.hs b/src/PaymentServer/Server.hs index f55b779b99f7a3395a02aca78a0f636e358f2542..6eea6abed47c088abf55b9aea4b66bb3658c53c7 100644 --- a/src/PaymentServer/Server.hs +++ b/src/PaymentServer/Server.hs @@ -23,6 +23,9 @@ import PaymentServer.Redemption ( RedemptionAPI , redemptionServer ) +import PaymentServer.Issuer + ( trivialIssue + ) import PaymentServer.Persistence ( VoucherDatabase ) @@ -36,7 +39,7 @@ type PaymentServerAPI paymentServer :: VoucherDatabase d => d -> Server PaymentServerAPI paymentServer d = stripeServer d - :<|> redemptionServer d + :<|> redemptionServer trivialIssue d paymentServerAPI :: Proxy PaymentServerAPI paymentServerAPI = Proxy diff --git a/test/SpecRedemption.hs b/test/SpecRedemption.hs index e9c05108d596bfc232cb7cf749026960587212db..d75ee75f9f37fbe30db45f91d8b7da6dbc64f1ac 100644 --- a/test/SpecRedemption.hs +++ b/test/SpecRedemption.hs @@ -52,9 +52,14 @@ import Util.Spec import Util.WAI ( postJSON ) +import PaymentServer.Issuer + ( BlindedToken + , ChallengeBypass(ChallengeBypass) + , Issuer + , trivialIssue + ) import PaymentServer.Redemption ( RedemptionAPI - , BlindedToken , Redeem(Redeem) , Result(Failed, Succeeded) , redemptionServer @@ -69,8 +74,8 @@ import PaymentServer.Persistence redemptionAPI :: Proxy RedemptionAPI redemptionAPI = Proxy -app :: VoucherDatabase d => d -> Application -app = serve redemptionAPI . redemptionServer +app :: VoucherDatabase d => Issuer -> d -> Application +app issue = serve redemptionAPI . redemptionServer issue path = "/" @@ -96,7 +101,7 @@ instance VoucherDatabase VoucherDatabaseTestDouble where spec_redemption :: Spec spec_redemption = parallel $ do database <- runIO memory - with (return . app $ database) $ + with (return $ app trivialIssue database) $ do describe (printf "error behavior of POST %s" (show path)) $ do @@ -124,7 +129,7 @@ spec_redemption = parallel $ do describe "redemption" $ do - with (return . app $ RefuseRedemption NotPaid) $ + with (return $ app trivialIssue (RefuseRedemption NotPaid)) $ it "receives a failure response when the voucher is not paid" $ property $ \(voucher :: Voucher) (tokens :: [BlindedToken]) -> propertyRedeem path voucher tokens 400 @@ -135,15 +140,18 @@ spec_redemption = parallel $ do , matchHeaders = ["Content-Type" <:> "application/json;charset=utf-8"] } - with (return $ app PermitRedemption) $ + with (return $ app trivialIssue PermitRedemption) $ it "receive a success response when redemption succeeds" $ property $ \(voucher :: Voucher) (tokens :: [BlindedToken]) -> - propertyRedeem path voucher tokens 200 - -- TODO: Get some real crypto involved to be able to replace these - -- dummy values. - { matchBody = matchJSONBody $ Succeeded "" [] "" - , matchHeaders = ["Content-Type" <:> "application/json;charset=utf-8"] - } + let + (ChallengeBypass key signatures proof) = trivialIssue tokens + in + propertyRedeem path voucher tokens 200 + -- TODO: Get some real crypto involved to be able to replace these + -- dummy values. + { matchBody = matchJSONBody $ Succeeded key signatures proof + , matchHeaders = ["Content-Type" <:> "application/json;charset=utf-8"] + } -- it "receive 200 (OK) when the voucher is paid and previously redeemed with the same tokens" $ -- property $ \(voucher :: Voucher) (tokens :: [BlindedToken]) ->