How to create a LetsEncrypt compatible JWT with Node.js? - node.js

I am trying to POST a JSON Web Token to the Lets Encrypt new registration endpoint using Node.js. How do I create this token? This is some code I've been experimenting with to try to generate a token that Let's Encrypt's webserver will accept:
var jwt = require('jsonwebtoken');
var jws = require('jws');
var crypto = require('crypto');
var pem = require('pem');
var jose = require('node-jose');
var keystore = jose.JWK.createKeyStore();
var key;
var props = {
//kid: 'gBdaS-adsfasdfasdfsa',
alg: 'HS256',
//use: 'enc',
n: "pK7LuT2hxkWnYRl1Tcw9iAy9-_TqvHp2wh6EcHq_wglsNmtpxAe9gNGZevWu6T2O1aEmPYkgy7Q1meKNifenFuWicDcSSenkMM0JApfdveiVqjBA81EL0Y76T8i2JolggGXbiSa_ZRGwG-0FPDSIX3Jy5mQgOn-t-zrhD9yLDn2N7zzFqCBOtxzrwz1HEtN8QWZAFAzOceyyL6C791lGOk9SYYekxyuZkwkzhDEsoqR7fN6hmu6IfIU8hF5kt8M_Gef30wt5dUESvcTNdmQmq_L1QYA8qYO6-T0mC0zIpHpwQnANYOSZBCz1uE-vwS17MlfnUwGkPHJXWThlMZqZmQ",
e: "AQAB"
};
keystore.generate("oct", 256, props).
then(function(result) {
console.log(result);
var obj = {
header: {
alg: "HS256",
jwk: result,
nonce: "kajdfksajdf39393"
},
payload: {
"resource": "new-reg",
"contact": [
"mailto:cert-admin#example.com",
"tel:+12025551212"
]
},
secret: 'has a van',
};
const signature = jws.sign(obj);
console.log(signature);
});
}
This actually does generate a valid JWT:
eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJvY3QiLCJraWQiOiJXeVFRNFNJV2I4SGJIM2ZrQnRLOHdnR1NibU9zaGZNeUNWODZyTWdoOTR3IiwiYWxnIjoiSFMyNTYiLCJuIjoicEs3THVUMmh4a1duWVJsMVRjdzlpQXk5LV9UcXZIcDJ3aDZFY0hxX3dnbHNObXRweEFlOWdOR1pldld1NlQyTzFhRW1QWWtneTdRMW1lS05pZmVuRnVXaWNEY1NTZW5rTU0wSkFwZmR2ZWlWcWpCQTgxRUwwWTc2VDhpMkpvbGdnR1hiaVNhX1pSR3dHLTBGUERTSVgzSnk1bVFnT24tdC16cmhEOXlMRG4yTjd6ekZxQ0JPdHh6cnd6MUhFdE44UVdaQUZBek9jZXl5TDZDNzkxbEdPazlTWVlla3h5dVprd2t6aERFc29xUjdmTjZobXU2SWZJVThoRjVrdDhNX0dlZjMwd3Q1ZFVFU3ZjVE5kbVFtcV9MMVFZQThxWU82LVQwbUMweklwSHB3UW5BTllPU1pCQ3oxdUUtdndTMTdNbGZuVXdHa1BISlhXVGhsTVpxWm1RIiwiZSI6IkFRQUIifSwibm9uY2UiOiJrYWpkZmtzYWpkZjM5MzkzIn0.eyJyZXNvdXJjZSI6Im5ldy1yZWciLCJjb250YWN0IjpbIm1haWx0bzpjZXJ0LWFkbWluQGV4YW1wbGUuY29tIiwidGVsOisxMjAyNTU1MTIxMiJdfQ.RiHTdM_k1eLUJaGx4b59w8-hEQ-J0SpZjPIeGWhh1yg
However, when I try to POST it to the new registration endpoint, I get the following error:
{ "type": "urn:acme:error:malformed", "detail": "Parse error reading JWS", "status": 400 }
The testing code is a collection of code snippets I've put together after Googling this for a few hours. I understand there are LetsEncrypt servers I can run, but don't want to do that. I want to actually generate the requests and callbacks directly in Node.js because I want to run all this from AWS Lambda functions (no servers involved here).
I did find one example of a JWT token that actually seems to work, sort of. I say "sort of" because the response from this example is:
{ "type": "urn:acme:error:badNonce", "detail": "JWS has invalid anti-replay nonce 5H63XwyOHKpAETFpHR8stXSkhkqhlAY1xV7VsCnOrs", "status": 400}
This at least tells me the JWT token is being parsed and the Nonce is being looked at. When I decode this JWT, I see this:
It looks like this guy used RSA 256 to create this JWT. I'm not sure where the values "e" and "n" came from?
How do I recreate the above working sample with Node.JS / Jose?

