Parse.com Cloud Code RSA Verification - Android In App Purchase Verification - javascript

I'm trying to verify an RSA signature on Parse.com Cloud Code. Basically I am trying to do the receipt verification for an Android In App Purchase on the server.
Parse.com crypto module does not suppor the verify method. So I found a library online that I imported.
var KJUR = require("cloud/jsrsasign-4.7.0/npm/lib/jsrsasign.js");
var verifier = new KJUR.crypto.Signature({alg: "SHA1withRSA", prov: "cryptojs/jsrsa"});
verifier.initVerifyByCertificatePEM(publicKey);
verifier.updateString(purchaseData);
//verifier.updateHex(hexValue);
var result = verifier.verify(signature);
I am doing something wrong, but can't really tell what. I might be putting the signature, publicKey and purchaseData in the wrong places.
The purchaseData looks like this: (per Android specs, I altered the data)
var purchaseData = {
orderID: "12999763169854705758.1300523466622834",
packageName: "com.blabla.bla",
productID: e.purchase.SKU,
purchaseTime: new moment(time).valueOf(),
purchaseState: 0,
developerPayload: "74571d75-98b8-4327-942d-5379309c9033",
purchaseToken: "klsDmifojfknmbojimkkkdkm.AO-J1OyXvZ3RH1aPiPD2MIdOUu00FrCnuTCjl1-K3ZD4Puu0zXDPTOAKH3Dc1hq1DZwiNI-AgXwW18gDV3eU9kXCR1IwhADLvVeOSkyu5kzdUBoVNdA42Zc"
};
I get the following error:
Result: TypeError: Cannot call method 'bitLength' of undefined
at RSAKey._rsasign_verifyWithMessageHash [as verifyWithMessageHash] (jsrsasign-4.7.0/npm/lib/jsrsasign.js:251:3675)
at verify (jsrsasign-4.7.0/npm/lib/jsrsasign.js:230:10483)
at main.js:43:24
If you have any prior experience doing this, I would appreciate your help. Thanks

Here's how to do it:
var KJUR = require("cloud/jsrsasign.js");
var publicKey =
"-----BEGIN PUBLIC KEY-----\n" +
// your public key from google play
"-----END PUBLIC KEY-----\n";
var verifier = new KJUR.crypto.Signature({alg: "SHA1withRSA"});
verifier.init(publicKey);
verifier.updateString(signedData); // signedData from IAB response
var result = verifier.verify(KJUR.b64utohex(signature));
Be sure to convert the signature from base64 to hex.

I'm guessing things have changed a little with updates to jsrasign - my solution looks like this:
cloud/lib/crypto.js:
// jsrasign expects to be running in a browser and expects these to be in the global namespace
var navigator = {},
window = {};
// Include contents of jsrsasign-latest-all-min.js from https://kjur.github.io/jsrsasign/
// ------------- Snip -------------
// Expose a Validate method
exports.Validate = function(sText, sPublicKey, sSignature) {
var cVerifier = new KJUR.crypto.Signature({ alg: 'SHA1withRSA' });
cVerifier.init("-----BEGIN PUBLIC KEY-----\n" + sPublicKey + "-----END PUBLIC KEY-----\n");
cVerifier.updateString(sText);
return cVerifier.verify(b64utohex(sSignature));
};
cloud/MakePurchase.js:
var Crypto = require('cloud/lib/crypto'),
// You should have got this from https://play.google.com/apps/publish
sPublicKey = 'SomethingSlightlySecretUsing64CharactersWithNoSpacesOrNewLines';
// Assume you have done something to get back a Google receipt object containing:
// json: A stringified JSON object with the purchase details
// signature: A base64 string
// payload: Data you might have set when you made the purchase
if (Crypto.Validate(cReceipt.json, sPublicKey, cReceipt.signature)) {
// Purchase confirmed
}

Related

How do i get RSACryptoServiceProvider to verify a message using public key and signature

