mirror of
https://github.com/servo/servo.git
synced 2025-08-02 20:20:14 +01:00
crypto: Implement encrypt/decrypt for AES-CBC + JWK support (#33795)
* Add support for raw importKey with AES-CBC Signed-off-by: Daniel Adams <msub2official@gmail.com> * Support JWK import/export, importKey for AES-CBC Signed-off-by: Daniel Adams <msub2official@gmail.com> * Implement encrypt/decrypt for AES-CBC Signed-off-by: Daniel Adams <msub2official@gmail.com> * Update expectations Signed-off-by: Daniel Adams <msub2official@gmail.com> * Update Cargo.lock Signed-off-by: Daniel Adams <msub2official@gmail.com> * Pass MutableHandleObject as arg instead of returning raw pointer Signed-off-by: Daniel Adams <msub2official@gmail.com> * Swap order of checks in generate_key_aes_cbc - Fixes WPT tests that expect to error on algorithm first before usages Signed-off-by: Daniel Adams <msub2official@gmail.com> * Avoid potential GC hazard with array_buffer_ptr Signed-off-by: Daniel Adams <msub2official@gmail.com> * Update expectations for discards context Signed-off-by: Daniel Adams <msub2official@gmail.com> --------- Signed-off-by: Daniel Adams <msub2official@gmail.com>
This commit is contained in:
parent
397c5adf79
commit
45267c9f28
55 changed files with 163938 additions and 2799 deletions
|
@ -39,6 +39,8 @@ bincode = { workspace = true }
|
|||
bitflags = { workspace = true }
|
||||
bluetooth_traits = { workspace = true }
|
||||
canvas_traits = { workspace = true }
|
||||
cbc = { workspace = true }
|
||||
cipher = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
content-security-policy = { workspace = true }
|
||||
cookie = { workspace = true }
|
||||
|
|
|
@ -281,6 +281,10 @@ DOMInterfaces = {
|
|||
'weakReferenceable': True,
|
||||
},
|
||||
|
||||
'SubtleCrypto': {
|
||||
'inRealms': ['Encrypt', 'Decrypt', 'GenerateKey', 'ImportKey', 'ExportKey']
|
||||
},
|
||||
|
||||
#FIXME(jdm): This should be 'register': False, but then we don't generate enum types
|
||||
'TestBinding': {
|
||||
'inRealms': ['PromiseAttribute', 'PromiseNativeHandler'],
|
||||
|
@ -323,31 +327,10 @@ DOMInterfaces = {
|
|||
'canGc': ['Abort', 'GetResponseXML', 'Response'],
|
||||
},
|
||||
|
||||
'XRSession': {
|
||||
'inRealms': ['RequestReferenceSpace', 'UpdateRenderState', 'UpdateTargetFrameRate'],
|
||||
'canGc': ['End', 'RequestReferenceSpace'],
|
||||
},
|
||||
|
||||
'XRSystem': {
|
||||
'inRealms': ['RequestSession', 'SupportsSessionMode'],
|
||||
},
|
||||
|
||||
'XRBoundedReferenceSpace': {
|
||||
'canGc': ['BoundsGeometry'],
|
||||
},
|
||||
|
||||
'XRRay': {
|
||||
'canGc': ['Origin', 'Direction'],
|
||||
},
|
||||
|
||||
'XRRigidTransform': {
|
||||
'canGc': ['Position', 'Orientation', 'Inverse'],
|
||||
},
|
||||
|
||||
'XRReferenceSpace': {
|
||||
'canGc': ['GetOffsetReferenceSpace'],
|
||||
},
|
||||
|
||||
'XRFrame': {
|
||||
'canGc': ['GetViewerPose', 'GetPose', 'GetJointPose'],
|
||||
},
|
||||
|
@ -356,8 +339,25 @@ DOMInterfaces = {
|
|||
'canGc': ['GetPose'],
|
||||
},
|
||||
|
||||
'SubtleCrypto': {
|
||||
'inRealms': ['GenerateKey', 'ExportKey']
|
||||
}
|
||||
'XRRay': {
|
||||
'canGc': ['Origin', 'Direction'],
|
||||
},
|
||||
|
||||
'XRReferenceSpace': {
|
||||
'canGc': ['GetOffsetReferenceSpace'],
|
||||
},
|
||||
|
||||
'XRRigidTransform': {
|
||||
'canGc': ['Position', 'Orientation', 'Inverse'],
|
||||
},
|
||||
|
||||
'XRSession': {
|
||||
'inRealms': ['RequestReferenceSpace', 'UpdateRenderState', 'UpdateTargetFrameRate'],
|
||||
'canGc': ['End', 'RequestReferenceSpace'],
|
||||
},
|
||||
|
||||
'XRSystem': {
|
||||
'inRealms': ['RequestSession', 'SupportsSessionMode'],
|
||||
},
|
||||
|
||||
}
|
||||
|
|
|
@ -85,6 +85,8 @@ pub enum Error {
|
|||
InvalidModification,
|
||||
/// NotReadableError DOMException
|
||||
NotReadable,
|
||||
/// DataError DOMException
|
||||
Data,
|
||||
/// OperationError DOMException
|
||||
Operation,
|
||||
|
||||
|
@ -139,6 +141,7 @@ pub fn throw_dom_exception(cx: SafeJSContext, global: &GlobalScope, result: Erro
|
|||
Error::TypeMismatch => DOMErrorName::TypeMismatchError,
|
||||
Error::InvalidModification => DOMErrorName::InvalidModificationError,
|
||||
Error::NotReadable => DOMErrorName::NotReadableError,
|
||||
Error::Data => DOMErrorName::DataError,
|
||||
Error::Operation => DOMErrorName::OperationError,
|
||||
Error::Type(message) => unsafe {
|
||||
assert!(!JS_IsExceptionPending(*cx));
|
||||
|
|
|
@ -85,6 +85,10 @@ impl CryptoKey {
|
|||
self.algorithm.borrow().to_string()
|
||||
}
|
||||
|
||||
pub fn usages(&self) -> &[KeyUsage] {
|
||||
&self.usages
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> &Handle {
|
||||
&self.handle
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ pub enum DOMErrorName {
|
|||
DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR,
|
||||
EncodingError,
|
||||
NotReadableError,
|
||||
DataError,
|
||||
OperationError,
|
||||
}
|
||||
|
||||
|
@ -75,6 +76,7 @@ impl DOMErrorName {
|
|||
"DataCloneError" => Some(DOMErrorName::DataCloneError),
|
||||
"EncodingError" => Some(DOMErrorName::EncodingError),
|
||||
"NotReadableError" => Some(DOMErrorName::NotReadableError),
|
||||
"DataError" => Some(DOMErrorName::DataError),
|
||||
"OperationError" => Some(DOMErrorName::OperationError),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -123,6 +125,7 @@ impl DOMException {
|
|||
"The encoding operation (either encoded or decoding) failed."
|
||||
},
|
||||
DOMErrorName::NotReadableError => "The I/O read operation failed.",
|
||||
DOMErrorName::DataError => "Provided data is inadequate.",
|
||||
DOMErrorName::OperationError => {
|
||||
"The operation failed for an operation-specific reason."
|
||||
},
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use aes::cipher::block_padding::Pkcs7;
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
|
||||
use aes::{Aes128, Aes192, Aes256};
|
||||
use base64::prelude::*;
|
||||
use dom_struct::dom_struct;
|
||||
use js::conversions::ConversionResult;
|
||||
use js::jsapi::JSObject;
|
||||
use js::jsval::ObjectValue;
|
||||
use js::rust::MutableHandleObject;
|
||||
use js::typedarray::ArrayBufferU8;
|
||||
use servo_rand::{RngCore, ServoRng};
|
||||
|
||||
|
@ -18,7 +24,11 @@ use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
|
|||
CryptoKeyMethods, KeyType, KeyUsage,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{
|
||||
AesKeyGenParams, Algorithm, AlgorithmIdentifier, KeyAlgorithm, KeyFormat, SubtleCryptoMethods,
|
||||
AesCbcParams, AesKeyGenParams, Algorithm, AlgorithmIdentifier, JsonWebKey, KeyAlgorithm,
|
||||
KeyFormat, SubtleCryptoMethods,
|
||||
};
|
||||
use crate::dom::bindings::codegen::UnionTypes::{
|
||||
ArrayBufferViewOrArrayBuffer, ArrayBufferViewOrArrayBufferOrJsonWebKey,
|
||||
};
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
|
@ -26,6 +36,7 @@ use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
|||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::trace::RootedTraceableBox;
|
||||
use crate::dom::cryptokey::{CryptoKey, Handle};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
|
@ -80,6 +91,13 @@ const NAMED_CURVE_P521: &str = "P-521";
|
|||
#[allow(dead_code)]
|
||||
static SUPPORTED_CURVES: &[&str] = &[NAMED_CURVE_P256, NAMED_CURVE_P384, NAMED_CURVE_P521];
|
||||
|
||||
type Aes128CbcEnc = cbc::Encryptor<Aes128>;
|
||||
type Aes128CbcDec = cbc::Decryptor<Aes128>;
|
||||
type Aes192CbcEnc = cbc::Encryptor<Aes192>;
|
||||
type Aes192CbcDec = cbc::Decryptor<Aes192>;
|
||||
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
|
||||
type Aes256CbcDec = cbc::Decryptor<Aes256>;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct SubtleCrypto {
|
||||
reflector_: Reflector,
|
||||
|
@ -115,6 +133,118 @@ impl SubtleCrypto {
|
|||
}
|
||||
|
||||
impl SubtleCryptoMethods for SubtleCrypto {
|
||||
/// <https://w3c.github.io/webcrypto/#SubtleCrypto-method-encrypt>
|
||||
fn Encrypt(
|
||||
&self,
|
||||
cx: JSContext,
|
||||
algorithm: AlgorithmIdentifier,
|
||||
key: &CryptoKey,
|
||||
data: ArrayBufferViewOrArrayBuffer,
|
||||
comp: InRealm,
|
||||
) -> Rc<Promise> {
|
||||
let normalized_algorithm = normalize_algorithm(cx, algorithm, "encrypt");
|
||||
let promise = Promise::new_in_current_realm(comp);
|
||||
let data = match data {
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
|
||||
};
|
||||
|
||||
let (task_source, canceller) = self.task_source_with_canceller();
|
||||
let this = Trusted::new(self);
|
||||
let trusted_promise = TrustedPromise::new(promise.clone());
|
||||
let trusted_key = Trusted::new(key);
|
||||
let alg = normalized_algorithm.clone();
|
||||
let key_alg = key.algorithm();
|
||||
let valid_usage = key.usages().contains(&KeyUsage::Encrypt);
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(encrypt: move || {
|
||||
let subtle = this.root();
|
||||
let promise = trusted_promise.root();
|
||||
let key = trusted_key.root();
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
|
||||
let text = match alg {
|
||||
Ok(NormalizedAlgorithm::AesCbcParams(key_gen_params)) => {
|
||||
if !valid_usage || key_gen_params.name != key_alg {
|
||||
Err(Error::InvalidAccess)
|
||||
} else {
|
||||
match subtle.encrypt_aes_cbc(
|
||||
key_gen_params, &key, &data, cx, array_buffer_ptr.handle_mut()
|
||||
) {
|
||||
Ok(_) => Ok(array_buffer_ptr.handle()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Err(Error::NotSupported),
|
||||
};
|
||||
match text {
|
||||
Ok(text) => promise.resolve_native(&*text),
|
||||
Err(e) => promise.reject_error(e),
|
||||
}
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
|
||||
promise
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#SubtleCrypto-method-decrypt>
|
||||
fn Decrypt(
|
||||
&self,
|
||||
cx: JSContext,
|
||||
algorithm: AlgorithmIdentifier,
|
||||
key: &CryptoKey,
|
||||
data: ArrayBufferViewOrArrayBuffer,
|
||||
comp: InRealm,
|
||||
) -> Rc<Promise> {
|
||||
let normalized_algorithm = normalize_algorithm(cx, algorithm, "decrypt");
|
||||
let promise = Promise::new_in_current_realm(comp);
|
||||
let data = match data {
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
|
||||
};
|
||||
|
||||
let (task_source, canceller) = self.task_source_with_canceller();
|
||||
let this = Trusted::new(self);
|
||||
let trusted_promise = TrustedPromise::new(promise.clone());
|
||||
let trusted_key = Trusted::new(key);
|
||||
let alg = normalized_algorithm.clone();
|
||||
let key_alg = key.algorithm();
|
||||
let valid_usage = key.usages().contains(&KeyUsage::Decrypt);
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(decrypt: move || {
|
||||
let subtle = this.root();
|
||||
let promise = trusted_promise.root();
|
||||
let key = trusted_key.root();
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
|
||||
let text = match alg {
|
||||
Ok(NormalizedAlgorithm::AesCbcParams(key_gen_params)) => {
|
||||
if !valid_usage || key_gen_params.name != key_alg {
|
||||
Err(Error::InvalidAccess)
|
||||
} else {
|
||||
match subtle.decrypt_aes_cbc(
|
||||
key_gen_params, &key, &data, cx, array_buffer_ptr.handle_mut()
|
||||
) {
|
||||
Ok(_) => Ok(array_buffer_ptr.handle()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Err(Error::NotSupported),
|
||||
};
|
||||
match text {
|
||||
Ok(text) => promise.resolve_native(&*text),
|
||||
Err(e) => promise.reject_error(e),
|
||||
}
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
|
||||
promise
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#SubtleCrypto-method-generateKey>
|
||||
fn GenerateKey(
|
||||
&self,
|
||||
|
@ -156,8 +286,80 @@ impl SubtleCryptoMethods for SubtleCrypto {
|
|||
promise
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#SubtleCrypto-method-importKey>
|
||||
fn ImportKey(
|
||||
&self,
|
||||
cx: JSContext,
|
||||
format: KeyFormat,
|
||||
key_data: ArrayBufferViewOrArrayBufferOrJsonWebKey,
|
||||
algorithm: AlgorithmIdentifier,
|
||||
extractable: bool,
|
||||
key_usages: Vec<KeyUsage>,
|
||||
comp: InRealm,
|
||||
) -> Rc<Promise> {
|
||||
let normalized_algorithm = normalize_algorithm(cx, algorithm, "importKey");
|
||||
let promise = Promise::new_in_current_realm(comp);
|
||||
if let Err(e) = normalized_algorithm {
|
||||
promise.reject_error(e);
|
||||
return promise;
|
||||
}
|
||||
|
||||
// TODO: Figure out a way to Send this data so per-algorithm JWK checks can happen
|
||||
let data = match key_data {
|
||||
ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBufferView(view) => view.to_vec(),
|
||||
ArrayBufferViewOrArrayBufferOrJsonWebKey::JsonWebKey(json_web_key) => {
|
||||
if let Some(mut data_string) = json_web_key.k {
|
||||
while data_string.len() % 4 != 0 {
|
||||
data_string.push_str("=");
|
||||
}
|
||||
match BASE64_STANDARD.decode(data_string.to_string()) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
promise.reject_error(Error::Syntax);
|
||||
return promise;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
promise.reject_error(Error::Syntax);
|
||||
return promise;
|
||||
}
|
||||
},
|
||||
ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBuffer(array_buffer) => {
|
||||
array_buffer.to_vec()
|
||||
},
|
||||
};
|
||||
|
||||
let (task_source, canceller) = self.task_source_with_canceller();
|
||||
let this = Trusted::new(self);
|
||||
let trusted_promise = TrustedPromise::new(promise.clone());
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(import_key: move || {
|
||||
let subtle = this.root();
|
||||
let promise = trusted_promise.root();
|
||||
let alg = match normalized_algorithm {
|
||||
Ok(NormalizedAlgorithm::Algorithm(name)) => name,
|
||||
_ => {
|
||||
promise.reject_error(Error::NotSupported);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let imported_key = match alg.name.as_str() {
|
||||
ALG_AES_CBC => subtle.import_key_aes_cbc(format, &data, extractable, key_usages),
|
||||
_ => Err(Error::NotSupported),
|
||||
};
|
||||
match imported_key {
|
||||
Ok(k) => promise.resolve_native(&k),
|
||||
Err(e) => promise.reject_error(e),
|
||||
};
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
|
||||
promise
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#SubtleCrypto-method-exportKey>
|
||||
#[allow(unsafe_code)]
|
||||
fn ExportKey(&self, format: KeyFormat, key: &CryptoKey, comp: InRealm) -> Rc<Promise> {
|
||||
let promise = Promise::new_in_current_realm(comp);
|
||||
|
||||
|
@ -187,11 +389,18 @@ impl SubtleCryptoMethods for SubtleCrypto {
|
|||
};
|
||||
match exported_key {
|
||||
Ok(k) => {
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
|
||||
create_buffer_source::<ArrayBufferU8>(cx, &k, array_buffer_ptr.handle_mut())
|
||||
.expect("failed to create buffer source for exported key.");
|
||||
promise.resolve_native(&array_buffer_ptr.get())
|
||||
match k {
|
||||
AesExportedKey::Raw(k) => {
|
||||
let cx = GlobalScope::get_cx();
|
||||
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
|
||||
create_buffer_source::<ArrayBufferU8>(cx, &k, array_buffer_ptr.handle_mut())
|
||||
.expect("failed to create buffer source for exported key.");
|
||||
promise.resolve_native(&array_buffer_ptr.get())
|
||||
},
|
||||
AesExportedKey::Jwk(k) => {
|
||||
promise.resolve_native(&k)
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => promise.reject_error(e),
|
||||
}
|
||||
|
@ -207,6 +416,7 @@ impl SubtleCryptoMethods for SubtleCrypto {
|
|||
pub enum NormalizedAlgorithm {
|
||||
#[allow(dead_code)]
|
||||
Algorithm(SubtleAlgorithm),
|
||||
AesCbcParams(SubtleAesCbcParams),
|
||||
AesKeyGenParams(SubtleAesKeyGenParams),
|
||||
}
|
||||
|
||||
|
@ -227,6 +437,26 @@ impl From<DOMString> for SubtleAlgorithm {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubtleAesCbcParams {
|
||||
#[allow(dead_code)]
|
||||
pub name: String,
|
||||
pub iv: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<RootedTraceableBox<AesCbcParams>> for SubtleAesCbcParams {
|
||||
fn from(params: RootedTraceableBox<AesCbcParams>) -> Self {
|
||||
let iv = match ¶ms.iv {
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
|
||||
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
|
||||
};
|
||||
SubtleAesCbcParams {
|
||||
name: params.parent.name.to_string(),
|
||||
iv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubtleAesKeyGenParams {
|
||||
#[allow(dead_code)]
|
||||
|
@ -259,6 +489,14 @@ fn normalize_algorithm(
|
|||
return Err(Error::Syntax);
|
||||
};
|
||||
match (algorithm.name.str().to_uppercase().as_str(), operation) {
|
||||
(ALG_AES_CBC, "encrypt") | (ALG_AES_CBC, "decrypt") => {
|
||||
let params_result =
|
||||
AesCbcParams::new(cx, value.handle()).map_err(|_| Error::Operation)?;
|
||||
let ConversionResult::Success(params) = params_result else {
|
||||
return Err(Error::Syntax);
|
||||
};
|
||||
Ok(NormalizedAlgorithm::AesCbcParams(params.into()))
|
||||
},
|
||||
(ALG_AES_CBC, "generateKey") => {
|
||||
let params_result =
|
||||
AesKeyGenParams::new(cx, value.handle()).map_err(|_| Error::Operation)?;
|
||||
|
@ -267,13 +505,96 @@ fn normalize_algorithm(
|
|||
};
|
||||
Ok(NormalizedAlgorithm::AesKeyGenParams(params.into()))
|
||||
},
|
||||
_ => Err(Error::NotSupported),
|
||||
(ALG_AES_CBC, "importKey") => Ok(NormalizedAlgorithm::Algorithm(SubtleAlgorithm {
|
||||
name: ALG_AES_CBC.to_string(),
|
||||
})),
|
||||
_ => return Err(Error::NotSupported),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl SubtleCrypto {
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
fn encrypt_aes_cbc(
|
||||
&self,
|
||||
params: SubtleAesCbcParams,
|
||||
key: &CryptoKey,
|
||||
data: &[u8],
|
||||
cx: JSContext,
|
||||
handle: MutableHandleObject,
|
||||
) -> Result<(), Error> {
|
||||
if params.iv.len() != 16 {
|
||||
return Err(Error::Operation);
|
||||
}
|
||||
|
||||
let mut plaintext = Vec::from(data);
|
||||
let iv = GenericArray::from_slice(¶ms.iv);
|
||||
|
||||
let ct = match key.handle() {
|
||||
Handle::Aes128(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes128CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&mut plaintext)
|
||||
},
|
||||
Handle::Aes192(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes192CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&mut plaintext)
|
||||
},
|
||||
Handle::Aes256(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes256CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&mut plaintext)
|
||||
},
|
||||
};
|
||||
|
||||
create_buffer_source::<ArrayBufferU8>(cx, &ct, handle)
|
||||
.expect("failed to create buffer source for exported key.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
fn decrypt_aes_cbc(
|
||||
&self,
|
||||
params: SubtleAesCbcParams,
|
||||
key: &CryptoKey,
|
||||
data: &[u8],
|
||||
cx: JSContext,
|
||||
handle: MutableHandleObject,
|
||||
) -> Result<(), Error> {
|
||||
if params.iv.len() != 16 {
|
||||
return Err(Error::Operation);
|
||||
}
|
||||
|
||||
let mut ciphertext = Vec::from(data);
|
||||
let iv = GenericArray::from_slice(¶ms.iv);
|
||||
|
||||
let plaintext = match key.handle() {
|
||||
Handle::Aes128(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes128CbcDec::new(key_data, iv)
|
||||
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
|
||||
.map_err(|_| Error::Operation)?
|
||||
},
|
||||
Handle::Aes192(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes192CbcDec::new(key_data, iv)
|
||||
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
|
||||
.map_err(|_| Error::Operation)?
|
||||
},
|
||||
Handle::Aes256(data) => {
|
||||
let key_data = GenericArray::from_slice(&data);
|
||||
Aes256CbcDec::new(key_data, iv)
|
||||
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
|
||||
.map_err(|_| Error::Operation)?
|
||||
},
|
||||
};
|
||||
|
||||
create_buffer_source::<ArrayBufferU8>(cx, plaintext, handle)
|
||||
.expect("failed to create buffer source for exported key.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
fn generate_key_aes_cbc(
|
||||
&self,
|
||||
|
@ -281,9 +602,14 @@ impl SubtleCrypto {
|
|||
key_gen_params: SubtleAesKeyGenParams,
|
||||
extractable: bool,
|
||||
) -> Result<DomRoot<CryptoKey>, Error> {
|
||||
if !matches!(key_gen_params.length, 128 | 192 | 256) {
|
||||
return Err(Error::Operation);
|
||||
}
|
||||
let mut rand = vec![0; key_gen_params.length as usize];
|
||||
self.rng.borrow_mut().fill_bytes(&mut rand);
|
||||
let handle = match key_gen_params.length {
|
||||
128 => Handle::Aes128(rand),
|
||||
192 => Handle::Aes192(rand),
|
||||
256 => Handle::Aes256(rand),
|
||||
_ => return Err(Error::Operation),
|
||||
};
|
||||
|
||||
if usages.iter().any(|usage| {
|
||||
!matches!(
|
||||
|
@ -295,15 +621,6 @@ impl SubtleCrypto {
|
|||
return Err(Error::Syntax);
|
||||
}
|
||||
|
||||
let mut rand = vec![0; key_gen_params.length as usize];
|
||||
self.rng.borrow_mut().fill_bytes(&mut rand);
|
||||
let handle = match key_gen_params.length {
|
||||
128 => Handle::Aes128(rand),
|
||||
192 => Handle::Aes192(rand),
|
||||
256 => Handle::Aes256(rand),
|
||||
_ => return Err(Error::Operation),
|
||||
};
|
||||
|
||||
Ok(CryptoKey::new(
|
||||
&self.global(),
|
||||
KeyType::Secret,
|
||||
|
@ -317,18 +634,95 @@ impl SubtleCrypto {
|
|||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
fn export_key_aes_cbc(&self, format: KeyFormat, key: &CryptoKey) -> Result<Vec<u8>, Error> {
|
||||
fn import_key_aes_cbc(
|
||||
&self,
|
||||
format: KeyFormat,
|
||||
data: &[u8],
|
||||
extractable: bool,
|
||||
usages: Vec<KeyUsage>,
|
||||
) -> Result<DomRoot<CryptoKey>, Error> {
|
||||
if usages.iter().any(|usage| {
|
||||
!matches!(
|
||||
usage,
|
||||
KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
|
||||
)
|
||||
}) || usages.is_empty()
|
||||
{
|
||||
return Err(Error::Syntax);
|
||||
}
|
||||
if !matches!(format, KeyFormat::Raw | KeyFormat::Jwk) {
|
||||
return Err(Error::NotSupported);
|
||||
}
|
||||
let handle = match data.len() * 8 {
|
||||
128 => Handle::Aes128(data.to_vec()),
|
||||
192 => Handle::Aes192(data.to_vec()),
|
||||
256 => Handle::Aes256(data.to_vec()),
|
||||
_ => return Err(Error::Data),
|
||||
};
|
||||
Ok(CryptoKey::new(
|
||||
&self.global(),
|
||||
KeyType::Secret,
|
||||
extractable,
|
||||
KeyAlgorithm {
|
||||
name: DOMString::from(ALG_AES_CBC),
|
||||
},
|
||||
usages,
|
||||
handle,
|
||||
))
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
fn export_key_aes_cbc(
|
||||
&self,
|
||||
format: KeyFormat,
|
||||
key: &CryptoKey,
|
||||
) -> Result<AesExportedKey, Error> {
|
||||
match format {
|
||||
KeyFormat::Raw => match key.handle() {
|
||||
Handle::Aes128(key) => Ok(key.as_slice().to_vec()),
|
||||
Handle::Aes192(key) => Ok(key.as_slice().to_vec()),
|
||||
Handle::Aes256(key) => Ok(key.as_slice().to_vec()),
|
||||
Handle::Aes128(key) => Ok(AesExportedKey::Raw(key.as_slice().to_vec())),
|
||||
Handle::Aes192(key) => Ok(AesExportedKey::Raw(key.as_slice().to_vec())),
|
||||
Handle::Aes256(key) => Ok(AesExportedKey::Raw(key.as_slice().to_vec())),
|
||||
},
|
||||
KeyFormat::Jwk => {
|
||||
// TODO: Support jwk
|
||||
Err(Error::NotSupported)
|
||||
let (alg, k) = match key.handle() {
|
||||
Handle::Aes128(key) => data_to_jwk_params("A128CBC", key.as_slice()),
|
||||
Handle::Aes192(key) => data_to_jwk_params("A192CBC", key.as_slice()),
|
||||
Handle::Aes256(key) => data_to_jwk_params("A256CBC", key.as_slice()),
|
||||
};
|
||||
let jwk = JsonWebKey {
|
||||
alg: Some(alg),
|
||||
crv: None,
|
||||
d: None,
|
||||
dp: None,
|
||||
dq: None,
|
||||
e: None,
|
||||
ext: Some(key.Extractable()),
|
||||
k: Some(k),
|
||||
key_ops: None,
|
||||
kty: Some(DOMString::from("oct")),
|
||||
n: None,
|
||||
oth: None,
|
||||
p: None,
|
||||
q: None,
|
||||
qi: None,
|
||||
use_: None,
|
||||
x: None,
|
||||
y: None,
|
||||
};
|
||||
Ok(AesExportedKey::Jwk(jwk))
|
||||
},
|
||||
_ => Err(Error::NotSupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AesExportedKey {
|
||||
Raw(Vec<u8>),
|
||||
Jwk(JsonWebKey),
|
||||
}
|
||||
|
||||
fn data_to_jwk_params(alg: &str, key: &[u8]) -> (DOMString, DOMString) {
|
||||
let mut data = BASE64_STANDARD.encode(key);
|
||||
data.retain(|c| c != '=');
|
||||
(DOMString::from(alg), DOMString::from(data))
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ enum KeyFormat { "raw", "spki", "pkcs8", "jwk" };
|
|||
|
||||
[SecureContext,Exposed=(Window,Worker),Pref="dom.crypto.subtle.enabled"]
|
||||
interface SubtleCrypto {
|
||||
// Promise<any> encrypt(AlgorithmIdentifier algorithm,
|
||||
// CryptoKey key,
|
||||
// BufferSource data);
|
||||
// Promise<any> decrypt(AlgorithmIdentifier algorithm,
|
||||
// CryptoKey key,
|
||||
// BufferSource data);
|
||||
Promise<any> encrypt(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
BufferSource data);
|
||||
Promise<any> decrypt(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
BufferSource data);
|
||||
// Promise<any> sign(AlgorithmIdentifier algorithm,
|
||||
// CryptoKey key,
|
||||
// BufferSource data);
|
||||
|
@ -48,11 +48,11 @@ interface SubtleCrypto {
|
|||
// CryptoKey baseKey,
|
||||
// optional unsigned long? length = null);
|
||||
|
||||
// Promise<CryptoKey> importKey(KeyFormat format,
|
||||
// (BufferSource or JsonWebKey) keyData,
|
||||
// AlgorithmIdentifier algorithm,
|
||||
// boolean extractable,
|
||||
// sequence<KeyUsage> keyUsages );
|
||||
Promise<CryptoKey> importKey(KeyFormat format,
|
||||
(BufferSource or JsonWebKey) keyData,
|
||||
AlgorithmIdentifier algorithm,
|
||||
boolean extractable,
|
||||
sequence<KeyUsage> keyUsages );
|
||||
Promise<any> exportKey(KeyFormat format, CryptoKey key);
|
||||
|
||||
// Promise<any> wrapKey(KeyFormat format,
|
||||
|
@ -85,3 +85,37 @@ dictionary AesDerivedKeyParams : Algorithm {
|
|||
dictionary AesCbcParams : Algorithm {
|
||||
required BufferSource iv;
|
||||
};
|
||||
|
||||
// JWK
|
||||
dictionary RsaOtherPrimesInfo {
|
||||
// The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
|
||||
DOMString r;
|
||||
DOMString d;
|
||||
DOMString t;
|
||||
};
|
||||
|
||||
dictionary JsonWebKey {
|
||||
// The following fields are defined in Section 3.1 of JSON Web Key
|
||||
DOMString kty;
|
||||
DOMString use;
|
||||
sequence<DOMString> key_ops;
|
||||
DOMString alg;
|
||||
|
||||
// The following fields are defined in JSON Web Key Parameters Registration
|
||||
boolean ext;
|
||||
|
||||
// The following fields are defined in Section 6 of JSON Web Algorithms
|
||||
DOMString crv;
|
||||
DOMString x;
|
||||
DOMString y;
|
||||
DOMString d;
|
||||
DOMString n;
|
||||
DOMString e;
|
||||
DOMString p;
|
||||
DOMString q;
|
||||
DOMString dp;
|
||||
DOMString dq;
|
||||
DOMString qi;
|
||||
sequence<RsaOtherPrimesInfo> oth;
|
||||
DOMString k;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue