diff --git a/docs/api.md b/docs/api.md index 21ca503ef6565fea59bf11378bdf0ec795777dc9..98350a6f9242d5c036f3ec92b38323c47c8f4461 100644 --- a/docs/api.md +++ b/docs/api.md @@ -117,3 +117,19 @@ If the key cannot be charged because it is already charged with too many Orders "charged": <AMOUNT_CHARGED> } ``` + +## `GET /api/json/token/pubkey` + +Retrieves the pubkey the server is using currently for token signatures. +This Method does not require authentication + +### Example Response + +```json +{ + "date": "2023-03", + "standard": "pcks8", + "format": "pem", + "pubkey_pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1knnta8kCSClaIPECAGZ\npD75MIRVD20Ucc/SQP3BFHVCoBhwLt77V3ORpwYph8Wzk9QNYjwXwme8Dd8CCdu1\noLKXbneUn3gR1f/yu2ghih64Qs7DRbIVCILDUmO3PCCePB811Dz5cBABjbUg64p3\nOJbJDtbWxcZYYd5GH3VOo0yhk7RKSZxGNzCnwtzRvhKzdl0hI5F8POA6rkql4WbA\nghXyspdSC3e0s6AN9plTGxzysW0Du/a3ly2WA3ycpQO9HWyxepl8AblYfUTRm5lb\ngX9q6JYbGQvkZCd1ejEmhpIZfiwXZsBo1dygCgjlBfIXfLgfAfQATLYOuubdAWUB\nfQIDAQAB\n-----END PUBLIC KEY-----" +} +``` diff --git a/pass/app/Crypto.js b/pass/app/Crypto.js index 0dd03b82d82bc336636daea8b6c546f71b23dc1e..62b2810fe97a98647b7baa80c738200b16358eb3 100644 --- a/pass/app/Crypto.js +++ b/pass/app/Crypto.js @@ -4,6 +4,8 @@ const crypto = require("crypto"); const BlindSignature = require("blind-signatures"); var BigInteger = require("jsbn").BigInteger; const NodeRSA = require("node-rsa"); +const dayjs = require("dayjs"); +const RedisClient = require("./RedisClient"); class Crypto { #dayjs; @@ -13,27 +15,26 @@ class Crypto { constructor() { this.#dayjs = require("dayjs"); this.#dayjs_format = config.get("crypto.private_key.date_format"); - let Redis = require("ioredis"); - this.#redis = new Redis({ - host: config.get("redis.host"), - }); } - async private_key_get(seed_date, expiration_date) { - let cache_key = - "private_key_" + - seed_date.format(this.#dayjs_format) + - "_" + - expiration_date.format(this.#dayjs_format); + /** + * Retrieves the Private Key for the given date + * Private Keys are generated from seeds using the Year and the month + * of the given date + * + * @param {dayjs.Dayjs} date Date/Month for the private Key seed + * @returns {NodeRSA} + */ + async get_private_key(date) { + let cache_key = "private_key:" + date.format(this.#dayjs_format); // Check if the private key already exists in cache - let cached_key = await this.#redis.get(cache_key); + let redis_client = RedisClient.CLIENT(); + let cached_key = await redis_client.get(cache_key); + let private_key = new NodeRSA(); if (cached_key === null) { let seed = - config.get("crypto.private_key.seed") + - seed_date.format(this.#dayjs_format) + - "_" + - expiration_date.format(this.#dayjs_format); + config.get("crypto.private_key.seed") + date.format(this.#dayjs_format); let sha512 = md.sha512.create(); sha512.update(seed); seed = sha512.digest().toHex(); @@ -57,9 +58,13 @@ class Crypto { ); // Store the key in cache - await this.#redis.set(cache_key, private_key.exportKey("pkcs8")); + await redis_client + .pipeline() + .set(cache_key, 600, private_key.exportKey("pkcs8")) + .expireat(date.add(2, "month").unix()) + .exec(); } else { - let private_key_data = await this.#redis.get(cache_key); + let private_key_data = await redis_client.get(cache_key); private_key = private_key.importKey(private_key_data, "pkcs8"); } return private_key; diff --git a/pass/config/default.json b/pass/config/default.json index f6d3c7d2e986f743f9d91c4b56db8a642089955f..500368d078660971d30074713bc0e725914a036c 100644 --- a/pass/config/default.json +++ b/pass/config/default.json @@ -51,7 +51,7 @@ "private_key": { "seed": "<insert_secret_seed>", "bit_length": 2048, - "date_format": "MM/YYYY" + "date_format": "YYYY-MM" } }, "payments": { @@ -61,4 +61,4 @@ "client_id": "<INSERT_PAYPAL_CLIENT_ID>" } } -} \ No newline at end of file +} diff --git a/pass/routes/api.js b/pass/routes/api.js index 62ce846b16a096a731f8670418001d7f6bcbb03d..b6b9904e86c3941c1a73db6d0d4e7f6c496ff81c 100644 --- a/pass/routes/api.js +++ b/pass/routes/api.js @@ -5,8 +5,11 @@ const config = require("config"); const Key = require("../app/Key"); const Order = require("../app/Order"); const Manual = require("../app/payment_processor/Manual"); +const Crypto = require("../app/Crypto"); +const dayjs = require("dayjs"); +const NodeRSA = require("node-rsa"); -router.use((req, res, next) => { +router.use("/key", (req, res, next) => { let auth_token = req.get("Authorization"); let authorized = false; if (auth_token) { @@ -18,7 +21,7 @@ router.use((req, res, next) => { } if (!authorized) { res.status(401).json({ - code: 404, + code: 401, error: "You are not authorized for API usage", }); } else { @@ -66,7 +69,7 @@ router.post("/key/create", async (req, res) => { code: 423, error: reason, key: key.get_key(), - charged: 0 + charged: 0, }); }); }); @@ -157,11 +160,28 @@ router.post("/key/:key/charge", async (req, res) => { code: 423, error: reason, key: key.get_key(), - charged: 0 + charged: 0, }); }); }); +router.get("/token/pubkey", async (req, res) => { + let crypto = new Crypto(); + let now = dayjs(); + /** + * @type {NodeRSA} + */ + let private_key = await crypto.get_private_key(now); + + let public_key_components = private_key.exportKey("public"); + res.json({ + date: now.format(config.get("crypto.private_key.date_format")), + standard: "pcks8", + format: "pem", + pubkey_pem: public_key_components, + }); +}); + router.use((req, res) => { res.status(404).json({ code: 404,