Skip to content

Commit

Permalink
zkgroup: Move blob padding/unpadding into Rust
Browse files Browse the repository at this point in the history
Previously this was defined in the app layers, because zkgroup's
original codegen didn't support custom exception types. However, we
can now move it to a common implementation in Rust.
  • Loading branch information
jrose-signal committed Nov 8, 2021
1 parent 014f190 commit 8cf5683
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ private Native() {}
public static native byte[] GroupPublicParams_GetGroupIdentifier(byte[] groupPublicParams);

public static native void GroupSecretParams_CheckValidContents(byte[] obj);
public static native byte[] GroupSecretParams_DecryptBlob(byte[] params, byte[] ciphertext);
public static native byte[] GroupSecretParams_DecryptBlobWithPadding(byte[] params, byte[] ciphertext);
public static native byte[] GroupSecretParams_DecryptProfileKey(byte[] params, byte[] profileKey, UUID uuid);
public static native UUID GroupSecretParams_DecryptUuid(byte[] params, byte[] uuid);
public static native byte[] GroupSecretParams_DeriveFromMasterKey(byte[] masterKey);
public static native byte[] GroupSecretParams_EncryptBlobDeterministic(byte[] params, byte[] randomness, byte[] plaintext);
public static native byte[] GroupSecretParams_EncryptBlobWithPaddingDeterministic(byte[] params, byte[] randomness, byte[] plaintext, int paddingLen);
public static native byte[] GroupSecretParams_EncryptProfileKey(byte[] params, byte[] profileKey, UUID uuid);
public static native byte[] GroupSecretParams_EncryptUuid(byte[] params, UUID uuid);
public static native byte[] GroupSecretParams_GenerateDeterministic(byte[] randomness);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,35 +62,13 @@ public byte[] encryptBlob(byte[] plaintext) throws VerificationFailedException {
}

public byte[] encryptBlob(SecureRandom secureRandom, byte[] plaintext) throws VerificationFailedException {

byte[] paddedPlaintext = new byte[plaintext.length + 4];
System.arraycopy(plaintext, 0, paddedPlaintext, 4, plaintext.length);

byte[] random = new byte[RANDOM_LENGTH];

secureRandom.nextBytes(random);

return Native.GroupSecretParams_EncryptBlobDeterministic(groupSecretParams.getInternalContentsForJNI(), random, paddedPlaintext);
return Native.GroupSecretParams_EncryptBlobWithPaddingDeterministic(groupSecretParams.getInternalContentsForJNI(), random, plaintext, 0);
}