I think the answer here is to just use the letsencrypt node.js NPM package. No need to develop ACME protocol from scratch, as this library seems to do it.

Related

Generate a JWT in Node using inputs like jwt IO

I have a header, payload, and a public/private key. I can plug these all into JWT.io and it works as expected, but I'm struggling how to use these same variables with a node library like jsonwebtoken or other similar options. They seem to take a secret and sign a payload as far as I can see, which doesn't seem to line up with my inputs. I need to dynamically generate this token request so I must have the function in Node.
Thanks for any tips.
Have a look at the jsonwebtoken NPM package, which offers amongst other methods, a sign method:
var jwt = require('jsonwebtoken');
var privateKey = fs.readFileSync('private.key');
var payload = { foo: 'bar' };
var token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
As #jps has pointed out, you need the private key to sign and the public key to verify.
The header will be automatically generated and will include both properties (alg and typ) you have mentioned in your comment. You can add additional properties by passing them in the options.header parameter.
I'm struggling how to use these same variables with a node library
import * as jose from 'jose';
const privateKey = await jose.importPKCS8(privateKeyPEM); // private key just like on jwt.io
const jwt = await new jose.SignJWT(payload) // payload just like on jwt.io
.setProtectedHeader(header) // header just like on jwt.io
.sign(privateKey);
Of course there's more to be discovered if you need it.

How do I generate the correct TOTP with Node with correct Headers and SHA512 hashed Token?

