bun icon indicating copy to clipboard operation
bun copied to clipboard

TypeError with Crypto's `createPublicKey` fn

Open mattmilan-dev opened this issue 6 months ago • 8 comments

What version of Bun is running?

1.0.21+837cbd60d

What platform is your computer?

Darwin 23.2.0 arm64 arm

What steps can reproduce the bug?

Run the following JS code in bun run or bun repl

import { createPublicKey } from 'crypto';

const b64str = 'MDswDQYJKoZIhvcNAQEBBQADKgAwJwIg3wUvyMOfq7G6dT5bIM6keoShd9YGwP7PIc2Tfa8Q99ECAwEAAQ==';

// convert the b64 sring to a buffer
const buffer = Buffer.from(b64str, 'base64');

// create and log the key
const key = createPublicKey({
  key: buffer,
  type: 'spki',
  format: 'der'
});

console.log(key);

// Output:
// NodeJS (v20.10.0): PublicKeyObject [KeyObject] { [Symbol(kKeyType)]: 'public' }
// Bun Run (v1.0.21):
//  TypeError: Invalid public key
//    at node:crypto:77:61

What is the expected behavior?

The code should console log the public key object as such:

mattmilan.dev@matt server % node crypto-test.js 
PublicKeyObject [KeyObject] { [Symbol(kKeyType)]: 'public' }

What do you see instead?

mattmilan.dev@matt server % bun run crypto-test.js 
4 | 
5 | // convert the b64 sring to a buffer
6 | const buffer = Buffer.from(b64str, 'base64');
7 | 
8 | // create and log the key
9 | const key = createPublicKey({
                ^
TypeError: Invalid public key
      at node:crypto:77:61
      at /Users/mattmilan.dev/Projects/portal/server/crypto-test.js:9:13

Additional information

The version of node tested on was v20.10.0 as a comparison. The reason for discovering this was due to it being used in quite a critical AWS project (https://github.com/awslabs/aws-jwt-verify) [A common library for decoding JWT's via AWS's Cognito].

For additional reference see here (https://github.com/awslabs/aws-jwt-verify/blob/2a818d9a57cc3a6c0bdea8cc9cb2e04a92e2a95c/src/node-web-compat-node.ts#L24) for the breaking case of the aws-jwt-verify package.

mattmilan-dev avatar Jan 05 '24 18:01 mattmilan-dev

@cirospaciari Mind taking a look at this?

Electroid avatar Jan 08 '24 17:01 Electroid

Interesting, if we re-export in nodejs using the code bellow it gives a different base64 and this can be successfully imported in Bun:

console.log(
  key
    .export({
      type: "spki",
      format: "der",
    })
    .toString("base64"),
);

outputs:

MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAN8FL8jDn6uxunU+WyDOpHqEoXfWBsD+zyHNk32vEPfRAgMBAAE=

using this base64 string works fine in both and gives the same base64 as export output: image image

Will take a deeper look

cirospaciari avatar Jan 08 '24 17:01 cirospaciari

Looking at BoringSSL flow we got a negative big number when decoding the RSA value.

BN_R_NEGATIVE_NUMBER (is negative) -> RSA_R_BAD_ENCODING -> EVP_R_DECODE_ERROR

When trying to use the key or the re-exported key in bun or node publicEncrypt we got ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE, and when using an RSA key generated by generateKeyPairSync we can parse and encrypt using the public key.

console.log(publicEncrypt(key, Buffer.from("Hello")).toString("base64"));

the parse error is probably BoringSSL failing earlier than OpenSSL on a badly formatted/encoded key.

@mattmilan-dev using the provided key can you sign/verify in nodejs? can you provide a signature so we can test this public key? This would help a lot.

cirospaciari avatar Jan 08 '24 21:01 cirospaciari

Thanks for looking into this, and apologies for not being any help further so far. Unfortunately the public key I provided is a conversion into B64 (via buffer) of one of our JWKs used in AWS's Cognito and therefore I don't have the private key available to sign anything with. I could generate a new key pair from scratch on my machine but i feel this would be ineffective if the error is created by something AWS are doing to create their keys which causes the TypeError within bun. If there's any other thing i can do with the JWKs to give any assistance feel free to let me know and i'd be more than happy to take a look.

mattmilan-dev avatar Jan 09 '24 11:01 mattmilan-dev

No problem I will think in something, and look further, will try to get some environment to simulate the same behavior.

cirospaciari avatar Jan 09 '24 12:01 cirospaciari

I think I'm having the same problem with bun and aws-jwt-verify Screenshot 2024-01-10 at 12 51 53

95tuanle avatar Jan 10 '24 17:01 95tuanle

I have also been led to this thread exactly from trying to verify AWS Cognito JWTs with aws-jwt-verify using Bun.

madsbuch avatar Feb 09 '24 12:02 madsbuch

Looks like a bug in the DER encoder of aws-jwt-verify that does not add a leading 0-byte if the MSB (most significant bit) of the public key modulus is 1. In NodeJS this doesn't throw, because OpenSSL is friendly enough (?) to still parse the modulus as a positive number, but in bun it leads to the modulus being parsed as a negative number as @cirospaciari saw. I think that's actually right, because the number is supposed to be encoded in 2's complement per DER spec (https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#%5B%7B%22num%22%3A41%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22FitH%22%7D%2C799%5D)

Will fix in aws-jwt-verify (update: should be fixed with https://github.com/awslabs/aws-jwt-verify/pull/155)

ottokruse avatar Feb 09 '24 17:02 ottokruse

since it was a DER specification issue, and fixed in the latest version of aws-jwt-verify I will close the issue, if anyone encountered this issue after upgrading to the latest version feel free to comment/reopen

cirospaciari avatar Mar 04 '24 14:03 cirospaciari