From c3bf3be7cb81ead270d56667621974fd485c1ea0 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Sat, 11 Mar 2023 21:17:09 +0100
Subject: [PATCH] signing tokens with fixed time

---
 pass/routes/api.js | 107 ++++++++++++++++++++++++++++++---------------
 1 file changed, 71 insertions(+), 36 deletions(-)

diff --git a/pass/routes/api.js b/pass/routes/api.js
index 04bc7ec..1d3d20f 100644
--- a/pass/routes/api.js
+++ b/pass/routes/api.js
@@ -1,10 +1,7 @@
 var express = require("express");
 var router = express.Router();
 
-const {
-  body,
-  validationResult
-} = require("express-validator");
+const { body, validationResult } = require("express-validator");
 
 const config = require("config");
 const Key = require("../app/Key");
@@ -187,19 +184,33 @@ router.get("/token/pubkey", async (req, res) => {
   });
 });
 
-router.post("/token/sign", body("key").notEmpty(), body("date").notEmpty().custom(value => {
-  // Make sure the date is either for the last month or this month
-  let date = dayjs(value, config.get("crypto.private_key.date_format"));
-  let min = dayjs().millisecond(0).second(0).minute(0).hour(0).date(1).subtract(1, "month");
-  let max = min.add(2, "month");
-  if (!date.isValid() || date.isBefore(min) || !date.isBefore(max)) {
-    return Promise.reject("Submitted Date format is invalid");
-  }
-  return true;
-}),
-  body("blinded_tokens").notEmpty().withMessage("Blinded Tokens need to be defined")
-    .isArray({ min: 1, max: 10 }).withMessage("You can supply between 1 and 10 tokens to sign")
-    .custom(value => {
+router.post(
+  "/token/sign",
+  body("key").notEmpty(),
+  body("date")
+    .notEmpty()
+    .custom((value) => {
+      // Make sure the date is either for the last month or this month
+      let date = dayjs(value, config.get("crypto.private_key.date_format"));
+      let min = dayjs()
+        .millisecond(0)
+        .second(0)
+        .minute(0)
+        .hour(0)
+        .date(1)
+        .subtract(1, "month");
+      let max = min.add(2, "month");
+      if (!date.isValid() || date.isBefore(min) || !date.isBefore(max)) {
+        return Promise.reject("Submitted Date format is invalid");
+      }
+      return true;
+    }),
+  body("blinded_tokens")
+    .notEmpty()
+    .withMessage("Blinded Tokens need to be defined")
+    .isArray({ min: 1, max: 10 })
+    .withMessage("You can supply between 1 and 10 tokens to sign")
+    .custom((value) => {
       let checked_tokens = {};
       for (let i = 0; i < value.length; i++) {
         if (value[i] in checked_tokens) {
@@ -209,41 +220,65 @@ router.post("/token/sign", body("key").notEmpty(), body("date").notEmpty().custo
         }
       }
       return true;
-    })
-  , async (req, res) => {
+    }),
+  async (req, res) => {
     const errors = validationResult(req);
     if (!errors.isEmpty()) {
       res.status(422).json(errors);
       return;
     }
     let key = await Key.GET_KEY(req.body.key, false);
-    let date = dayjs(req.body.date, config.get("crypto.private_key.date_format"));
+    let date = dayjs(
+      req.body.date,
+      config.get("crypto.private_key.date_format")
+    );
     let blinded_tokens = req.body.blinded_tokens;
     let signed_tokens = {};
 
+    if (key.get_charge() < blinded_tokens.length) {
+      res.status(422).json({
+        message: "Invalid Key",
+      });
+    }
+
+    // Make signing requests always the same duration to prevent timing attacks on the private key
+    let duration_millis = 1000;
+    let start_time = dayjs();
+
     let crypto = new Crypto();
-    crypto.get_private_key(date).then(private_key => {
-      for (let i = 0; i < blinded_tokens.length; i++) {
-        let blinded_token = blinded_tokens[i];
-        signed_tokens[blinded_token] = crypto.sign(blinded_token, private_key).toString();
-      }
+    await crypto
+      .get_private_key(date)
+      .then((private_key) => {
+        for (let i = 0; i < blinded_tokens.length; i++) {
+          let blinded_token = blinded_tokens[i];
+          signed_tokens[blinded_token] = crypto
+            .sign(blinded_token, private_key)
+            .toString();
+        }
+      })
+      .catch((reason) => {
+        console.error(reason);
+        res.status(500).json({
+          status: 500,
+          message: "Couldn't load private key",
+        });
+      });
+
+    let missing_millis = Math.max(
+      duration_millis - dayjs().diff(start_time, "millisecond"),
+      0
+    );
 
+    setTimeout(() => {
       res.status(201).json({
         key: key.get_key,
         discharged: blinded_tokens.length,
         date: date.format(config.get("crypto.private_key.date_format")),
-        signed_tokens: signed_tokens
+        signed_tokens: signed_tokens,
       });
-    }).catch(reason => {
-      console.error(reason);
-      res.status(500).json({
-        status: 500,
-        message: "Couldn't load private key"
-      });
-    });
-
-
-  });
+    }, missing_millis);
+  }
+);
 
 router.use((req, res) => {
   res.status(404).json({
-- 
GitLab