Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ext/crypto): curve25519 import export #16140

Merged
merged 1 commit into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 87 additions & 19 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"AES-CBC": null,
"AES-GCM": null,
"AES-KW": null,
"Ed25519": null,
"X25519": null,
},
"deriveBits": {
Expand Down Expand Up @@ -1049,6 +1050,10 @@
result = exportKeyEd25519(format, key, innerKey);
break;
}
case "X25519": {
result = exportKeyX25519(format, key, innerKey);
break;
}
case "AES-CTR":
case "AES-CBC":
case "AES-GCM":
Expand Down Expand Up @@ -2142,7 +2147,7 @@
return constructKey(
"public",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
Expand Down Expand Up @@ -2173,7 +2178,7 @@
return constructKey(
"public",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
Expand Down Expand Up @@ -2204,7 +2209,7 @@
return constructKey(
"private",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
Expand Down Expand Up @@ -2299,7 +2304,7 @@
// 9.
if (jwk.d !== undefined) {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const privateKeyData = ops.op_crypto_base64url(jwk.d);
const privateKeyData = ops.op_crypto_base64url_decode(jwk.d);

const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
Expand All @@ -2311,13 +2316,13 @@
return constructKey(
"private",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
} else {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const publicKeyData = ops.op_crypto_base64url(jwk.d);
const publicKeyData = ops.op_crypto_base64url_decode(jwk.x);

const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
Expand All @@ -2329,7 +2334,7 @@
return constructKey(
"public",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
Expand Down Expand Up @@ -2422,7 +2427,7 @@
return constructKey(
"private",
extractable,
[],
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
Expand All @@ -2438,7 +2443,7 @@
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_KEY_USAGES["X25519"].private,
["deriveKey", "deriveBits"],
u,
),
) !== undefined
Expand Down Expand Up @@ -2504,7 +2509,7 @@
// 9.
if (jwk.d !== undefined) {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const privateKeyData = ops.op_crypto_base64url(jwk.d);
const privateKeyData = ops.op_crypto_base64url_decode(jwk.d);

const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
Expand All @@ -2516,13 +2521,13 @@
return constructKey(
"private",
extractable,
[],
usageIntersection(keyUsages, ["deriveKey", "deriveBits"]),
algorithm,
handle,
);
} else {
// https://www.rfc-editor.org/rfc/rfc8037#section-2
const publicKeyData = ops.op_crypto_base64url(jwk.d);
const publicKeyData = ops.op_crypto_base64url_decode(jwk.x);

const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
Expand Down Expand Up @@ -3310,9 +3315,6 @@
private: ["deriveKey", "deriveBits"],
jwkUse: "enc",
},
"X25519": {
private: ["deriveKey", "deriveBits"],
},
};

function importKeyRSA(
Expand Down Expand Up @@ -4046,13 +4048,16 @@
);
}

const pkcs8Der = ops.op_export_pkcs8_ed25519(innerKey);
const pkcs8Der = ops.op_export_pkcs8_ed25519(
new Uint8Array([0x04, 0x22, ...innerKey]),
);
pkcs8Der[15] = 0x20;
return pkcs8Der.buffer;
}
case "jwk": {
const x = key[_type] === "private"
? ops.op_jwk_x_ed25519(innerKey)
: ops.op_crypto_base64url(innerKey);
: ops.op_crypto_base64url_encode(innerKey);
const jwk = {
kty: "OKP",
alg: "EdDSA",
Expand All @@ -4062,7 +4067,7 @@
ext: key[_extractable],
};
if (key[_type] === "private") {
jwk.d = ops.op_crypto_base64url(innerKey);
jwk.d = ops.op_crypto_base64url_encode(innerKey);
}
return jwk;
}
Expand All @@ -4071,6 +4076,66 @@
}
}

function exportKeyX25519(format, key, innerKey) {
switch (format) {
case "raw": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}

// 2-3.
return innerKey.buffer;
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}

const spkiDer = ops.op_export_spki_x25519(innerKey);
return spkiDer.buffer;
}
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}

const pkcs8Der = ops.op_export_pkcs8_x25519(
new Uint8Array([0x04, 0x22, ...innerKey]),
);
pkcs8Der[15] = 0x20;
return pkcs8Der.buffer;
}
case "jwk": {
if (key[_type] === "private") {
throw new DOMException("Not implemented", "NotSupportedError");
}
const x = ops.op_crypto_base64url_encode(innerKey);
const jwk = {
kty: "OKP",
crv: "X25519",
x,
"key_ops": key.usages,
ext: key[_extractable],
};
return jwk;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}