A recent school project I was assigned has a coding challenge we have to complete. The challenge has multiple parts, and the final part is uploading to a private GitHub repo and submitting a completion request by making a POST request under certain conditions.
I have successfully completed the other parts of the challenge and am stuck on submitting the request. The submission has to follow these rules:
Build your solution request
First, construct a JSON string like below:
{
"github_url": "https://github.com/YOUR_ACCOUNT/GITHUB_REPOSITORY",
"contact_email": "YOUR_EMAIL"
}
Fill in your email address for YOUR_EMAIL, and the private Github repository with your solution in YOUR_ACCOUNT/GITHUB_REPOSITORY.
Then, make an HTTP POST request to the following URL with the JSON string as the body part.
CHALLENGE_URL
Content type
The Content-Type: of the request must be application/json.
Authorization
The URL is protected by HTTP Basic Authentication, which is explained on Chapter 2 of RFC2617, so you have to provide an Authorization: header field in your POST request.
For the userid of HTTP Basic Authentication, use the same email address you put in the JSON string.
For the password , provide a 10-digit time-based one time password conforming to RFC6238
TOTP.
Authorization password
For generating the TOTP password, you will need to use the following setup:
You have to generate a correct TOTP password according to RFC6238
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 "APICHALLENGE" (not including double quotations).
Shared secret examples
For example, if the userid is "email#example.com", the token shared secret is "email#example.comAPICHALLENGE" (without quotes).
If your POST request succeeds, the server returns HTTP status code 200 .
I have tried to follow this outline very carefully, and testing my work in different ways. However, it seems I can't get it right. We are supposed to make the request from a Node server backend. This is what I have done so far. I created a new npm project with npm init and installed the dependencies you will see in the code below:
const axios = require('axios');
const base64 = require('base-64');
const utf8 = require('utf8');
const { totp } = require('otplib');
const reqJSON =
{
github_url: GITHUB_URL,
contact_email: MY_EMAIL
}
const stringData = JSON.stringify(reqJSON);
const URL = CHALLENGE_URL;
const sharedSecret = reqJSON.contact_email + "APICHALLENGE";
totp.options = { digits: 10, algorithm: "sha512" }
const myTotp = totp.generate(sharedSecret);
const isValid = totp.check(myTotp, sharedSecret);
console.log("Token Info:", {myTotp, isValid});
const authStringUTF = reqJSON.contact_email + ":" + myTotp;
const bytes = utf8.encode(authStringUTF);
const encoded = base64.encode(bytes);
const createReq = async () =>
{
try
{
// set the headers
const config = {
headers: {
'Content-Type': 'application/json',
"Authorization": "Basic " + encoded
}
};
console.log("Making req", {URL, reqJSON, config});
const res = await axios.post(URL, stringData, config);
console.log(res.data);
}
catch (err)
{
console.error(err.response.data);
}
};
createReq();
As far as I understand, I'm not sure where I'm making a mistake. I have tried to be very careful in my understanding of the requirements. I have briefly looked into all of the documents the challenge outlines, and gathered the necessary requirements needed to correctly generate a TOTP under the given conditions.
I have found the npm package otplib can satisfy these requirements with the options I have passed in.
However, my solution is incorrect. When I try to submit my solution, I get the error message, "Invalid token, wrong code". Can someone please help me see what I'm doing wrong?
I really don't want all my hard work to be for nothing, as this was a lengthy project.
Thank you so much in advance for your time and help on this. I am very grateful.
The Readme of the package otplib states:
// TOTP defaults
{
// ...includes all HOTP defaults
createHmacKey: totpCreateHmacKey,
epoch: Date.now(),
step: 30,
window: 0,
}
So the default value for epoch (T0) is Date.now() which is the RFC standard. The task description defines that T0 is 0.
You need to change the default value for epoch to 0:
totp.options = { digits: 10, algorithm: "sha512", epoch: 0 }

Implementing Poloniex crypto using Node.js

I am trying to implement the Poloniex account notification websocket API in Node.js
https://docs.poloniex.com/#websocket-api
The command is:
{ "command": "subscribe", "channel": "1000", "key": "", "payload": "nonce=", "sign": "").hexdigest()>" }
The first parts are easy however I am stuck on the sign parameter. This involves
1) encrypting my secret using hmca_sha512
2) updating it with the nonce?
3) converting it to a hex digest?
So far my code reads as follows however the last line has me stuck:
ws2.on('open', function open() {
var nonce = Date.now();
ws.send(JSON.stringify({ 'command': 'subscribe',
'channel': 1000,
'key': apiKey,
'payload': `nonce=${nonce}`,
**'sign': "<hmac_sha512(secret).update("nonce=<epoch ms>").hexdigest()>"**
}));
});
I think I can achieve the hmac_sha512 by using:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha512', apiSecret);
I think I can convert a value to a hex digest using:
.toString('hex');
I am not sure of how to implement the .update("nonce=") part. I can partially update this to be as below however am lost on the "update()" function.
.update(`nonce=${nonce}`)

Verify JWT from Google Chat POST request

