Anyone successfully loaded the OpenPGP.js lib with...
# suitescript
w
Anyone successfully loaded the OpenPGP.js lib with SS2.1?
b
thats actually one of the first i tried
had to shim
Crypto.getRandomValues
w
care to explain more? And did you get it to work? 😄
b
keep in mind that I never followed through with actually using it, but I recall getting the examples from the getting started guide working
i was moderately surprised at the time since they used Promises
w
I'm currently trying to get the examples working, but I'm stuck at "t.Set is not a constructor" when importing openpgp.min.js
b
i personally recall having to implement *Crypto.getRandomValues as a global, b*ut I might be misremembering that particular memory, I was going through a bunch of crypto libraries at the time
w
ok. Have you had the need to pgp-encrypt? and did you go some other route instead?
b
never went through with using pgp, explored if it was possible, was a tentative yes, never actually did it in netsuite
by far the safest route to doing crypto is to do it on your own server acting as middleware
w
ok. There's an enhancement request for PGP encryption. Maybe I'll wait a couple of decades on that 😉
Nah, I think since they released the SFTP/Bank Connectivity SuiteApp (or whatever it is called) I think they'll also support pgp-encryption in a couple of releases. I think that atleast a couple of banks here i EU requires pgp-encryption of payment-files.
b
i do get that error when i try
though that probably indicates another problem
Set is in 2.1
w
hmm, If I use the openpgp.js-file instead of the min, I get global.Set... and yeah I now realize it is referring to the new Set type.
I do have
Copy code
@NApiVersion 2.1
atleast 🙂
b
oh hey, i still have my shim.js i used
Copy code
define(["./getRandomValuesPolyfill.js", "./base64.js"], function(getRandomValuesPolyfill, base64) {
  (function() {
      //this.window = this;
      this.self = this;
      this.navigator = {
        userAgent: ""
      };
      this.crypto = {
        getRandomValues: getRandomValuesPolyfill
      };
      this.atob = base64.Base64.atob;
      this.btoa = base64.Base64.btoa;
    })()
  
  return {}
})
evidently i put more work into it than i remember
w
so I need to have this as a shim somehow in the amdconfig, right?
b
actually no, i tried forever getting shim to work and decided it didnt work
w
ok... so I import this shim.js as a lib in my main script? 🙂
b
i chose to take advantage of a quirk in that the top level global is shared
so its a custom module
keep in mind i was doing a bunch of crypto at the time, not all of this may be required
w
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 */
define(['N/log', 'N/runtime','./lib/shim.js''./lib/openpgp.js'],
    /**
 * @param{log} log
 * @param{runtime} runtime
 * @param{shim} shim
 * @param{openpgp} openpgp
 */
    (log, runtime,shim,openpgp) => {
        /**
         * Defines the Scheduled script trigger point.
         * @param {Object} scriptContext
         * @param {string} scriptContext.type - Script execution context. Use values from the scriptContext.InvocationType enum.
         * @since 2015.2
         */
        const execute = (scriptContext) => {
            log.debug('testing');
        }
        return {execute}
    });
