Skip to content

Commit

Permalink
feat(runtime,cli,serverless): support RSA-OAEP for `SubtleCrypto#encr…
Browse files Browse the repository at this point in the history
…ypt` & `SubtleCrypto#decrypt` (#889)
  • Loading branch information
akitaSummer authored May 21, 2023
1 parent 502a898 commit 62447ba
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changeset/pink-carpets-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@lagon/cli': patch
'@lagon/runtime': patch
'@lagon/serverless': patch
---

Support RSA-OAEP for `SubtleCrypto#encrypt` & `SubtleCrypto#decrypto`
88 changes: 88 additions & 0 deletions crates/runtime/tests/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,94 @@ async fn crypto_decrypt_aes_ctr() {
.await;
}

#[tokio::test]
async fn crypto_encrypt_rsa_oaep() {
utils::setup();
let (send, receiver) = utils::create_isolate(
IsolateOptions::new(
"export async function handler() {
const key = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
},
true,
['encrypt'],
);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
key.publicKey,
new TextEncoder().encode('hello, world'),
);
return new Response(`${ciphertext instanceof Uint8Array} ${ciphertext.length}`);
}"
.into(),
)
.tick_timeout(Duration::from_secs(5))
.total_timeout(Duration::from_secs(10)),
);
send(Request::default());

utils::assert_response(
&receiver,
Response::builder()
.header(CONTENT_TYPE, "text/plain;charset=UTF-8")
.body("true 128".into())
.unwrap(),
)
.await;
}

#[tokio::test]
async fn crypto_decrypt_rsa_oaep() {
utils::setup();
let (send, receiver) = utils::create_isolate(
IsolateOptions::new(
"export async function handler() {
const key = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
},
true,
['encrypt'],
);
const counter = crypto.getRandomValues(new Uint8Array(16));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
key.publicKey,
new TextEncoder().encode('hello, world'),
);
const text = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
key.privateKey,
ciphertext,
);
return new Response(new TextDecoder().decode(text));
}"
.into(),
)
.tick_timeout(Duration::from_secs(5))
.total_timeout(Duration::from_secs(10)),
);
send(Request::default());

utils::assert_response(
&receiver,
Response::builder()
.header(CONTENT_TYPE, "text/plain;charset=UTF-8")
.body("hello, world".into())
.unwrap(),
)
.await;
}

#[tokio::test]
async fn crypto_hkdf_derive_bits() {
utils::setup();
Expand Down
14 changes: 14 additions & 0 deletions crates/runtime_crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub enum Algorithm {
RsaPss(u32),
RsassaPkcs1v15,
Ecdsa(Sha),
RsaOaep(Option<Vec<u8>>),
}

#[derive(Debug)]
Expand Down Expand Up @@ -123,6 +124,19 @@ pub fn extract_algorithm_object(

return Ok(Algorithm::AesCtr(counter, length));
}

if name == "RSA-OAEP" {
let label_key = v8_string(scope, "label").into();
let label = match algorithm.get(scope, label_key) {
Some(label) => match label.is_uint8_array() {
false => None,
true => Some(extract_v8_uint8array(label)?),
},
None => None,
};

return Ok(Algorithm::RsaOaep(label));
}
}

Err(anyhow!("Algorithm not supported"))
Expand Down
15 changes: 15 additions & 0 deletions crates/runtime_crypto/src/methods/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use ctr::cipher::StreamCipher;
use ctr::Ctr128BE;
use ctr::Ctr32BE;
use ctr::Ctr64BE;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::Oaep;
use rsa::RsaPrivateKey;
use sha2::Sha256;

use crate::{Aes256Gcm, Algorithm};

Expand Down Expand Up @@ -38,6 +42,17 @@ pub fn decrypt(algorithm: Algorithm, key_value: Vec<u8>, data: Vec<u8>) -> Resul
"invalid counter length. Currently supported 32/64/128 bits",
)),
},
Algorithm::RsaOaep(label) => {
let private_key = RsaPrivateKey::from_pkcs1_der(&key_value)?;
let padding = match label {
Some(buf) => Oaep::new_with_label::<Sha256, String>(String::from_utf8(buf)?),
None => Oaep::new::<Sha256>(),
};

private_key
.decrypt(padding, &data)
.map_err(|e| anyhow!(e.to_string()))
}
_ => Err(anyhow!("Algorithm not supported")),
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/runtime_crypto/src/methods/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ use ctr::cipher::StreamCipher;
use ctr::Ctr128BE;
use ctr::Ctr32BE;
use ctr::Ctr64BE;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::rand_core::OsRng;
use rsa::Oaep;
use rsa::RsaPrivateKey;
use sha2::Sha256;

use crate::{Aes256Gcm, Algorithm};

Expand Down Expand Up @@ -35,6 +40,19 @@ pub fn encrypt(algorithm: Algorithm, key_value: Vec<u8>, data: Vec<u8>) -> Resul
"invalid counter length. Currently supported 32/64/128 bits",
)),
},
Algorithm::RsaOaep(label) => {
let public_key = RsaPrivateKey::from_pkcs1_der(&key_value)?.to_public_key();
let mut rng = OsRng;
let padding = match label {
Some(buf) => Oaep::new_with_label::<Sha256, String>(String::from_utf8(buf)?),
None => Oaep::new::<Sha256>(),
};
let encrypted = public_key
.encrypt(&mut rng, padding, &data)
.map_err(|_| anyhow!("Encryption failed"))?;

Ok(encrypted)
}
_ => Err(anyhow!("Algorithm not supported")),
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/pages/runtime-apis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ The standard `CryptoSubtle` object. [See the documentation on MDN](https://devel
| RSA-PSS || | | | |
| ECDSA || | | | |
| HMAC || | | | |
| RSA-OAEP | | | | | |
| RSA-OAEP | | | | | |
| AES-CTR | || | ||
| AES-CBC | || | ||
| AES-GCM | || | ||
Expand Down

1 comment on commit 62447ba

@vercel
Copy link

@vercel vercel bot commented on 62447ba May 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./packages/docs

lagon-docs.vercel.app
docs-lagon.vercel.app
docs-git-main-lagon.vercel.app
docs.lagon.app

Please sign in to comment.