From ca77a78420a3b61a3609b632835a16fb57d4e2aa Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Tue, 15 Nov 2022 12:20:37 +0100
Subject: [PATCH] order gets signed after purchase

---
 pass/app/Crypto.js                   | 64 +++++++++++++++++-----------
 pass/app/Order.js                    | 38 +++++++++++++++--
 pass/resources/js/checkout_paypal.js |  2 +-
 pass/routes/checkout/checkout.js     |  5 ++-
 pass/routes/checkout/paypal.js       | 29 +++++++++++--
 5 files changed, 102 insertions(+), 36 deletions(-)

diff --git a/pass/app/Crypto.js b/pass/app/Crypto.js
index bb56135..ad70e2f 100644
--- a/pass/app/Crypto.js
+++ b/pass/app/Crypto.js
@@ -1,6 +1,9 @@
 const config = require("config");
 const { pki, random } = require("node-forge");
 const crypto = require("crypto");
+const BlindSignature = require("blind-signatures");
+var BigInteger = require("jsbn").BigInteger;
+const NodeRSA = require("node-rsa");
 
 class Crypto {
   #dayjs;
@@ -17,24 +20,21 @@ class Crypto {
   }
 
   private_key_get_current() {
-    let seed_date = this.#dayjs().format(this.#dayjs_format);
+    let seed_date = this.#dayjs();
     return this.#private_key_get(seed_date);
   }
 
   private_key_get_last() {
-    let seed_date = this.#dayjs()
-      .subtract(1, "month")
-      .format(this.#dayjs_format);
+    let seed_date = this.#dayjs().subtract(1, "month");
     return this.#private_key_get(seed_date);
   }
 
   async #private_key_get(seed_date) {
-    let private_key = {};
-
-    let cache_key = "private_keys" + seed_date;
+    let cache_key = "private_key_" + seed_date.format(this.#dayjs_format);
 
     // Check if the private key already exists in cache
     let cached_key = await this.#redis.get(cache_key);
+    let private_key = new NodeRSA();
     if (cached_key === null) {
       let seed = config.get("crypto.private_key.seed") + seed_date;
 
@@ -43,31 +43,45 @@ class Crypto {
       prng_instance.seedFileSync = () => seed;
 
       let keypair = pki.rsa.generateKeyPair({
-        bits: 4096,
+        bits: config.get("crypto.private_key.bit_length"),
         prng: prng_instance,
         workers: -1,
       });
+      let private_key_pem = pki
+        .privateKeyToPem(keypair.privateKey)
+        .trim()
+        .replace(/\r\n/g, "\n");
 
-      private_key = {
-        n: keypair.privateKey.n.toString(),
-        e: keypair.privateKey.e.toString(),
-        d: keypair.privateKey.d.toString(),
-        p: keypair.privateKey.p.toString(),
-        q: keypair.privateKey.q.toString(),
-        dmp1: keypair.privateKey.dP.toString(),
-        dmq1: keypair.privateKey.dQ.toString(),
-        coeff: keypair.privateKey.qInv.toString(),
-      };
+      private_key = private_key.importKey(
+        Buffer.from(private_key_pem, "utf8"),
+        "private"
+      );
 
       // Store the key in cache
-      await this.#redis.set(cache_key, JSON.stringify(private_key));
+      await this.#redis.set(cache_key, private_key.exportKey("pkcs8"));
     } else {
-      private_key = JSON.parse(await this.#redis.get(cache_key));
+      let private_key_data = await this.#redis.get(cache_key);
+      private_key = private_key.importKey(private_key_data, "pkcs8");
     }
-
     return private_key;
   }
 
+  async sign(encrypted_sales_receipts, order_date) {
+    let private_key = await this.#private_key_get(order_date);
+    console.log(private_key);
+    let signed_encrypted_sales_receipts = [];
+
+    for (let i = 0; i < encrypted_sales_receipts.length; i++) {
+      signed_encrypted_sales_receipts.push(
+        BlindSignature.sign({
+          blinded: new BigInteger(encrypted_sales_receipts[i]),
+          key: private_key,
+        }).toString()
+      );
+    }
+    return signed_encrypted_sales_receipts;
+  }
+
   /**
    * Creates an hmac hash for purchase data so we can check it later
    */
@@ -84,8 +98,8 @@ class Crypto {
       amount: parseInt(amount),
       unit_size: parseInt(unit_size),
       price_per_unit: parseFloat(price_per_unit),
-      public_key_n: public_key_n,
-      public_key_e: public_key_e,
+      public_key_n: new String(public_key_n),
+      public_key_e: new String(public_key_e),
     });
     let forge = require("node-forge");
     let hmac = forge.hmac.create();
@@ -111,8 +125,8 @@ class Crypto {
       amount: parseInt(amount),
       unit_size: parseInt(unit_size),
       price_per_unit: parseFloat(price_per_unit),
-      public_key_n: public_key_n,
-      public_key_e: public_key_e,
+      public_key_n: new String(public_key_n),
+      public_key_e: new String(public_key_e),
     });
     let forge = require("node-forge");
     let hmac = forge.hmac.create();
diff --git a/pass/app/Order.js b/pass/app/Order.js
index b6ac4ec..bbe4afd 100644
--- a/pass/app/Order.js
+++ b/pass/app/Order.js
@@ -1,4 +1,5 @@
 const config = require("config");
+const Crypto = require("./Crypto");
 const dayjs = require("dayjs");
 
 class Order {
@@ -34,12 +35,14 @@ class Order {
   #unit_size;
   #price_per_unit;
   #encrypted_sales_receipts;
+  #signatures;
   #payment_completed;
   #payment_method_link; // Stores a link to an entry of the payment methods payment i.e. PayPal order id
 
   /**
    * Data populated by context
    */
+  #order_date;
   #create_mode;
   #redis_client;
 
@@ -48,14 +51,23 @@ class Order {
     amount,
     unit_size,
     price_per_unit,
-    encrypted_sales_receipts
+    encrypted_sales_receipts,
+    signatures
   ) {
-    this.#order_id = parseInt(order_id);
+    this.#order_id = new String(order_id);
+    this.#order_date = dayjs.unix(this.#order_id.substr(0, 10));
+
     this.#amount = parseInt(amount);
     this.#unit_size = parseInt(unit_size);
     this.#price_per_unit = parseFloat(price_per_unit);
     this.#encrypted_sales_receipts = encrypted_sales_receipts;
 
+    if (signatures) {
+      this.#signatures = signatures;
+    } else {
+      this.#signatures = [];
+    }
+
     this.#payment_completed = false;
     this.#create_mode = true;
 
@@ -81,6 +93,10 @@ class Order {
     return this.#payment_method_link;
   }
 
+  getSignatures() {
+    return this.#signatures;
+  }
+
   isPaymentComplete() {
     return this.#payment_completed;
   }
@@ -89,6 +105,16 @@ class Order {
     this.#payment_method_link = payment_method_link;
   }
 
+  async signOrder() {
+    let mgcrypto = new Crypto();
+
+    let signed_encrypted_sales_receipts = mgcrypto.sign(
+      this.#encrypted_sales_receipts,
+      this.#order_date
+    );
+    this.#signatures = await signed_encrypted_sales_receipts;
+  }
+
   static async LOAD_ORDER_FROM_ID(order_id) {
     return new Promise((resolve, reject) => {
       let Redis = require("ioredis");
@@ -106,7 +132,8 @@ class Order {
           order_data.amount,
           order_data.unit_size,
           order_data.price_per_unit,
-          order_data.encrypted_sales_receipts
+          JSON.parse(order_data.encrypted_sales_receipts),
+          JSON.parse(order_data.signatures)
         );
         if (order_data.payment_method_link) {
           loaded_order.setPaymentMethodLink(
@@ -140,7 +167,10 @@ class Order {
         amount: this.#amount,
         unit_size: this.#unit_size,
         price_per_unit: this.#price_per_unit,
-        encrypted_sales_receipts: this.#encrypted_sales_receipts,
+        encrypted_sales_receipts: JSON.stringify(
+          this.#encrypted_sales_receipts
+        ),
+        signatures: JSON.stringify(this.#signatures),
         payment_completed: this.#payment_completed,
         payment_method_link: JSON.stringify(this.#payment_method_link),
       })
diff --git a/pass/resources/js/checkout_paypal.js b/pass/resources/js/checkout_paypal.js
index cec426d..91b0eb5 100644
--- a/pass/resources/js/checkout_paypal.js
+++ b/pass/resources/js/checkout_paypal.js
@@ -70,7 +70,7 @@ function execute_payment_paypal(encrypted_sales_receipts) {
               .then((orderData) => {
                 let paymentEvent = new CustomEvent("payment-complete", {
                   detail: {
-                    orderData,
+                    signatures: orderData.signatures,
                   },
                   bubbles: true,
                   cancelable: true,
diff --git a/pass/routes/checkout/checkout.js b/pass/routes/checkout/checkout.js
index 229098e..1ae76b2 100644
--- a/pass/routes/checkout/checkout.js
+++ b/pass/routes/checkout/checkout.js
@@ -35,8 +35,8 @@ router.get(
     let crypto = new Crypto();
     let private_key = await crypto.private_key_get_current();
     params.crypto = {
-      N: private_key.n,
-      E: private_key.e,
+      N: private_key.keyPair.n,
+      E: private_key.keyPair.e,
     };
 
     // Generate hmac hash of the payment data so we are able to verify them when the client submits them again
@@ -87,6 +87,7 @@ router.use(
           req.body.public_key_e
         )
       ) {
+        console.log("Invalid integrity");
         return Promise.reject("Integrity is not matching");
       }
       return true;
diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js
index 1cde1f4..7927052 100644
--- a/pass/routes/checkout/paypal.js
+++ b/pass/routes/checkout/paypal.js
@@ -18,6 +18,7 @@ router.post("/", async (req, res, next) => {
     req.body.price_per_unit,
     req.body.encrypted_sales_receipts
   );
+
   order
     .save()
     .then(() => {
@@ -38,10 +39,30 @@ router.post("/", async (req, res, next) => {
 // capture payment & store order information or fullfill order
 router.post("/capture", async (req, res) => {
   Order.LOAD_ORDER_FROM_ID(req.body.order_id).then((loaded_order) => {
-    let paypal_order_id = loaded_order.getPaymentMethodLink().id;
-    capturePayment(paypal_order_id).then((captureData) =>
-      res.json(captureData)
-    );
+    loaded_order
+      .signOrder()
+      .then(() => {
+        let paypal_order_id = loaded_order.getPaymentMethodLink().id;
+        capturePayment(paypal_order_id)
+          .then((captureData) => {
+            loaded_order
+              .save()
+              .then(() => {
+                captureData.signatures = loaded_order.getSignatures();
+                res.json(captureData);
+              })
+              .catch((error) => {
+                res.status(400).json({ errors: [{ msg: error }] });
+              });
+          })
+          .catch((error) => {
+            res.status(400).json({ errors: [{ msg: error }] });
+          });
+      })
+      .catch((error) => {
+        res.status(400).json({ errors: [{ msg: error }] });
+      });
+
     //res.json(captureData);
     // TODO: store payment information such as the transaction ID
   });
-- 
GitLab