Like this then?
b
yes
theoretically the amd spec allows the dependencies to load in whatever order
but i find that netsuite does it sequentially
w
do you remember what libs you were referring to with the polyfill and base64?
Sorry if I'm bothering you too much!
b
should be in npm
guessing from the name, i might have renamed it
w
I've found one, I'll try those. Thanks!
b
keep in mind that a lot of the node stuff actually requires a packaging tool like browserify
w
alright, I guess I'll need to read up on that then! awesome guidance!
b
if im feeling lazy, i use https://wzrd.in/
❤️ 1
w
haha, that's me!
Now it seems to be loading ok. (I think I used another base64 lib with other names for the atob,btoa functions) It doesn't give me any errors, but it stops after one await call
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 */
define(['N/log', 'N/runtime','./lib/shim.js','./lib/openpgp.js'],
    /**
 * @param{log} log
 * @param{runtime} runtime
 * @param{shim} shim
 * @param{openpgp} openpgp
 */
    (log, runtime,shim,openpgp) => {
        /**
         * Defines the Scheduled script trigger point.
         * @param {Object} scriptContext
         * @param {string} scriptContext.type - Script execution context. Use values from the scriptContext.InvocationType enum.
         * @since 2015.2
         */
        const execute = (scriptContext) => {
            log.debug('start script');

            async function test(){
                log.debug('test start');
                // put keys in backtick (``) to avoid errors caused by spaces or tabs
                const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
                
xo0EX4r8UwEEANl9YnR4IpDGossbtPISCCdIaqHNZDdbzk6jWugT3YakHIT+GHal
...
3o7T7Yv3bdHk0urZnnl3jbYYJJ/1AOgDdqheh1INZTytnLGUKeFNupdVpXjFn377
7FCc
=Qr6K
-----END PGP PUBLIC KEY BLOCK-----`;
                const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----

xcFGBF+K/FMBBADZfWJ0eCKQxqLLG7TyEggnSGqhzWQ3W85Oo1roE92GpByE/hh2
...
JJ/1AOgDdqheh1INZTytnLGUKeFNupdVpXjFn3777FCc
=YVRc
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
                const passphrase = `testing`; // what the private key is encrypted with

                log.debug('after constants...');
                const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
                log.debug('after await openpgp.key.readArmored...'); //THIS IS LOGGED
                await privateKey.decrypt(passphrase);
                log.debug('after await privateKey.decrypt...'); //THIS IS NOT LOGGED
                const { data: encrypted } = await openpgp.encrypt({
                    message: openpgp.message.fromText('Hello, World!'),                 // input as Message object
                    publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption
                    privateKeys: [privateKey]                                           // for signing (optional)
                });
                log.debug('after await openpgp.encrypt...');
                log.debug(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
                const { data: decrypted } = await openpgp.decrypt({
                    message: await openpgp.message.readArmored(encrypted),              // parse armored message
                    publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional)
                    privateKeys: [privateKey]                                           // for decryption
                });
                log.debug(decrypted); // 'Hello, World!'
            }

            test();
            log.debug('complete');
        }
        return {execute}
    });
(the log.debug('complete') is logged, but I suppose that is because it is an async function)
b
whats with the ... in your keys
w
I've just truncated them for length in the message
b
thats unfortunate, normally you can try the debugger but that doesnt work with 2.1
you can try adding logs to the open pgp functions to see what your error is
w
thanks. I'll try that and different ves
versions that are available
b
after minor experimentation, you should probably add a catch to your promise chain
evidently suitescript 2.1 ignores or maybe doesnt even have a global unhandled error handler
w
promise chain? how do you mean? do you mean I should add try/catch around everything inside the async function?
nice catch
b
your test function returns a Promise, use the catch method to see errors
w
cannot read property 'decrypt' of undefined. I removed the part about "web worker" because thought it wasn't applicable on server suitescripts. Maybe that was required.
probably something in it that inits the privateKey-object...
I really don't understand the syntax of: const {keys: [privateKey]} = await openpgp.key.readArmored(privateKeyArmored);
b
id log the stack too
but my guess is privateKey is undefined
w
Gonna try the other sample from openpgp.js with multiple private keys (but with a single one instead)
b
Copy code
async function readArmored(armoredText) {
  try {
    const input = await _armor2.default.decode(armoredText);
    if (!(input.type === _enums2.default.armor.public_key || input.type === _enums2.default.armor.private_key)) {
      throw new Error('Armored text not of type key');
    }
    return read(input.data);
  } catch (e) {
    const result = { keys: [], err: [] };
    result.err.push(e);
    return result;
  }
}
that will be the relevant part of open pgp for you
check the err key
w
ok!
like this:
Copy code
const { keys: [privateKey], err: [readArmoredErrors]} = await openpgp.key.readArmored(privateKeyArmored);
log.debug('error.key',JSON.stringify(readArmoredErrors));
b
that might not log properly, but you may get a log
i believe that netsuite made some changes to how logging an Error works in 2.1
logging an error object will not output the same log as logging the stringified error object
w
yeah, I noticed that when I tried to JSON.stringify(e) in the catch as well. It returned an empty object.
My dev-environment is so wierd. Sometimes the script get stuck in pending even if there are no other scripts running...
b
my guess is that its related to Error Object Properties
they didnt want useless error logs
you need to keep in mind that your netsuite account is shared with other accounts
other accounts could be running scripts
i like using suitelets instead for testing
w
huh? Is the script queue dependant on other accounts? Is it just because it is a dev-env? If I have suitecloud processors, I shouldn't have to wait for others, right?
b
the server running your script is running scripts from other accounts too
it only has so many threads it can create to run them
you might get access to more of those threads with more levels of suitecloud plus, but that probably wont help if your server has hit the maximum number of threads it can run at once
w
yay... got an error now at least.
Error: Misformed armored text
Gonna doublecheck the key then.
Removed all spaces/tabs introduced by webstorm. New error. So much fun 😄 Error during parsing. This message / key probably does not conform to a valid OpenPGP format.
b
if you want, you can share what you are using for your public and private key with me and i could try it
obviously give me a test one
w
you sure?
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 */
define(['N/log', 'N/runtime','./lib/shim.js','./lib/openpgp.js'],
    /**
 * @param{log} log
 * @param{runtime} runtime
 * @param{shim} shim
 * @param{openpgp} openpgp
 */
    (log, runtime,shim,openpgp) => {
        /**
         * Defines the Scheduled script trigger point.
         * @param {Object} scriptContext
         * @param {string} scriptContext.type - Script execution context. Use values from the scriptContext.InvocationType enum.
         * @since 2015.2
         */
        const execute = (scriptContext) => {
            log.debug('start script');

                    async function test(){

                    try {
                        const publicKeysArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.5.2
Comment: <https://openpgpjs.org>

xk0EX4s+JQECALHyDkjtRrB9nvc5DD6Swdyi0rQc/x3Yxt6iP08OUt+UimkG
jfAP/LmwTi5DrhLviE98fiDBzRljjb5zLS7rtJMAEQEAAc0UYmlsbCA8Ymls
bEBob21lLmNvbT7CdQQQAQgAHwUCX4s+JQYLCQcIAwIEFQgKAgMWAgECGQEC
GwMCHgEACgkQ1Y9hTVbPxV3RoAH+MqJq9OSfW+n9vVrzSfSHiF3sTN08T2o8
xU9tz4yEOuEdmVLZdrzFGYm5XyI7VO9UEhiqkKXCMRrg/EQli9yysM5NBF+L
PiUBAf9ViMyeyYldp3puNUqdCFXEJiFGKqpk05X264gwAtJ1soNFZ1OJtr0J
N6yPl4g/io+T8bw9F2We4RxpsgUImn0jABEBAAHCXwQYAQgACQUCX4s+JQIb
DAAKCRDVj2FNVs/FXbUQAgCvCuHeJW7sag4rjQE0Nn7AV0Hhrlha7QLbu9BC
kTVZ7e8arsNUbLTixF3T7+uLYaxMzRhxBMeiJxqIdk1hIPa/
=F5HS
-----END PGP PUBLIC KEY BLOCK-----`;
                        const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.5.2
Comment: <https://openpgpjs.org>

xcBmBF+LPiUBAgCx8g5I7UawfZ73OQw+ksHcotK0HP8d2Mbeoj9PDlLflIpp
Bo3wD/y5sE4uQ64S74hPfH4gwc0ZY42+cy0u67STABEBAAH+CQMIbOGq4B5I
BqXg/S2xufEamp8ZQ0FwLFeeYDvDi0mbS0w9yNKmc5Fc3SmD7wtUupckqNEO
xWdn6nmEUXRGPLEtgae1GJvC5EbS60g6pmX+mq5T4L7wxqlDMd0PHFBhkcIY
C3Tn5WNdSWeBs659POYc1hAbpwIEK7LkDz0KwMqNXV4IzlqEazsQsdVw+pgr
42Mri8sWvUIUGFOsT+fKH+RDKcyt8kzMauliAxFOxeElh9XqM7p6/BedtA0q
MBAqvD7cUVQ+Js5QE29QyQHaGWZ5ih38ndKvzRRiaWxsIDxiaWxsQGhvbWUu
Y29tPsJ1BBABCAAfBQJfiz4lBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIeAQAK
CRDVj2FNVs/FXdGgAf4yomr05J9b6f29WvNJ9IeIXexM3TxPajzFT23PjIQ6
4R2ZUtl2vMUZiblfIjtU71QSGKqQpcIxGuD8RCWL3LKwx8BmBF+LPiUBAf9V
iMyeyYldp3puNUqdCFXEJiFGKqpk05X264gwAtJ1soNFZ1OJtr0JN6yPl4g/
io+T8bw9F2We4RxpsgUImn0jABEBAAH+CQMIYi9pIyYo/3fgkCKZXkwOiRw7
cPP9hswBTo1paQHSlgnShN91E0CHMhnJYCIWifX8dR9tw8FomTCF8Xs8zLOy
bYZBwMrJM7yYfaOII83Jpop6sJpoFuuDK7vezoa+zdYTBObmuA8xuom3+6YX
y6oFsb3Pdu8TBL4H7O9vj8Be71J5+SqFmTwYbImxPpJzFIU/8M1EJATE0ImM
fxaO6t5mTd+mALO//X2c+WkGDtBvClSesgkdiBYlkyU20tc8HfejjbIejSgx
qFdleaJNvB+syz0uuNDSwl8EGAEIAAkFAl+LPiUCGwwACgkQ1Y9hTVbPxV21
EAIArwrh3iVu7GoOK40BNDZ+wFdB4a5YWu0C27vQQpE1We3vGq7DVGy04sRd
0+/ri2GsTM0YcQTHoicaiHZNYSD2vw==
=cIyF
-----END PGP PRIVATE KEY BLOCK-----`;    // encrypted private key
                        const passphrase = `password`; // what the private key is encrypted with
                        const message = 'Hello, World!';

                        const { keys: [privateKey], err: [readArmoredErrors]} = await openpgp.key.readArmored(privateKeyArmored);
                        log.debug('error.key',readArmoredErrors);
                        await privateKey.decrypt(passphrase)

                        const publicKeys = await Promise.all(publicKeysArmored.map(async (key) => {
                            return (await openpgp.key.readArmored(key)).keys[0];
                        }));

                        const { data: encrypted } = await openpgp.encrypt({
                            message: openpgp.message.fromText(message),   // input as Message object
                            publicKeys,                                   // for encryption
                            privateKeys: [privateKey]                     // for signing (optional)
                        });
                        console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'




                } catch (e) {
                    log.debug('ERROR','e.stack:'+ e.stack + 'e.message:' + e.message);
                }
            }

            test();
            log.debug('complete');
        }
        return {execute}
    });
message has been deleted
I'm using "https://www.npmjs.com/package/base64-js" in the shim... maybe I should try another one
gonna try js-base64 instead...
b
Error: Mandatory blank line missing between armor headers and armor data
w
what?? 😄
b
maybe something got lost in the formatting
w
maybe, at least I got error messages now, thanks to you!
b
you may also want to try using a more normal key generation method
generate your key using gpg4win
w
will do!
b
couldnt get your example to work, I got errors about a Symbol
using the standard example worked fine
w
which example did you use then?
b
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(["./shim.js", "./openpgp.js"], function (shim, openpgp) {
  async function test() {
    const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.5.2
Comment: <https://openpgpjs.org>

xk0EX4s+JQECALHyDkjtRrB9nvc5DD6Swdyi0rQc/x3Yxt6iP08OUt+UimkG
jfAP/LmwTi5DrhLviE98fiDBzRljjb5zLS7rtJMAEQEAAc0UYmlsbCA8Ymls
bEBob21lLmNvbT7CdQQQAQgAHwUCX4s+JQYLCQcIAwIEFQgKAgMWAgECGQEC
GwMCHgEACgkQ1Y9hTVbPxV3RoAH+MqJq9OSfW+n9vVrzSfSHiF3sTN08T2o8
xU9tz4yEOuEdmVLZdrzFGYm5XyI7VO9UEhiqkKXCMRrg/EQli9yysM5NBF+L
PiUBAf9ViMyeyYldp3puNUqdCFXEJiFGKqpk05X264gwAtJ1soNFZ1OJtr0J
N6yPl4g/io+T8bw9F2We4RxpsgUImn0jABEBAAHCXwQYAQgACQUCX4s+JQIb
DAAKCRDVj2FNVs/FXbUQAgCvCuHeJW7sag4rjQE0Nn7AV0Hhrlha7QLbu9BC
kTVZ7e8arsNUbLTixF3T7+uLYaxMzRhxBMeiJxqIdk1hIPa/
=F5HS
-----END PGP PUBLIC KEY BLOCK-----`;
    const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.5.2
Comment: <https://openpgpjs.org>

xcBmBF+LPiUBAgCx8g5I7UawfZ73OQw+ksHcotK0HP8d2Mbeoj9PDlLflIpp
Bo3wD/y5sE4uQ64S74hPfH4gwc0ZY42+cy0u67STABEBAAH+CQMIbOGq4B5I
BqXg/S2xufEamp8ZQ0FwLFeeYDvDi0mbS0w9yNKmc5Fc3SmD7wtUupckqNEO
xWdn6nmEUXRGPLEtgae1GJvC5EbS60g6pmX+mq5T4L7wxqlDMd0PHFBhkcIY
C3Tn5WNdSWeBs659POYc1hAbpwIEK7LkDz0KwMqNXV4IzlqEazsQsdVw+pgr
42Mri8sWvUIUGFOsT+fKH+RDKcyt8kzMauliAxFOxeElh9XqM7p6/BedtA0q
MBAqvD7cUVQ+Js5QE29QyQHaGWZ5ih38ndKvzRRiaWxsIDxiaWxsQGhvbWUu
Y29tPsJ1BBABCAAfBQJfiz4lBgsJBwgDAgQVCAoCAxYCAQIZAQIbAwIeAQAK
CRDVj2FNVs/FXdGgAf4yomr05J9b6f29WvNJ9IeIXexM3TxPajzFT23PjIQ6
4R2ZUtl2vMUZiblfIjtU71QSGKqQpcIxGuD8RCWL3LKwx8BmBF+LPiUBAf9V
iMyeyYldp3puNUqdCFXEJiFGKqpk05X264gwAtJ1soNFZ1OJtr0JN6yPl4g/
io+T8bw9F2We4RxpsgUImn0jABEBAAH+CQMIYi9pIyYo/3fgkCKZXkwOiRw7
cPP9hswBTo1paQHSlgnShN91E0CHMhnJYCIWifX8dR9tw8FomTCF8Xs8zLOy
bYZBwMrJM7yYfaOII83Jpop6sJpoFuuDK7vezoa+zdYTBObmuA8xuom3+6YX
y6oFsb3Pdu8TBL4H7O9vj8Be71J5+SqFmTwYbImxPpJzFIU/8M1EJATE0ImM
fxaO6t5mTd+mALO//X2c+WkGDtBvClSesgkdiBYlkyU20tc8HfejjbIejSgx
qFdleaJNvB+syz0uuNDSwl8EGAEIAAkFAl+LPiUCGwwACgkQ1Y9hTVbPxV21
EAIArwrh3iVu7GoOK40BNDZ+wFdB4a5YWu0C27vQQpE1We3vGq7DVGy04sRd
0+/ri2GsTM0YcQTHoicaiHZNYSD2vw==
=cIyF
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
    const passphrase = `password`; // what the private key is encrypted with

    const {
      keys: [privateKey],
    } = await openpgp.key.readArmored(privateKeyArmored);

    await privateKey.decrypt(passphrase);

    const { data: encrypted } = await openpgp.encrypt({
      message: openpgp.message.fromText("Hello, World!"), // input as Message object
      publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption
      privateKeys: [privateKey], // for signing (optional)
    });
    log.debug("encrypted", encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
    const { data: decrypted } = await openpgp.decrypt({
      message: await openpgp.message.readArmored(encrypted), // parse armored message
      publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional)
      privateKeys: [privateKey], // for decryption
    });
    log.debug("decrypted", decrypted); // 'Hello, World!'
  }

  function onRequest(context) {
    log.debug("context", context);

    test().catch(function (e) {
      log.error("e", e);
      log.error("e", e.stack);
    });
  }

  return {
    onRequest: onRequest,
  };
});
actually, got your example to work too (after fixing the keys missing new line)
w
Smart to use a suitelet
b
i told you about the suitelet
w
yeah
hmm, I seem to be having some issues with both of my base64 libs.
b
well, that would be a difference
i honeslty havent found a base64 library i like
it used to have problems with amd that required you to fix the file
however it looks like the package was updated, so it might be better now
i usually dont tell people which base64 package i use to see if they can find a better one
w
yeah, that's second one I have. I saw that it should be called the same way that you had in your shim. But I get an undefined error in that when atob is called.
I browserfied it using the page you posted
lol. your sample code worked without a hitch.
b
well, what you shared is sorta wrong
publicKeysArmored is not an array
you use object destructuing on an object that doesnt always have an err key
you can probably see that the code i used had only changes to the logs
when compared to the getting started example
i just tried out the new base64 package, its nicer now
works great with amd now, though now requires 2.1
w
ok, I copied the multiple public-keys sample and just gave it one key. that's why they had put publicKeysArmored as an array. stupid...
what no... Ok. I need to read this. Thank you... facepalm
b
minor note, the base64 library does not actually need to be browserified
things that are written to work in both browser and node tend not to need it
w
ok
I think I messed it up in several steps and made it worse and worse. 🤣 But I'm happy now that it works! Thank you again for your patience, you're awesome!
b
tell me how it goes with real files
suitescript 1 and 2 had terrible performance with binary operations
from what ive seen, 2.1 isnt much better
w
I will if I ever get that far. Just the "Hello world!" takes about a minute. Gonna try with a full file to see the performance. I had a customer that had the need for this more than a year ago, but I don't recall 2.1 being available then. I'm gonna see if they still need it or if they have gone another route. Why I got the undefined error was because I used a browserfied version of the js-base64 npm-module. When using the dist from github it works fine.
Small file 19kb
b
well, something in decryption of the private key is much slower than it should be
w
running a 1,5mb file... it is not fast. 😄
medium file 1.5mb
a
I wanted to enrich this thread with the following article: SuiteScript 2.1 Differences (more specifically, to a difference we've observed with Error Object Properties)
b
do you plan on documenting that
Copy code
log.error({
    title: 'An Error',
    details: e
});
is not the same as
Copy code
log.error({
    title: 'An Error',
    details: JSON.stringify(e)
});
a
does e contain enumerable and non-enumerable properties
b
e is an error
the logging methods are supposed to stringify their inputs if its not a string
they dont for errors
there is a good reason that they dont stringify errors, but its not documented behavior