Node.js: axios and basic authorization issues with TOTP post request

0

I'm trying to complete a challenge that requires to create a TOTP password and send a post request with it using basic auth. If the request succeeds, I will receive an email with further instructions. I'm coding everything in Node.js.

I created a TOTP algorithm that I tested with the results obtained in the rfc6238 specs (https://www.rfc-editor.org/rfc/rfc6238.txt) and everything works fine, so the TOTP generated should be OK.

For the challenge these are parte of the instructions:

TOTP's Time Step X is 30 seconds. T0 is 0. Use HMAC-SHA-512 for the hash function, instead of the default HMAC-SHA-1. Token shared secret is the userid followed by ASCII string value "CODE000000000000" (not including double quotations).

The TOTP must be 10 digits

THE ERROR: When I send the request I keep getting error 401, unauthorized.

I am worried that I'm missing something else in my code. Maybe in my axios post request? Please take a look and let me know. I left my test code and the table of the rfc so that you too can verify it works.

const crypto = require("crypto");

// TOTP and HOTP use the same algorithm, the only difference
// is that the "count" param in TOTPs is time based
function generateTOTP(algorithm, secret, count, digits) {

  // Convert the string to binary data in the form of a sequence of bytes
  secret = Buffer.from(secret);

  // Writes value to buf at the specified offset with the specified endianness
  let countBuffer = Buffer.alloc(8, 0);
  countBuffer.writeUInt32BE(count, 4);

  // Creates and returns an Hmac object that uses the given algorithm and key
  const hmac_result = crypto.createHmac(algorithm, secret)
    .update(countBuffer)
    .digest("hex");

  // Chose the last byte of the hmac to do the dynamic truncation
  const offset = parseInt(hmac_result.charAt(hmac_result.length - 1), 16);

  // Dynamic truncation
  let totp = parseInt(hmac_result.substr(offset * 2, 2 * 4), 16);
  totp = totp & 0x7fffffff;
  // Get only the digits needed
  totp = totp % (10 ** digits);
  // If there are not enough digits pad with 0
  totp = totp.toString().padStart(digits, "0");

  return totp;
}

// Date is a string. From where should calculate the unix epoch time
// in steps of 30 seconds
function getCount(date = new Date().toString()) {
  const epoch = Math.round(new Date(date).getTime() / 1000.0);
  return Math.floor(epoch / 30);
}

// Copied from: https://www.rfc-editor.org/rfc/rfc6238.txt
// PAGE 15

// I used this results to verify that my algorithm works properly, I also
// tested other dates but for this snippet I will leave only these 3.

// +-------------+--------------+------------------+----------+--------+
// |  Time (sec) |   UTC Time   | Value of T (hex) |   TOTP   |  Mode  |
// +-------------+--------------+------------------+----------+--------+
// |  1111111109 |  2005-03-18  | 00000000023523EC | 07081804 |  SHA1  |
// |             |   01:58:29   |                  |          |        |
// |  1111111109 |  2005-03-18  | 00000000023523EC | 68084774 | SHA256 |
// |             |   01:58:29   |                  |          |        |
// |  1111111109 |  2005-03-18  | 00000000023523EC | 25091201 | SHA512 |
// |             |   01:58:29   |                  |          |        |
// +-------------+--------------+------------------+----------+--------+

// All of these tests succeed, the TOTP matches the one in the table
let time = getCount("2005-03-18 01:58:29 UTC");

let secret = "12345678901234567890";
let totp = generateTOTP("sha1", secret, time, 8);
console.log("sha1  ", totp, "should be 07081804");

// To create a TOTP password with sha256 and sha512 the secret length must
// be 32 for sha256 and 64 for sha512.
// Look up the errata of 6238 for more (https://www.rfc-editor.org/errata/eid5132)

secret = "12345678901234567890";
secret = secret + secret;
secret = secret.substr(0, 32);
totp = generateTOTP("sha256", secret, time, 8)
console.log("sha256", totp, "should be 68084774");

secret = "12345678901234567890";
secret = secret + secret + secret + secret;
secret = secret.substr(0, 64);
totp = generateTOTP("sha512", secret, time, 8)
console.log("sha512", totp, "should be 25091201");

// This is the code for my post request
const axios = require("axios");

// The secret is set up as follows
secret = "name.surname@email.comCODE000000000000";
secret = secret + secret + secret + secret;
secret = secret.substr(0, 64);

// To generate the real TOTP, get the unix epoch time
time = Math.round(Date.now() / 1000);
// Matches the unix epoch clock https://www.epochconverter.com/clock
console.log(time);
// Ge the steps in T = 30
time = Math.floor(time / 30);
totp = generateTOTP("sha512", secret, time, 10)

console.log(totp);

// The contact_email field is also the username used in the auth
const data = {
  "github_url": "https://github.com/user/repo",
  "contact_email": "name.surname@email.com"
}

const username = "name.surname@email.com";
const credentials = Buffer.from(`${username}:${totp}`, "utf8").toString("base64")
console.log(credentials);

// I keep getting 401 unauthorized
axios
  .post("https://api.something/hello", {
    data: data,
    // Not sure if withCredentials should be used, I tried removing it but no difference
    withCredentials: true,
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Accept": "*/*",
      "Content-Type": "application/json"
    }
  })
  .then(res => console.log(res))
  .catch(err => console.log(err.message));
javascript
node.js
cryptography
axios
authorization
asked on Stack Overflow Apr 4, 2020 by devamat • edited Jun 20, 2020 by Community

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0