Add project files.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::api;
|
||||
use crate::auth::AuthCredentialWithPniZkcPresentation;
|
||||
use crate::common::constants::*;
|
||||
use crate::common::errors::*;
|
||||
use crate::common::simple_types::*;
|
||||
|
||||
#[derive(derive_more::From)]
|
||||
pub enum AnyAuthCredentialPresentation {
|
||||
V4(AuthCredentialWithPniZkcPresentation),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialDefault, derive_more::TryFrom)]
|
||||
#[try_from(repr)]
|
||||
enum PresentationVersion {
|
||||
// V1-V3 are no longer supported.
|
||||
#[partial_default]
|
||||
V4 = PRESENTATION_VERSION_4,
|
||||
}
|
||||
|
||||
impl From<PresentationVersion> for u8 {
|
||||
fn from(value: PresentationVersion) -> Self {
|
||||
value as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyAuthCredentialPresentation {
|
||||
pub fn new(presentation_bytes: &[u8]) -> Result<Self, ZkGroupDeserializationFailure> {
|
||||
let first = *presentation_bytes
|
||||
.first()
|
||||
.ok_or(ZkGroupDeserializationFailure::new::<Self>())?;
|
||||
let version = PresentationVersion::try_from(first)
|
||||
.map_err(|_| ZkGroupDeserializationFailure::new::<Self>())?;
|
||||
match version {
|
||||
PresentationVersion::V4 => Ok(crate::deserialize::<
|
||||
AuthCredentialWithPniZkcPresentation,
|
||||
>(presentation_bytes)?
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_aci_ciphertext(&self) -> api::groups::UuidCiphertext {
|
||||
match self {
|
||||
AnyAuthCredentialPresentation::V4(presentation) => presentation.aci_ciphertext(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pni_ciphertext(&self) -> api::groups::UuidCiphertext {
|
||||
match self {
|
||||
AnyAuthCredentialPresentation::V4(presentation) => presentation.pni_ciphertext(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_redemption_time(&self) -> Timestamp {
|
||||
match self {
|
||||
AnyAuthCredentialPresentation::V4(presentation) => presentation.redemption_time(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AnyAuthCredentialPresentation {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
AnyAuthCredentialPresentation::V4(presentation) => presentation.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright 2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use libsignal_core::{Aci, Pni};
|
||||
use partial_default::PartialDefault;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::auth::AnyAuthCredentialPresentation;
|
||||
use crate::groups::GroupSecretParams;
|
||||
use crate::{
|
||||
RandomnessBytes, ServerPublicParams, ZkGroupDeserializationFailure, ZkGroupVerificationFailure,
|
||||
};
|
||||
|
||||
mod zkc;
|
||||
pub use zkc::{
|
||||
AuthCredentialWithPniZkc, AuthCredentialWithPniZkcPresentation,
|
||||
AuthCredentialWithPniZkcResponse,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialDefault, derive_more::From)]
|
||||
pub enum AuthCredentialWithPni {
|
||||
#[partial_default]
|
||||
Zkc(AuthCredentialWithPniZkc),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialDefault, derive_more::From)]
|
||||
pub enum AuthCredentialWithPniResponse {
|
||||
#[partial_default]
|
||||
Zkc(AuthCredentialWithPniZkcResponse),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialDefault, derive_more::TryFrom)]
|
||||
#[try_from(repr)]
|
||||
pub enum AuthCredentialWithPniVersion {
|
||||
#[partial_default]
|
||||
Zkc = 3,
|
||||
}
|
||||
|
||||
impl AuthCredentialWithPni {
|
||||
pub fn new(bytes: &[u8]) -> Result<Self, ZkGroupDeserializationFailure> {
|
||||
let first = bytes
|
||||
.first()
|
||||
.ok_or_else(ZkGroupDeserializationFailure::new::<Self>)?;
|
||||
let version = AuthCredentialWithPniVersion::try_from(*first)
|
||||
.map_err(|_| ZkGroupDeserializationFailure::new::<Self>())?;
|
||||
match version {
|
||||
AuthCredentialWithPniVersion::Zkc => {
|
||||
crate::common::serialization::deserialize(bytes).map(Self::Zkc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn present(
|
||||
&self,
|
||||
public_params: &ServerPublicParams,
|
||||
group_secret_params: &GroupSecretParams,
|
||||
randomness: RandomnessBytes,
|
||||
) -> AnyAuthCredentialPresentation {
|
||||
match self {
|
||||
Self::Zkc(credential) => AnyAuthCredentialPresentation::V4(credential.present(
|
||||
public_params,
|
||||
group_secret_params,
|
||||
randomness,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthCredentialWithPniResponse {
|
||||
pub fn new(bytes: &[u8]) -> Result<Self, ZkGroupDeserializationFailure> {
|
||||
let first = bytes
|
||||
.first()
|
||||
.ok_or_else(ZkGroupDeserializationFailure::new::<Self>)?;
|
||||
let version = AuthCredentialWithPniVersion::try_from(*first)
|
||||
.map_err(|_| ZkGroupDeserializationFailure::new::<Self>())?;
|
||||
match version {
|
||||
AuthCredentialWithPniVersion::Zkc => {
|
||||
crate::common::serialization::deserialize(bytes).map(Self::Zkc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive(
|
||||
self,
|
||||
public_params: &ServerPublicParams,
|
||||
aci: Aci,
|
||||
pni: Pni,
|
||||
redemption_time: crate::Timestamp,
|
||||
) -> Result<AuthCredentialWithPni, ZkGroupVerificationFailure> {
|
||||
match self {
|
||||
Self::Zkc(credential) => credential
|
||||
.receive(aci, pni, redemption_time, public_params)
|
||||
.map(AuthCredentialWithPni::Zkc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AuthCredentialWithPni {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Zkc(z) => z.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AuthCredentialWithPniResponse {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Zkc(z) => z.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use libsignal_core::{Aci, Pni};
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zkcredential::credentials::{CredentialKeyPair, CredentialPublicKey};
|
||||
|
||||
use crate::api::auth::auth_credential_with_pni::AuthCredentialWithPniVersion;
|
||||
use crate::common::constants::PRESENTATION_VERSION_4;
|
||||
use crate::common::serialization::VersionByte;
|
||||
use crate::common::simple_types::{RandomnessBytes, Timestamp};
|
||||
use crate::crypto::uid_encryption;
|
||||
use crate::crypto::uid_struct::UidStruct;
|
||||
use crate::groups::{GroupPublicParams, GroupSecretParams, UuidCiphertext};
|
||||
use crate::{ServerPublicParams, ServerSecretParams, ZkGroupVerificationFailure};
|
||||
|
||||
const CREDENTIAL_LABEL: &[u8] = b"20240222_Signal_AuthCredentialZkc";
|
||||
|
||||
/// Authentication credential implemented using [`zkcredential`].
|
||||
///
|
||||
/// The same credential as [`crate::api::auth::AuthCredentialWithPni`] but
|
||||
/// implemented using the types and mechanism from the `zkcredential` crate.
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct AuthCredentialWithPniZkc {
|
||||
version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
|
||||
credential: zkcredential::credentials::Credential,
|
||||
aci: UidStruct,
|
||||
pni: UidStruct,
|
||||
redemption_time: Timestamp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct AuthCredentialWithPniZkcResponse {
|
||||
version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
|
||||
proof: zkcredential::issuance::IssuanceProof,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct AuthCredentialWithPniZkcPresentation {
|
||||
version: VersionByte<PRESENTATION_VERSION_4>,
|
||||
proof: zkcredential::presentation::PresentationProof,
|
||||
aci_ciphertext: uid_encryption::Ciphertext,
|
||||
pni_ciphertext: uid_encryption::Ciphertext,
|
||||
redemption_time: Timestamp,
|
||||
}
|
||||
|
||||
impl AuthCredentialWithPniZkcResponse {
|
||||
pub fn issue_credential(
|
||||
aci: Aci,
|
||||
pni: Pni,
|
||||
redemption_time: Timestamp,
|
||||
params: &ServerSecretParams,
|
||||
randomness: RandomnessBytes,
|
||||
) -> Self {
|
||||
Self::issue_credential_for_key(
|
||||
aci,
|
||||
pni,
|
||||
redemption_time,
|
||||
¶ms.generic_credential_key_pair,
|
||||
randomness,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn receive(
|
||||
self,
|
||||
aci: Aci,
|
||||
pni: Pni,
|
||||
redemption_time: Timestamp,
|
||||
public_params: &ServerPublicParams,
|
||||
) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
|
||||
self.receive_for_key(
|
||||
aci,
|
||||
pni,
|
||||
redemption_time,
|
||||
&public_params.generic_credential_public_key,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn issue_credential_for_key(
|
||||
aci: Aci,
|
||||
pni: Pni,
|
||||
redemption_time: Timestamp,
|
||||
credential_key: &CredentialKeyPair,
|
||||
randomness: RandomnessBytes,
|
||||
) -> Self {
|
||||
let proof = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
|
||||
.add_attribute(&UidStruct::from_service_id(aci.into()))
|
||||
.add_attribute(&UidStruct::from_service_id(pni.into()))
|
||||
.add_public_attribute(&redemption_time)
|
||||
.issue(credential_key, randomness);
|
||||
|
||||
Self {
|
||||
version: VersionByte,
|
||||
proof,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn receive_for_key(
|
||||
self,
|
||||
aci: Aci,
|
||||
pni: Pni,
|
||||
redemption_time: Timestamp,
|
||||
public_key: &CredentialPublicKey,
|
||||
) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
|
||||
if !redemption_time.is_day_aligned() {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
let aci = UidStruct::from_service_id(aci.into());
|
||||
let pni = UidStruct::from_service_id(pni.into());
|
||||
|
||||
let raw_credential = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
|
||||
.add_attribute(&aci)
|
||||
.add_attribute(&pni)
|
||||
.add_public_attribute(&redemption_time)
|
||||
.verify(public_key, self.proof)?;
|
||||
|
||||
Ok(AuthCredentialWithPniZkc {
|
||||
credential: raw_credential,
|
||||
version: VersionByte,
|
||||
aci,
|
||||
pni,
|
||||
redemption_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthCredentialWithPniZkc {
|
||||
pub fn present(
|
||||
&self,
|
||||
public_params: &ServerPublicParams,
|
||||
group_secret_params: &GroupSecretParams,
|
||||
randomness: RandomnessBytes,
|
||||
) -> AuthCredentialWithPniZkcPresentation {
|
||||
self.present_for_key(
|
||||
&public_params.generic_credential_public_key,
|
||||
group_secret_params,
|
||||
randomness,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn present_for_key(
|
||||
&self,
|
||||
public_key: &CredentialPublicKey,
|
||||
group_secret_params: &GroupSecretParams,
|
||||
randomness: RandomnessBytes,
|
||||
) -> AuthCredentialWithPniZkcPresentation {
|
||||
let Self {
|
||||
aci,
|
||||
credential,
|
||||
pni,
|
||||
redemption_time,
|
||||
version: _,
|
||||
} = self;
|
||||
|
||||
let proof = zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
|
||||
.add_attribute(aci, &group_secret_params.uid_enc_key_pair)
|
||||
.add_attribute(pni, &group_secret_params.uid_enc_key_pair)
|
||||
.present(public_key, credential, randomness);
|
||||
|
||||
AuthCredentialWithPniZkcPresentation {
|
||||
aci_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.aci),
|
||||
pni_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.pni),
|
||||
proof,
|
||||
redemption_time: *redemption_time,
|
||||
version: VersionByte,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthCredentialWithPniZkcPresentation {
|
||||
pub fn verify(
|
||||
&self,
|
||||
params: &ServerSecretParams,
|
||||
group_public_params: &GroupPublicParams,
|
||||
redemption_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
self.verify_for_key(
|
||||
¶ms.generic_credential_key_pair,
|
||||
group_public_params,
|
||||
redemption_time,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn verify_for_key(
|
||||
&self,
|
||||
credential_key: &CredentialKeyPair,
|
||||
group_public_params: &GroupPublicParams,
|
||||
redemption_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
|
||||
.add_attribute(
|
||||
&self.aci_ciphertext,
|
||||
&group_public_params.uid_enc_public_key,
|
||||
)
|
||||
.add_attribute(
|
||||
&self.pni_ciphertext,
|
||||
&group_public_params.uid_enc_public_key,
|
||||
)
|
||||
.add_public_attribute(&redemption_time)
|
||||
.verify(credential_key, &self.proof)
|
||||
.map_err(|_| ZkGroupVerificationFailure)
|
||||
}
|
||||
|
||||
pub fn aci_ciphertext(&self) -> UuidCiphertext {
|
||||
UuidCiphertext {
|
||||
reserved: Default::default(),
|
||||
ciphertext: self.aci_ciphertext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pni_ciphertext(&self) -> UuidCiphertext {
|
||||
UuidCiphertext {
|
||||
reserved: Default::default(),
|
||||
ciphertext: self.pni_ciphertext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redemption_time(&self) -> Timestamp {
|
||||
self.redemption_time
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use zkcredential::RANDOMNESS_LEN;
|
||||
|
||||
use super::*;
|
||||
use crate::SECONDS_PER_DAY;
|
||||
|
||||
#[test]
|
||||
fn issue_receive_present() {
|
||||
const ACI: Aci = Aci::from_uuid_bytes([b'a'; 16]);
|
||||
const PNI: Pni = Pni::from_uuid_bytes([b'p'; 16]);
|
||||
const REDEMPTION_TIME: Timestamp = Timestamp::from_epoch_seconds(12345 * SECONDS_PER_DAY);
|
||||
|
||||
let credential_key = CredentialKeyPair::generate([1; RANDOMNESS_LEN]);
|
||||
let public_key = credential_key.public_key();
|
||||
let group_secret_params = GroupSecretParams::generate([2; RANDOMNESS_LEN]);
|
||||
|
||||
let response = AuthCredentialWithPniZkcResponse::issue_credential_for_key(
|
||||
ACI,
|
||||
PNI,
|
||||
REDEMPTION_TIME,
|
||||
&credential_key,
|
||||
[3; RANDOMNESS_LEN],
|
||||
);
|
||||
|
||||
let credential = response
|
||||
.receive_for_key(ACI, PNI, REDEMPTION_TIME, public_key)
|
||||
.expect("is valid");
|
||||
|
||||
let presentation =
|
||||
credential.present_for_key(public_key, &group_secret_params, [4; RANDOMNESS_LEN]);
|
||||
|
||||
presentation
|
||||
.verify_for_key(
|
||||
&credential_key,
|
||||
&group_secret_params.get_public_params(),
|
||||
REDEMPTION_TIME,
|
||||
)
|
||||
.expect("can verify")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use aes_gcm_siv::aead::Aead;
|
||||
use aes_gcm_siv::aead::generic_array::GenericArray;
|
||||
use aes_gcm_siv::{Aes256GcmSiv, KeyInit};
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common::constants::*;
|
||||
use crate::common::errors::*;
|
||||
use crate::common::serialization::ReservedByte;
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
use crate::crypto::uid_encryption;
|
||||
use crate::{api, crypto};
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct GroupMasterKey {
|
||||
pub(crate) bytes: [u8; GROUP_MASTER_KEY_LEN],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct GroupSecretParams {
|
||||
reserved: ReservedByte,
|
||||
master_key: GroupMasterKey,
|
||||
group_id: GroupIdentifierBytes,
|
||||
blob_key: AesKeyBytes,
|
||||
pub(crate) uid_enc_key_pair: crypto::uid_encryption::KeyPair,
|
||||
pub(crate) profile_key_enc_key_pair: crypto::profile_key_encryption::KeyPair,
|
||||
}
|
||||
|
||||
impl AsRef<uid_encryption::KeyPair> for GroupSecretParams {
|
||||
fn as_ref(&self) -> &uid_encryption::KeyPair {
|
||||
&self.uid_enc_key_pair
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct GroupPublicParams {
|
||||
reserved: ReservedByte,
|
||||
group_id: GroupIdentifierBytes,
|
||||
pub(crate) uid_enc_public_key: crypto::uid_encryption::PublicKey,
|
||||
pub(crate) profile_key_enc_public_key: crypto::profile_key_encryption::PublicKey,
|
||||
}
|
||||
|
||||
impl GroupMasterKey {
|
||||
pub fn new(bytes: [u8; GROUP_MASTER_KEY_LEN]) -> Self {
|
||||
GroupMasterKey { bytes }
|
||||
}
|
||||
}
|
||||
|
||||
const ENCRYPTED_BLOB_PADDING_LENGTH_SIZE: usize = std::mem::size_of::<u32>();
|
||||
|
||||
impl GroupSecretParams {
|
||||
pub fn generate(randomness: RandomnessBytes) -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Random_GroupSecretParams_Generate",
|
||||
&randomness,
|
||||
);
|
||||
let master_key = GroupMasterKey::new(sho.squeeze_as_array());
|
||||
GroupSecretParams::derive_from_master_key(master_key)
|
||||
}
|
||||
|
||||
pub fn derive_from_master_key(master_key: GroupMasterKey) -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_GroupMasterKey_GroupSecretParams_DeriveFromMasterKey",
|
||||
&master_key.bytes,
|
||||
);
|
||||
let group_id: GroupIdentifierBytes = sho.squeeze_as_array();
|
||||
let blob_key: AesKeyBytes = sho.squeeze_as_array();
|
||||
let uid_enc_key_pair = crypto::uid_encryption::KeyPair::derive_from(sho.as_mut());
|
||||
let profile_key_enc_key_pair =
|
||||
crypto::profile_key_encryption::KeyPair::derive_from(sho.as_mut());
|
||||
|
||||
Self {
|
||||
reserved: Default::default(),
|
||||
master_key,
|
||||
group_id,
|
||||
blob_key,
|
||||
uid_enc_key_pair,
|
||||
profile_key_enc_key_pair,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_master_key(&self) -> GroupMasterKey {
|
||||
self.master_key
|
||||
}
|
||||
|
||||
pub fn get_group_identifier(&self) -> GroupIdentifierBytes {
|
||||
self.group_id
|
||||
}
|
||||
|
||||
pub fn get_public_params(&self) -> GroupPublicParams {
|
||||
GroupPublicParams {
|
||||
reserved: Default::default(),
|
||||
uid_enc_public_key: self.uid_enc_key_pair.public_key,
|
||||
profile_key_enc_public_key: self.profile_key_enc_key_pair.public_key,
|
||||
group_id: self.group_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_service_id(
|
||||
&self,
|
||||
service_id: libsignal_core::ServiceId,
|
||||
) -> api::groups::UuidCiphertext {
|
||||
let uid = crypto::uid_struct::UidStruct::from_service_id(service_id);
|
||||
self.encrypt_uid_struct(uid)
|
||||
}
|
||||
|
||||
pub fn encrypt_uid_struct(
|
||||
&self,
|
||||
uid: crypto::uid_struct::UidStruct,
|
||||
) -> api::groups::UuidCiphertext {
|
||||
let ciphertext = self.uid_enc_key_pair.encrypt(&uid);
|
||||
api::groups::UuidCiphertext {
|
||||
reserved: Default::default(),
|
||||
ciphertext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_service_id(
|
||||
&self,
|
||||
ciphertext: api::groups::UuidCiphertext,
|
||||
) -> Result<libsignal_core::ServiceId, ZkGroupVerificationFailure> {
|
||||
crypto::uid_encryption::UidEncryptionDomain::decrypt(
|
||||
&self.uid_enc_key_pair,
|
||||
&ciphertext.ciphertext,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn encrypt_profile_key(
|
||||
&self,
|
||||
profile_key: api::profiles::ProfileKey,
|
||||
user_id: libsignal_core::Aci,
|
||||
) -> api::groups::ProfileKeyCiphertext {
|
||||
self.encrypt_profile_key_bytes(profile_key.bytes, user_id)
|
||||
}
|
||||
|
||||
pub fn encrypt_profile_key_bytes(
|
||||
&self,
|
||||
profile_key_bytes: ProfileKeyBytes,
|
||||
user_id: libsignal_core::Aci,
|
||||
) -> api::groups::ProfileKeyCiphertext {
|
||||
let profile_key = crypto::profile_key_struct::ProfileKeyStruct::new(
|
||||
profile_key_bytes,
|
||||
uuid::Uuid::from(user_id).into_bytes(),
|
||||
);
|
||||
let ciphertext = self.profile_key_enc_key_pair.encrypt(&profile_key);
|
||||
api::groups::ProfileKeyCiphertext {
|
||||
reserved: Default::default(),
|
||||
ciphertext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_profile_key(
|
||||
&self,
|
||||
ciphertext: api::groups::ProfileKeyCiphertext,
|
||||
user_id: libsignal_core::Aci,
|
||||
) -> Result<api::profiles::ProfileKey, ZkGroupVerificationFailure> {
|
||||
let profile_key_struct =
|
||||
crypto::profile_key_encryption::ProfileKeyEncryptionDomain::decrypt(
|
||||
&self.profile_key_enc_key_pair,
|
||||
&ciphertext.ciphertext,
|
||||
uuid::Uuid::from(user_id).into_bytes(),
|
||||
)?;
|
||||
Ok(api::profiles::ProfileKey {
|
||||
bytes: profile_key_struct.bytes,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encrypt_blob(&self, randomness: RandomnessBytes, plaintext: &[u8]) -> Vec<u8> {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Random_GroupSecretParams_EncryptBlob",
|
||||
&randomness,
|
||||
);
|
||||
let nonce_vec = sho.squeeze_as_array::<AESGCM_NONCE_LEN>();
|
||||
let mut ciphertext_vec = self.encrypt_blob_aesgcmsiv(&self.blob_key, &nonce_vec, plaintext);
|
||||
ciphertext_vec.extend(nonce_vec);
|
||||
ciphertext_vec.extend([0u8]); // reserved byte
|
||||
ciphertext_vec
|
||||
}
|
||||
|
||||
pub fn encrypt_blob_with_padding(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
plaintext: &[u8],
|
||||
padding_len: u32,
|
||||
) -> Vec<u8> {
|
||||
let full_length =
|
||||
ENCRYPTED_BLOB_PADDING_LENGTH_SIZE + plaintext.len() + padding_len as usize;
|
||||
let mut padded_plaintext = Vec::with_capacity(full_length);
|
||||
padded_plaintext.extend_from_slice(&padding_len.to_be_bytes());
|
||||
padded_plaintext.extend_from_slice(plaintext);
|
||||
padded_plaintext.resize(full_length, 0);
|
||||
self.encrypt_blob(randomness, &padded_plaintext)
|
||||
}
|
||||
|
||||
pub fn decrypt_blob(&self, ciphertext: &[u8]) -> Result<Vec<u8>, ZkGroupVerificationFailure> {
|
||||
if ciphertext.len() < AESGCM_NONCE_LEN + 1 {
|
||||
// AESGCM_NONCE_LEN = 12 bytes for IV
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
let unreserved_len = ciphertext.len() - 1;
|
||||
let (ciphertext, nonce) = ciphertext[..unreserved_len]
|
||||
.split_last_chunk::<AESGCM_NONCE_LEN>()
|
||||
.expect("checked length already");
|
||||
self.decrypt_blob_aesgcmsiv(&self.blob_key, nonce, ciphertext)
|
||||
}
|
||||
|
||||
pub fn decrypt_blob_with_padding(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>, ZkGroupVerificationFailure> {
|
||||
let mut decrypted = self.decrypt_blob(ciphertext)?;
|
||||
|
||||
let (padding_len_bytes, plaintext_plus_padding) = decrypted
|
||||
.split_first_chunk::<ENCRYPTED_BLOB_PADDING_LENGTH_SIZE>()
|
||||
.ok_or(ZkGroupVerificationFailure)?;
|
||||
|
||||
let padding_len = u32::from_be_bytes(*padding_len_bytes);
|
||||
if plaintext_plus_padding.len() < padding_len as usize {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
decrypted.truncate(decrypted.len() - padding_len as usize);
|
||||
decrypted.drain(..ENCRYPTED_BLOB_PADDING_LENGTH_SIZE);
|
||||
Ok(decrypted)
|
||||
}
|
||||
|
||||
fn encrypt_blob_aesgcmsiv(&self, key: &[u8], nonce: &[u8], plaintext: &[u8]) -> Vec<u8> {
|
||||
let key = GenericArray::from_slice(key);
|
||||
let aead_cipher = Aes256GcmSiv::new(key);
|
||||
let nonce = GenericArray::from_slice(nonce);
|
||||
aead_cipher
|
||||
.encrypt(nonce, plaintext)
|
||||
.expect("aead encrypt failure")
|
||||
}
|
||||
|
||||
fn decrypt_blob_aesgcmsiv(
|
||||
&self,
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>, ZkGroupVerificationFailure> {
|
||||
if ciphertext.len() < AESGCM_TAG_LEN {
|
||||
// AESGCM_TAG_LEN = 16 bytes for tag
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
let key = GenericArray::from_slice(key);
|
||||
let aead_cipher = Aes256GcmSiv::new(key);
|
||||
let nonce = GenericArray::from_slice(nonce);
|
||||
match aead_cipher.decrypt(nonce, ciphertext) {
|
||||
Ok(plaintext_vec) => Ok(plaintext_vec),
|
||||
Err(_) => Err(ZkGroupVerificationFailure),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupPublicParams {
|
||||
pub fn get_group_identifier(&self) -> GroupIdentifierBytes {
|
||||
self.group_id
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_aesgcmsiv_vec1() {
|
||||
// https://tools.ietf.org/html/rfc8452#appendix-C
|
||||
|
||||
let group_secret_params = GroupSecretParams::generate([0u8; RANDOMNESS_LEN]);
|
||||
|
||||
let plaintext_vec = vec![
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let key = [
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let nonce = [
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let ciphertext = [
|
||||
0x4a, 0x6a, 0x9d, 0xb4, 0xc8, 0xc6, 0x54, 0x92, 0x01, 0xb9, 0xed, 0xb5, 0x30, 0x06,
|
||||
0xcb, 0xa8, 0x21, 0xec, 0x9c, 0xf8, 0x50, 0x94, 0x8a, 0x7c, 0x86, 0xc6, 0x8a, 0xc7,
|
||||
0x53, 0x9d, 0x02, 0x7f, 0xe8, 0x19, 0xe6, 0x3a, 0xbc, 0xd0, 0x20, 0xb0, 0x06, 0xa9,
|
||||
0x76, 0x39, 0x76, 0x32, 0xeb, 0x5d,
|
||||
];
|
||||
|
||||
let calc_ciphertext =
|
||||
group_secret_params.encrypt_blob_aesgcmsiv(&key, &nonce, &plaintext_vec);
|
||||
|
||||
assert!(calc_ciphertext[..ciphertext.len()] == ciphertext[..]);
|
||||
|
||||
let calc_plaintext = group_secret_params
|
||||
.decrypt_blob_aesgcmsiv(&key, &nonce, &calc_ciphertext)
|
||||
.unwrap();
|
||||
assert!(calc_plaintext[..] == plaintext_vec[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aesgcmsiv_vec2() {
|
||||
// https://tools.ietf.org/html/rfc8452#appendix-C
|
||||
|
||||
let group_secret_params = GroupSecretParams::generate([0u8; RANDOMNESS_LEN]);
|
||||
|
||||
let plaintext_vec = vec![
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x4d, 0xb9, 0x23, 0xdc, 0x79, 0x3e, 0xe6, 0x49, 0x7c, 0x76, 0xdc, 0xc0,
|
||||
0x3a, 0x98, 0xe1, 0x08,
|
||||
];
|
||||
|
||||
let key = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let nonce = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let ciphertext = [
|
||||
0xf3, 0xf8, 0x0f, 0x2c, 0xf0, 0xcb, 0x2d, 0xd9, 0xc5, 0x98, 0x4f, 0xcd, 0xa9, 0x08,
|
||||
0x45, 0x6c, 0xc5, 0x37, 0x70, 0x3b, 0x5b, 0xa7, 0x03, 0x24, 0xa6, 0x79, 0x3a, 0x7b,
|
||||
0xf2, 0x18, 0xd3, 0xea, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let calc_ciphertext =
|
||||
group_secret_params.encrypt_blob_aesgcmsiv(&key, &nonce, &plaintext_vec);
|
||||
|
||||
assert!(calc_ciphertext[..ciphertext.len()] == ciphertext[..]);
|
||||
|
||||
let calc_plaintext = group_secret_params
|
||||
.decrypt_blob_aesgcmsiv(&key, &nonce, &calc_ciphertext)
|
||||
.unwrap();
|
||||
assert!(calc_plaintext[..] == plaintext_vec[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_with_padding() {
|
||||
let group_secret_params = GroupSecretParams::generate([0u8; RANDOMNESS_LEN]);
|
||||
let plaintext = b"secret team";
|
||||
|
||||
{
|
||||
let expected_ciphertext_hex = "3798afe9c65ffb35a63b2c048b16f19dd50ee9acc33cc925667a9abad4d4c6f86675fa8e32243e0831203700";
|
||||
|
||||
let calc_ciphertext =
|
||||
group_secret_params.encrypt_blob_with_padding([0u8; RANDOMNESS_LEN], plaintext, 0);
|
||||
assert_eq!(hex::encode(&calc_ciphertext), expected_ciphertext_hex);
|
||||
|
||||
let calc_plaintext = group_secret_params
|
||||
.decrypt_blob_with_padding(&calc_ciphertext)
|
||||
.unwrap();
|
||||
assert_eq!(calc_plaintext[..], plaintext[..]);
|
||||
}
|
||||
|
||||
{
|
||||
let expected_ciphertext_hex = "880a70e071b33f81e1219842c8514f34901abb734c191292ac325455d898da000484080099c620f86675fa8e32243e0831203700";
|
||||
|
||||
let calc_ciphertext =
|
||||
group_secret_params.encrypt_blob_with_padding([0u8; RANDOMNESS_LEN], plaintext, 8);
|
||||
assert_eq!(hex::encode(&calc_ciphertext), expected_ciphertext_hex);
|
||||
|
||||
let calc_plaintext = group_secret_params
|
||||
.decrypt_blob_with_padding(&calc_ciphertext)
|
||||
.unwrap();
|
||||
assert_eq!(calc_plaintext[..], plaintext[..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common::serialization::ReservedByte;
|
||||
use crate::crypto;
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialDefault)]
|
||||
pub struct ProfileKeyCiphertext {
|
||||
pub(crate) reserved: ReservedByte,
|
||||
pub(crate) ciphertext: crypto::profile_key_encryption::Ciphertext,
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
//
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common::constants::*;
|
||||
use crate::common::errors::*;
|
||||
use crate::common::serialization::{ReservedByte, VersionByte};
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
use crate::{api, crypto};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct ServerSecretParams {
|
||||
reserved: ReservedByte,
|
||||
// Now unused
|
||||
auth_credentials_key_pair: crypto::credentials::KeyPair<crypto::credentials::AuthCredential>,
|
||||
|
||||
// Now unused
|
||||
pub(crate) profile_key_credentials_key_pair:
|
||||
crypto::credentials::KeyPair<crypto::credentials::ProfileKeyCredential>,
|
||||
|
||||
sig_key_pair: crypto::signature::KeyPair,
|
||||
receipt_credentials_key_pair:
|
||||
crypto::credentials::KeyPair<crypto::credentials::ReceiptCredential>,
|
||||
|
||||
// Now unused
|
||||
pni_credentials_key_pair: crypto::credentials::KeyPair<crypto::credentials::PniCredential>,
|
||||
|
||||
expiring_profile_key_credentials_key_pair:
|
||||
crypto::credentials::KeyPair<crypto::credentials::ExpiringProfileKeyCredential>,
|
||||
|
||||
// Now unused
|
||||
auth_credentials_with_pni_key_pair:
|
||||
crypto::credentials::KeyPair<crypto::credentials::AuthCredentialWithPni>,
|
||||
|
||||
pub(crate) generic_credential_key_pair: zkcredential::credentials::CredentialKeyPair,
|
||||
pub(crate) endorsement_key_pair: zkcredential::endorsements::ServerRootKeyPair,
|
||||
}
|
||||
|
||||
impl AsRef<zkcredential::endorsements::ServerRootKeyPair> for ServerSecretParams {
|
||||
fn as_ref(&self) -> &zkcredential::endorsements::ServerRootKeyPair {
|
||||
&self.endorsement_key_pair
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct ServerPublicParams {
|
||||
reserved: ReservedByte,
|
||||
// Now unused
|
||||
auth_credentials_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
// Now unused
|
||||
pub(crate) profile_key_credentials_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
sig_public_key: crypto::signature::PublicKey,
|
||||
receipt_credentials_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
// Now unused
|
||||
pni_credentials_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
expiring_profile_key_credentials_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
// Now unused
|
||||
auth_credentials_with_pni_public_key: crypto::credentials::PublicKey,
|
||||
|
||||
pub(crate) generic_credential_public_key: zkcredential::credentials::CredentialPublicKey,
|
||||
pub(crate) endorsement_public_key: zkcredential::endorsements::ServerRootPublicKey,
|
||||
}
|
||||
|
||||
impl AsRef<zkcredential::endorsements::ServerRootPublicKey> for ServerPublicParams {
|
||||
fn as_ref(&self) -> &zkcredential::endorsements::ServerRootPublicKey {
|
||||
&self.endorsement_public_key
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerSecretParams {
|
||||
pub fn generate(randomness: RandomnessBytes) -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Random_ServerSecretParams_Generate",
|
||||
&randomness,
|
||||
);
|
||||
|
||||
let auth_credentials_key_pair = crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let profile_key_credentials_key_pair = crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let sig_key_pair = crypto::signature::KeyPair::generate(&mut sho);
|
||||
let receipt_credentials_key_pair = crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let pni_credentials_key_pair = crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let expiring_profile_key_credentials_key_pair =
|
||||
crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let auth_credentials_with_pni_key_pair = crypto::credentials::KeyPair::generate(&mut sho);
|
||||
let generic_credential_key_pair =
|
||||
zkcredential::credentials::CredentialKeyPair::generate(randomness);
|
||||
let endorsement_key_pair =
|
||||
zkcredential::endorsements::ServerRootKeyPair::generate(randomness);
|
||||
|
||||
Self {
|
||||
reserved: Default::default(),
|
||||
auth_credentials_key_pair,
|
||||
profile_key_credentials_key_pair,
|
||||
sig_key_pair,
|
||||
receipt_credentials_key_pair,
|
||||
pni_credentials_key_pair,
|
||||
expiring_profile_key_credentials_key_pair,
|
||||
auth_credentials_with_pni_key_pair,
|
||||
generic_credential_key_pair,
|
||||
endorsement_key_pair,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_endorsement_root_key_pair(&self) -> EndorsementServerRootKeyPair {
|
||||
EndorsementServerRootKeyPair {
|
||||
reserved: Default::default(),
|
||||
key_pair: self.endorsement_key_pair.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_public_params(&self) -> ServerPublicParams {
|
||||
ServerPublicParams {
|
||||
reserved: Default::default(),
|
||||
auth_credentials_public_key: self.auth_credentials_key_pair.get_public_key(),
|
||||
profile_key_credentials_public_key: self
|
||||
.profile_key_credentials_key_pair
|
||||
.get_public_key(),
|
||||
sig_public_key: self.sig_key_pair.get_public_key(),
|
||||
receipt_credentials_public_key: self.receipt_credentials_key_pair.get_public_key(),
|
||||
pni_credentials_public_key: self.pni_credentials_key_pair.get_public_key(),
|
||||
expiring_profile_key_credentials_public_key: self
|
||||
.expiring_profile_key_credentials_key_pair
|
||||
.get_public_key(),
|
||||
auth_credentials_with_pni_public_key: self
|
||||
.auth_credentials_with_pni_key_pair
|
||||
.get_public_key(),
|
||||
generic_credential_public_key: self.generic_credential_key_pair.public_key().clone(),
|
||||
endorsement_public_key: self.endorsement_key_pair.public_key().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(&self, randomness: RandomnessBytes, message: &[u8]) -> NotarySignatureBytes {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Random_ServerSecretParams_Sign",
|
||||
&randomness,
|
||||
);
|
||||
self.sig_key_pair.sign(message, &mut sho)
|
||||
}
|
||||
|
||||
/// Checks that `current_time` is within the validity window defined by
|
||||
/// `redemption_time`.
|
||||
///
|
||||
/// All times are relative to SystemTime::UNIX_EPOCH,
|
||||
/// but we don't actually use SystemTime because it's too small on 32-bit Linux.
|
||||
pub(crate) fn check_auth_credential_redemption_time(
|
||||
redemption_time: Timestamp,
|
||||
current_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
let acceptable_start_time = redemption_time
|
||||
.checked_sub_seconds(SECONDS_PER_DAY)
|
||||
.ok_or(ZkGroupVerificationFailure)?;
|
||||
let acceptable_end_time = redemption_time
|
||||
.checked_add_seconds(2 * SECONDS_PER_DAY)
|
||||
.ok_or(ZkGroupVerificationFailure)?;
|
||||
|
||||
if !(acceptable_start_time..=acceptable_end_time).contains(¤t_time) {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_auth_credential_presentation(
|
||||
&self,
|
||||
group_public_params: api::groups::GroupPublicParams,
|
||||
presentation: &api::auth::AnyAuthCredentialPresentation,
|
||||
current_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
Self::check_auth_credential_redemption_time(
|
||||
presentation.get_redemption_time(),
|
||||
current_time,
|
||||
)?;
|
||||
|
||||
match presentation {
|
||||
api::auth::AnyAuthCredentialPresentation::V4(presentation) => {
|
||||
presentation.verify(self, &group_public_params, presentation.redemption_time())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_profile_key_credential_presentation(
|
||||
&self,
|
||||
group_public_params: api::groups::GroupPublicParams,
|
||||
presentation: &api::profiles::AnyProfileKeyCredentialPresentation,
|
||||
current_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
match presentation {
|
||||
api::profiles::AnyProfileKeyCredentialPresentation::V1(_) => {
|
||||
Err(ZkGroupVerificationFailure)
|
||||
}
|
||||
|
||||
api::profiles::AnyProfileKeyCredentialPresentation::V2(_) => {
|
||||
Err(ZkGroupVerificationFailure)
|
||||
}
|
||||
|
||||
api::profiles::AnyProfileKeyCredentialPresentation::V3(presentation) => self
|
||||
.verify_expiring_profile_key_credential_presentation(
|
||||
group_public_params,
|
||||
presentation,
|
||||
current_time,
|
||||
),
|
||||
api::profiles::AnyProfileKeyCredentialPresentation::V4(presentation) => self
|
||||
.verify_expiring_profile_key_credential_presentation(
|
||||
group_public_params,
|
||||
presentation,
|
||||
current_time,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_expiring_profile_key_credential_presentation<const V: u8>(
|
||||
&self,
|
||||
group_public_params: api::groups::GroupPublicParams,
|
||||
presentation: &api::profiles::ExpiringProfileKeyCredentialPresentation<V>,
|
||||
current_time: Timestamp,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
let credentials_key_pair = self.expiring_profile_key_credentials_key_pair;
|
||||
let uid_enc_public_key = group_public_params.uid_enc_public_key;
|
||||
let profile_key_enc_public_key = group_public_params.profile_key_enc_public_key;
|
||||
|
||||
presentation.proof.verify(
|
||||
credentials_key_pair,
|
||||
presentation.uid_enc_ciphertext,
|
||||
uid_enc_public_key,
|
||||
presentation.profile_key_enc_ciphertext,
|
||||
profile_key_enc_public_key,
|
||||
presentation.credential_expiration_time,
|
||||
V >= PRESENTATION_VERSION_4,
|
||||
)?;
|
||||
|
||||
if presentation.credential_expiration_time <= current_time {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn issue_expiring_profile_key_credential(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
request: &api::profiles::ProfileKeyCredentialRequest,
|
||||
aci: libsignal_core::Aci,
|
||||
commitment: api::profiles::ProfileKeyCommitment,
|
||||
credential_expiration_time: Timestamp,
|
||||
) -> Result<api::profiles::ExpiringProfileKeyCredentialResponse, ZkGroupVerificationFailure>
|
||||
{
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20220508_Random_ServerSecretParams_IssueExpiringProfileKeyCredential",
|
||||
&randomness,
|
||||
);
|
||||
|
||||
request.proof.verify(
|
||||
request.public_key,
|
||||
request.ciphertext,
|
||||
commitment.commitment,
|
||||
)?;
|
||||
|
||||
let uid = crypto::uid_struct::UidStruct::from_service_id(aci.into());
|
||||
let blinded_credential_with_secret_nonce = self
|
||||
.expiring_profile_key_credentials_key_pair
|
||||
.create_blinded_expiring_profile_key_credential(
|
||||
uid,
|
||||
request.public_key,
|
||||
request.ciphertext,
|
||||
credential_expiration_time,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
let proof = crypto::proofs::ExpiringProfileKeyCredentialIssuanceProof::new(
|
||||
self.expiring_profile_key_credentials_key_pair,
|
||||
request.public_key,
|
||||
request.ciphertext,
|
||||
blinded_credential_with_secret_nonce,
|
||||
uid,
|
||||
credential_expiration_time,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
Ok(api::profiles::ExpiringProfileKeyCredentialResponse {
|
||||
reserved: Default::default(),
|
||||
blinded_credential: blinded_credential_with_secret_nonce
|
||||
.get_blinded_expiring_profile_key_credential(),
|
||||
credential_expiration_time,
|
||||
proof,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn issue_receipt_credential(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
request: &api::receipts::ReceiptCredentialRequest,
|
||||
receipt_expiration_time: Timestamp,
|
||||
receipt_level: ReceiptLevel,
|
||||
) -> api::receipts::ReceiptCredentialResponse {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20210919_Random_ServerSecretParams_IssueReceiptCredential",
|
||||
&randomness,
|
||||
);
|
||||
|
||||
let blinded_credential_with_secret_nonce = self
|
||||
.receipt_credentials_key_pair
|
||||
.create_blinded_receipt_credential(
|
||||
request.public_key,
|
||||
request.ciphertext,
|
||||
receipt_expiration_time,
|
||||
receipt_level,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
let proof = crypto::proofs::ReceiptCredentialIssuanceProof::new(
|
||||
self.receipt_credentials_key_pair,
|
||||
request.public_key,
|
||||
request.ciphertext,
|
||||
blinded_credential_with_secret_nonce,
|
||||
receipt_expiration_time,
|
||||
receipt_level,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
api::receipts::ReceiptCredentialResponse {
|
||||
reserved: Default::default(),
|
||||
receipt_expiration_time,
|
||||
receipt_level,
|
||||
blinded_credential: blinded_credential_with_secret_nonce
|
||||
.get_blinded_receipt_credential(),
|
||||
proof,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_receipt_credential_presentation(
|
||||
&self,
|
||||
presentation: &api::receipts::ReceiptCredentialPresentation,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
presentation.proof.verify(
|
||||
self.receipt_credentials_key_pair,
|
||||
presentation.get_receipt_struct(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerPublicParams {
|
||||
pub fn get_endorsement_public_key(&self) -> EndorsementPublicKey {
|
||||
EndorsementPublicKey {
|
||||
reserved: Default::default(),
|
||||
public_key: self.endorsement_public_key.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: NotarySignatureBytes,
|
||||
) -> Result<(), ZkGroupVerificationFailure> {
|
||||
self.sig_public_key.verify(message, signature)
|
||||
}
|
||||
|
||||
pub fn create_profile_key_credential_request_context(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
aci: libsignal_core::Aci,
|
||||
profile_key: api::profiles::ProfileKey,
|
||||
) -> api::profiles::ProfileKeyCredentialRequestContext {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Random_ServerPublicParams_CreateProfileKeyCredentialRequestContext",
|
||||
&randomness,
|
||||
);
|
||||
let uid_bytes = uuid::Uuid::from(aci).into_bytes();
|
||||
let profile_key_struct =
|
||||
crypto::profile_key_struct::ProfileKeyStruct::new(profile_key.bytes, uid_bytes);
|
||||
|
||||
let commitment_with_secret_nonce =
|
||||
crypto::profile_key_commitment::CommitmentWithSecretNonce::new(
|
||||
profile_key_struct,
|
||||
uid_bytes,
|
||||
);
|
||||
|
||||
let key_pair = crypto::profile_key_credential_request::KeyPair::generate(&mut sho);
|
||||
let ciphertext_with_secret_nonce = key_pair.encrypt(profile_key_struct, &mut sho);
|
||||
|
||||
let proof = crypto::proofs::ProfileKeyCredentialRequestProof::new(
|
||||
key_pair,
|
||||
ciphertext_with_secret_nonce,
|
||||
commitment_with_secret_nonce,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
api::profiles::ProfileKeyCredentialRequestContext {
|
||||
reserved: Default::default(),
|
||||
aci_bytes: uid_bytes,
|
||||
profile_key_bytes: profile_key_struct.bytes,
|
||||
key_pair,
|
||||
ciphertext_with_secret_nonce,
|
||||
proof,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_expiring_profile_key_credential(
|
||||
&self,
|
||||
context: &api::profiles::ProfileKeyCredentialRequestContext,
|
||||
response: &api::profiles::ExpiringProfileKeyCredentialResponse,
|
||||
current_time: Timestamp,
|
||||
) -> Result<api::profiles::ExpiringProfileKeyCredential, ZkGroupVerificationFailure> {
|
||||
response.proof.verify(
|
||||
self.expiring_profile_key_credentials_public_key,
|
||||
context.key_pair.get_public_key(),
|
||||
context.aci_bytes,
|
||||
context.ciphertext_with_secret_nonce.get_ciphertext(),
|
||||
response.blinded_credential,
|
||||
response.credential_expiration_time,
|
||||
)?;
|
||||
|
||||
if !response.credential_expiration_time.is_day_aligned() {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
let days_remaining = response
|
||||
.credential_expiration_time
|
||||
.saturating_seconds_since(current_time)
|
||||
/ SECONDS_PER_DAY;
|
||||
if days_remaining == 0 || days_remaining > 7 {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
let credential = context
|
||||
.key_pair
|
||||
.decrypt_blinded_expiring_profile_key_credential(response.blinded_credential);
|
||||
|
||||
Ok(api::profiles::ExpiringProfileKeyCredential {
|
||||
reserved: Default::default(),
|
||||
credential,
|
||||
aci_bytes: context.aci_bytes,
|
||||
profile_key_bytes: context.profile_key_bytes,
|
||||
credential_expiration_time: response.credential_expiration_time,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_expiring_profile_key_credential_presentation<const V: u8>(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
group_secret_params: api::groups::GroupSecretParams,
|
||||
expiring_profile_key_credential: api::profiles::ExpiringProfileKeyCredential,
|
||||
) -> api::profiles::ExpiringProfileKeyCredentialPresentation<V> {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20220508_Random_ServerPublicParams_CreateExpiringProfileKeyCredentialPresentation",
|
||||
&randomness,
|
||||
);
|
||||
|
||||
let uid_enc_key_pair = group_secret_params.uid_enc_key_pair;
|
||||
let profile_key_enc_key_pair = group_secret_params.profile_key_enc_key_pair;
|
||||
let credentials_public_key = self.expiring_profile_key_credentials_public_key;
|
||||
|
||||
let uid = expiring_profile_key_credential.aci();
|
||||
let uuid_ciphertext = group_secret_params.encrypt_service_id(uid.into());
|
||||
let profile_key_ciphertext = group_secret_params
|
||||
.encrypt_profile_key_bytes(expiring_profile_key_credential.profile_key_bytes, uid);
|
||||
|
||||
let proof = crypto::proofs::ExpiringProfileKeyCredentialPresentationProof::new(
|
||||
uid_enc_key_pair,
|
||||
profile_key_enc_key_pair,
|
||||
credentials_public_key,
|
||||
expiring_profile_key_credential.credential,
|
||||
uuid_ciphertext.ciphertext,
|
||||
profile_key_ciphertext.ciphertext,
|
||||
expiring_profile_key_credential.aci_bytes,
|
||||
expiring_profile_key_credential.profile_key_bytes,
|
||||
V >= PRESENTATION_VERSION_4,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
api::profiles::ExpiringProfileKeyCredentialPresentation {
|
||||
version: VersionByte,
|
||||
proof,
|
||||
uid_enc_ciphertext: uuid_ciphertext.ciphertext,
|
||||
profile_key_enc_ciphertext: profile_key_ciphertext.ciphertext,
|
||||
credential_expiration_time: expiring_profile_key_credential.credential_expiration_time,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_receipt_credential_request_context(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
receipt_serial_bytes: ReceiptSerialBytes,
|
||||
) -> api::receipts::ReceiptCredentialRequestContext {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20210919_Random_ServerPublicParams_CreateReceiptCredentialRequestContext",
|
||||
&randomness,
|
||||
);
|
||||
|
||||
let key_pair = crypto::receipt_credential_request::KeyPair::generate(&mut sho);
|
||||
let ciphertext_with_secret_nonce = key_pair.encrypt(receipt_serial_bytes, &mut sho);
|
||||
|
||||
api::receipts::ReceiptCredentialRequestContext {
|
||||
reserved: Default::default(),
|
||||
receipt_serial_bytes,
|
||||
key_pair,
|
||||
ciphertext_with_secret_nonce,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_receipt_credential(
|
||||
&self,
|
||||
context: &api::receipts::ReceiptCredentialRequestContext,
|
||||
response: &api::receipts::ReceiptCredentialResponse,
|
||||
) -> Result<api::receipts::ReceiptCredential, ZkGroupVerificationFailure> {
|
||||
if !response.receipt_expiration_time.is_day_aligned() {
|
||||
return Err(ZkGroupVerificationFailure);
|
||||
}
|
||||
|
||||
let receipt_struct = crypto::receipt_struct::ReceiptStruct::new(
|
||||
context.receipt_serial_bytes,
|
||||
response.receipt_expiration_time,
|
||||
response.receipt_level,
|
||||
);
|
||||
response.proof.verify(
|
||||
self.receipt_credentials_public_key,
|
||||
context.key_pair.get_public_key(),
|
||||
context.ciphertext_with_secret_nonce.get_ciphertext(),
|
||||
response.blinded_credential,
|
||||
receipt_struct,
|
||||
)?;
|
||||
let credential = context
|
||||
.key_pair
|
||||
.decrypt_blinded_receipt_credential(response.blinded_credential);
|
||||
Ok(api::receipts::ReceiptCredential {
|
||||
reserved: Default::default(),
|
||||
credential,
|
||||
receipt_expiration_time: response.receipt_expiration_time,
|
||||
receipt_level: response.receipt_level,
|
||||
receipt_serial_bytes: context.receipt_serial_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_receipt_credential_presentation(
|
||||
&self,
|
||||
randomness: RandomnessBytes,
|
||||
receipt_credential: &api::receipts::ReceiptCredential,
|
||||
) -> api::receipts::ReceiptCredentialPresentation {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20210919_Random_ServerPublicParams_CreateReceiptCredentialPresentation",
|
||||
&randomness,
|
||||
);
|
||||
let proof = crypto::proofs::ReceiptCredentialPresentationProof::new(
|
||||
self.receipt_credentials_public_key,
|
||||
receipt_credential.credential,
|
||||
&mut sho,
|
||||
);
|
||||
api::receipts::ReceiptCredentialPresentation {
|
||||
reserved: Default::default(),
|
||||
proof,
|
||||
receipt_expiration_time: receipt_credential.receipt_expiration_time,
|
||||
receipt_level: receipt_credential.receipt_level,
|
||||
receipt_serial_bytes: receipt_credential.receipt_serial_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct EndorsementServerRootKeyPair {
|
||||
reserved: ReservedByte,
|
||||
pub(crate) key_pair: zkcredential::endorsements::ServerRootKeyPair,
|
||||
}
|
||||
|
||||
impl AsRef<zkcredential::endorsements::ServerRootKeyPair> for EndorsementServerRootKeyPair {
|
||||
fn as_ref(&self) -> &zkcredential::endorsements::ServerRootKeyPair {
|
||||
&self.key_pair
|
||||
}
|
||||
}
|
||||
|
||||
impl EndorsementServerRootKeyPair {
|
||||
pub fn public_key(&self) -> EndorsementPublicKey {
|
||||
EndorsementPublicKey {
|
||||
reserved: self.reserved,
|
||||
public_key: self.key_pair.public_key().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct EndorsementPublicKey {
|
||||
reserved: ReservedByte,
|
||||
public_key: zkcredential::endorsements::ServerRootPublicKey,
|
||||
}
|
||||
|
||||
impl AsRef<zkcredential::endorsements::ServerRootPublicKey> for EndorsementPublicKey {
|
||||
fn as_ref(&self) -> &zkcredential::endorsements::ServerRootPublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common::serialization::ReservedByte;
|
||||
use crate::crypto;
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialDefault)]
|
||||
pub struct UuidCiphertext {
|
||||
pub(crate) reserved: ReservedByte,
|
||||
pub(crate) ciphertext: crypto::uid_encryption::Ciphertext,
|
||||
}
|
||||
Reference in New Issue
Block a user