I have a bot in NodeJS connected to Google Chat using HTTPs endpoints. I am using express to receive requests. I need to verify that all requests come from Google, and want to do this using the Bearer Token that Google Sends with requests.
My problem is that I am struggling to find a way to verify the tokens.
I have captured the token and tried a GET reuqes to https://oauth2.googleapis.com/tokeninfo?id_token=ey... (where ey... is the token start).
Which returns:
"error": "invalid_token",
"error_description": "Invalid Value"
}
I have tried what Google recommends:
var token = req.headers.authorization.split(/[ ]+/);
client.verifyIdToken({
idToken: token[1],
audience: JSON.parse(process.env.valid_client_ids)
}).then((ticket) => {
gchatHandler.handleGChat(req.body, res);
}).catch(console.error);
And get the following error:
Error: No pem found for envelope: {"alg":"RS256","kid":"d...1","typ":"JWT"}
Any idea where I should head from here?
Edit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com found this, investigating how to use it. The kid matches the one I get.
Worked it out, eventually.
You need to hit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com to get a JSON file containing the keys linked to their KIDs.
Then when a request arrives, use jsonwebtoken (NPM) to decode the token and extract the KID from the header.
Use the KID to find the matching public key in the response from the website above, then use the verify function to make sure the token matches the public key.
You also need to pass the audience and issuer options to verify, to validate that it is your particular service account hitting the bot.
The solution above maybe the correct for Google Chat, but in my experience Google services (e.g. Google Tasks) use OIDC tokens, which can be validated with verifyIdToken function.
Adding my solution here, since your question/answer was the closest thing I was able to find to my problem
So, In case if you need to sign a request from your own code
on client, send requests with OIDC token
import {URL} from 'url';
import {GoogleAuth} from 'google-auth-library';
// will use default auth or GOOGLE_APPLICATION_CREDENTIALS path to SA file
// you must validate email of this identity on the server!
const auth = new GoogleAuth({});
export const request = async ({url, ...options}) => {
const targetAudience = new URL(url as string).origin;
const client = await auth.getIdTokenClient(targetAudience);
return await client.request({...options, url});
};
await request({ url: 'https://my-domain.com/endpoint1', method: 'POST', data: {} })
on the server, validate OIDC (Id token)
const auth = new OAuth2Client();
const audience = 'https://my-domain.com';
// to validate
const token = req.headers.authorization.split(/[ ]+/)[1];
const ticket = await auth.verifyIdToken({idToken: token, audience });
if (ticket.getPayload().email !== SA_EMAIL) {
throw new Error('request was signed with different SA');
}
// all good
Read more about Google OpenID Connect Tokens

Accessing Office365 API with Client Credentials Flow

I am trying to develop a node application that would be able to access my Outlook.com mails.
I am trying to do it in a way it doens't require me to enter my credentials, the application will know them (user name and password). I am not too worried about storing them in the config of my application.
I am using simple-oauth2 but I keep getting an error. The following is the code that is trying to retrieve the Oauth token:
const credentials = {
client: {
id: this.appId,
secret: this.appSecret,
},
auth: {
tokenHost: "https://login.microsoftonline.com",
authorizePath: "common/oauth2/v2.0/authorize",
tokenPath: "common/oauth2/v2.0/token",
},
};
const oathClient = oauth2.create(credentials);
const tokenConfig = {
username: "zzz#outlook.com",
password: "xxxxx",
scope: "Mail.Read",
};
const result = await oathClient.ownerPassword.getToken(tokenConfig);
const token = oathClient.accessToken.create(result);
However when calling get token I get the following response:
"error": "invalid_grant",
"error_description": "AADSTS70000: The grant is not supported by this API version\r\nTrace ID: 91935472-5d7b-4210-9a56-341fbda12a00\r\nCorrelation ID: 6b075f4e-b649-493e-a87b-c74f0e427b47\r\nTimestamp: 2017-08-19 14:00:33Z",
"error_codes": [ 70000],
I have aded the application in apps.dev.microsoft.com
Added a platform (Web API) for it.
Added the "Mail.Read" permission on Microsoft Grah
And I am using the apikey and secret I generated there.
Googling looks like all the examples I find are to connect is using a client certificate. Is it possible to use the API using the API credentials?
If the only way is using certificates, is there a way I can use simple-oauth2 for that?
Ok, looks like I was using the wrong method.
Trying to access using the ClientCredentials module on simple-ouath2 and its workign now:
const tokenConfig = {
scope: "https://graph.microsoft.com/.default",
};
const result = await oathClient.clientCredentials.getToken(tokenConfig);

Resources