function exportKeyEC(format, key, innerKey) {
switch (format) {
case "raw": {
Expand Down Expand Up @@ -4391,7 +4456,10 @@
// 7.
if (length === null) {
return secret.buffer;
} else if (secret.length * 8 < length) {
} else if (
length === 0 || secret.buffer.byteLength * 8 < length ||
secret.length * 8 < length
) {
throw new DOMException("Invalid length", "OperationError");
} else {
return secret.subarray(0, length / 8).buffer;
Expand Down
6 changes: 4 additions & 2 deletions ext/crypto/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub fn op_import_spki_ed25519(key_data: &[u8], out: &mut [u8]) -> bool {
#[op(fast)]
pub fn op_import_pkcs8_ed25519(key_data: &[u8], out: &mut [u8]) -> bool {
// 2-3.
// This should probably use OneAsymmetricKey instead
let pk_info = match PrivateKeyInfo::from_der(key_data) {
Ok(pk_info) => pk_info,
Err(_) => return false,
Expand All @@ -81,10 +82,10 @@ pub fn op_import_pkcs8_ed25519(key_data: &[u8], out: &mut [u8]) -> bool {
}
// 6.
// CurvePrivateKey ::= OCTET STRING
if pk_info.private_key.len() != 32 {
if pk_info.private_key.len() != 34 {
return false;
}
out.copy_from_slice(pk_info.private_key);
out.copy_from_slice(&pk_info.private_key[2..]);
true
}

Expand All @@ -103,6 +104,7 @@ pub fn op_export_spki_ed25519(pubkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {

#[op]
pub fn op_export_pkcs8_ed25519(pkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {
// This should probably use OneAsymmetricKey instead
let pk_info = rsa::pkcs8::PrivateKeyInfo {
public_key: None,
algorithm: rsa::pkcs8::AlgorithmIdentifier {
Expand Down
15 changes: 12 additions & 3 deletions ext/crypto/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
op_crypto_random_uuid::decl(),
op_crypto_wrap_key::decl(),
op_crypto_unwrap_key::decl(),
op_crypto_base64url::decl(),
op_crypto_base64url_decode::decl(),
op_crypto_base64url_encode::decl(),
x25519::op_generate_x25519_keypair::decl(),
x25519::op_derive_bits_x25519::decl(),
x25519::op_import_spki_x25519::decl(),
Expand All @@ -113,6 +114,8 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
ed25519::op_export_spki_ed25519::decl(),
ed25519::op_export_pkcs8_ed25519::decl(),
ed25519::op_jwk_x_ed25519::decl(),
x25519::op_export_spki_x25519::decl(),
x25519::op_export_pkcs8_x25519::decl(),
])
.state(move |state| {
if let Some(seed) = maybe_seed {
Expand All @@ -124,12 +127,18 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
}

#[op]
pub fn op_crypto_base64url(data: String) -> ZeroCopyBuf {
pub fn op_crypto_base64url_decode(data: String) -> ZeroCopyBuf {
let data: Vec<u8> =
base64::encode_config(data, base64::URL_SAFE_NO_PAD).into();
base64::decode_config(data, base64::URL_SAFE_NO_PAD).unwrap();
data.into()
}

#[op]
pub fn op_crypto_base64url_encode(data: ZeroCopyBuf) -> String {
let data: String = base64::encode_config(data, base64::URL_SAFE_NO_PAD);
data
}

#[op]
pub fn op_crypto_get_random_values(
state: &mut OpState,
Expand Down
37 changes: 35 additions & 2 deletions ext/crypto/x25519.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use curve25519_dalek::montgomery::MontgomeryPoint;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::ZeroCopyBuf;
use elliptic_curve::pkcs8::PrivateKeyInfo;
use elliptic_curve::subtle::ConstantTimeEq;
use rand::rngs::OsRng;
use rand::RngCore;
use spki::der::Decode;
use spki::der::Encode;

#[op(fast)]
pub fn op_generate_x25519_keypair(pkey: &mut [u8], pubkey: &mut [u8]) {
Expand Down Expand Up @@ -66,6 +69,7 @@ pub fn op_import_spki_x25519(key_data: &[u8], out: &mut [u8]) -> bool {
#[op(fast)]
pub fn op_import_pkcs8_x25519(key_data: &[u8], out: &mut [u8]) -> bool {
// 2-3.
// This should probably use OneAsymmetricKey instead
let pk_info = match PrivateKeyInfo::from_der(key_data) {
Ok(pk_info) => pk_info,
Err(_) => return false,
Expand All @@ -81,9 +85,38 @@ pub fn op_import_pkcs8_x25519(key_data: &[u8], out: &mut [u8]) -> bool {
}
// 6.
// CurvePrivateKey ::= OCTET STRING
if pk_info.private_key.len() != 32 {
if pk_info.private_key.len() != 34 {
return false;
}
out.copy_from_slice(pk_info.private_key);
out.copy_from_slice(&pk_info.private_key[2..]);
true
}

#[op]
pub fn op_export_spki_x25519(pubkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {
let key_info = spki::SubjectPublicKeyInfo {
algorithm: spki::AlgorithmIdentifier {
// id-X25519
oid: X25519_OID,
parameters: None,
},
subject_public_key: pubkey,
};
Ok(key_info.to_vec()?.into())
}

#[op]
pub fn op_export_pkcs8_x25519(pkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {
// This should probably use OneAsymmetricKey instead
let pk_info = rsa::pkcs8::PrivateKeyInfo {
public_key: None,
algorithm: rsa::pkcs8::AlgorithmIdentifier {
// id-X25519
oid: X25519_OID,
parameters: None,
},
private_key: pkey, // OCTET STRING
};

Ok(pk_info.to_vec()?.into())
}
Loading