I'm updating a NetSuite integration for DocuSign to convert all scripting to SuiteScript 2.0 and update the authentication to utilize the JWT grant method, seeing as the current method looks like it will be deprecated for new applications in a couple weeks, and completely some time next year. I do have a couple questions about it all, though, that I'm having trouble answering.
For starters, when setting the IAT and EXP values for the body, am I getting this value based on the current server time (that varies depending on which time zone the account's datacenter happens to reside), or should it be based on getting the UTC time instead? The documentation didn't seem clear enough to me on that matter.
Additionally, I am unclear how to go about creating the signature. Retrieving and preparing the necessary data, aside form the aforementioned issue, is not a problem. But, arranging the data to be encoded based on what I've seen available within the 2.0 modules as compared to examples I've seen for creating the signature don't seem to align so well. For example, this is an approximation of what I have seen:
var header = { typ : "JWT" , alg : "RS256" };
var body = { iss : "abc123" , sub : "def456" , iat : 123456789 , exp : 123456789 + (60 * 60) , scope : "signature" };
var encHeader = base64UrlEncode(JSON.stringify(header));
var encBody = base64UrlEncode(JSON.stringify(body));
// the key pairs are stored elsewhere, and the functions represent a way to retrieve the key contents
var publickKey = retrievePublicKey();
var privateKey = retrievePrivateKey();
var signature = RS256Encode(
encHeader + "." + encBody ,
publicKey ,
privateKey
);
The examples show different encryption functions that all take in three arguments in same way demonstrated above. However, when looking at what is available within the native SuiteScript 2.0 modules, I don't see anything quite comparable. I see that the crypto module (which gets pulled into other modules it seems) does have the ability to perform the required RSA SHA256 encoding, but not quite sure what the deal is to make sure that all the pieces are taken in together correctly.
I've taken a look at the JavaScript libraries for JWT creation at https://jwt.io/, but I have no experience with attempting to incorporate node modules into a SuiteScript project, if it's even possible.
So, is there a way to natively construct a JWT within SuiteScript, or am I going to have to find a way to be able to reference a node module in a script?
-- EDIT --
Ok, so it looks like I can use "Asynchronous Module Definition" (AMD) to import modules into a script by creating a JSON config file and adding the following to a script file's JSDoc header:
*#NAmdConfig /path/to/myModule.json
I figure that since I need it to be a relative path given that this project will be distributed to other accounts, something like this ought to work if the JSON config file is in the same directory:
*#NAmdConfig ./nodeModules.json
But I'm having a heck of a time determining exactly how to setup the JSON config. I can't quite seem to find anything that really helps determine how to build it correctly. The biggest issue is determining whether or not a module is AMD or non-AMD, and in the case of non-AMD whether to attempt to import the scripts under the CJS section or the ESM section.
For any additional reference, I used NPM to install the jose module since it's compatible with JavaScript/Node and has the encryption methods I need.
npm install jose
Right now, the path for it in the source project looks something like this:
SuiteScripts/Project_Name/lib/node_modules/jose
And the script file that will be referencing the module is located under this path:
SuiteScripts/Project_Name/lib
/**
*#NApiVersion 2.x
*/
define(['N/encode', 'N/crypto/certificate'], function(encode, cert){
function signIt(payload, ttl){
if(typeof payload.exp == 'undefined'){
var secondsSinceEpoch = Math.round(Date.now() / 1000);
var expAt = secondsSinceEpoch + (ttl || 60);
payload["exp"] = expAt;
payload["iat"] = secondsSinceEpoch;
}
log.debug({
title:'payload',
details: JSON.stringify(payload)});
var header = encode.convert({
string: JSON.stringify({
type:'JWT',
alg:'RS256'
}),
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64_URL_SAFE
}).replace(/=+$/, '');
var body = encode.convert({
string: JSON.stringify(payload),
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64_URL_SAFE}).replace(/=+$/, '');
var signer = cert.createSigner({
certId:'custcertificate_sample', //from Setup -> Company -> Certificates
algorithm: cert.HashAlg.SHA256
});
signer.update(header +'.'+ body);
var sig = signer.sign({
outputEncoding:encode.Encoding.BASE_64_URL_SAFE
}).replace(/=+$/, '');
return [header, body, sig].join('.');
}
return {
signIt: signIt
}
});
The uploaded key was generated like:
openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -out public.pem -pubout
openssl req -key private.pem -new -x509 -days 3650 -subj "/C=CA/ST=Courtenay/O=Rule of Tech/OU=Information unit/CN=jwt.kotn.com" -out cert.pem
and then I just used a text editor to concatenate the cert and private key from cert.pem and private.pem:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Related
I am using RSA encryption with nodejs crypto module.
I want encrypt message with PRIVATE KEY and decrypt with PUBLIC KEY.
also always make different result with same message using padding scheme like encryption using public key.
So I used basic crypto module like below
var crypto = require('crypto');
var fs = require('fs');
const path = require('path');
var PRIVKEY = fs.readFileSync(path.join(__dirname, 'private.key'), 'utf8');
var PUBKEY = fs.readFileSync(path.join(__dirname, 'pub.key'), 'utf8');
// RSA PRIVATE ENCRYPT -> PUBLIC DECRYPT //
myMSG = "apple";
console.log('myMSG SIZE:', myMSG.length);
function privENC_pubDEC(originMSG){
encmsg = crypto.privateEncrypt(PRIVKEY, Buffer.from(originMSG, 'utf8') ).toString('base64');
msg = crypto.publicDecrypt(PUBKEY, Buffer.from(encmsg, 'base64'));
console.log("Encrypted with private key : "+encmsg);
console.log(msg.toString());
}
// RSA PUBLIC ENCRYPT -> PRVATE DECRYPT //
function pubENC_privDEC(originMSG){
encmsg = crypto.publicEncrypt({key:PUBKEY, padding:crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(originMSG, 'utf8') ).toString('base64');
msg = crypto.privateDecrypt({key:PRIVKEY, padding:crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(encmsg, 'base64'));
console.log("\nEncrypted with public key : "+encmsg);
console.log(msg.toString());
}
privENC_pubDEC(myMSG);
pubENC_privDEC(myMSG);
Result
C:\Users\LSW>node crypto.js
myMSG SIZE: 5
Encrypted with private key : fbUZwj+UZP92HQYRc+EJTqSztJTY/Sit5axPZ0NVBuDAC8ZwvvC96pxxDGpra4Yg8MjcXyjvnT8rrrgHu0T0wA==
apple
Encrypted with public key : ze+5TdWtR8hkpNPIVa5HSasOxs3Pr8FA/1/zUGqDUQmIhs/miWt5pgU9kIAiryKfgGa0+p9RfHPMwZ1VMSA7Bw==
apple
C:\Users\LSW>node crypto.js
myMSG SIZE: 5
Encrypted with private key : fbUZwj+UZP92HQYRc+EJTqSztJTY/Sit5axPZ0NVBuDAC8ZwvvC96pxxDGpra4Yg8MjcXyjvnT8rrrgHu0T0wA==
apple
Encrypted with public key : OdEpjloUDWI8+YjWkE5cmBC/fJL2QnRLKBXfjaP5h5qyB1OMcm9JGGNSTiAAL2u8O5jjdQAavB9Rn+cdRDjLyA==
apple
C:\Users\LSW>node crypto.js
myMSG SIZE: 5
Encrypted with private key : fbUZwj+UZP92HQYRc+EJTqSztJTY/Sit5axPZ0NVBuDAC8ZwvvC96pxxDGpra4Yg8MjcXyjvnT8rrrgHu0T0wA==
apple
Encrypted with public key : INspxkyFu2AWGVYwSvOGOPH1fhE3qVVxiqz+SmyHU8wTDNKHj4gVVHqO+8AZOJvi4NfyekI2MMwpFDU4mUjEXA==
apple
PUBLIC ENCRYPT -> PRVATE DECRYPT is operated well I expected. it always return different result because of padding scheme.
But PRIVATE ENCRYPT -> PUBLIC DECRYPT is always return same message although used padding scheme.
Is there any solution make it different message with Nodejs crypto module???
This is expected behavior according to the padding schemes for RSA signing and encryption as implemented by OpenSSL, which crypto leverages.
I am not sure what you want to use the functions privateEncrypt() and publicDecrypt() for. If your intent is to sign data, then see my update below. Anyway, for these functions, the crypto documentation explains that it only exposes RSA_PKCS1_PADDING which OpenSSL maps to the deterministic RSASSA-PKCS1-v1_5 padding scheme under the hood. This means that for the same key and the same data, the resulting data will be the same.
For encryption and decryption, with publicEncrypt() and privateDecrypt(), you have selected the RSA_PKCS1_PADDING mode. This translates to the RSAES-PKCS1-v1_5, a scheme that includes random elements, which cause the different outputs that you observe in your repeated runs. According to the documentation, crypto uses RSA_PKCS1_OAEP_PADDING padding by default. This stands for Optimal asymmetric encryption padding, which is non-deterministic as well.
For a summary of the PKCS#1-defined schemes, see PKCS#1 Schemes.
Update: You may want to use the Sign class instead of the privateEncrypt() and publicDecrypt() functions. Its sign() function does support a probabilistic padding mode, which OpenSSL supports via RSASSA-PSS. Using your example code as a starting point, it would look something like this:
const sign = crypto.createSign('SHA256')
sign.update(Buffer.from(originMSG, 'utf8'))
signature = sign.sign({key:PRIVKEY, padding:crypto.constants.RSA_PKCS1_PSS_PADDING}).toString('base64')
The signature will be different every time. Note that you can not "decrypt" it, it is a one-way operation. You can only verify it, using the public key with the Verify class:
const verify = crypto.createVerify('SHA256')
verify.update(Buffer.from(originMSG, 'utf8'))
verifyRes = verify.verify({key:PUBKEY, padding:crypto.constants.RSA_PKCS1_PSS_PADDING}, Buffer.from(signature, 'base64'))
I am new to Dojo and I want to be able to use the RSA module to encrypt some information using a public key that is received from a website.
Is there any information or examples on how to do this?
I have found the library dojox.encoding.crypto.RSAKey but there is no information:
https://dojotoolkit.org/reference-guide/1.9/dojox/encoding/crypto/RSAKey.html
I found Dojo because I was researching: http://www-cs-students.stanford.edu/~tjw/jsbn/
Now I want to learn Dojo but my primary reason to use it is the RSA library.
Ps.: I don't want to be lectured on the dangers of RSA on the client side, neither why I shouldn't be doing RSA on JavaScript side.
EDITED: replaced example values with provided ones.
I'm not familiar with RSA, but general use of dojo's RSAKey module would be as follow (according to its source code):
require([
"dojo/dom",
"dojox/encoding/crypto/RSAKey"
],function(dom, RSAKeyModule){
var RSAKey = new RSAKeyModule();
var n= "8efebfa74157b9447e1bc729d5e2a459ee888e87dc7ed764b473e513edba7696a957871ff7a4941ed360d1b42a9788bdc52a8b659357dc8f252e6cc5f5bbf5c659cc9e9837df4ca6eee1c47889b055ac3802bb9491e88483491b08dff9e9472d99341134bcfc4ecf38915553bda08f943089377a95c7118febcef2841288aedb1b8ba22e211da2ab527d26d7accf2e05421260a23f06cf2b13e0ffd51e8f401bc113768027ad29c371564d179c82639061272e4f940bf50ba6490933f788715f8c268dd2c85a461e899ba416a51557fec7a9a4f1ed3df95cf5bd14bb529dd331b9a79828366a9589deb32e730369cd62352ef7fdd9297e1193f4a33e01289a6f"
var e = "10001";
RSAKey.setPublic(n, e);
var encrypted = RSAKey.encrypt("abc");
dom.byId("result").innerHTML = encrypted;
});
<script src="https://ajax.googleapis.com/ajax/libs/dojo/1.10.4/dojo/dojo.js"></script>
<div id="result"></div>
See dojo/request/xhr module - could be helpful for receiving key.
I'm working on a suitescript to integrate NetSuite with the Walmart Marketplace APIs. And, as the another OP here says it right their documentation pretty much says if you don't use Java you're on your own.
I'm looking for a way to do the same either in suitescript or javascript.
Instruction from Walmart's API documentation:
Sign the byte array representation of this data by:
Decoding the Base 64, PKCS-8 representation of your private key. Note that the key is encoded using PKCS-8. Libraries in various languages offer the ability to specify that the key is in this format and not in other conflicting formats such as PKCS-1. Use this byte representation of your key to sign the data using SHA-256 With RSA. Encode the resulting signature using Base 64.
And, a java code from their documentation to do the same:
public static String signData(String stringToBeSigned, String encodedPrivateKey) {
String signatureString = null;
try {
byte[] encodedKeyBytes = Base64.decodeBase64(encodedPrivateKey);
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encodedKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey myPrivateKey = kf.generatePrivate(privSpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(myPrivateKey);
byte[] data = stringToBeSigned.getBytes("UTF-8");
signature.update(data);
byte[] signedBytes = signature.sign();
signatureString = Base64.encodeBase64String(signedBytes);
} catch (Exception e) {
e.printStackTrace();
}
return signatureString;
}
For reference, here's the similar thing asked for dot net. Any help would be appreciated.
I tried developing a SAML connector in Javascript once and found several libraries that deal with different key file formats etc. I got fairly far along but the time to run some of the scripts was incredible (imagine trying to login but the process taking two minutes to decide your login was valid)
At that point I switched to an external system and managed the SSO with Netsuite's inbound SSO.
It doesn't look like things have improved that much with NS in the crypto department even with SS 2.0.
I'd tend to package this into two parts. Generate your files in Suitescript and pass them through a java based web service that handles the signing requirements. Minimizes the amount of Java you have to write and keeps your transaction extraction/formatting scripts under easy control.
I found a library (jsrsasign) that will do the Walmart signature from NetSuite server side in under 4 seconds! (Marketplace has gone to OAuth2, but I'm stuck with signing as a Drop Ship Vendor)
/**
*#NApiVersion 2.x
*#NScriptType ScheduledScript
*/
define(['N/log', 'N/https', '../lib/jsrsasign-master/jsrsasign-all-min'],
function(log, https) {
function execute(context) {
var pkcs8Der = {Your Walmart Private Key};
var pkcs8Pem = [
'-----BEGIN PRIVATE KEY-----',
pkcs8Der.match(/.{0,64}/g).join('\n'),
'-----END PRIVATE KEY-----'
].join('\n');
var tStamp = Date.now()
var stringToSign = [
tStamp,
{Your Walmart Comsumer Id},
{Request URL},
{Request Method (All Caps)}
].join('\n') + '\n';
var sig = new KJUR.crypto.Signature({"alg": "SHA256withRSA"});
sig.init(pkcs8Pem);
var sigVal = hextob64(sig.signString(stringToSign));
log.audit({title: 'Signature', details: sigVal});
log.audit({title: 'Timestamp', details: tStamp});
}
return {
execute: execute,
};
}
);
I had to add the following code to the jsrsasign-all-min.js library file for the Scheduled Script to load the module:
var navigator = {}, window = undefined;
I'm getting a 'error:0D07209B:asn1 encoding routines:ASN1_get_object:too long' when trying to sign a object with a PrivateKey I generated, in Node.js.
The buf is a simple object encoded with node-cbor
var ecdh = crypto.createECDH('secp256k1')
ecdh.generateKeys()
var sign = crypto.createSign('RSA-SHA256')
sign.update(buf)
var buf_signed = sign.sign('-----BEGIN PRIVATE KEY-----\n' +
ecdh.getPrivateKey('base64') +
'\n-----END PRIVATE KEY-----' +
'\n-----BEGIN CERTIFICATE-----' +
'\n-----END CERTIFICATE-----', 'binary')
Would the Certificate be strictly necessary? Am I missing any information in the PEM string?
Any help is appreciated, thank you :)
It turns out I was missing that for EC Digital Signing, the right way to do it is using ECDSA.
Node.js doesn't implement it natively, but this module makes a good job of doing so:
https://www.npmjs.com/package/ecdsa
I've been trying to get Authorization for Amazon's s3 rest api going. It's pretty damn complicated.
Because I'm trying to make a simple GET request from an admin page on my website, I'm just trying to do this through Javascript. Here are the instructions for constructing the Signature for the Authorization header:
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ))
To keep us sane, they give us a few examples, with the following givens:
var AWSSecretAccessKey = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY,
StringToSign = 'GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg;'
The output for this in their docs is bWq2s1WEIj+Ydj0vQ697zp+IXMU=. Based on the following I am getting ZGVjNzNmNTE0MGU4OWQxYTg3NTg0M2MxZDM5NjIyZDI0MGQxZGY0ZQ==:
function encode_utf8(s) {
return unescape(encodeURIComponent(s));
}
I used code.google.com's CryptoJS.HmacSHA1 function for the SHA1 hashing. My final Signature function looks like this:
var signature = btoa( CryptoJS.HmacSHA1( aws_secret, encode_utf8( StringToSign) ) );
What is going wrong here???
I actually found the answer from an SO question with reference to google's older (2.0) CrytpoJs library. You need:
2.0.0-crypto-sha1.js
2.0.0-hmac-min.js
Then you create your signature as so:
Signature = btoa( Crypto.HMAC(Crypto.SHA1, encode_utf8(StringToSign), aws_secret, { asString: true }) )
I couldn't find a way to to get Strings instead of Bits in the new version.