From 2491652926ea9a7e938466ab3a52fd242410e67b Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Thu, 12 Sep 2019 15:17:44 -0400
Subject: [PATCH] Ristretto happy-path, at least.

It's total amateur hour but that's what I am so who is surprised?
---
 src/PaymentServer/Issuer.hs     |  12 ++--
 src/PaymentServer/Main.hs       |   5 +-
 src/PaymentServer/Redemption.hs |  16 ++++-
 src/PaymentServer/Ristretto.hs  | 114 ++++++++++++++++++++++++++------
 4 files changed, 116 insertions(+), 31 deletions(-)

diff --git a/src/PaymentServer/Issuer.hs b/src/PaymentServer/Issuer.hs
index 267a488..8ed7eb4 100644
--- a/src/PaymentServer/Issuer.hs
+++ b/src/PaymentServer/Issuer.hs
@@ -46,13 +46,13 @@ data ChallengeBypass =
 -- | 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] -> IO ChallengeBypass
+type Issuer = [BlindedToken] -> IO (Maybe ChallengeBypass)
 
 -- | trivialIssue makes up and returns some nonsense values that satisfy the
 -- structural requirements but not the semantic ones.
 trivialIssue :: Issuer
 trivialIssue tokens =
-  return $
+  return . Just $
   ChallengeBypass
   "fake-public-key"
   (replicate (length tokens) "fake-signature")
@@ -65,5 +65,9 @@ ristrettoIssue
   :: SigningKey    -- ^ The key to provide to the PrivacyPass signer.
   -> Issuer        -- ^ An issuer using the given key.
 ristrettoIssue signingKey tokens = do
-  (publicKey, tokens, proof) <- ristretto signingKey tokens
-  return $ ChallengeBypass publicKey tokens proof
+  issuance <- ristretto signingKey tokens
+  case issuance of
+    Right (publicKey, tokens, proof) -> return . Just $ ChallengeBypass publicKey tokens proof
+    Left err -> do
+      putStrLn . show $ err
+      return Nothing
diff --git a/src/PaymentServer/Main.hs b/src/PaymentServer/Main.hs
index 041ea9a..2613964 100644
--- a/src/PaymentServer/Main.hs
+++ b/src/PaymentServer/Main.hs
@@ -39,6 +39,7 @@ import Options.Applicative
   , ParserInfo
   , option
   , auto
+  , str
   , optional
   , long
   , help
@@ -82,7 +83,7 @@ sample = ServerConfig
     <> help "Which issuer to use: trivial or ristretto"
     <> showDefault
     <> value Trivial )
-  <*> optional (option auto
+  <*> optional (option str
   ( long "signing-key"
     <> help "The base64 encoded signing key (ristretto only)"
     <> showDefault ) )
@@ -91,7 +92,7 @@ sample = ServerConfig
     <> help "Which database to use: sqlite3 or memory"
     <> showDefault
     <> value Memory )
-  <*> optional ( option auto
+  <*> optional ( option str
   ( long "database-path"
     <> help "Path to on-disk database (sqlite3 only)"
     <> showDefault ) )
diff --git a/src/PaymentServer/Redemption.hs b/src/PaymentServer/Redemption.hs
index eb16b7e..4eb5b29 100644
--- a/src/PaymentServer/Redemption.hs
+++ b/src/PaymentServer/Redemption.hs
@@ -54,6 +54,7 @@ import Crypto.Hash
   )
 import PaymentServer.Persistence
   ( VoucherDatabase(redeemVoucher)
+  , RedeemError(NotPaid, AlreadyRedeemed)
   , Fingerprint
   , Voucher
   )
@@ -122,10 +123,19 @@ 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
+    Left NotPaid -> do
+      liftIO $ putStrLn "Attempt to redeem unpaid voucher"
+      throwError jsonErr400
+    Left AlreadyRedeemed -> do
+      liftIO $ putStrLn "Attempt to double-spend paid voucher"
+      throwError jsonErr400
     Right () -> do
