Skip to content

Commit

Permalink
fix(ext/crypto): curve25519 import export (#16140)
Browse files Browse the repository at this point in the history
  • Loading branch information
panva authored Oct 4, 2022
1 parent fde9381 commit 7742ad7
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 85 deletions.
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

0 comments on commit 7742ad7

Please sign in to comment.