public byte[] decryptBlob(byte[] blobCiphertext) throws VerificationFailedException {
byte[] newContents = Native.GroupSecretParams_DecryptBlob(groupSecretParams.getInternalContentsForJNI(), blobCiphertext);

if (newContents.length < 4) {
throw new VerificationFailedException();
}

byte[] padLenBytes = new byte[4];
System.arraycopy(newContents, 0, padLenBytes, 0, 4);
int padLen = ByteBuffer.wrap(newContents).getInt();
if (newContents.length < (4 + padLen)) {
throw new VerificationFailedException();
}

byte[] depaddedContents = new byte[newContents.length - (4 + padLen)];
System.arraycopy(newContents, 4, depaddedContents, 0, newContents.length - (4 + padLen));

return depaddedContents;
return Native.GroupSecretParams_DecryptBlobWithPadding(groupSecretParams.getInternalContentsForJNI(), blobCiphertext);
}

}
4 changes: 2 additions & 2 deletions node/Native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ export function GroupMasterKey_CheckValidContents(Obj: Serialized<GroupMasterKey
export function GroupPublicParams_CheckValidContents(Obj: Serialized<GroupPublicParams>): void;
export function GroupPublicParams_GetGroupIdentifier(groupPublicParams: Serialized<GroupPublicParams>): Buffer;
export function GroupSecretParams_CheckValidContents(Obj: Serialized<GroupSecretParams>): void;
export function GroupSecretParams_DecryptBlob(params: Serialized<GroupSecretParams>, ciphertext: Buffer): Buffer;
export function GroupSecretParams_DecryptBlobWithPadding(params: Serialized<GroupSecretParams>, ciphertext: Buffer): Buffer;
export function GroupSecretParams_DecryptProfileKey(params: Serialized<GroupSecretParams>, profileKey: Serialized<ProfileKeyCiphertext>, uuid: Uuid): Serialized<ProfileKey>;
export function GroupSecretParams_DecryptUuid(params: Serialized<GroupSecretParams>, uuid: Serialized<UuidCiphertext>): Uuid;
export function GroupSecretParams_DeriveFromMasterKey(masterKey: Serialized<GroupMasterKey>): Serialized<GroupSecretParams>;
export function GroupSecretParams_EncryptBlobDeterministic(params: Serialized<GroupSecretParams>, randomness: Buffer, plaintext: Buffer): Buffer;
export function GroupSecretParams_EncryptBlobWithPaddingDeterministic(params: Serialized<GroupSecretParams>, randomness: Buffer, plaintext: Buffer, paddingLen: number): Buffer;
export function GroupSecretParams_EncryptProfileKey(params: Serialized<GroupSecretParams>, profileKey: Serialized<ProfileKey>, uuid: Uuid): Serialized<ProfileKeyCiphertext>;
export function GroupSecretParams_EncryptUuid(params: Serialized<GroupSecretParams>, uuid: Uuid): Serialized<UuidCiphertext>;
export function GroupSecretParams_GenerateDeterministic(randomness: Buffer): Serialized<GroupSecretParams>;
Expand Down
32 changes: 4 additions & 28 deletions node/zkgroup/groups/ClientZkGroupCipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import ProfileKeyCiphertext from './ProfileKeyCiphertext';
import ProfileKey from '../profiles/ProfileKey';
import GroupSecretParams from './GroupSecretParams';
import { UUIDType, fromUUID, toUUID } from '../internal/UUIDUtil';
import { SignalClientErrorBase } from '../../Errors';

export default class ClientZkGroupCipher {
groupSecretParams: GroupSecretParams;
Expand Down Expand Up @@ -73,41 +72,18 @@ export default class ClientZkGroupCipher {
}

encryptBlobWithRandom(random: Buffer, plaintext: Buffer): Buffer {
const paddedPlaintext = Buffer.alloc(plaintext.length + 4);
plaintext.copy(paddedPlaintext, 4);
return NativeImpl.GroupSecretParams_EncryptBlobDeterministic(
return NativeImpl.GroupSecretParams_EncryptBlobWithPaddingDeterministic(
this.groupSecretParams.getContents(),
random,
paddedPlaintext
plaintext,
0
);
}

decryptBlob(blobCiphertext: Buffer): Buffer {
const newContents = NativeImpl.GroupSecretParams_DecryptBlob(
return NativeImpl.GroupSecretParams_DecryptBlobWithPadding(
this.groupSecretParams.getContents(),
blobCiphertext
);

if (newContents.length < 4) {
throw new SignalClientErrorBase(
'BAD LENGTH',
'VerificationFailed',
'decryptBlob'
);
}

const padLen = newContents.readInt32BE(0);
if (newContents.length < 4 + padLen) {
throw new SignalClientErrorBase(
'BAD LENGTH',
'VerificationFailed',
'decryptBlob'
);
}

const depaddedContents = Buffer.alloc(newContents.length - (4 + padLen));
newContents.copy(depaddedContents, 0, 4, newContents.length - padLen);

return depaddedContents;
}
}
2 changes: 1 addition & 1 deletion rust/bridge/jni/bin/gen_java_decl.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def print_usage_and_exit():
"("
r"WARN: Can't find .*\. This usually means that this type was incompatible or not found\.|"
r"WARN: Missing `\[defines\]` entry for `feature = \".*\"` in cbindgen config\.|"
r"WARN: Skip libsignal-bridge::_ - \(not `pub`\)\.|"
r"WARN: Skip libsignal-bridge::.+ - \(not `pub`\)\.|"
r"WARN: Couldn't find path for Array\(Path\(GenericPath \{ .+ \}\), Name\(\"LEN\"\)\), skipping associated constants"
")")

Expand Down
9 changes: 5 additions & 4 deletions rust/bridge/shared/src/zkgroup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,21 @@ fn GroupSecretParams_DecryptProfileKey(
}

#[bridge_fn_buffer]
fn GroupSecretParams_EncryptBlobDeterministic(
fn GroupSecretParams_EncryptBlobWithPaddingDeterministic(
params: Serialized<GroupSecretParams>,
randomness: &[u8; RANDOMNESS_LEN],
plaintext: &[u8],
padding_len: u32,
) -> Result<Vec<u8>> {
params.encrypt_blob(*randomness, plaintext)
params.encrypt_blob_with_padding(*randomness, plaintext, padding_len)
}

#[bridge_fn_buffer]
fn GroupSecretParams_DecryptBlob(
fn GroupSecretParams_DecryptBlobWithPadding(
params: Serialized<GroupSecretParams>,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
params.decrypt_blob(ciphertext)
params.decrypt_blob_with_padding(ciphertext)
}

#[bridge_fn]
Expand Down
72 changes: 72 additions & 0 deletions rust/zkgroup/src/api/groups/group_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
//

use std::convert::TryInto;

use crate::api;
use crate::common::constants::*;
use crate::common::errors::*;
Expand Down Expand Up @@ -42,6 +44,8 @@ impl GroupMasterKey {
}
}

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(
Expand Down Expand Up @@ -174,6 +178,21 @@ impl GroupSecretParams {
}
}

pub fn encrypt_blob_with_padding(
&self,
randomness: RandomnessBytes,
plaintext: &[u8],
padding_len: u32,
) -> Result<Vec<u8>, ZkGroupError> {
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>, ZkGroupError> {
if ciphertext.len() < AESGCM_NONCE_LEN + 1 {
// AESGCM_NONCE_LEN = 12 bytes for IV
Expand All @@ -185,6 +204,25 @@ impl GroupSecretParams {
self.decrypt_blob_aesgcmsiv(&self.blob_key, nonce, ciphertext)
}

pub fn decrypt_blob_with_padding(self, ciphertext: &[u8]) -> Result<Vec<u8>, ZkGroupError> {
let mut decrypted = self.decrypt_blob(ciphertext)?;

if decrypted.len() < ENCRYPTED_BLOB_PADDING_LENGTH_SIZE {
return Err(ZkGroupError::DecryptionFailure);
}
let (padding_len_bytes, plaintext_plus_padding) =
decrypted.split_at(ENCRYPTED_BLOB_PADDING_LENGTH_SIZE);

let padding_len = u32::from_be_bytes(padding_len_bytes.try_into().expect("correct size"));
if plaintext_plus_padding.len() < padding_len as usize {
return Err(ZkGroupError::DecryptionFailure);
}

decrypted.truncate(decrypted.len() - padding_len as usize);
decrypted.drain(..ENCRYPTED_BLOB_PADDING_LENGTH_SIZE);
Ok(decrypted)
}

fn encrypt_blob_aesgcmsiv(
&self,
key: &[u8],
Expand Down Expand Up @@ -311,4 +349,38 @@ mod tests {
.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)
.unwrap();
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)
.unwrap();
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[..]);
}
}
}
23 changes: 3 additions & 20 deletions swift/Sources/SignalClient/zkgroup/ClientZkGroupCipher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,38 +63,21 @@ public class ClientZkGroupCipher {
}

public func encryptBlob(randomness: Randomness, plaintext: [UInt8]) throws -> [UInt8] {
let paddedPlaintext = Array(repeating: 0, count: 4) + plaintext

return try groupSecretParams.withUnsafePointerToSerialized { groupSecretParams in
try randomness.withUnsafePointerToBytes { randomness in
try invokeFnReturningArray {
signal_group_secret_params_encrypt_blob_deterministic($0, $1, groupSecretParams, randomness, paddedPlaintext, paddedPlaintext.count)
signal_group_secret_params_encrypt_blob_with_padding_deterministic($0, $1, groupSecretParams, randomness, plaintext, plaintext.count, 0)
}
}
}
}

public func decryptBlob(blobCiphertext: [UInt8]) throws -> [UInt8] {
var newContents = try groupSecretParams.withUnsafePointerToSerialized { groupSecretParams in
return try groupSecretParams.withUnsafePointerToSerialized { groupSecretParams in
try invokeFnReturningArray {
signal_group_secret_params_decrypt_blob($0, $1, groupSecretParams, blobCiphertext, blobCiphertext.count)
signal_group_secret_params_decrypt_blob_with_padding($0, $1, groupSecretParams, blobCiphertext, blobCiphertext.count)
}
}

if newContents.count < 4 {
throw SignalError.verificationFailed("decrypted ciphertext too short")
}

var paddingLen = newContents.withUnsafeBytes({ $0.load(fromByteOffset: 0, as: UInt32.self) })
paddingLen = UInt32(bigEndian: paddingLen)

if newContents.count < (4 + paddingLen) {
throw SignalError.verificationFailed("decrypted ciphertext too short")
}

newContents.removeLast(Int(paddingLen))
newContents.removeFirst(4)
return newContents
}

}
25 changes: 13 additions & 12 deletions swift/Sources/SignalFfi/signal_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1201,18 +1201,19 @@ SignalFfiError *signal_group_secret_params_decrypt_profile_key(unsigned char (*o
const unsigned char (*profile_key)[SignalPROFILE_KEY_CIPHERTEXT_LEN],
const uint8_t (*uuid)[16]);

SignalFfiError *signal_group_secret_params_encrypt_blob_deterministic(const unsigned char **out,
size_t *out_len,
const unsigned char (*params)[SignalGROUP_SECRET_PARAMS_LEN],
const uint8_t (*randomness)[SignalRANDOMNESS_LEN],
const unsigned char *plaintext,
size_t plaintext_len);

SignalFfiError *signal_group_secret_params_decrypt_blob(const unsigned char **out,
size_t *out_len,
const unsigned char (*params)[SignalGROUP_SECRET_PARAMS_LEN],
const unsigned char *ciphertext,
size_t ciphertext_len);
SignalFfiError *signal_group_secret_params_encrypt_blob_with_padding_deterministic(const unsigned char **out,
size_t *out_len,
const unsigned char (*params)[SignalGROUP_SECRET_PARAMS_LEN],
const uint8_t (*randomness)[SignalRANDOMNESS_LEN],
const unsigned char *plaintext,
size_t plaintext_len,
uint32_t padding_len);

SignalFfiError *signal_group_secret_params_decrypt_blob_with_padding(const unsigned char **out,
size_t *out_len,
const unsigned char (*params)[SignalGROUP_SECRET_PARAMS_LEN],
const unsigned char *ciphertext,
size_t ciphertext_len);

SignalFfiError *signal_server_secret_params_generate_deterministic(unsigned char (*out)[SignalSERVER_SECRET_PARAMS_LEN],
const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
Expand Down

0 comments on commit 8cf5683

Please sign in to comment.