Skip to content

Commit 2143423

Browse files
committed
Implement more key pair generation
1 parent 4921f48 commit 2143423

File tree

5 files changed

+228
-18
lines changed

5 files changed

+228
-18
lines changed

src/node/internal/crypto.d.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,15 @@ export interface EdKeyPairOptions {
137137
}
138138

139139
export interface DhKeyPairOptions {
140-
prime?: BufferSource;
141-
primeLength?: number;
140+
primeOrGroup: BufferSource | number | string;
142141
generator?: number;
143142
}
144143

145144
export function generateRsaKeyPair(options: RsaKeyPairOptions): CryptoKeyPair;
146145
export function generateDsaKeyPair(options: DsaKeyPairOptions): CryptoKeyPair;
147146
export function generateEcKeyPair(options: EcKeyPairOptions): CryptoKeyPair;
148147
export function generateEdKeyPair(options: EdKeyPairOptions): CryptoKeyPair;
149-
export function generateDhKeyPair(
150-
options: string | DhKeyPairOptions
151-
): CryptoKeyPair;
148+
export function generateDhKeyPair(options: DhKeyPairOptions): CryptoKeyPair;
152149

153150
// Spkac
154151
export function verifySpkac(input: ArrayBufferView | ArrayBuffer): boolean;

src/node/internal/crypto_keys.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,10 @@ export function generateKeyPairSync(
804804
) as KeyObjectPair;
805805
}
806806
case 'dh': {
807+
if (generator != null) {
808+
validateInt32(generator, 'options.generator', 0);
809+
}
810+
807811
if (group != null || groupName != null) {
808812
if (prime != null) {
809813
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
@@ -820,7 +824,10 @@ export function generateKeyPairSync(
820824
validateString(g, 'options.group');
821825

822826
return handleKeyEncoding(
823-
cryptoImpl.generateDhKeyPair(g)
827+
cryptoImpl.generateDhKeyPair({
828+
primeOrGroup: g,
829+
generator: generator! as number,
830+
})
824831
) as KeyObjectPair;
825832
}
826833

@@ -844,14 +851,18 @@ export function generateKeyPairSync(
844851
);
845852
}
846853

847-
if (generator != null) {
848-
validateInt32(generator, 'options.generator', 0);
854+
if (prime) {
855+
return handleKeyEncoding(
856+
cryptoImpl.generateDhKeyPair({
857+
primeOrGroup: prime as BufferSource,
858+
generator: generator as number,
859+
})
860+
) as KeyObjectPair;
849861
}
850862

851863
return handleKeyEncoding(
852864
cryptoImpl.generateDhKeyPair({
853-
prime: prime as BufferSource,
854-
primeLength: primeLength as number,
865+
primeOrGroup: primeLength as number,
855866
generator: generator as number,
856867
})
857868
) as KeyObjectPair;

src/workerd/api/node/crypto-keys.c++

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@ class AsymmetricKey final: public CryptoKey::Impl {
326326
ncrypto::EVPKeyPointer key;
327327
bool isPrivate;
328328
};
329+
330+
int getCurveFromName(kj::StringPtr name) {
331+
int nid = EC_curve_nist2nid(name.begin());
332+
if (nid == NID_undef) nid = OBJ_sn2nid(name.begin());
333+
return nid;
334+
}
329335
} // namespace
330336

331337
kj::OneOf<kj::String, jsg::BufferSource, SubtleCrypto::JsonWebKey> CryptoImpl::exportKey(
@@ -635,15 +641,140 @@ CryptoKeyPair CryptoImpl::generateDsaKeyPair(DsaKeyPairOptions options) {
635641
}
636642

637643
CryptoKeyPair CryptoImpl::generateEcKeyPair(EcKeyPairOptions options) {
638-
JSG_FAIL_REQUIRE(Error, "Not yet implemented");
644+
ncrypto::ClearErrorOnReturn clearErrorOnReturn;
645+
646+
auto nid = getCurveFromName(options.namedCurve);
647+
JSG_REQUIRE(nid != NID_undef, Error, "Invalid or unsupported curve");
648+
649+
auto paramEncoding =
650+
options.paramEncoding == "named"_kj ? OPENSSL_EC_NAMED_CURVE : OPENSSL_EC_EXPLICIT_CURVE;
651+
652+
auto ecPrivateKey = ncrypto::ECKeyPointer::NewByCurveName(nid);
653+
JSG_REQUIRE(ecPrivateKey, Error, "Failed to initialize key");
654+
JSG_REQUIRE(ecPrivateKey.generate(), Error, "Failed to generate private key");
655+
656+
EC_KEY_set_enc_flags(ecPrivateKey, paramEncoding);
657+
658+
auto ecPublicKey = ncrypto::ECKeyPointer::NewByCurveName(nid);
659+
JSG_REQUIRE(EC_KEY_set_public_key(ecPublicKey, EC_KEY_get0_public_key(ecPrivateKey)), Error,
660+
"Failed to derive public key");
661+
662+
auto privateKey = ncrypto::EVPKeyPointer::New();
663+
JSG_REQUIRE(privateKey.assign(ecPrivateKey), Error, "Failed to assign private key");
664+
665+
auto publicKey = ncrypto::EVPKeyPointer::New();
666+
JSG_REQUIRE(publicKey.assign(ecPublicKey), Error, "Failed to assign public key");
667+
668+
ecPrivateKey.release();
669+
ecPublicKey.release();
670+
671+
auto pubKey = AsymmetricKey::NewPublic(kj::mv(publicKey));
672+
JSG_REQUIRE(pubKey, Error, "Failed to create public key");
673+
auto pvtKey = AsymmetricKey::NewPrivate(kj::mv(privateKey));
674+
JSG_REQUIRE(pvtKey, Error, "Failed to create private key");
675+
676+
return CryptoKeyPair{
677+
.publicKey = jsg::alloc<CryptoKey>(kj::mv(pubKey)),
678+
.privateKey = jsg::alloc<CryptoKey>(kj::mv(pvtKey)),
679+
};
639680
}
640681

641682
CryptoKeyPair CryptoImpl::generateEdKeyPair(EdKeyPairOptions options) {
642-
JSG_FAIL_REQUIRE(Error, "Not yet implemented");
683+
ncrypto::ClearErrorOnReturn clearErrorOnReturn;
684+
685+
auto nid = ([&] {
686+
if (options.type == "ed25519") {
687+
return EVP_PKEY_ED25519;
688+
}
689+
if (options.type == "x25519") {
690+
return EVP_PKEY_X25519;
691+
}
692+
return NID_undef;
693+
})();
694+
JSG_REQUIRE(nid != NID_undef, Error, "Invalid or unsupported curve");
695+
696+
auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(nid);
697+
JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
698+
JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen");
699+
700+
// Generate the key
701+
EVP_PKEY* pkey = nullptr;
702+
JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
703+
704+
auto generated = ncrypto::EVPKeyPointer(pkey);
705+
706+
auto publicKey = AsymmetricKey::NewPublic(generated.clone());
707+
JSG_REQUIRE(publicKey, Error, "Failed to create public key");
708+
auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
709+
JSG_REQUIRE(privateKey, Error, "Failed to create private key");
710+
711+
return CryptoKeyPair{
712+
.publicKey = jsg::alloc<CryptoKey>(kj::mv(publicKey)),
713+
.privateKey = jsg::alloc<CryptoKey>(kj::mv(privateKey)),
714+
};
643715
}
644716

645-
CryptoKeyPair CryptoImpl::generateDhKeyPair(kj::OneOf<DhKeyPairOptions, kj::String>) {
646-
JSG_FAIL_REQUIRE(Error, "Not yet implemented");
717+
CryptoKeyPair CryptoImpl::generateDhKeyPair(DhKeyPairOptions options) {
718+
ncrypto::ClearErrorOnReturn clearErrorOnReturn;
719+
720+
static constexpr uint32_t kStandardizedGenerator = 2;
721+
722+
ncrypto::EVPKeyPointer key_params;
723+
auto generator = options.generator.orDefault(kStandardizedGenerator);
724+
725+
KJ_SWITCH_ONEOF(options.primeOrGroup) {
726+
KJ_CASE_ONEOF(group, kj::String) {
727+
std::string_view group_name(group.begin(), group.size());
728+
auto found = ncrypto::DHPointer::FindGroup(group_name);
729+
JSG_REQUIRE(found, Error, "Invalid or unsupported group");
730+
731+
auto bn_g = ncrypto::BignumPointer::New();
732+
JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, "Failed to set generator");
733+
734+
auto dh = ncrypto::DHPointer::New(kj::mv(found), kj::mv(bn_g));
735+
JSG_REQUIRE(dh, Error, "Failed to create DH key");
736+
737+
key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));
738+
}
739+
KJ_CASE_ONEOF(prime, jsg::BufferSource) {
740+
ncrypto::BignumPointer bn(prime.asArrayPtr().begin(), prime.size());
741+
742+
auto bn_g = ncrypto::BignumPointer::New();
743+
JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, "Failed to set generator");
744+
745+
auto dh = ncrypto::DHPointer::New(kj::mv(bn), kj::mv(bn_g));
746+
JSG_REQUIRE(dh, Error, "Failed to create DH key");
747+
748+
key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));
749+
}
750+
KJ_CASE_ONEOF(length, uint32_t) {
751+
// TODO(later): BoringSSL appears to not implement DH key generation
752+
// from a prime length the same way Node.js does. For now, defer this
753+
// and come back to implement later.
754+
JSG_FAIL_REQUIRE(Error, "Generating DH keys from a prime length is not yet implemented");
755+
}
756+
}
757+
758+
JSG_REQUIRE(key_params, Error, "Failed to create keygen context");
759+
auto ctx = key_params.newCtx();
760+
JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
761+
JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen context");
762+
763+
// Generate the key
764+
EVP_PKEY* pkey = nullptr;
765+
JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
766+
767+
auto generated = ncrypto::EVPKeyPointer(pkey);
768+
769+
auto publicKey = AsymmetricKey::NewPublic(generated.clone());
770+
JSG_REQUIRE(publicKey, Error, "Failed to create public key");
771+
auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
772+
JSG_REQUIRE(privateKey, Error, "Failed to create private key");
773+
774+
return CryptoKeyPair{
775+
.publicKey = jsg::alloc<CryptoKey>(kj::mv(publicKey)),
776+
.privateKey = jsg::alloc<CryptoKey>(kj::mv(privateKey)),
777+
};
647778
}
648779

