current commit
261106.4 · crypto: prevent stale OpenSSL errors
#include <ncrypto.h>
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_rsa.h"
#include "gtest/gtest.h"
#include "node_options.h"
#include "node_test_fixture.h"
#include "openssl/err.h"

using v8::Local;
using v8::Object;
using v8::String;

/*
 * This test verifies that an object created by LoadBIO supports BIO_tell
 * and BIO_seek, otherwise PEM_read_bio_PrivateKey fails on some keys
 * (if OpenSSL needs to rewind pointer between pem_read_bio_key()
 * and pem_read_bio_key_legacy() inside PEM_read_bio_PrivateKey).
 */
class NodeCryptoEnv : public EnvironmentTestFixture {};

TEST_F(NodeCryptoEnv, LoadBIO) {
  v8::HandleScope handle_scope(isolate_);
  Argv argv;
  Env env{handle_scope, argv};
  //  just put a random string into BIO
  Local<String> key = String::NewFromUtf8(isolate_, "abcdef").ToLocalChecked();
  ncrypto::BIOPointer bio(node::crypto::LoadBIO(*env, key));
#ifndef OPENSSL_IS_BORINGSSL
  const int ofs = 2;
  ASSERT_EQ(BIO_seek(bio.get(), ofs), ofs);
  ASSERT_EQ(BIO_tell(bio.get()), ofs);
#endif
  ASSERT_EQ(ERR_peek_error(), 0UL) << "There should not have left "
                                      "any errors on the OpenSSL error stack\n";
}

TEST_F(NodeCryptoEnv, KeyObjectEqualsClearsErrorQueue) {
  v8::HandleScope handle_scope(isolate_);
  Argv argv;
  Env env{handle_scope, argv};

  ERR_clear_error();
  auto result = node::LoadEnvironment(*env, R"JS(
    const { generateKeyPairSync } = require('node:crypto');
    const rsa = generateKeyPairSync('rsa', { modulusLength: 2048 });
    const ec = generateKeyPairSync('ec', { namedCurve: 'P-256' });
    return rsa.publicKey.equals(ec.publicKey);
  )JS")
                    .ToLocalChecked();
  EXPECT_TRUE(result->IsFalse());
  EXPECT_EQ(ERR_peek_error(), 0UL);
}

TEST_F(NodeCryptoEnv, InvalidEcJwkClearsErrorQueue) {
  v8::HandleScope handle_scope(isolate_);
  Argv argv;
  Env env{handle_scope, argv};

  ERR_clear_error();
  auto result = node::LoadEnvironment(*env, R"JS(
    const { diffieHellman, generateKeyPairSync } = require('node:crypto');
    const { privateKey } = generateKeyPairSync(
      'ec', { namedCurve: 'P-256' });
    try {
      diffieHellman({
        privateKey,
        publicKey: {
          key: {
            kty: 'EC',
            crv: 'P-256',
            x: 'AAAA',
            y: 'AAAA',
          },
          format: 'jwk',
        },
      });
      return false;
    } catch (error) {
      return error.code === 'ERR_CRYPTO_INVALID_JWK';
    }
  )JS")
                    .ToLocalChecked();
  EXPECT_TRUE(result->IsTrue());
  EXPECT_EQ(ERR_peek_error(), 0UL);
}

#if NCRYPTO_USE_OPENSSL_PROVIDER
TEST_F(NodeCryptoEnv, ExportIncompleteRsaPrivateKeyAsJwk) {
  v8::HandleScope handle_scope(isolate_);
  Argv argv;
  Env env{handle_scope, argv};

  ncrypto::Rsa rsa;
  auto n = ncrypto::BignumPointer::New();
  auto e = ncrypto::BignumPointer::New();
  ASSERT_TRUE(n.setWord(3233));
  ASSERT_TRUE(e.setWord(17));
  ASSERT_TRUE(rsa.setPublicKey(std::move(n), std::move(e)));

  auto pkey = ncrypto::EVPKeyPointer::NewRSA(rsa);
  ASSERT_TRUE(pkey);
  auto key = node::crypto::KeyObjectData::CreateAsymmetric(
      node::crypto::kKeyTypePrivate, std::move(pkey));

  v8::TryCatch try_catch(isolate_);
  EXPECT_FALSE(node::crypto::ExportJWKRsaKey(*env, key, Object::New(isolate_)));
  EXPECT_TRUE(try_catch.HasCaught());
}
#endif