diff --git a/docs/api.md b/docs/api.md index 98350a6f9242d5c036f3ec92b38323c47c8f4461..4376832f0f9f11080957f8b381f93f7d24540b05 100644 --- a/docs/api.md +++ b/docs/api.md @@ -133,3 +133,39 @@ This Method does not require authentication "pubkey_pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1knnta8kCSClaIPECAGZ\npD75MIRVD20Ucc/SQP3BFHVCoBhwLt77V3ORpwYph8Wzk9QNYjwXwme8Dd8CCdu1\noLKXbneUn3gR1f/yu2ghih64Qs7DRbIVCILDUmO3PCCePB811Dz5cBABjbUg64p3\nOJbJDtbWxcZYYd5GH3VOo0yhk7RKSZxGNzCnwtzRvhKzdl0hI5F8POA6rkql4WbA\nghXyspdSC3e0s6AN9plTGxzysW0Du/a3ly2WA3ycpQO9HWyxepl8AblYfUTRm5lb\ngX9q6JYbGQvkZCd1ejEmhpIZfiwXZsBo1dygCgjlBfIXfLgfAfQATLYOuubdAWUB\nfQIDAQAB\n-----END PUBLIC KEY-----" } ``` + +## `POST /api/json/token/sign` + +Signs submitted blinded tokens with the servers private key. The submitted key needs to hold a charge for each token or the request will fail. +The Key will be discharged by this action. +You cannot submit more than 10 tokens to be signed. + +### Parameters + +```json +{ + "key": <KEY_TO_BE_DISCHARGED>, + "date": <DATE_AS_SUPPLIED_IN_TOKEN_PUBKEY>, + "blinded_tokens": [ + "<BLINDED_TOKEN_BIGINT_AS_STRING>", + ... + ] +} +``` + +### Example Response + +If all blinded tokens were signed successfully response code will be `201`. +If the key isn't charged enough for the amount of tokens or if the date supplied is not valid response code will be `422` + +```json +{ + "key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "discharged": <AMOUNT_THAT_WAS_DISCHARGED>, + "date": <DATE_AS_SUPPLIED_IN_TOKEN_PUBKEY>, + "signed_tokens": { + "<BLINDED_TOKEN_BIGINT_AS_STRING>": "<SIGNATURE_BIGINT_AS_STRING>", + ... + } +} +``` diff --git a/pass/app/Crypto.js b/pass/app/Crypto.js index 62b2810fe97a98647b7baa80c738200b16358eb3..cfb719a96c2acc1d68baa200c700a347c3516561 100644 --- a/pass/app/Crypto.js +++ b/pass/app/Crypto.js @@ -60,8 +60,8 @@ class Crypto { // Store the key in cache await redis_client .pipeline() - .set(cache_key, 600, private_key.exportKey("pkcs8")) - .expireat(date.add(2, "month").unix()) + .set(cache_key, private_key.exportKey("pkcs8")) + .expireat(cache_key, date.add(2, "month").unix()) .exec(); } else { let private_key_data = await redis_client.get(cache_key); @@ -70,6 +70,19 @@ class Crypto { return private_key; } + /** + * + * @param {String} blinded_token + * @param {NodeRSA} private_key + * @returns {BigInteger} + */ + sign(blinded_token, private_key) { + return BlindSignature.sign({ + blinded: new BigInteger(blinded_token), + key: private_key + }); + } + async validateMetaGerPassCode( generation_month, expiration_month, @@ -116,20 +129,7 @@ class Crypto { } } - async sign(encrypted_sales_receipts, order_month, expiration_month) { - let private_key = await this.private_key_get(order_month, expiration_month); - 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 diff --git a/pass/routes/api.js b/pass/routes/api.js index b6b9904e86c3941c1a73db6d0d4e7f6c496ff81c..04bc7ec4f6e75bf1b729b87df697b102468d90f8 100644 --- a/pass/routes/api.js +++ b/pass/routes/api.js @@ -1,6 +1,11 @@ var express = require("express"); var router = express.Router(); +const { + body, + validationResult +} = require("express-validator"); + const config = require("config"); const Key = require("../app/Key"); const Order = require("../app/Order"); @@ -182,6 +187,64 @@ 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 => { + let checked_tokens = {}; + for (let i = 0; i < value.length; i++) { + if (value[i] in checked_tokens) { + return Promise.reject("The supplied tokens need to be unique"); + } else { + checked_tokens[value[i]] = true; + } + } + return true; + }) + , 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 blinded_tokens = req.body.blinded_tokens; + let signed_tokens = {}; + + 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(); + } + + 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 + }); + }).catch(reason => { + console.error(reason); + res.status(500).json({ + status: 500, + message: "Couldn't load private key" + }); + }); + + + }); + router.use((req, res) => { res.status(404).json({ code: 404,