649780
} // namespace workerd::api::node

src/workerd/api/node/crypto.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,16 @@ class CryptoImpl final: public jsg::Object {
234234
};
235235

236236
struct DhKeyPairOptions {
237-
jsg::Optional<jsg::BufferSource> prime;
238-
jsg::Optional<uint32_t> primeLength;
237+
kj::OneOf<jsg::BufferSource, uint32_t, kj::String> primeOrGroup;
239238
jsg::Optional<uint32_t> generator;
240-
JSG_STRUCT(prime, primeLength, generator);
239+
JSG_STRUCT(primeOrGroup, generator);
241240
};
242241

243242
CryptoKeyPair generateRsaKeyPair(RsaKeyPairOptions options);
244243
CryptoKeyPair generateDsaKeyPair(DsaKeyPairOptions options);
245244
CryptoKeyPair generateEcKeyPair(EcKeyPairOptions options);
246245
CryptoKeyPair generateEdKeyPair(EdKeyPairOptions options);
247-
CryptoKeyPair generateDhKeyPair(kj::OneOf<DhKeyPairOptions, kj::String>);
246+
CryptoKeyPair generateDhKeyPair(DhKeyPairOptions options);
248247

249248
bool verifySpkac(kj::Array<const kj::byte> input);
250249
kj::Maybe<jsg::BufferSource> exportPublicKey(jsg::Lock& js, kj::Array<const kj::byte> input);