I generated a private and public key in javascript like this.
import crypto from "crypto";
/*export const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
});*/
const pair = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
export const privateKey = pair.privateKey.export({
type: "pkcs1",
format: "pem",
});
export const publicKey = pair.publicKey.export({
type: "pkcs1",
format: "pem",
});
Then i use the private key to create a signature for a jsonfile like this, and the public key to verify it before i return the signature.
//Lav signatur
const signate = crypto.createSign("SHA384");
signate.update(Buffer.from(licenseRelationship, "utf-8"));
const signature = signate.sign(privateKey, "hex");
const verifier = crypto.createVerify("SHA384");
// verificer signature, besked
verifier.update(Buffer.from(licenseRelationship, "utf-8"));
const verificationResult = verifier.verify(publicKey, signature, "hex");
This works perfectly, and then i return the json and the signature as a http response.
I recieve it in c# code and store the two components so im can use them later on request.
Upon request i fetch the two components and want to use the signature to check if the json has been tampered with.
I also has the public key in this code.
I do it like this.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature,
HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
if (result)
{
log.write("Message verified ", null);
} else
{
log.write("Message not Verified ", null);
}
All debug code and exception handling removed.
I'm a crypto virgin, and am trying to understand this. But i must have misunderstood something serious.
I have the public key as a string (not base64 encoded)
Ive checked the json, and it is the exact same bytes when signed in Javascript as when being verified in c#
The public key is not used in this process. That has to be wrong i think ?
How do i get the public key into the RWACryptoServiceProvider ?
Im sure im using RWACryptoServiceProvider wrong.
EDIT....:
Ive tried this instead, but still to no avail.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
byte[] asBytes = Encoding.ASCII.GetBytes(DataStorage.Instance.PUBLIC_KEY);
char[] publicKeyAsArray = Encoding.ASCII.GetChars(asBytes);
ReadOnlySpan<char> publicKeyChars = publicKeyAsArray;
RSA rsa = RSA.Create();
try
{
rsa.ImportFromPem(publicKeyChars);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
} catch (CryptographicException cex)
{
log.write("Something went wrong with the crypto verification process", cex);
}
.
.
.
Thankyou for your time.

Error in JavaScript Elliptic curve cryptography to import the private key: Bad private key?

I want to encrypt and decrypt the message based on ECIES using this library JavaScript Elliptic curve cryptography library.
I want to import my private key and get the public key from it because I do not have a new private key generated each time I run the code.
The code:
var eccrypto = require("eccrypto");
var privateKeyB = 'efae5b8156d785913e244c39ca5b9bee1a46875d123d2f49bbeb0a91474118cf';
var publicKeyB = eccrypto.getPublic(privateKeyB);
console.log(publicKeyB.toString('hex'))
// Encrypting the message for B.
eccrypto.encrypt(publicKeyB, Buffer.from("msg to b")).then(function(encrypted) {
// B decrypting the message.
eccrypto.decrypt(privateKeyB, encrypted).then(function(plaintext) {
console.log("Message to part B:", plaintext.toString());
});
});
However, the code is not working and shows this error:
throw new Error(message || "Assertion failed");
^
Error: Bad private key
eccrypto.getPublic() expects a Buffer as argument, not a string. Try this instead:
var eccrypto = require("eccrypto");
var privateKeyB = Buffer.from('efae5b8156d785913e244c39ca5b9bee1a46875d123d2f49bbeb0a91474118cf', 'hex');
var publicKeyB = eccrypto.getPublic(privateKeyB);
console.log(publicKeyB.toString('hex'))
// Encrypting the message for B.
eccrypto.encrypt(publicKeyB, Buffer.from("msg to b")).then(function(encrypted) {
// B decrypting the message.
eccrypto.decrypt(privateKeyB, encrypted).then(function(plaintext) {
console.log("Message to part B:", plaintext.toString());
});
});

.NET Core 5.0 to Javascript DFH Key exchange not working

