Encryption-at-rest: Support for AES-256-GCM and ChaCha20-Poly1305 for S/MIME (closes #161)

This commit is contained in:
Maurus Decimus
2026-06-25 09:53:07 +02:00
parent 9525a891f3
commit ab7d81e486
13 changed files with 406 additions and 88 deletions

View File

@@ -16,6 +16,7 @@ use crate::{
},
};
use common::{
Server,
auth::{
AccessToken, Permissions, PermissionsGroup,
credential::{ApiKey, AppPassword},
@@ -23,6 +24,7 @@ use common::{
},
cache::invalidate::CacheInvalidationBuilder,
ipc::CacheInvalidation,
storage::encryption::{EncryptionMethod, parse_public_key},
};
use directory::core::secret::{SecretVerificationResult, hash_secret, verify_mfa_secret_hash};
use jmap_proto::{error::set::SetError, request::MaybeInvalid, types::state::State};
@@ -33,8 +35,8 @@ use registry::{
enums::{CredentialType, StorageQuota},
prelude::{MASKED_PASSWORD, Object, ObjectInner, ObjectType, Property},
structs::{
Account, AccountPassword, AccountSettings, Credential, CredentialPermissions, OtpAuth,
SecondaryCredential,
Account, AccountPassword, AccountSettings, Credential, CredentialPermissions,
EncryptionAtRest, OtpAuth, PublicKey, SecondaryCredential,
},
},
types::{datetime::UTCDateTime, id::ObjectId},
@@ -105,6 +107,22 @@ pub(crate) async fn account_set(
}
}
if account.encryption_at_rest != old_account.encryption_at_rest
&& let Some(algorithm) =
unsupported_pgp_algorithm(set.server, &account.encryption_at_rest).await?
{
account = old_account.clone();
set.response.not_updated.append(
id,
SetError::invalid_properties()
.with_property(Property::EncryptionAtRest)
.with_description(format!(
"{algorithm} is only supported for S/MIME encryption, but the selected public key is an OpenPGP key."
)),
);
break 'outer;
}
set.response.updated.append(id, None);
}
}
@@ -897,6 +915,32 @@ pub(crate) async fn credential_query(
Ok(response)
}
async fn unsupported_pgp_algorithm(
server: &Server,
encryption_at_rest: &EncryptionAtRest,
) -> trc::Result<Option<&'static str>> {
let (settings, algorithm) = match encryption_at_rest {
EncryptionAtRest::Aes256Gcm(settings) => (settings, "AES-256-GCM"),
EncryptionAtRest::ChaCha20Poly1305(settings) => (settings, "ChaCha20-Poly1305"),
_ => return Ok(None),
};
if let Some(public_key) = server
.registry()
.object::<PublicKey>(settings.public_key)
.await
.caused_by(trc::location!())?
&& matches!(
parse_public_key(&public_key),
Ok(Some(params)) if params.method == EncryptionMethod::PGP
)
{
Ok(Some(algorithm))
} else {
Ok(None)
}
}
pub(crate) fn validate_credential_permissions(
access_token: &AccessToken,
credential: &SecondaryCredential,