src/workerd/api/node/tests/crypto_keys-test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
generateKeySync,
1212
generateKeyPair,
1313
generateKeyPairSync,
14+
generatePrimeSync,
15+
getDiffieHellman,
1416
} from 'node:crypto';
1517
import { Buffer } from 'node:buffer';
1618

@@ -1953,3 +1955,73 @@ export const generate_rsa_key_pair_enc = {
19531955
strictEqual(imported.asymmetricKeyDetails.publicExponent, 65537n);
19541956
},
19551957
};
1958+
1959+
export const generate_ec_key_pair = {
1960+
test() {
1961+
const { privateKey, publicKey } = generateKeyPairSync('ec', {
1962+
namedCurve: 'P-256',
1963+
});
1964+
strictEqual(publicKey.type, 'public');
1965+
strictEqual(publicKey.asymmetricKeyDetails.namedCurve, 'prime256v1');
1966+
strictEqual(privateKey.type, 'private');
1967+
strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'prime256v1');
1968+
},
1969+
};
1970+
1971+
export const generate_ed25519_key_pair = {
1972+
test() {
1973+
const { privateKey, publicKey } = generateKeyPairSync('ed25519', {});
1974+
strictEqual(publicKey.type, 'public');
1975+
strictEqual(privateKey.type, 'private');
1976+
strictEqual(publicKey.asymmetricKeyType, 'ed25519');
1977+
strictEqual(privateKey.asymmetricKeyType, 'ed25519');
1978+
},
1979+
};
1980+
1981+
export const generate_x25519_key_pair = {
1982+
test() {
1983+
const { privateKey, publicKey } = generateKeyPairSync('x25519', {});
1984+
strictEqual(publicKey.type, 'public');
1985+
strictEqual(privateKey.type, 'private');
1986+
strictEqual(publicKey.asymmetricKeyType, 'x25519');
1987+
strictEqual(privateKey.asymmetricKeyType, 'x25519');
1988+
},
1989+
};
1990+
1991+
export const generate_dh_key_pair = {
1992+
test() {
1993+
const { privateKey, publicKey } = generateKeyPairSync('dh', {
1994+
group: 'modp14',
1995+
});
1996+
strictEqual(publicKey.type, 'public');
1997+
strictEqual(privateKey.type, 'private');
1998+
strictEqual(publicKey.asymmetricKeyType, 'dh');
1999+
strictEqual(privateKey.asymmetricKeyType, 'dh');
2000+
},
2001+
};
2002+
2003+
export const generate_dh_from_fixed_prime = {
2004+
test() {
2005+
const prime = generatePrimeSync(1024);
2006+
const { privateKey, publicKey } = generateKeyPairSync('dh', { prime });
2007+
strictEqual(publicKey.type, 'public');
2008+
strictEqual(privateKey.type, 'private');
2009+
strictEqual(publicKey.asymmetricKeyType, 'dh');
2010+
strictEqual(privateKey.asymmetricKeyType, 'dh');
2011+
},
2012+
};
2013+
2014+
export const generate_dh_key_pair_by_length = {
2015+
test() {
2016+
throws(
2017+
() =>
2018+
generateKeyPairSync('dh', {
2019+
primeLength: 1048,
2020+
}),
2021+
{
2022+
message:
2023+
'Generating DH keys from a prime length is not yet implemented',
2024+
}
2025+
);
2026+
},
2027+
};

0 commit comments

Comments
 (0)