We are trying to get a browser based app using JS to exchange keys with a .Net Core 5.0 server using Eliptical Curve Diffie Hellman.
Our app requires a shared secret on both ends for some specific processing we do (not encryption, but our own process), and we want that secret to be derived
rather than transmitted for security purposes.
We have searched around for a suitable solution and patching various ones together, we have come up with this, but it fails with an exception on the C# side.
Basically, our pseudo code is as follows (we have only gotten to Step 3 so far):
Client (Bob) generates a ECDH / P-256 key pair in the client using the window.crypto.subtle library.
Bob exports the public key from this pair and issues a GET to the server (Alice) to get her public key (passing the bobPublicKeyB64 as a query arg).
Alice takes the incoming request and calls a C# method to use Bob's public key to create a shared secret.
Alice stores this shared secret in memcache and returns her public key to Bob.
Bob then uses Alice's public key to get his own shared secret and stores it in "session storage" on the browser.
The code that sends Bob's public key to Alice is not shown here, it is a standard XMLHttpRequest. It does work, however, and the server is invoked as witnessed by the failures in the C# code. We have verified that the bobPublicKeyB64 is exactly the same between Bob when he sends it and Alice when she gets it.
Once this is working, we will harden the storage methodology on both ends, but first we need to get the exchange working.
The comments in the Alice (C#) block of code show where it fails.
the getDerivedKey method (commented out for now) came from this post - ECDH nodejs and C# key exchange
and it fails with a different exception (I am sure that both failures are due to some mismatch between the JS library and the .Net implementation, but we cannot get a handle on it).
Any and all help is greatly appreciated - the basic question is "How can we get Bob and Alice on speaking terms, when Bob is JS and Alice is C#?"
Following is the JS code on Bob:
async function setUpDFHKeys() {
let bobPublicKeyB64;
let bobPrivateKeyB64;
try {
const bobKey = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
["deriveKey"]
);
const publicKeyData = await window.crypto.subtle.exportKey("raw", bobKey.publicKey);
const publicKeyBytes = new Uint8Array(publicKeyData);
const publicKeyB64 = btoa(publicKeyBytes);
bobPublicKeyB64 = publicKeyB64;
const privateKeyData = await window.crypto.subtle.exportKey("pkcs8", bobKey.privateKey);
const privateKeyBytes = new Uint8Array(privateKeyData);
const privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
bobPrivateKeyB64 = privateKeyB64;
}
catch (error) {
console.log("Could not setup DFH Keys " + error);
}};
Following is the C# code on Alice:
private string WorkWithJSPublicKey(string bobPublicKeyB64, out string alicePublicKey)
{
try
{
//
// Alice is this server.
// Bob is a browser that uses the window.crypto.subtle.generateKey with 'ECDH' and 'P-256' as parameters
// and window.crypto.subtle.exportKey of the key.publicKey in "raw" format (this is then converted to a B64 string using btoa)
//
using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
//
// Get the public key info from Bob and convert to B64 string to return to Alice
//
Span<byte> exported = new byte[alice.KeySize];
int len = 0;
alice.TryExportSubjectPublicKeyInfo(exported, out len);
alicePublicKey = Convert.ToBase64String(exported.Slice(0, len));
//
// Get Alice's private key to use to generate a shared secret
//
byte[] alicePrivateKey = alice.ExportECPrivateKey();
//
// Import Bob's public key after converting it to bytes
//
var bobPubKeyBytes = Convert.FromBase64String(bobPublicKeyB64);
//
// TRY THIS... (Bombs with "The specified curve 'nistP256' or its parameters are not valid for this platform").
//
// getDerivedKey(bobPubKeyBytes, alice);
//
// This throws exception ("The provided data is tagged with 'Universal' class value '20', but it should have been 'Universal' class value '16'.")
//
alice.ImportSubjectPublicKeyInfo(bobPubKeyBytes, out len);
//
// Once Alice knows about Bob, create a shared secret and return it.
//
byte[] sharedSecret = alice.DeriveKeyMaterial(alice.PublicKey);
return Convert.ToBase64String(sharedSecret);
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
alicePublicKey = string.Empty;
return string.Empty;
}
}
And the code for the getDerivedKey (borrowed from ECDH nodejs and C# key exchange ) is shown below:
static byte[] getDerivedKey(byte[] key1, ECDiffieHellman alice)
{
byte[] keyX = new byte[key1.Length / 2];
byte[] keyY = new byte[keyX.Length];
Buffer.BlockCopy(key1, 1, keyX, 0, keyX.Length);
Buffer.BlockCopy(key1, 1 + keyX.Length, keyY, 0, keyY.Length);
ECParameters parameters = new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = {
X = keyX,
Y = keyY,
},
};
byte[] derivedKey;
using (ECDiffieHellman bob = ECDiffieHellman.Create(parameters))
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
{
return derivedKey = alice.DeriveKeyFromHash(bobPublic, HashAlgorithmName.SHA256);
}
}
The solution is simpler if the public key on the Web Crypto side is exported in X.509/SPKI format rather than as a raw key, since .NET 5 has a dedicated import method, ImportSubjectPublicKeyInfo(), for this format. Furthermore, this is consistent with the C# code where the public key is also exported in X.509/SPKI format. In the following example, the Web Crypto code exports the public key in X.509/SPKI format.
Step 1 - Web Crypto side (Bob): Generate EC key pair
The following Web Crypto code creates an ECDH key pair and exports the public key in X.509/SPKI format (and the private key in PKCS8 format). Note that exporting in PKCS8 format does not work under Firefox due to a bug, see also here:
setUpDFHKeys().then(() => {});
async function setUpDFHKeys() {
var bobKey = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
["deriveKey"]
);
var publicKeyData = await window.crypto.subtle.exportKey("spki", bobKey.publicKey);
var publicKeyBytes = new Uint8Array(publicKeyData);
var publicKeyB64 = btoa(String.fromCharCode.apply(null, publicKeyBytes));
console.log("Bob's public: \n" + publicKeyB64.replace(/(.{56})/g,'$1\n'));
var privateKeyData = await window.crypto.subtle.exportKey("pkcs8", bobKey.privateKey);
var privateKeyBytes = new Uint8Array(privateKeyData);
var privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
console.log("Bob's private:\n" + privateKeyB64.replace(/(.{56})/g,'$1\n'));
};
A possible output is the following key pair, which is used as the key pair of the Web Crypto side in the further course:
Bob's public: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2X9cW7P2g4Db3BEgfjs8lwpgukPY4Qg3mcLwVJW+WwA7lbiz+N1MIL3y+JumBF1qIdyx24r5+Sr4c4iYsTWh2w==
Bob's private: MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg72fE/+7WX5aKAMiy8kTkCTVeGR8oOlKuoQ8iXTQWmxGhRANCAATZf1xbs/aDgNvcESB+OzyXCmC6Q9jhCDeZwvBUlb5bADuVuLP43UwgvfL4m6YEXWoh3LHbivn5KvhziJixNaHb
Step 2 - C# side (Alice): Import public key from Web Crypto side and generate shared secret
The following C# code generates an ECDH key pair, exports the public key in X.509/SPKI format (and the private key in PKCS8 format) and determines the shared secret. To obtain the shared secret, the Web Crypto side's public key in X.509/SPKI format is imported with ImportSubjectPublicKeyInfo().
string alicePublicKey;
string bobPublicKeyB64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2X9cW7P2g4Db3BEgfjs8lwpgukPY4Qg3mcLwVJW+WwA7lbiz+N1MIL3y+JumBF1qIdyx24r5+Sr4c4iYsTWh2w==";
string sharedSecret = WorkWithJSPublicKey(bobPublicKeyB64, out alicePublicKey);
Console.WriteLine("Alice's shared secret: " + sharedSecret);
with
private static string WorkWithJSPublicKey(string bobPublicKeyB64, out string alicePublicKey)
{
alicePublicKey = null;
using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
alicePublicKey = Convert.ToBase64String(alice.ExportSubjectPublicKeyInfo());
Console.WriteLine("Alice's public: " + alicePublicKey);
Console.WriteLine("Alice's private: " + Convert.ToBase64String(alice.ExportPkcs8PrivateKey()));
ECDiffieHellman bob = ECDiffieHellman.Create();
bob.ImportSubjectPublicKeyInfo(Convert.FromBase64String(bobPublicKeyB64), out _);
byte[] sharedSecret = alice.DeriveKeyMaterial(bob.PublicKey);
return Convert.ToBase64String(sharedSecret);
}
}
A possible output is the following key pair and shared secret. The key pair is used as the key pair of the C# side in the further course:
Alice's public: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJVUW57L2QeswZhnIp5gjMSiHhqyOVTsPUq2QwHv+R4jQetMQ8JDT+3VQyP/dPpskUhzDd3lKxdRBaiZrWby+VQ==
Alice's private: MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbho81UNFdNwULs7IoWk1wSy2PP9soSlt4/bveAtoPBOhRANCAAQlVRbnsvZB6zBmGcinmCMxKIeGrI5VOw9SrZDAe/5HiNB60xDwkNP7dVDI/90+myRSHMN3eUrF1EFqJmtZvL5V
Alice's shared secret: hayYCAA23oC98d1SxhFpfiYgY5DVElmEno4851HtgKM=
Step 3 - Web Crypto side (Bob): Import public key from C# side and generate shared secret
The following Web Crypto code creates the shared secret. For this the public key of the C# side must be imported. Note that (analogous to the export) the import in PKCS8 format does not work under Firefox due to a bug:
getSharedSecret().then(() => {});
async function getSharedSecret() {
var bobPrivateKeyB64 = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg72fE/+7WX5aKAMiy8kTkCTVeGR8oOlKuoQ8iXTQWmxGhRANCAATZf1xbs/aDgNvcESB+OzyXCmC6Q9jhCDeZwvBUlb5bADuVuLP43UwgvfL4m6YEXWoh3LHbivn5KvhziJixNaHb';
var alicePublicKeyB64 = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJVUW57L2QeswZhnIp5gjMSiHhqyOVTsPUq2QwHv+R4jQetMQ8JDT+3VQyP/dPpskUhzDd3lKxdRBaiZrWby+VQ==';
var privateKey = await window.crypto.subtle.importKey(
"pkcs8",
new Uint8Array(_base64ToArrayBuffer(bobPrivateKeyB64)),
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
);
var publicKey = await window.crypto.subtle.importKey(
"spki",
new Uint8Array(_base64ToArrayBuffer(alicePublicKeyB64)),
{ name: "ECDH", namedCurve: "P-256"},
true,
[]
);
var sharedSecret = await window.crypto.subtle.deriveBits(
{ name: "ECDH", namedCurve: "P-256", public: publicKey },
privateKey,
256
);
var sharedSecretHash = await crypto.subtle.digest('SHA-256', sharedSecret);
var sharedSecretHashB64 = btoa(String.fromCharCode.apply(null, new Uint8Array(sharedSecretHash)));
console.log("Bob's shared secret: " + sharedSecretHashB64.replace(/(.{64})/g,'$1\n'));
};
// from https://stackoverflow.com/a/21797381/9014097
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
On the Web Crypto side, this results in the shared secret:
Bob's shared secret: hayYCAA23oC98d1SxhFpfiYgY5DVElmEno4851HtgKM=
in accordance with the C# side.
Note that DeriveKeyMaterial() on the C# side does not return the actual shared secret S, but the SHA-256 hash H(S) of the shared secret. Since hashes are not reversible, the actual shared secret cannot be determined. Therefore, the only option is to create H(S) on the Web Crypto side by explicit hashing with SHA-256, see also here.

