crypto: decode PKCS#1 keys through providers · nodejs/node@ca8ee04 · GitHub
Skip to content

Commit ca8ee04

Browse files
panvaaduh95
authored andcommitted
crypto: decode PKCS#1 keys through providers
Import RSA public keys through OSSL_DECODER on OpenSSL 3 so the resulting keys stay provider-backed. Preserve the PKCS#1 input structure and the ASN.1 encodings accepted by the legacy decoder. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: #66108 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 54a625e commit ca8ee04

3 files changed

Lines changed: 327 additions & 2 deletions

File tree

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 46 additions & 2 deletions

‎test/cctest/test_node_crypto.cc‎

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,193 @@ TEST(NodeCrypto, KeyAlgorithmNames) {
8989
EXPECT_FALSE(empty.isA(static_cast<const char*>(nullptr)));
9090
}
9191

92+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
93+
TEST(NodeCrypto, ProviderPkcs1PublicKeyImport) {
94+
ncrypto::ClearErrorOnReturn clear_errors;
95+
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
96+
ASSERT_TRUE(ctx);
97+
ASSERT_TRUE(ctx.initForKeygen());
98+
ASSERT_TRUE(ctx.setRsaKeygenBits(2048));
99+
EVP_PKEY* raw = nullptr;
100+
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
101+
EVPKeyPointer key(raw);
102+
103+
for (const auto format :
104+
{EVPKeyPointer::PKFormatType::PEM, EVPKeyPointer::PKFormatType::DER}) {
105+
const EVPKeyPointer::PublicKeyEncodingConfig config(
106+
false, format, EVPKeyPointer::PKEncodingType::PKCS1);
107+
auto encoded = key.writePublicKey(config);
108+
ASSERT_TRUE(encoded);
109+
const BUF_MEM* mem = encoded.value;
110+
ASSERT_NE(mem, nullptr);
111+
const ncrypto::Buffer<const unsigned char> input{
112+
reinterpret_cast<const unsigned char*>(mem->data), mem->length};
113+
auto imported = EVPKeyPointer::TryParsePublicKey(config, input);
114+
ASSERT_TRUE(imported);
115+
EXPECT_NE(EVP_PKEY_get0_provider(imported.value.get()), nullptr);
116+
EXPECT_TRUE(imported.value.isA(KeyAlgorithm::RSA));
117+
EXPECT_EQ(EVP_PKEY_eq(key.get(), imported.value.get()), 1);
118+
}
119+
}
120+
121+
namespace {
122+
struct RsaLoadTestContext {
123+
OSSL_FUNC_BIO_read_ex_fn* read = nullptr;
124+
int selection = 0;
125+
int loads = 0;
126+
int frees = 0;
127+
};
128+
129+
void* RsaLoadTestDecoderNew(void* context) {
130+
return context;
131+
}
132+
133+
void RsaLoadTestDecoderFree(void*) {}
134+
135+
int RsaLoadTestDecode(void* context,
136+
OSSL_CORE_BIO* input,
137+
int selection,
138+
OSSL_CALLBACK* callback,
139+
void* arg,
140+
OSSL_PASSPHRASE_CALLBACK*,
141+
void*) {
142+
auto* state = static_cast<RsaLoadTestContext*>(context);
143+
unsigned char sentinel = 0;
144+
size_t size = 0;
145+
// Only exercise construction and reference ownership, not ASN.1 parsing.
146+
if (!state->read(input, &sentinel, 1, &size) || size != 1 ||
147+
sentinel != 0x42) {
148+
return 0;
149+
}
150+
state->selection = selection;
151+
char type[] = "RSA";
152+
const OSSL_PARAM params[] = {
153+
OSSL_PARAM_utf8_string(
154+
OSSL_OBJECT_PARAM_DATA_TYPE, type, sizeof(type) - 1),
155+
OSSL_PARAM_octet_string(
156+
OSSL_OBJECT_PARAM_REFERENCE, &state, sizeof(state)),
157+
OSSL_PARAM_END,
158+
};
159+
return callback(params, arg);
160+
}
161+
162+
void* RsaLoadTestLoad(const void* reference, size_t size) {
163+
if (size != sizeof(RsaLoadTestContext*)) return nullptr;
164+
auto* state = *static_cast<RsaLoadTestContext* const*>(reference);
165+
state->loads++;
166+
return state;
167+
}
168+
169+
void RsaLoadTestFree(void* context) {
170+
static_cast<RsaLoadTestContext*>(context)->frees++;
171+
}
172+
173+
int RsaLoadTestHas(const void*, int selection) {
174+
return (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) == 0;
175+
}
176+
177+
const OSSL_ALGORITHM* RsaLoadTestQuery(void*, int operation, int* no_cache) {
178+
*no_cache = 0;
179+
static const OSSL_DISPATCH decoder[] = {
180+
{OSSL_FUNC_DECODER_NEWCTX,
181+
reinterpret_cast<void (*)(void)>(RsaLoadTestDecoderNew)},
182+
{OSSL_FUNC_DECODER_FREECTX,
183+
reinterpret_cast<void (*)(void)>(RsaLoadTestDecoderFree)},
184+
{OSSL_FUNC_DECODER_DECODE,
185+
reinterpret_cast<void (*)(void)>(RsaLoadTestDecode)},
186+
{0, nullptr},
187+
};
188+
static const OSSL_DISPATCH keymgmt[] = {
189+
{OSSL_FUNC_KEYMGMT_LOAD,
190+
reinterpret_cast<void (*)(void)>(RsaLoadTestLoad)},
191+
{OSSL_FUNC_KEYMGMT_FREE,
192+
reinterpret_cast<void (*)(void)>(RsaLoadTestFree)},
193+
{OSSL_FUNC_KEYMGMT_HAS, reinterpret_cast<void (*)(void)>(RsaLoadTestHas)},
194+
{0, nullptr},
195+
};
196+
static const OSSL_ALGORITHM decoders[] = {
197+
{"RSA",
198+
"provider=node-test-rsa-load,input=der,structure=type-specific",
199+
decoder,
200+
"Test RSA decoder without export"},
201+
{nullptr, nullptr, nullptr, nullptr},
202+
};
203+
static const OSSL_ALGORITHM keymgmts[] = {
204+
{"RSA",
205+
"provider=node-test-rsa-load",
206+
keymgmt,
207+
"Test RSA reference load"},
208+
{nullptr, nullptr, nullptr, nullptr},
209+
};
210+
if (operation == OSSL_OP_DECODER) return decoders;
211+
return operation == OSSL_OP_KEYMGMT ? keymgmts : nullptr;
212+
}
213+
214+
void RsaLoadTestTeardown(void* context) {
215+
delete static_cast<RsaLoadTestContext*>(context);
216+
}
217+
218+
int RsaLoadTestProviderInit(const OSSL_CORE_HANDLE*,
219+
const OSSL_DISPATCH* in,
220+
const OSSL_DISPATCH** out,
221+
void** context) {
222+
auto state = std::make_unique<RsaLoadTestContext>();
223+
for (; in->function_id != 0; in++) {
224+
if (in->function_id == OSSL_FUNC_BIO_READ_EX) {
225+
state->read = OSSL_FUNC_BIO_read_ex(in);
226+
}
227+
}
228+
if (state->read == nullptr) return 0;
229+
static const OSSL_DISPATCH dispatch[] = {
230+
{OSSL_FUNC_PROVIDER_QUERY_OPERATION,
231+
reinterpret_cast<void (*)(void)>(RsaLoadTestQuery)},
232+
{OSSL_FUNC_PROVIDER_TEARDOWN,
233+
reinterpret_cast<void (*)(void)>(RsaLoadTestTeardown)},
234+
{0, nullptr},
235+
};
236+
*context = state.release();
237+
*out = dispatch;
238+
return 1;
239+
}
240+
} // namespace
241+
242+
TEST(NodeCrypto, ProviderPkcs1PublicKeyLoadWithoutExport) {
243+
ncrypto::ClearErrorOnReturn clear_errors;
244+
ncrypto::DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> libctx(
245+
OSSL_LIB_CTX_new());
246+
ASSERT_TRUE(libctx);
247+
ASSERT_EQ(OSSL_PROVIDER_add_builtin(
248+
libctx.get(), "node-test-rsa-load", RsaLoadTestProviderInit),
249+
1);
250+
auto* provider = OSSL_PROVIDER_load(libctx.get(), "node-test-rsa-load");
251+
auto unload_provider =
252+
node::OnScopeLeave([provider] { OSSL_PROVIDER_unload(provider); });
253+
ASSERT_NE(provider, nullptr);
254+
auto* state = static_cast<RsaLoadTestContext*>(
255+
OSSL_PROVIDER_get0_provider_ctx(provider));
256+
OSSL_LIB_CTX* previous_libctx = OSSL_LIB_CTX_set0_default(libctx.get());
257+
auto restore_libctx = node::OnScopeLeave(
258+
[previous_libctx] { OSSL_LIB_CTX_set0_default(previous_libctx); });
259+
260+
// Neither decoder export nor keymgmt import is available in this provider.
261+
const unsigned char sentinel[] = {0x42};
262+
const EVPKeyPointer::PublicKeyEncodingConfig config(
263+
false,
264+
EVPKeyPointer::PKFormatType::DER,
265+
EVPKeyPointer::PKEncodingType::PKCS1);
266+
{
267+
auto imported = EVPKeyPointer::TryParsePublicKey(config, {sentinel, 1});
268+
ASSERT_TRUE(imported);
269+
EXPECT_EQ(EVP_PKEY_get0_provider(imported.value.get()), provider);
270+
EXPECT_TRUE(imported.value.isA(KeyAlgorithm::RSA));
271+
EXPECT_EQ(state->selection, EVP_PKEY_PUBLIC_KEY);
272+
EXPECT_EQ(state->loads, 1);
273+
EXPECT_EQ(state->frees, 0);
274+
}
275+
EXPECT_EQ(state->frees, 1);
276+
}
277+
#endif
278+
92279
TEST(NodeCrypto, UnsupportedRawExports) {
93280
using Error = EVPKeyPointer::RawExportError;
94281
EVPKeyPointer key;
Lines changed: 94 additions & 0 deletions

0 commit comments

Comments
 (0)