0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 03:52:17 +02:00

Use an enum for BackupAuthCredential's level

This commit is contained in:
ravi-signal 2024-04-19 14:46:49 -04:00 committed by GitHub
parent 9db74365d9
commit 9204831745
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 254 additions and 126 deletions

View File

@ -60,19 +60,20 @@ public final class BackupAuthTest extends SecureRandomTest {
GenericServerSecretParams.generate(createSecureRandom(SERVER_SECRET_RANDOM));
Instant timestamp = Instant.now().truncatedTo(ChronoUnit.DAYS);
BackupAuthCredentialResponse response =
request.issueCredential(timestamp, 1L, serverSecretParams);
request.issueCredential(timestamp, BackupLevel.MESSAGES, serverSecretParams);
BackupAuthCredential credential =
context.receiveResponse(response, serverSecretParams.getPublicParams(), 1L);
context.receiveResponse(response, serverSecretParams.getPublicParams());
Assert.assertArrayEquals(SERIALIZED_BACKUP_ID, credential.getBackupId());
Assert.assertArrayEquals(
SERIALIZED_BACKUP_ID,
credential.present(serverSecretParams.getPublicParams()).getBackupId());
Assert.assertEquals(BackupLevel.MESSAGES, credential.getBackupLevel());
}
@Test
public void testBackupAuthCredentialIntegration() throws VerificationFailedException {
final long receiptLevel = 10L;
final BackupLevel backupLevel = BackupLevel.MESSAGES;
// SERVER
// Generate keys
@ -90,16 +91,12 @@ public final class BackupAuthTest extends SecureRandomTest {
Instant timestamp = Instant.now().truncatedTo(ChronoUnit.DAYS);
BackupAuthCredentialResponse response =
request.issueCredential(
timestamp, receiptLevel, serverSecretParams, createSecureRandom(TEST_ARRAY_32_1));
timestamp, backupLevel, serverSecretParams, createSecureRandom(TEST_ARRAY_32_1));
// CLIENT
// Gets stored credential
BackupAuthCredential credential =
context.receiveResponse(response, serverPublicParams, receiptLevel);
Assert.assertThrows(
"Wrong receipt level",
VerificationFailedException.class,
() -> context.receiveResponse(response, serverPublicParams, receiptLevel + 1));
BackupAuthCredential credential = context.receiveResponse(response, serverPublicParams);
Assert.assertEquals(backupLevel, credential.getBackupLevel());
// CLIENT
// Generates a presentation
@ -111,7 +108,7 @@ public final class BackupAuthTest extends SecureRandomTest {
presentation.verify(serverSecretParams);
presentation.verify(timestamp.plus(1, ChronoUnit.DAYS), serverSecretParams);
Assert.assertArrayEquals(credential.getBackupId(), presentation.getBackupId());
Assert.assertEquals(receiptLevel, presentation.getReceiptLevel());
Assert.assertEquals(backupLevel, presentation.getBackupLevel());
Assert.assertThrows(
"Credential should be expired after 2 days",

View File

@ -117,21 +117,22 @@ public final class Native {
public static native void BackupAuthCredentialPresentation_CheckValidContents(byte[] presentationBytes) throws Exception;
public static native byte[] BackupAuthCredentialPresentation_GetBackupId(byte[] presentationBytes);
public static native long BackupAuthCredentialPresentation_GetReceiptLevel(byte[] presentationBytes);
public static native int BackupAuthCredentialPresentation_GetBackupLevel(byte[] presentationBytes);
public static native void BackupAuthCredentialPresentation_Verify(byte[] presentationBytes, long now, byte[] serverParamsBytes) throws Exception;
public static native void BackupAuthCredentialRequestContext_CheckValidContents(byte[] contextBytes) throws Exception;
public static native byte[] BackupAuthCredentialRequestContext_GetRequest(byte[] contextBytes);
public static native byte[] BackupAuthCredentialRequestContext_New(byte[] backupKey, UUID uuid);
public static native byte[] BackupAuthCredentialRequestContext_ReceiveResponse(byte[] contextBytes, byte[] responseBytes, byte[] paramsBytes, long expectedReceiptLevel) throws Exception;
public static native byte[] BackupAuthCredentialRequestContext_ReceiveResponse(byte[] contextBytes, byte[] responseBytes, byte[] paramsBytes) throws Exception;
public static native void BackupAuthCredentialRequest_CheckValidContents(byte[] requestBytes) throws Exception;
public static native byte[] BackupAuthCredentialRequest_IssueDeterministic(byte[] requestBytes, long redemptionTime, long receiptLevel, byte[] paramsBytes, byte[] randomness);
public static native byte[] BackupAuthCredentialRequest_IssueDeterministic(byte[] requestBytes, long redemptionTime, int backupLevel, byte[] paramsBytes, byte[] randomness);
public static native void BackupAuthCredentialResponse_CheckValidContents(byte[] responseBytes) throws Exception;
public static native void BackupAuthCredential_CheckValidContents(byte[] paramsBytes) throws Exception;
public static native byte[] BackupAuthCredential_GetBackupId(byte[] credentialBytes);
public static native int BackupAuthCredential_GetBackupLevel(byte[] credentialBytes);
public static native byte[] BackupAuthCredential_PresentDeterministic(byte[] credentialBytes, byte[] serverParamsBytes, byte[] randomness) throws Exception;
public static native void CallLinkAuthCredentialPresentation_CheckValidContents(byte[] presentationBytes) throws Exception;

View File

@ -48,4 +48,9 @@ public final class BackupAuthCredential extends ByteArray {
public byte[] getBackupId() {
return Native.BackupAuthCredential_GetBackupId(getInternalContentsForJNI());
}
public BackupLevel getBackupLevel() {
return BackupLevel.fromValue(
Native.BackupAuthCredential_GetBackupLevel(getInternalContentsForJNI()));
}
}

View File

@ -42,7 +42,8 @@ public final class BackupAuthCredentialPresentation extends ByteArray {
return Native.BackupAuthCredentialPresentation_GetBackupId(getInternalContentsForJNI());
}
public long getReceiptLevel() {
return Native.BackupAuthCredentialPresentation_GetReceiptLevel(getInternalContentsForJNI());
public BackupLevel getBackupLevel() {
return BackupLevel.fromValue(
Native.BackupAuthCredentialPresentation_GetBackupLevel(getInternalContentsForJNI()));
}
}

View File

@ -29,29 +29,29 @@ public final class BackupAuthCredentialRequest extends ByteArray {
*
* @param timestamp Must be a round number of days. Use {@link Instant#truncatedTo} to ensure
* this.
* @param receiptLevel The receiptLevel that this credential is authorized for
* @param backupLevel The {@link BackupLevel} that this credential is authorized for
* @param params The params that will be used by the verifying server to verify this credential.
*/
public BackupAuthCredentialResponse issueCredential(
Instant timestamp, long receiptLevel, GenericServerSecretParams params) {
return issueCredential(timestamp, receiptLevel, params, new SecureRandom());
Instant timestamp, BackupLevel backupLevel, GenericServerSecretParams params) {
return issueCredential(timestamp, backupLevel, params, new SecureRandom());
}
/**
* Issues a BackupAuthCredential, using a dedicated source of randomness.
*
* <p>This can be used to make tests deterministic. Prefer {@link #issueCredential(Instant, long,
* GenericServerSecretParams)} if the source of randomness doesn't matter.
* <p>This can be used to make tests deterministic. Prefer {@link #issueCredential(Instant,
* BackupLevel, GenericServerSecretParams)} if the source of randomness doesn't matter.
*
* @param timestamp Must be a round number of days. Use {@link Instant#truncatedTo} to ensure
* this.
* @param receiptLevel The receiptLevel that this credential is authorized for
* @param backupLevel The {@link BackupLevel} that this credential is authorized for
* @param params The params that will be used by the verifying server to verify this credential.
* @param secureRandom Used to hide the server's secrets and make the issued credential unique.
*/
public BackupAuthCredentialResponse issueCredential(
Instant timestamp,
long receiptLevel,
BackupLevel backupLevel,
GenericServerSecretParams params,
SecureRandom secureRandom) {
byte[] random = new byte[RANDOM_LENGTH];
@ -61,7 +61,7 @@ public final class BackupAuthCredentialRequest extends ByteArray {
Native.BackupAuthCredentialRequest_IssueDeterministic(
getInternalContentsForJNI(),
timestamp.getEpochSecond(),
receiptLevel,
backupLevel.getValue(),
params.getInternalContentsForJNI(),
random);

View File

@ -44,9 +44,7 @@ public final class BackupAuthCredentialRequestContext extends ByteArray {
}
public BackupAuthCredential receiveResponse(
BackupAuthCredentialResponse response,
GenericServerPublicParams params,
long expectedReceiptLevel)
BackupAuthCredentialResponse response, GenericServerPublicParams params)
throws VerificationFailedException {
final byte[] newContents =
filterExceptions(
@ -55,8 +53,7 @@ public final class BackupAuthCredentialRequestContext extends ByteArray {
Native.BackupAuthCredentialRequestContext_ReceiveResponse(
getInternalContentsForJNI(),
response.getInternalContentsForJNI(),
params.getInternalContentsForJNI(),
expectedReceiptLevel));
params.getInternalContentsForJNI()));
try {
return new BackupAuthCredential(newContents);

View File

@ -0,0 +1,39 @@
//
// Copyright 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.libsignal.zkgroup.backups;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public enum BackupLevel {
// This must match the Rust version of the enum.
MESSAGES(200),
MEDIA(201);
private static final Map<Integer, BackupLevel> LOOKUP =
Arrays.stream(BackupLevel.values())
.collect(Collectors.toMap(BackupLevel::getValue, Function.identity()));
private final int value;
BackupLevel(int value) {
this.value = value;
}
int getValue() {
return this.value;
}
public static BackupLevel fromValue(int value) {
BackupLevel backupLevel = LOOKUP.get(value);
if (backupLevel == null) {
throw new IllegalArgumentException("Invalid backup level: " + value);
}
return backupLevel;
}
}

5
node/Native.d.ts vendored
View File

@ -143,12 +143,13 @@ export function BackupAuthCredentialPresentation_Verify(presentationBytes: Buffe
export function BackupAuthCredentialRequestContext_CheckValidContents(contextBytes: Buffer): void;
export function BackupAuthCredentialRequestContext_GetRequest(contextBytes: Buffer): Buffer;
export function BackupAuthCredentialRequestContext_New(backupKey: Buffer, uuid: Uuid): Buffer;
export function BackupAuthCredentialRequestContext_ReceiveResponse(contextBytes: Buffer, responseBytes: Buffer, paramsBytes: Buffer, expectedReceiptLevel: bigint): Buffer;
export function BackupAuthCredentialRequestContext_ReceiveResponse(contextBytes: Buffer, responseBytes: Buffer, paramsBytes: Buffer): Buffer;
export function BackupAuthCredentialRequest_CheckValidContents(requestBytes: Buffer): void;
export function BackupAuthCredentialRequest_IssueDeterministic(requestBytes: Buffer, redemptionTime: Timestamp, receiptLevel: bigint, paramsBytes: Buffer, randomness: Buffer): Buffer;
export function BackupAuthCredentialRequest_IssueDeterministic(requestBytes: Buffer, redemptionTime: Timestamp, backupLevel: number, paramsBytes: Buffer, randomness: Buffer): Buffer;
export function BackupAuthCredentialResponse_CheckValidContents(responseBytes: Buffer): void;
export function BackupAuthCredential_CheckValidContents(paramsBytes: Buffer): void;
export function BackupAuthCredential_GetBackupId(credentialBytes: Buffer): Buffer;
export function BackupAuthCredential_GetBackupLevel(credentialBytes: Buffer): number;
export function BackupAuthCredential_PresentDeterministic(credentialBytes: Buffer, serverParamsBytes: Buffer, randomness: Buffer): Buffer;
export function CallLinkAuthCredentialPresentation_CheckValidContents(presentationBytes: Buffer): void;
export function CallLinkAuthCredentialPresentation_GetUserId(presentationBytes: Buffer): Serialized<UuidCiphertext>;

View File

@ -57,6 +57,7 @@ import {
ReceiptCredentialRequest,
ReceiptCredentialRequestContext,
ReceiptCredentialResponse,
BackupLevel,
} from '../zkgroup/';
import { Aci, Pni } from '../Address';
import { LibSignalErrorBase, Uuid } from '..';
@ -723,7 +724,7 @@ describe('ZKGroup', () => {
);
it('testDeterministic', () => {
const receiptLevel = 1n;
const backupLevel = BackupLevel.Messages;
const context = BackupAuthCredentialRequestContext.create(
BACKUP_KEY,
TEST_USER_ID
@ -738,19 +739,19 @@ describe('ZKGroup', () => {
const startOfDay = now - (now % SECONDS_PER_DAY);
const response = request.issueCredential(
startOfDay,
receiptLevel,
backupLevel,
serverSecretParams
);
const credential = context.receive(
response,
serverSecretParams.getPublicParams(),
receiptLevel
serverSecretParams.getPublicParams()
);
assert.equal(backupLevel, credential.getBackupLevel());
assertArrayEquals(SERIALIZED_BACKUP_ID, credential.getBackupId());
});
it('testIntegration', () => {
const receiptLevel = 10n;
const backupLevel = BackupLevel.Messages;
const serverSecretParams =
GenericServerSecretParams.generateWithRandom(SERVER_SECRET_RANDOM);
@ -768,20 +769,14 @@ describe('ZKGroup', () => {
const startOfDay = now - (now % SECONDS_PER_DAY);
const response = request.issueCredentialWithRandom(
startOfDay,
receiptLevel,
backupLevel,
serverSecretParams,
TEST_ARRAY_32_1
);
// client
const credential = context.receive(
response,
serverPublicParams,
receiptLevel
);
assert.throws(() =>
context.receive(response, serverPublicParams, receiptLevel + 1n)
);
const credential = context.receive(response, serverPublicParams);
assert.equal(backupLevel, credential.getBackupLevel());
const presentation = credential.presentWithRandom(
serverPublicParams,
TEST_ARRAY_32_2

View File

@ -11,6 +11,7 @@ import { RANDOM_LENGTH } from '../internal/Constants';
import GenericServerPublicParams from '../GenericServerPublicParams';
import BackupAuthCredentialPresentation from './BackupAuthCredentialPresentation';
import BackupLevel from './BackupLevel';
export default class BackupAuthCredential extends ByteArray {
private readonly __type?: never;
@ -42,4 +43,12 @@ export default class BackupAuthCredential extends ByteArray {
getBackupId(): Buffer {
return Native.BackupAuthCredential_GetBackupId(this.contents);
}
getBackupLevel(): BackupLevel {
const n: number = Native.BackupAuthCredential_GetBackupLevel(this.contents);
if (!(n in BackupLevel)) {
throw new TypeError(`Invalid BackupLevel ${n}`);
}
return n;
}
}

View File

@ -11,6 +11,7 @@ import { RANDOM_LENGTH } from '../internal/Constants';
import GenericServerSecretParams from '../GenericServerSecretParams';
import BackupAuthCredentialResponse from './BackupAuthCredentialResponse';
import BackupLevel from './BackupLevel';
export default class BackupAuthCredentialRequest extends ByteArray {
private readonly __type?: never;
@ -21,13 +22,13 @@ export default class BackupAuthCredentialRequest extends ByteArray {
issueCredential(
timestamp: number,
receiptLevel: bigint,
backupLevel: BackupLevel,
params: GenericServerSecretParams
): BackupAuthCredentialResponse {
const random = randomBytes(RANDOM_LENGTH);
return this.issueCredentialWithRandom(
timestamp,
receiptLevel,
backupLevel,
params,
random
);
@ -35,7 +36,7 @@ export default class BackupAuthCredentialRequest extends ByteArray {
issueCredentialWithRandom(
timestamp: number,
receiptLevel: bigint,
backupLevel: BackupLevel,
params: GenericServerSecretParams,
random: Buffer
): BackupAuthCredentialResponse {
@ -43,7 +44,7 @@ export default class BackupAuthCredentialRequest extends ByteArray {
Native.BackupAuthCredentialRequest_IssueDeterministic(
this.contents,
timestamp,
receiptLevel,
backupLevel,
params.contents,
random
)

View File

@ -44,15 +44,13 @@ export default class BackupAuthCredentialRequestContext extends ByteArray {
receive(
response: BackupAuthCredentialResponse,
params: GenericServerPublicParams,
expectedReceiptLevel: bigint
params: GenericServerPublicParams
): BackupAuthCredential {
return new BackupAuthCredential(
Native.BackupAuthCredentialRequestContext_ReceiveResponse(
this.contents,
response.contents,
params.contents,
expectedReceiptLevel
params.contents
)
);
}

View File

@ -0,0 +1,11 @@
//
// Copyright 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
// This must match the Rust version of the enum.
enum BackupLevel {
Messages = 200,
Media = 201,
}
export default BackupLevel;

View File

@ -73,6 +73,7 @@ export { default as BackupAuthCredentialPresentation } from './backups/BackupAut
export { default as BackupAuthCredentialRequest } from './backups/BackupAuthCredentialRequest';
export { default as BackupAuthCredentialRequestContext } from './backups/BackupAuthCredentialRequestContext';
export { default as BackupAuthCredentialResponse } from './backups/BackupAuthCredentialResponse';
export { default as BackupLevel } from './backups/BackupLevel';
// Group Send

View File

@ -20,7 +20,7 @@ use serde::Deserialize;
use uuid::Uuid;
use zkgroup::backups::{
BackupAuthCredential, BackupAuthCredentialPresentation, BackupAuthCredentialRequest,
BackupAuthCredentialRequestContext, BackupAuthCredentialResponse,
BackupAuthCredentialRequestContext, BackupAuthCredentialResponse, BackupLevel,
};
use crate::support::*;
@ -995,7 +995,7 @@ fn BackupAuthCredentialRequest_CheckValidContents(
fn BackupAuthCredentialRequest_IssueDeterministic(
request_bytes: &[u8],
redemption_time: Timestamp,
receipt_level: u64,
backup_level: AsType<BackupLevel, u8>,
params_bytes: &[u8],
randomness: &[u8; RANDOMNESS_LEN],
) -> Vec<u8> {
@ -1006,7 +1006,7 @@ fn BackupAuthCredentialRequest_IssueDeterministic(
let response = request.issue(
redemption_time.as_seconds(),
receipt_level,
backup_level.into_inner(),
&params,
*randomness,
);
@ -1025,7 +1025,6 @@ fn BackupAuthCredentialRequestContext_ReceiveResponse(
context_bytes: &[u8],
response_bytes: &[u8],
params_bytes: &[u8],
expected_receipt_level: u64,
) -> Result<Vec<u8>, ZkGroupVerificationFailure> {
let context = bincode::deserialize::<BackupAuthCredentialRequestContext>(context_bytes)
.expect("should have been parsed previously");
@ -1034,7 +1033,7 @@ fn BackupAuthCredentialRequestContext_ReceiveResponse(
let params = bincode::deserialize::<GenericServerPublicParams>(params_bytes)
.expect("should have been parsed previously");
let credential = context.receive(response, &params, expected_receipt_level)?;
let credential = context.receive(response, &params)?;
Ok(zkgroup::serialize(&credential))
}
@ -1052,6 +1051,13 @@ fn BackupAuthCredential_GetBackupId(credential_bytes: &[u8]) -> [u8; 16] {
credential.backup_id()
}
#[bridge_fn]
fn BackupAuthCredential_GetBackupLevel(credential_bytes: &[u8]) -> u8 {
let credential = bincode::deserialize::<BackupAuthCredential>(credential_bytes)
.expect("should have been parsed previously");
credential.backup_level() as u8
}
#[bridge_fn]
fn BackupAuthCredential_PresentDeterministic(
credential_bytes: &[u8],
@ -1096,10 +1102,10 @@ fn BackupAuthCredentialPresentation_GetBackupId(presentation_bytes: &[u8]) -> [u
}
#[bridge_fn(ffi = false, node = false)]
fn BackupAuthCredentialPresentation_GetReceiptLevel(presentation_bytes: &[u8]) -> ReceiptLevel {
fn BackupAuthCredentialPresentation_GetBackupLevel(presentation_bytes: &[u8]) -> u8 {
let presentation = bincode::deserialize::<BackupAuthCredentialPresentation>(presentation_bytes)
.expect("should have been parsed previously");
presentation.receipt_level()
presentation.backup_level() as u8
}
#[bridge_fn]

View File

@ -7,5 +7,5 @@ mod auth_credential;
pub use auth_credential::{
BackupAuthCredential, BackupAuthCredentialPresentation, BackupAuthCredentialRequest,
BackupAuthCredentialRequestContext, BackupAuthCredentialResponse,
BackupAuthCredentialRequestContext, BackupAuthCredentialResponse, BackupLevel,
};

View File

@ -45,6 +45,41 @@ impl zkcredential::attributes::RevealedAttribute for BackupIdPoint {
const CREDENTIAL_LABEL: &[u8] = b"20231003_Signal_BackupAuthCredential";
// We make sure we serialize BackupLevel with plenty of room to expand to other
// u64 values later. But since it fits in a byte today, we stick to just a u8
// in the in-memory representation.
#[derive(
Copy,
Clone,
Serialize,
Deserialize,
PartialEq,
Eq,
PartialDefault,
Debug,
num_enum::TryFromPrimitive,
)]
#[serde(into = "u64", try_from = "u64")]
#[repr(u8)]
pub enum BackupLevel {
#[partial_default]
Messages = 200,
Media = 201,
}
impl From<BackupLevel> for u64 {
fn from(backup_level: BackupLevel) -> Self {
backup_level as u64
}
}
impl TryFrom<u64> for BackupLevel {
type Error = <BackupLevel as TryFrom<u8>>::Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
BackupLevel::try_from(value as u8)
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct BackupAuthCredentialRequestContext {
reserved: ReservedByte,
@ -107,17 +142,17 @@ impl BackupAuthCredentialRequest {
pub fn issue(
&self,
redemption_time: Timestamp,
receipt_level: ReceiptLevel,
backup_level: BackupLevel,
params: &GenericServerSecretParams,
randomness: RandomnessBytes,
) -> BackupAuthCredentialResponse {
BackupAuthCredentialResponse {
reserved: Default::default(),
redemption_time,
receipt_level,
backup_level,
blinded_credential: zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_public_attribute(&redemption_time)
.add_public_attribute(&receipt_level)
.add_public_attribute(&(backup_level as u64))
.add_blinded_revealed_attribute(&self.blinded_backup_id)
.issue(&params.credential_key, &self.public_key, randomness),
}
@ -128,7 +163,7 @@ impl BackupAuthCredentialRequest {
pub struct BackupAuthCredentialResponse {
reserved: ReservedByte,
redemption_time: Timestamp,
receipt_level: ReceiptLevel,
backup_level: BackupLevel,
blinded_credential: zkcredential::issuance::blind::BlindedIssuanceProof,
}
@ -137,23 +172,18 @@ impl BackupAuthCredentialRequestContext {
self,
response: BackupAuthCredentialResponse,
params: &GenericServerPublicParams,
expected_receipt_level: ReceiptLevel,
) -> Result<BackupAuthCredential, ZkGroupVerificationFailure> {
if response.redemption_time % SECONDS_PER_DAY != 0 {
return Err(ZkGroupVerificationFailure);
}
if response.receipt_level != expected_receipt_level {
return Err(ZkGroupVerificationFailure);
}
Ok(BackupAuthCredential {
reserved: Default::default(),
redemption_time: response.redemption_time,
receipt_level: response.receipt_level,
backup_level: response.backup_level,
credential: zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_public_attribute(&response.redemption_time)
.add_public_attribute(&expected_receipt_level)
.add_public_attribute(&(response.backup_level as u64))
.add_blinded_revealed_attribute(&self.blinded_backup_id)
.verify(
&params.credential_key,
@ -170,7 +200,7 @@ impl BackupAuthCredentialRequestContext {
pub struct BackupAuthCredential {
reserved: ReservedByte,
redemption_time: Timestamp,
receipt_level: ReceiptLevel,
backup_level: BackupLevel,
credential: zkcredential::credentials::Credential,
backup_id: [u8; 16],
}
@ -184,7 +214,7 @@ impl BackupAuthCredential {
BackupAuthCredentialPresentation {
version: Default::default(),
redemption_time: self.redemption_time,
receipt_level: self.receipt_level,
backup_level: self.backup_level,
backup_id: self.backup_id,
proof: zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
.add_revealed_attribute(&BackupIdPoint::new(&self.backup_id))
@ -195,12 +225,16 @@ impl BackupAuthCredential {
pub fn backup_id(&self) -> [u8; 16] {
self.backup_id
}
pub fn backup_level(&self) -> BackupLevel {
self.backup_level
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct BackupAuthCredentialPresentation {
version: ReservedByte,
receipt_level: ReceiptLevel,
backup_level: BackupLevel,
redemption_time: Timestamp,
proof: zkcredential::presentation::PresentationProof,
backup_id: [u8; 16],
@ -227,14 +261,14 @@ impl BackupAuthCredentialPresentation {
zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
.add_public_attribute(&self.redemption_time)
.add_public_attribute(&self.receipt_level)
.add_public_attribute(&(self.backup_level as u64))
.add_revealed_attribute(&BackupIdPoint::new(&self.backup_id))
.verify(&server_params.credential_key, &self.proof)
.map_err(|_| ZkGroupVerificationFailure)
}
pub fn receipt_level(&self) -> ReceiptLevel {
self.receipt_level
pub fn backup_level(&self) -> BackupLevel {
self.backup_level
}
pub fn backup_id(&self) -> [u8; 16] {
@ -244,11 +278,11 @@ impl BackupAuthCredentialPresentation {
#[cfg(test)]
mod tests {
use crate::backups::auth_credential::GenericServerSecretParams;
use crate::backups::auth_credential::{BackupLevel, GenericServerSecretParams};
use crate::backups::{
BackupAuthCredential, BackupAuthCredentialPresentation, BackupAuthCredentialRequestContext,
};
use crate::{RandomnessBytes, Timestamp, RANDOMNESS_LEN, SECONDS_PER_DAY};
use crate::{common, RandomnessBytes, Timestamp, RANDOMNESS_LEN, SECONDS_PER_DAY};
const DAY_ALIGNED_TIMESTAMP: Timestamp = 1681344000; // 2023-04-13 00:00:00 UTC
const KEY: [u8; 32] = [0x42u8; 32];
@ -262,8 +296,6 @@ mod tests {
}
fn generate_credential(redemption_time: Timestamp) -> BackupAuthCredential {
let receipt_level = 10;
// client generated materials; issuance request
let request_context = BackupAuthCredentialRequestContext::new(&KEY, &ACI);
let request = request_context.get_request();
@ -271,7 +303,7 @@ mod tests {
// server generated materials; issuance request -> issuance response
let blinded_credential = request.issue(
redemption_time,
receipt_level,
BackupLevel::Messages,
&server_secret_params(),
ISSUE_RAND,
);
@ -279,7 +311,7 @@ mod tests {
// client generated materials; issuance response -> redemption request
let server_public_params = server_secret_params().get_public_params();
request_context
.receive(blinded_credential, &server_public_params, receipt_level)
.receive(blinded_credential, &server_public_params)
.expect("credential should be valid")
}
@ -341,7 +373,8 @@ mod tests {
let valid_presentation =
credential.present(&server_secret_params().get_public_params(), PRESENT_RAND);
let invalid_presentation = BackupAuthCredentialPresentation {
receipt_level: 999,
// Credential was for BackupLevel::MESSAGES
backup_level: BackupLevel::Media,
..valid_presentation
};
invalid_presentation
@ -353,29 +386,11 @@ mod tests {
fn test_client_enforces_timestamp_granularity() {
let redemption_time: Timestamp = DAY_ALIGNED_TIMESTAMP + 60 * 60; // not on a day boundary!
let request_context = BackupAuthCredentialRequestContext::new(&KEY, &ACI);
let request = request_context.get_request();
let blinded_credential =
request.issue(redemption_time, 1, &server_secret_params(), ISSUE_RAND);
assert!(
request_context
.receive(
blinded_credential,
&server_secret_params().get_public_params(),
1
)
.is_err(),
"client should require that timestamp is on a day boundary"
);
}
#[test]
fn test_client_enforces_receipt_level() {
let request_context = BackupAuthCredentialRequestContext::new(&KEY, &ACI);
let request = request_context.get_request();
let blinded_credential = request.issue(
DAY_ALIGNED_TIMESTAMP,
1,
redemption_time,
BackupLevel::Messages,
&server_secret_params(),
ISSUE_RAND,
);
@ -384,10 +399,30 @@ mod tests {
.receive(
blinded_credential,
&server_secret_params().get_public_params(),
2
)
.is_err(),
"client should require receipt level 2"
"client should require that timestamp is on a day boundary"
);
}
#[test]
fn test_backup_level_serialization() {
let messages_bytes = common::serialization::serialize(&BackupLevel::Messages);
let media_byte = common::serialization::serialize(&BackupLevel::Media);
assert_eq!(messages_bytes.len(), 8);
assert_eq!(media_byte.len(), 8);
let messages_num: u64 =
common::serialization::deserialize(&messages_bytes).expect("valid u64");
let media_num: u64 = common::serialization::deserialize(&media_byte).expect("valid u64");
assert_eq!(messages_num, 200);
assert_eq!(media_num, 201);
let messages: BackupLevel =
common::serialization::deserialize(&messages_bytes).expect("valid level");
let media: BackupLevel =
common::serialization::deserialize(&media_byte).expect("valid level");
assert_eq!(messages, BackupLevel::Messages);
assert_eq!(media, BackupLevel::Media);
}
}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
use zkgroup::{RandomnessBytes, ReceiptLevel, Timestamp, RANDOMNESS_LEN};
use zkgroup::{RandomnessBytes, Timestamp, RANDOMNESS_LEN};
const DAY_ALIGNED_TIMESTAMP: Timestamp = 1681344000; // 2023-04-13 00:00:00 UTC
@ -21,7 +21,7 @@ fn test_backup_auth_request_response() {
// client receives in response to initial request
let redemption_time: Timestamp = DAY_ALIGNED_TIMESTAMP; // client validates it's day-aligned
let receipt_level: ReceiptLevel = 100; // client validates it's their expected receipt level
let backup_level = zkgroup::backups::BackupLevel::Messages; // client validates it's a valid backup level
// client generated materials; issuance request
let request_context =
@ -33,7 +33,7 @@ fn test_backup_auth_request_response() {
zkgroup::generic_server_params::GenericServerSecretParams::generate(randomness1);
let blinded_credential = request.issue(
redemption_time,
receipt_level,
backup_level,
&server_secret_params,
randomness2,
);
@ -41,9 +41,11 @@ fn test_backup_auth_request_response() {
// client generated materials; issuance response -> redemption request
let server_public_params = server_secret_params.get_public_params();
let credential = request_context
.receive(blinded_credential, &server_public_params, receipt_level)
.receive(blinded_credential, &server_public_params)
.expect("credential should be valid");
assert_eq!(credential.backup_level(), backup_level);
let presentation = credential.present(&server_public_params, randomness3);
// server verification of the credential presentation

View File

@ -40,4 +40,18 @@ public class BackupAuthCredential: ByteArray {
}
}
}
public var backupLevel: BackupLevel {
return failOnError {
let rawValue = try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningInteger {
signal_backup_auth_credential_get_backup_level($0, contents)
}
}
guard let backupLevel = BackupLevel(rawValue: rawValue) else {
throw SignalError.internalError("Invalid BackupLevel \(rawValue)")
}
return backupLevel
}
}
}

View File

@ -11,19 +11,19 @@ public class BackupAuthCredentialRequest: ByteArray {
try super.init(contents, checkValid: signal_backup_auth_credential_request_check_valid_contents)
}
public func issueCredential(timestamp: Date, receiptLevel: UInt64, params: GenericServerSecretParams) -> BackupAuthCredentialResponse {
public func issueCredential(timestamp: Date, backupLevel: BackupLevel, params: GenericServerSecretParams) -> BackupAuthCredentialResponse {
return failOnError {
self.issueCredential(timestamp: timestamp, receiptLevel: receiptLevel, params: params, randomness: try .generate())
self.issueCredential(timestamp: timestamp, backupLevel: backupLevel, params: params, randomness: try .generate())
}
}
public func issueCredential(timestamp: Date, receiptLevel: UInt64, params: GenericServerSecretParams, randomness: Randomness) -> BackupAuthCredentialResponse {
public func issueCredential(timestamp: Date, backupLevel: BackupLevel, params: GenericServerSecretParams, randomness: Randomness) -> BackupAuthCredentialResponse {
return failOnError {
try withUnsafeBorrowedBuffer { contents in
try params.withUnsafeBorrowedBuffer { params in
try randomness.withUnsafePointerToBytes { randomness in
try invokeFnReturningVariableLengthSerialized {
signal_backup_auth_credential_request_issue_deterministic($0, contents, UInt64(timestamp.timeIntervalSince1970), receiptLevel, params, randomness)
signal_backup_auth_credential_request_issue_deterministic($0, contents, UInt64(timestamp.timeIntervalSince1970), backupLevel.rawValue, params, randomness)
}
}
}

View File

@ -35,12 +35,12 @@ public class BackupAuthCredentialRequestContext: ByteArray {
}
}
public func receive(_ response: BackupAuthCredentialResponse, params: GenericServerPublicParams, expectedReceiptLevel: UInt64) throws -> BackupAuthCredential {
public func receive(_ response: BackupAuthCredentialResponse, params: GenericServerPublicParams) throws -> BackupAuthCredential {
return try withUnsafeBorrowedBuffer { contents in
try response.withUnsafeBorrowedBuffer { response in
try params.withUnsafeBorrowedBuffer { params in
try invokeFnReturningVariableLengthSerialized {
signal_backup_auth_credential_request_context_receive_response($0, contents, response, params, expectedReceiptLevel)
signal_backup_auth_credential_request_context_receive_response($0, contents, response, params)
}
}
}

View File

@ -0,0 +1,12 @@
//
// Copyright 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalFfi
public enum BackupLevel: UInt8 {
// This must match the Rust version of the enum.
case messages = 200, media = 201
}

View File

@ -1341,16 +1341,18 @@ SignalFfiError *signal_backup_auth_credential_request_context_get_request(Signal
SignalFfiError *signal_backup_auth_credential_request_check_valid_contents(SignalBorrowedBuffer request_bytes);
SignalFfiError *signal_backup_auth_credential_request_issue_deterministic(SignalOwnedBuffer *out, SignalBorrowedBuffer request_bytes, uint64_t redemption_time, uint64_t receipt_level, SignalBorrowedBuffer params_bytes, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_backup_auth_credential_request_issue_deterministic(SignalOwnedBuffer *out, SignalBorrowedBuffer request_bytes, uint64_t redemption_time, uint8_t backup_level, SignalBorrowedBuffer params_bytes, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_backup_auth_credential_response_check_valid_contents(SignalBorrowedBuffer response_bytes);
SignalFfiError *signal_backup_auth_credential_request_context_receive_response(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes, SignalBorrowedBuffer response_bytes, SignalBorrowedBuffer params_bytes, uint64_t expected_receipt_level);
SignalFfiError *signal_backup_auth_credential_request_context_receive_response(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes, SignalBorrowedBuffer response_bytes, SignalBorrowedBuffer params_bytes);
SignalFfiError *signal_backup_auth_credential_check_valid_contents(SignalBorrowedBuffer params_bytes);
SignalFfiError *signal_backup_auth_credential_get_backup_id(uint8_t (*out)[16], SignalBorrowedBuffer credential_bytes);
SignalFfiError *signal_backup_auth_credential_get_backup_level(uint8_t *out, SignalBorrowedBuffer credential_bytes);
SignalFfiError *signal_backup_auth_credential_present_deterministic(SignalOwnedBuffer *out, SignalBorrowedBuffer credential_bytes, SignalBorrowedBuffer server_params_bytes, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_backup_auth_credential_presentation_check_valid_contents(SignalBorrowedBuffer presentation_bytes);

View File

@ -429,7 +429,7 @@ class ZKGroupTests: TestCaseBase {
let aci = UUID(uuidString: "e74beed0-e70f-4cfd-abbb-7e3eb333bbac")!
let serializedBackupID: [UInt8] = [0xE3, 0x92, 0x6F, 0x11, 0xDD, 0xD1, 0x43, 0xE6, 0xDD, 0x0F, 0x20, 0xBF, 0xCB, 0x08, 0x34, 0x9E]
let serializedRequestCredential = Data(base64Encoded: "AISCxQa8OsFqphsQPxqtzJk5+jndpE3SJG6bfazQB3994Aersq2yNRgcARBoedBeoEfKIXdty6X7l6+TiPFAqDvojRSO8xaZOpKJOvWSDJIGn6EeMl2jOjx+IQg8d8M0AQ==")!
let receiptLevel: UInt64 = 1
let backupLevel = BackupLevel.messages
let context = BackupAuthCredentialRequestContext.create(backupKey: backupKey, aci: aci)
let request = context.getRequest()
@ -439,13 +439,14 @@ class ZKGroupTests: TestCaseBase {
let now = UInt64(Date().timeIntervalSince1970)
let startOfDay = now - (now % SECONDS_PER_DAY)
let response = request.issueCredential(timestamp: Date(timeIntervalSince1970: TimeInterval(startOfDay)), receiptLevel: receiptLevel, params: serverSecretParams, randomness: self.TEST_ARRAY_32_2)
let credential = try context.receive(response, params: serverPublicParams, expectedReceiptLevel: receiptLevel)
let response = request.issueCredential(timestamp: Date(timeIntervalSince1970: TimeInterval(startOfDay)), backupLevel: backupLevel, params: serverSecretParams, randomness: self.TEST_ARRAY_32_2)
let credential = try context.receive(response, params: serverPublicParams)
XCTAssertEqual(credential.backupID, serializedBackupID)
XCTAssertEqual(credential.backupLevel, backupLevel)
}
func testBackupAuthCredential() throws {
let receiptLevel: UInt64 = 10
let backupLevel = BackupLevel.messages
let serverSecretParams = GenericServerSecretParams.generate(randomness: self.TEST_ARRAY_32)
let serverPublicParams = serverSecretParams.getPublicParams()
@ -459,11 +460,11 @@ class ZKGroupTests: TestCaseBase {
// Server
let now = UInt64(Date().timeIntervalSince1970)
let startOfDay = now - (now % SECONDS_PER_DAY)
let response = request.issueCredential(timestamp: Date(timeIntervalSince1970: TimeInterval(startOfDay)), receiptLevel: receiptLevel, params: serverSecretParams, randomness: self.TEST_ARRAY_32_2)
let response = request.issueCredential(timestamp: Date(timeIntervalSince1970: TimeInterval(startOfDay)), backupLevel: backupLevel, params: serverSecretParams, randomness: self.TEST_ARRAY_32_2)
// Client
let credential = try context.receive(response, params: serverPublicParams, expectedReceiptLevel: receiptLevel)
XCTAssertThrowsError(try context.receive(response, params: serverPublicParams, expectedReceiptLevel: receiptLevel + 1))
let credential = try context.receive(response, params: serverPublicParams)
XCTAssertEqual(backupLevel, credential.backupLevel)
let presentation = credential.present(serverParams: serverPublicParams, randomness: self.TEST_ARRAY_32_3)