Postman: custom signing request with SHA256 and RSA

I wrote an interface to make requests to the internal audible api with python. Every API request needs to be signed with RSA SHA256.
Now I want to test the endpoints of the API with Postman and make use of the pre request script function. But I'm not firm with javascript. Maybe someone can help me in translate the following python function to a Postman script:
def sign_request(
request: httpx.Request, adp_token: str, private_key: str
) -> httpx.Request:
"""
Helper function who creates a signed requests for authentication.
:param request: The request to be signed
:param adp_token: the token is obtained after register as device
:param private_key: the rsa key obtained after register as device
:returns: The signed request
"""
method = request.method
path = request.url.path
query = request.url.query
body = request.content.decode("utf-8")
date = datetime.utcnow().isoformat("T") + "Z"
if query:
path += f"?{query}"
data = f"{method}\n{path}\n{date}\n{body}\n{adp_token}"
key = rsa.PrivateKey.load_pkcs1(private_key.encode())
cipher = rsa.pkcs1.sign(data.encode(), key, "SHA-256")
signed_encoded = base64.b64encode(cipher)
signed_header = {
"x-adp-token": adp_token,
"x-adp-alg": "SHA256withRSA:1.0",
"x-adp-signature": f"{signed_encoded.decode()}:{date}"
}
request.headers.update(signed_header)
return request
I found out how to get the request method and the body. I can get the path and query with pm.request.url.getPathWithQuery(). To add the headers to the request I use pm.request.headers.add.
But I doesn't know how to get the datetime in isoformat, join strings and sign the data.
I'm get it running with the pm lib. Thank you for your help.
The only issue is getting the private cert, who contains newlines, from env var gives an error with these code sig.init(privateKey);. I had to write the private cert string directly in the pre request script.
Here are my script.
eval( pm.globals.get('pmlib_code') );
var CryptoJS = require("crypto-js");
var moment = require("moment");
const adpToken = pm.environment.get("adp-token")
// private-key loaded from env var doesn't work because of newlines in it; bugfix
const privateKey = pm.environment.get("private-key")
// use private-key in pre request script directly make use of newline correctly
const privateKey2 = "-----BEGIN RSA PRIVATE KEY-----\nMIIE...==\n-----END RSA PRIVATE KEY-----\n"
signRequest(pm.request, adpToken, privateKey);
function signRequest(request, adpToken, privateKey2) {
const method = request.method;
const path = request.url.getPathWithQuery();
const body = request.body || "";
const date = moment.utc().format();
const data = `${method}\n${path}\n${date}\n${body}\n${adpToken}`;
var sig = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"});
sig.init(privateKey);
var hash = sig.signString(data);
const signedEncoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hash));
pm.request.headers.add({
key: 'x-adp-token',
value: adpToken
});
pm.request.headers.add({
key: 'x-adp-alg',
value: 'SHA256withRSA:1.0'
});
pm.request.headers.add({
key: 'x-adp-signature',
value: `${signedEncoded}:${date}`
});
}
UPDATE:
Now reading the device cert from env var works. I had to replace const privateKey = pm.environment.get("private-key") with const privateKey = pm.environment.get("private-key").replace(/\\n/g, "\n")
The problem to make this in Postman is that you can use only those packages available in sandbox. So for this, you have crypto-js as the unique package helper for crypto operations.
var CryptoJS = require("crypto-js");
var moment = require("moment");
signRequest(pm.request, "yourAdpToken", "yourPrivateKey")
function signRequest(request, adpToken, privateKey) {
const method = request.method;
const path = request.url.getPathWithQuery();
const body = request.body.raw;
const date = moment.utc().format();
const data = `${method}\n${path}\n${date}\n${body}\n${adpToken}`
const hash = CryptoJS.HmacSHA256(data, privateKey);
const signedEncoded = CryptoJS.enc.Base64.stringify(hash);
pm.request.headers.add({
key: 'x-adp-token',
value: adpToken
});
pm.request.headers.add({
key: 'x-adp-alg',
value: 'SHA256withRSA:1.0'
});
pm.request.headers.add({
key: 'x-adp-signature',
value: `${CryptoJS.enc.Base64.parse(signedEncoded)}:${date}`
});
}
Adding the above to the Pre-request Script will add your wanted headers to the request like this:
You may need to change the encode parts, check the encode options

