commit 86bf99d11cdc6669d86ea8b0661ce8b1ce5f8a70
parent 50a1a07b4bf14eff16e59d12fde105220a541093
Author: Joel-Haeberli <haebu@rubigen.ch>
Date: Wed, 5 Jun 2024 09:10:05 +0200
fix: finalize wire-gateway API
Diffstat:
8 files changed, 47 insertions(+), 67 deletions(-)
diff --git a/c2ec/db-postgres.go b/c2ec/db-postgres.go
@@ -7,6 +7,7 @@ import (
"math"
"os"
"strconv"
+ "strings"
"time"
"github.com/jackc/pgx/v5"
@@ -94,49 +95,21 @@ const PS_UPDATE_TRANSFER = "UPDATE " + TRANSFER_TABLE_NAME + " SET (" +
"(SELECT ((SELECT MAX(" + TRANSFER_FIELD_NAME_TRANSFERRED_ROW_ID + ") FROM " + TRANSFER_TABLE_NAME + " WHERE " + TRANSFER_FIELD_NAME_STATUS + "=0)+1))" +
") WHERE " + TRANSFER_FIELD_NAME_ID + "=$4"
-const PS_CONFIRMED_TRANSACTIONS_ASC = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
- " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "'" +
- " ORDER BY " + WITHDRAWAL_FIELD_NAME_CONFIRMED_ROW_ID + " ASC" +
- " LIMIT $1" +
- " OFFSET ($2 - ((SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal)-(SELECT COUNT(*) FROM c2ec.withdrawal)))" // SELECT * FROM c2ec.withdrawal WHERE withdrawal_status='confirmed' ORDER BY confirmed_row_id ASC LIMIT 920 OFFSET (103 - ((SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal)-(SELECT COUNT(*) FROM c2ec.withdrawal));
-
-const PS_CONFIRMED_TRANSACTIONS_DESC = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
- " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "'" +
- " ORDER BY " + WITHDRAWAL_FIELD_NAME_CONFIRMED_ROW_ID + " DESC" +
- " LIMIT $1" +
- " OFFSET $2" // SELECT * FROM c2ec.withdrawal WHERE withdrawal_status='confirmed' ORDER BY confirmed_row_id ASC LIMIT 920 OFFSET (103 - ((SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal)-(SELECT COUNT(*) FROM c2ec.withdrawal));
-
-const PS_CONFIRMED_TRANSACTIONS_ASC_MAX = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
- " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "'" +
- " ORDER BY " + WITHDRAWAL_FIELD_NAME_CONFIRMED_ROW_ID + " ASC" +
- " LIMIT $1" +
- " OFFSET ($2 - (SELECT MAX(confirmed_row_id)))" // (SELECT MIN(" + WITHDRAWAL_FIELD_NAME_CONFIRMED_ROW_ID + ") FROM " + WITHDRAWAL_TABLE_NAME + " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "')"
-
-const PS_CONFIRMED_TRANSACTIONS_DESC_MAX = "SELECT * FROM " + WITHDRAWAL_TABLE_NAME +
- " WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "'" +
- " ORDER BY " + WITHDRAWAL_FIELD_NAME_ID + " DESC" +
- " LIMIT $1" +
- " OFFSET ($2 - ((SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal)-(SELECT COUNT(*) FROM c2ec.withdrawal)))" //(SELECT MIN(" + WITHDRAWAL_FIELD_NAME_CONFIRMED_ROW_ID + ") FROM " + WITHDRAWAL_TABLE_NAME +" WHERE " + WITHDRAWAL_FIELD_NAME_STATUS + "='" + string(CONFIRMED) + "' AND )"
-
-const PS_GET_TRANSFERS_ASC = "SELECT * FROM " + TRANSFER_TABLE_NAME +
- " ORDER BY " + TRANSFER_FIELD_NAME_ROW_ID + " ASC" +
- " LIMIT $1" +
- " OFFSET $2"
-
-const PS_GET_TRANSFERS_DESC = "SELECT * FROM " + TRANSFER_TABLE_NAME +
- " ORDER BY " + TRANSFER_FIELD_NAME_ROW_ID + " DESC" +
- " LIMIT $1" +
- " OFFSET $2"
-
-const PS_GET_TRANSFERS_ASC_MAX = "SELECT * FROM " + TRANSFER_TABLE_NAME +
- " ORDER BY " + TRANSFER_FIELD_NAME_ROW_ID + " ASC" +
- " LIMIT $1" +
- " OFFSET $2" // (SELECT MAX(" + TRANSFER_FIELD_NAME_TRANSFERRED_ROW_ID + ") FROM " + TRANSFER_TABLE_NAME + " WHERE " + TRANSFER_FIELD_NAME_STATUS + "=0)"
-
-const PS_GET_TRANSFERS_DESC_MAX = "SELECT * FROM " + TRANSFER_TABLE_NAME +
- " ORDER BY " + TRANSFER_FIELD_NAME_ROW_ID + " DESC" +
- " LIMIT $1" +
- " OFFSET $2" // (SELECT MAX(" + TRANSFER_FIELD_NAME_TRANSFERRED_ROW_ID + ") FROM " + TRANSFER_TABLE_NAME + " WHERE " + TRANSFER_FIELD_NAME_STATUS + "=0)"
+const PS_CONFIRMED_TRANSACTIONS_ASC = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id > $1 ORDER BY confirmed_row_id ASC LIMIT $2"
+
+const PS_CONFIRMED_TRANSACTIONS_DESC = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id < $1 ORDER BY confirmed_row_id DESC LIMIT $2"
+
+const PS_CONFIRMED_TRANSACTIONS_ASC_MAX = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id > $1 ORDER BY confirmed_row_id ASC LIMIT $2"
+
+const PS_CONFIRMED_TRANSACTIONS_DESC_MAX = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id < (SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal) ORDER BY confirmed_row_id DESC LIMIT $1"
+
+const PS_GET_TRANSFERS_ASC = "SELECT * FROM c2ec.transfer WHERE transferred_row_id > $1 ORDER BY transferred_row_id ASC LIMIT $2"
+
+const PS_GET_TRANSFERS_DESC = "SELECT * FROM c2ec.transfer WHERE transferred_row_id < $1 ORDER BY transferred_row_id DESC LIMIT $2"
+
+const PS_GET_TRANSFERS_ASC_MAX = "SELECT * FROM c2ec.transfer WHERE transferred_row_id > $1 ORDER BY transferred_row_id ASC LIMIT $2"
+
+const PS_GET_TRANSFERS_DESC_MAX = "SELECT * FROM c2ec.transfer WHERE transferred_row_id < (SELECT MAX(transferred_row_id) FROM c2ec.transfer) ORDER BY transferred_row_id DESC LIMIT $1"
const PS_GET_TRANSFERS_BY_STATUS = "SELECT * FROM " + TRANSFER_TABLE_NAME +
" WHERE " + TRANSFER_FIELD_NAME_STATUS + "=$1"
@@ -499,29 +472,26 @@ func (db *C2ECPostgres) SetRetryCounter(withdrawalId int, retryCounter int) erro
func (db *C2ECPostgres) GetConfirmedWithdrawals(start int, delta int, since time.Time) ([]*Withdrawal, error) {
// +d / +s
- query := "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id > $1 ORDER BY confirmed_row_id ASC LIMIT $2" // PS_CONFIRMED_TRANSACTIONS_ASC
+ query := PS_CONFIRMED_TRANSACTIONS_ASC
if delta < 0 {
// d negatives indicates DESC ordering and backwards reading
// -d / +s
- query = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id < $1 ORDER BY confirmed_row_id DESC LIMIT $2" // PS_CONFIRMED_TRANSACTIONS_DESC
+ query = PS_CONFIRMED_TRANSACTIONS_DESC
if start < 0 {
// start negative indicates not explicitly given
// since -d is the case here we try reading the latest entries
// -d / -s
- query = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id < (SELECT MAX(confirmed_row_id) FROM c2ec.withdrawal) ORDER BY confirmed_row_id DESC LIMIT $1" // PS_CONFIRMED_TRANSACTIONS_DESC_MAX
+ query = PS_CONFIRMED_TRANSACTIONS_DESC_MAX
}
} else {
if start < 0 {
// +d / -s
- query = "SELECT * FROM c2ec.withdrawal WHERE confirmed_row_id > $1 ORDER BY confirmed_row_id ASC LIMIT $2" // PS_CONFIRMED_TRANSACTIONS_ASC_MAX
+ query = PS_CONFIRMED_TRANSACTIONS_ASC_MAX
}
}
limit := int(math.Abs(float64(delta)))
offset := start
- if delta < 0 {
- offset = start - int(limit)
- }
if offset < 0 {
offset = 0
}
@@ -535,7 +505,7 @@ func (db *C2ECPostgres) GetConfirmedWithdrawals(start int, delta int, since time
var row pgx.Rows
var err error
- if start < 0 && delta < 0 {
+ if strings.Count(query, "$") == 1 {
row, err = db.pool.Query(
db.ctx,
query,
@@ -778,45 +748,52 @@ func (db *C2ECPostgres) UpdateTransfer(
func (db *C2ECPostgres) GetTransfers(start int, delta int, since time.Time) ([]*Transfer, error) {
+ // +d / +s
query := PS_GET_TRANSFERS_ASC
if delta < 0 {
+ // d negatives indicates DESC ordering and backwards reading
+ // -d / +s
query = PS_GET_TRANSFERS_DESC
if start < 0 {
+ // start negative indicates not explicitly given
+ // since -d is the case here we try reading the latest entries
+ // -d / -s
query = PS_GET_TRANSFERS_DESC_MAX
}
} else {
if start < 0 {
+ // +d / -s
query = PS_GET_TRANSFERS_ASC_MAX
}
}
- limit := math.Abs(float64(delta))
+ limit := int(math.Abs(float64(delta)))
offset := start
- if delta < 0 {
- offset = start - int(limit)
- }
if offset < 0 {
offset = 0
}
+ if start < 0 {
+ start = 0
+ }
+
+ LogInfo("postgres", fmt.Sprintf("selected query=%s (\nparameters:\n delta=%d,\n start=%d, limit=%d,\n offset=%d,\n since=%d\n)", query, delta, start, limit, offset, since.Unix()))
+
var row pgx.Rows
var err error
- if start < 0 {
- // use MAX(id) instead of a concrete id, because start
- // identifier was negative. Inidicates to read the most
- // recent ids.
+
+ if strings.Count(query, "$") == 1 {
row, err = db.pool.Query(
db.ctx,
query,
limit,
- offset,
)
} else {
row, err = db.pool.Query(
db.ctx,
query,
- limit,
offset,
+ limit,
)
}
diff --git a/docs/content/acknowledgements.tex b/docs/content/acknowledgements.tex
@@ -2,7 +2,7 @@ I would like to thank Prof. Dr. Benjamin Fehrensen and Prof. Dr. Christian Groth
The GNU Taler team deserves a big thank you to discuss, reflect and sharpen the Terminals API which was an important part of the thesis.
-Also I thank my colleagues from the class who motivated me during the thesis. Especially I would like to thank Jan Fuhrer for the nice friday night coding sessions, Christian Blättler for the valuable discussion about Taler and Andy Bigler for the exchange about Android applications. They were crucial to gain a better understanding of how the components work and how I must do the implementation.
+Also I thank my colleagues from the class who motivated me during the thesis. Especially I would like to thank Jan Fuhrer for the nice friday night coding sessions, Christian Blättler for the valuable discussion about GNU Taler and Andy Bigler for the exchange about Android applications. They were crucial to gain a better understanding of how the components work and how I must do the implementation.
Additionally, I would like to thank Meret Staub for her critical thoughts during the proofreading of the thesis.
diff --git a/docs/content/architecture/wallee.tex b/docs/content/architecture/wallee.tex
@@ -5,7 +5,7 @@ Wallee offers level 1 PCI-DSS \cite{pci-dss} compliant payment processes to its
From the perspective of Wallee, the system looks as follows:
\begin{itemize}
- \item The Wallee terminal uses the Bank-Integration API of \textit{C2EC} to get notified about parameter selection and inform \textit{C2EC} about the payment.
+ \item The Wallee terminal uses the Bank-Integration API of C2EC to get notified about parameter selection and inform C2EC about the payment.
\item The Wallee terminal needs the credit card of the customer to authorize the payment.
\item The Wallee terminal uses the \textit{Wallee Backend} to authorize the payment using the supplied Android Till SDK \autoref{ref-wallee-till-api}
\end{itemize}
diff --git a/docs/content/implementation/d-security.tex b/docs/content/implementation/d-security.tex
@@ -2,13 +2,13 @@
\subsection{General Security Considerations}
-To review and validate the security of the design two cases were reviewed. The first mirrors the easiest attack (EAV eavesdropping and trying to abuse WOPID). The second case reviews where the most harm can possibly be done to the system.
+To review and validate the security of the design two cases were reviewed. The first mirrors the easiest attack (EAV eavesdropping and trying to abuse \textit{WOPID}). The second case reviews where the most harm can possibly be done to the system.
\subsubsection{EAV Abusing WOPID}
-The WOPID is used to link a reserve public key to a withdrawal operation. Since the registration is done through an API, an attacker could try to be first and register its own reserve public key before the customer. When the WOPID is somehow precomputable, an attacker could steal the money by registering their own reserve public key before the customer. This threat is mitigated by the request of the wallet resulting in a conflict response code when trying to add a reserve public key to an already registered withdrawal operation. The customer will see this error and not authorize the transaction and instead abort the withdrawal.
+The \textit{WOPID} is used to link a reserve public key to a withdrawal operation. Since the registration is done through an API, an attacker could try to be first and register its own reserve public key before the customer. When the \textit{WOPID} is somehow precomputable, an attacker could steal the money by registering their own reserve public key before the customer. This threat is mitigated by the request of the wallet resulting in a conflict response code when trying to add a reserve public key to an already registered withdrawal operation. The customer will see this error and not authorize the transaction and instead abort the withdrawal.
-Further a WOPID can be abused triggering a confirmation or an abort request at the Terminals API or an abort request at the Bank-Integration API. The confirmation or abort from the side of the terminal are mitigated through the authentication of the terminals. When the eavesdropping adversary (EAV) \cite{katz2020introduction} can somehow access the communication between a terminal and C2EC, the WOPID cannot be abused without also breaking the terminals credentials. What if the attacker decides to use the unauthenticated Bank-Integration API the wallet would normally use? The specification does not require some proof that the requester is the wallet owning the private key of the reserve. This could lead to tampering of the withdrawals in the time window of the confirmation of the payment. The problem could be mitigated by sending a signed token in the request (the request already is a POST request). The wallet could use its reserve private key to sign the token. The Bank-Integration API could then verify the token using the reserve public key assigned to the withdrawal operation. It is understandable that the risk is accepted, since a potential adversary would need to be sophisticated (needs to redirect requests of the wallet and read WOPID from the request). What about wallets run by people in countries which are politically not as stable as Switzerland and censorship is a problem? Maybe it's a good idea to add some mean of authentication to at least the abort endpoint of the Bank-Integration API. On the other hand the attacker needs access to the victims phone anyway and could possibly also use the keys.
+Further a \textit{WOPID} can be abused triggering a confirmation or an abort request at the Terminals API or an abort request at the Bank-Integration API. The confirmation or abort from the side of the terminal are mitigated through the authentication of the terminals. When the eavesdropping adversary (EAV) \cite{katz2020introduction} can somehow access the communication between a terminal and C2EC, the \textit{WOPID} cannot be abused without also breaking the terminals credentials. What if the attacker decides to use the unauthenticated Bank-Integration API the wallet would normally use? The specification does not require some proof that the requester is the wallet owning the private key of the reserve. This could lead to tampering of the withdrawals in the time window of the confirmation of the payment. The problem could be mitigated by sending a signed token in the request (the request already is a POST request). The wallet could use its reserve private key to sign the token. The Bank-Integration API could then verify the token using the reserve public key assigned to the withdrawal operation. It is understandable that the risk is accepted, since a potential adversary would need to be sophisticated (needs to redirect requests of the wallet and read \textit{WOPID} from the request). What about wallets run by people in countries which are politically not as stable as Switzerland and censorship is a problem? Maybe it's a good idea to add some mean of authentication to at least the abort endpoint of the Bank-Integration API. On the other hand the attacker needs access to the victims phone anyway and could possibly also use the keys.
\subsubsection{Trying To Withdraw Money Without Paying}
diff --git a/docs/thesis.pdf b/docs/thesis.pdf
Binary files differ.
diff --git a/presentation/friday.odp b/presentation/friday.odp
Binary files differ.
diff --git a/simulation/c2ec-simulation b/simulation/c2ec-simulation
Binary files differ.
diff --git a/simulation/sim-wire-watch.go b/simulation/sim-wire-watch.go
@@ -41,7 +41,10 @@ func WireWatch(finish chan interface{}, kill chan error) {
url := FormatUrl(
HISTORY_ENDPOINT,
map[string]string{},
- map[string]string{"long_poll_ms": strconv.Itoa(wirewatchLongPoll)},
+ map[string]string{
+ "delta": "1024", // this value is used by the wirewatch
+ "long_poll_ms": strconv.Itoa(wirewatchLongPoll),
+ },
)
fmt.Println("WIRE-WATCH: requesting status update for withdrawal", url)
response, status, err := HttpGet(