-      (ChallengeBypass key signatures proof) <- liftIO $ issue tokens
-      return $ Succeeded key signatures proof
+      result <- liftIO $ issue tokens
+      case result of
+        Just (ChallengeBypass key signatures proof) ->
+          return $ Succeeded key signatures proof
+        Nothing ->
+          throwError jsonErr400
 
 -- | 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/Ristretto.hs b/src/PaymentServer/Ristretto.hs
index 832e125..25ee3d3 100644
--- a/src/PaymentServer/Ristretto.hs
+++ b/src/PaymentServer/Ristretto.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
 {-# LANGUAGE EmptyDataDecls #-}
 
@@ -6,13 +7,19 @@ module PaymentServer.Ristretto
   , ristretto
   ) where
 
+import Control.Exception
+  ( bracket
+  , assert
+  )
 import Data.Text
   ( Text
   , unpack
+  , pack
   )
 
 import Foreign.Ptr
   ( Ptr
+  , nullPtr
   )
 import Foreign.C.String
   ( CString
@@ -23,6 +30,9 @@ import Foreign.C.String
 import Foreign.Marshal.Alloc
   ( free
   )
+import Foreign.Marshal.Array
+  ( withArray
+  )
 
 data C_BlindedToken
 data C_SignedToken
@@ -44,32 +54,92 @@ foreign import ccall "signing_key_sign" signing_key_sign :: Ptr C_SigningKey ->
 
 foreign import ccall "signed_token_encode_base64" signed_token_encode_base64 :: Ptr C_SignedToken -> IO CString
 
-foreign import ccall "batch_dleq_proof_new" batch_dleq_proof_new :: Ptr C_BlindedToken -> Ptr C_SignedToken -> Int -> Ptr C_SigningKey -> IO (Ptr C_BatchDLEQProof)
+foreign import ccall "batch_dleq_proof_new" batch_dleq_proof_new :: Ptr (Ptr C_BlindedToken) -> Ptr (Ptr C_SignedToken) -> Int -> Ptr C_SigningKey -> IO (Ptr C_BatchDLEQProof)
 foreign import ccall "batch_dleq_proof_encode_base64" batch_dleq_proof_encode_base64 :: Ptr C_BatchDLEQProof -> IO CString
 foreign import ccall "batch_dleq_proof_destroy" batch_dleq_proof_destroy :: Ptr C_BatchDLEQProof -> IO ()
 
+-- | Private type to represent the return value of ristretto.
+type Issuance =
+  ( Text         -- |^ The base64-encoded public key corresponding to the
+                 -- signing key which generated the signatures.
+  , [Text]       -- |^ A list of base64-encoded token signatures.
+  , Text         -- |^ The base64-encoded batch DLEQ proof that the signatures
+                 -- were made with the signing key corresponding to the public
+                 -- key.
+  )
+
+data RistrettoFailure
+  = SigningKeyAllocation
+  | SigningKeyDecoding
+  | BlindedTokenAllocation
+  | BlindedTokenDecoding
+  | SignedTokenAllocation
+  | TokenSigning
+  | SignedTokenEncoding
+  | ProofCreation
+  | SignedTokenPeek
+  | PublicKeyLookup
+  | PublicKeyEncoding
+  deriving (Show, Eq)
+
+
 ristretto
-  :: Text                       -- ^ The base64 encoded signing key.
-  -> [Text]                     -- ^ A list of the base64 blinded tokens.
-  -> IO (Text, [Text], Text)    -- ^ The base64 public key, list of base64 signed tokens, and the base64 proof.
-ristretto textSigningKey textTokens = do
-  let stringSigningKey = unpack textSigningKey
-  cStringSigningKey <- newCString stringSigningKey
-  signingKey <- signing_key_decode_base64 cStringSigningKey
-  let stringTokens = map unpack textTokens
-  cStringTokens <- mapM newCString stringTokens
-  blindedTokens <- mapM blinded_token_decode_base64 cStringTokens
-  signedTokens <- mapM (signing_key_sign signingKey) blindedTokens
-    -- encodedTokens <- map signed_token_encode_base64 signedTokens
-    -- proof <- batch_dleq_proof_new blindedTokens signedTokens (length blindedTokens) signingKey
-    -- encodedProof <- batch_dleq_proof_encode_base64 proof
-    -- publicKey <- signing_key_get_public_key signingKey
-    -- encodedPublicKey <- public_key_encode_base64 publicKey
-    -- ChallengeBypass
-    --   encodedPublicKey
-    --   encodedTokens
-    --   encodedProof
-  return (mempty, [], mempty)
+  :: Text                                  -- ^ The base64 encoded signing key.
+  -> [Text]                                -- ^ A list of the base64 blinded tokens.
+  -> IO (Either RistrettoFailure Issuance) -- ^ Left for an error, otherwise
+                                           -- Right with the ristretto results
+ristretto textSigningKey textTokens =
+  let
+    newProof blindedTokens signedTokens signingKey =
+      withArray blindedTokens $ \cBlindedTokensArray ->
+      withArray signedTokens $ \cSignedTokensArray ->
+      batch_dleq_proof_new cBlindedTokensArray cSignedTokensArray (length blindedTokens) signingKey
+
+    newEncodedProof blindedTokens signedTokens signingKey =
+      bracket (newProof blindedTokens signedTokens signingKey) batch_dleq_proof_destroy $ \proof ->
+      bracket (batch_dleq_proof_encode_base64 proof) free peekCString
+
+    stringSigningKey = unpack textSigningKey
+    stringTokens = map unpack textTokens
+  in
+    do
+      cStringSigningKey <- newCString stringSigningKey
+      case cStringSigningKey == nullPtr of
+        True -> return $ Left SigningKeyAllocation
+        False -> do
+          signingKey <- signing_key_decode_base64 cStringSigningKey
+          case signingKey == nullPtr of
+            True -> return $ Left SigningKeyDecoding
+            False -> do
+              cStringTokens <- mapM newCString stringTokens
+              case any (== nullPtr) cStringTokens of
+                True -> return $ Left BlindedTokenAllocation
+                False -> do
+                  blindedTokens <- mapM blinded_token_decode_base64 cStringTokens
+                  case any (== nullPtr) blindedTokens of
+                    True -> return $ Left BlindedTokenDecoding
+                    False -> do
+                      signedTokens <- mapM (signing_key_sign signingKey) blindedTokens
+                      case any (== nullPtr) signedTokens of
+                        True -> return $ Left TokenSigning
+                        False -> do
+                          encodedCStringSignedTokens <- mapM signed_token_encode_base64 signedTokens
+                          case any (== nullPtr) encodedCStringSignedTokens of
+                            True -> return $ Left SignedTokenEncoding
+                            False -> do
+                              encodedSignedTokens <- mapM peekCString encodedCStringSignedTokens
+                              encodedProof <- newEncodedProof blindedTokens signedTokens signingKey
+                              publicKey <- signing_key_get_public_key signingKey
+                              case publicKey == nullPtr of
+                                True -> return $ Left PublicKeyLookup
+                                False -> do
+                                  cStringEncodedPublicKey <- public_key_encode_base64 publicKey
+                                  case cStringEncodedPublicKey == nullPtr of
+                                    True -> return $ Left PublicKeyEncoding
+                                    False -> do
+                                      encodedPublicKey <- peekCString cStringEncodedPublicKey
+                                      return $ Right (pack encodedPublicKey, map pack encodedSignedTokens, pack encodedProof)
+
 
 -- | randomSigningKey generates a new signing key at random and returns it
 -- encoded as a base64 string.
-- 
GitLab