How to sign a JWT with a private key (pem) in CryptoJS?

I am trying to create a signed JWT in postman with the following code
function base64url(source) {
// Encode in classical base64
encodedSource = CryptoJS.enc.Base64.stringify(source);
// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');
// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');
return encodedSource;
}
function addIAT(request) {
var iat = Math.floor(Date.now() / 1000) + 257;
data.iat = iat;
return data;
}
var header = {
"typ": "JWT",
"alg": "HS256"
};
var data = {
"fname": "name",
"lname": "name",
"email": "email#domain.com",
"password": "abc123$"
};
data = addIAT(data);
var secret = 'myjwtsecret';
// encode header
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);
// encode data
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);
// build token
var token = encodedHeader + "." + encodedData;
// sign token
var signature = CryptoJS.HmacSHA256(token, secret);
signature = base64url(signature);
var signedToken = token + "." + signature;
postman.setEnvironmentVariable("payload", signedToken);
Code taken from https://gist.github.com/corbanb/db03150abbe899285d6a86cc480f674d .
I've been trying to input the PEM as the secret but does not work. Also can't find any HmacSHA256 overload that takes a PEM.
How can that be done?
The mention of postman changed this. I have a solution for you, but it's not exactly a clean way by any mean.
You'll need to create a request that you will need to execute whenever you open postman. Go as follows:
The purpose of this request is to side-load jsrsasign-js and storing it in a global Postman variable.
Once this is done, you can then use this content elsewhere. For every request you need a RSA256 JWT signature, the following pre-request script will update a variable (here, token) with the token:
var navigator = {};
var window = {};
eval(pm.globals.get("jsrsasign-js"));
function addIAT(request) {
var iat = Math.floor(Date.now() / 1000) + 257;
data.iat = iat;
return data;
}
var header = {"alg" : "RS256","typ" : "JWT"};
var data = {
"fname": "name",
"lname": "name",
"email": "email#domain.com",
"password": "abc123$"
};
data = addIAT(data);
var privateKey = "-----BEGIN RSA PRIVATE KEY----- \
MIIBOQIBAAJAcrqH0L91/j8sglOeroGyuKr1ABvTkZj0ATLBcvsA91/C7fipAsOn\
RqRPZr4Ja+MCx0Qvdc6JKXa5tSb51bNwxwIDAQABAkBPzI5LE+DuRuKeg6sLlgrJ\
h5+Bw9kUnF6btsH3R78UUANOk0gGlu9yUkYKUkT0SC9c6HDEKpSqILAUsXdx6SOB\
AiEA1FbR++FJ56CEw1BiP7l1drM9Mr1UVvUp8W71IsoZb1MCIQCKUafDLg+vPj1s\
HiEdrPZ3pvzvteXLSuniH15AKHEuPQIhAIsgB519UysMpXBDbtxJ64jGj8Z6/pOr\
NrwV80/EEz45AiBlgTLZ2w2LjuNIWnv26R0eBZ+M0jHGlD06wcZK0uLsCQIgT1kC\
uNcDTERjwEbFKJpXC8zTLSPcaEOlbiriIKMnpNw=\
-----END RSA PRIVATE KEY-----";
var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(data);
var sJWT = KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, privateKey);
pm.variables.set('token', sJWT);
In order:
I define mock window and navigator objects as jsrsasign-js needs them.
I then eval() the content of what we fetched earlier in order to rehydrate everything
The rest of your code is simple usage of jsrsasign-js. Your token info is there, and I've defined a private key there. You can change this or use an environment variable; it's just there for demo purposes. I then simply use the rehydrated library to sign it, and set the variable to the value of the signed JWT.
A PEM, as you refer to it, is a container format specifying a combination of public and/or private key. You're using it to sign using HMAC-SHA256, which operates on a shared secret. This obviously isn't going to work (unless you take the poor man's approach and use your public key as the shared secret).
Fortunately enough, there are other signature methods defined in the RFCs. For instance, there is a way to sign using RSA, and a very convenient way of defining a public key as a JSON web key (JWK). We're going to be leveraging both.
I've generated a key pair for testing, they're named out and out.pub. Generation tool is genrsa (and as such, they're an RSA keypair).
In order to sign, we're going to have to change a few things:
We're changing algorithms from HS256 to RS256, as explained above
We're going to need a new library to do the signing itself, as crypto-js does not support asymmetric key crypto. We'll fall back to the native crypto module, though there are pure-JS alternatives
The code:
var CryptoJS = require("crypto-js");
var keyFileContent = require("fs").readFileSync("./out");
var pubkey = require("fs").readFileSync("./out.pub");
var base64url = require("base64url");
var nJwt = require("njwt");
function addIAT(request) {
var iat = Math.floor(Date.now() / 1000) + 257;
data.iat = iat;
return data;
}
var header = {
"typ": "JWT",
"alg": "RS256"
};
var data = {
"fname": "name",
"lname": "name",
"email": "email#domain.com",
"password": "abc123$"
};
data = addIAT(data);
// encode header
var stringifiedHeader = JSON.stringify(header);
var encodedHeader = base64url(stringifiedHeader);
// encode data
var stringifiedData = JSON.stringify(data);
var encodedData = base64url(stringifiedData);
// build token
var token = encodedHeader + "." + encodedData;
// sign token
var signatureAlg = require("crypto").createSign("sha256");
signatureAlg.update(token);
var signature = signatureAlg.sign(keyFileContent);
signature = base64url(signature);
var signedToken = token + "." + signature;
console.log(signedToken);
// Verify
var verifier = new nJwt.Verifier();
verifier.setSigningAlgorithm('RS256');
verifier.setSigningKey(pubkey);
verifier.verify(signedToken, function() {
console.log(arguments);
});
And that's it! It's quite literally that simple, although I would not recommend rewriting the sign() function from crypto from scratch. Leave it to a library that has had thorough inspection by the community, and crypto is pretty serious business.

Categories

Resources