39

I need to generate a nonce (number generated only once) to remove the CSP rule 'unsafe-inline' and all the trusted URLs for scripts, improving the CSP score. Thus I need to have in the HTML

<script nonce="{{{nonce}}}" src="http://example.com/file.js">

I know the nonce must be unique with a method of calculation almost impossible to predict, it should have at least 128 bits (hence 16 bytes), and be encoded in base64. Is therefore this correct for node.js?

const crypto = require('crypto');
let nonce = crypto.randomBytes(16).toString('base64');
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109

4 Answers4

63

Just to confirm that indeed this does work in NodeJS for CSP nonces

const crypto = require('crypto');
let nonce = crypto.randomBytes(16).toString('base64');
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
  • 10
    `.toString('hex')` for hexadecimal. – Chloe Nov 01 '19 at 02:23
  • Do I put this inside the expressServer.js file? It does work there, but will that generate on each page load? Or just on server startup? – Lazerbrains Mar 21 '22 at 22:48
  • @Lazerbrains nonces serve to protect against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack), thus you need a different nonce per request. – João Pimentel Ferreira Mar 22 '22 at 09:49
  • I was able to implement this and have the nonce generating. But I cannot access it globally, is there a better way to declare the nonce variable so it is accessible globally? – Lazerbrains Mar 28 '22 at 15:57
  • one way is to place it in a datastore for retrieval and when you're querying for it and find it, you'd remove it from the datastore. Be sure to add an expiresAt incase the lookup request never comes that way you don't risk inflating your resources – Sgnl Jun 28 '22 at 09:41
6

I suggest using uuid for this: https://www.npmjs.com/package/uuid

Each uuid is exactly 16 bytes (128 bits) as desired, and you have a higher probability of your computer being hit by a meteor than generating a uuid collision.

Mitch Talmadge
  • 4,638
  • 3
  • 26
  • 44
  • 1
    Why not? `uuid` comply the [CSP3 spec](https://www.w3.org/TR/CSP3/#security-nonces): *The generated value SHOULD be at least 128 bits long (before encoding), and SHOULD be generated via a cryptographically secure random number generator in order to ensure that the value is difficult for an attacker to predict.* And `uuid` meet allowed char requirements: `base64-value = 1*( ALPHA / DIGIT / "+" / "/" / "-" / "_" )*2( "=" )` – granty Dec 17 '20 at 11:34
  • Is there an option to generate a hex using uuid? – sebastiansieber Oct 11 '21 at 15:29
2

You can use the builtin crypto.randomUUID() to generate UUIDv4 (version 4).

That's a 36 characters long string (288-bit) that encode a 128 bits UUID.

Of those 128 bits, only 122 bits are random, 4 bits are used to encode the UUID version (always version 4 for randomUUID()), and 2 other bits are fixed, so you lose 6 bits of randomness in total.

const crypto = require('crypto')
crypto.randomUUID()
'5a388e8e-09eb-4778-a242-ea663f3c7e5e'

The first 4 in -4478- indicates that it's a UUID version 4, that's 4 bits that are not random.

The first two bits of -a242- are fixed to 10 by the RFC spec, so those are not random either.

In total you have 122-bits of randomness in if you use UUIDv4 as a nonce.

You could generate a 128-bits totally random nonce with crypto.randomBytes(16) and encode it in either base64url (22 characters), base64 (24 characters), or hex (32 characters), all of those are shorter that UUIDv4 which is 36 characters.

UUIDv4 is longer (less compact) and has less random bits, but it's easier to read when you are troubleshooting and are easily recognizable as a random value.

crypto.randomUUID() # 36 characters
'5a388e8e-09eb-4778-a242-ea663f3c7e5e'

var nonce128bitvalue = crypto.randomBytes(16) # 16 * 8 = 128 bits

nonce128bitvalue.toString('base64url') # 22 characters long 
'q-UBP7J_AqOn1BWTBq1Tfw'

nonce128bitvalue.toString('base64') # 24 characters long
'q+UBP7J/AqOn1BWTBq1Tfw=='

nonce128bitvalue.toString('hex') # 36 characters long
'abe5013fb27f02a3a7d4159306ad537f'

As stated in the crypto.randomUUID() documentation:

Generates a random RFC 4122 version 4 UUID. The UUID is generated using a cryptographic pseudorandom number generator.

RubenLaguna
  • 21,435
  • 13
  • 113
  • 151
0

It is better:

  <script nonce="{{nonce}}" .. if you use HBS
    not <script nonce="{{{nonce}}}"
    in HBS convention: {{{body}}} vs {{title}} ,{{nonce}} or {{any_rendered_var}}

And in app.js of development environment:

    const crypto=require('crypto');
    var nonce=crypto.randomBytes(16).toString("hex");
    app.get('/', function(req, res) {res.render('index',{title:'Welcome',nonce:nonce});});

That does not protect you against cross scripting, because you just rendered to nonce a random value and your app does not know about any cross scripting or why did you render the random value of nonce or nnc or any other name of the variable.

But you should use helmet npm package in production!

so in server.js:

    const crypto=require('crypto');
    var nonce=crypto.randomBytes(16).toString("hex");
    app.get('/', function(req, res) {res.render('index',{title:'Welcome',nonce:nonce});});
    //add
    const helmet = require('helmet');

The helmet would block the nonce and in a browser you will see: <script nonce="" .. because CSP helmet requires: <script nonce="random_value_client===csp_helmet_random_value_server" .. to really prevent the cross scripting

Adem kriouane
  • 381
  • 20