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

Expose AES-GCM-SIV to Java and Swift

This commit is contained in:
Jack Lloyd 2020-11-30 15:53:28 -05:00
parent 84daf89942
commit 9f11b256a8
21 changed files with 463 additions and 18 deletions

2
Cargo.lock generated
View File

@ -704,6 +704,7 @@ checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
name = "libsignal-ffi"
version = "0.1.0"
dependencies = [
"aes-gcm-siv",
"async-trait",
"cbindgen",
"futures",
@ -717,6 +718,7 @@ dependencies = [
name = "libsignal-jni"
version = "0.1.0"
dependencies = [
"aes-gcm-siv",
"async-trait",
"futures",
"jni",

View File

@ -66,6 +66,11 @@ public final class Native {
private Native() {}
public static native byte[] Aes256GcmSiv_Decrypt(long aesGcmSiv, byte[] msg, byte[] nonce, byte[] associatedData);
public static native void Aes256GcmSiv_Destroy(long handle);
public static native byte[] Aes256GcmSiv_Encrypt(long aesGcmSiv, byte[] msg, byte[] nonce, byte[] associatedData);
public static native long Aes256GcmSiv_New(byte[] key);
public static native String DisplayableFingerprint_Format(byte[] local, byte[] remote);
public static native byte[] ECPrivateKey_Agree(long privateKeyHandle, long publicKeyHandle);

View File

@ -0,0 +1,33 @@
//
// Copyright 2020 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.libsignal.crypto;
import org.signal.client.internal.Native;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidKeyException;
class Aes256GcmSiv {
private final long handle;
public Aes256GcmSiv(byte[] key) throws InvalidKeyException {
this.handle = Native.Aes256GcmSiv_New(key);
}
@Override
protected void finalize() {
Native.Aes256GcmSiv_Destroy(this.handle);
}
byte[] encrypt(byte[] plaintext, byte[] nonce, byte[] associated_data)
throws InvalidMessageException, IllegalArgumentException {
return Native.Aes256GcmSiv_Encrypt(this.handle, plaintext, nonce, associated_data);
}
byte[] decrypt(byte[] ciphertext, byte[] nonce, byte[] associated_data)
throws InvalidMessageException {
return Native.Aes256GcmSiv_Decrypt(this.handle, ciphertext, nonce, associated_data);
}
}

View File

@ -23,13 +23,21 @@ public class Hex {
public static String toString(byte[] bytes, int offset, int length) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < length; i++) {
appendHexChar(buf, bytes[offset + i]);
appendHexCharWithPrefix(buf, bytes[offset + i]);
buf.append(", ");
}
return buf.toString();
}
public static String toStringCondensed(byte[] bytes) {
StringBuffer buf = new StringBuffer();
for (int i=0;i<bytes.length;i++) {
appendHexCharWithPrefix(buf, bytes[i]);
}
return buf.toString();
}
public static String toHexString(byte[] bytes) {
StringBuffer buf = new StringBuffer();
for (int i=0;i<bytes.length;i++) {
appendHexChar(buf, bytes[i]);
@ -58,8 +66,12 @@ public class Hex {
return out;
}
private static void appendHexChar(StringBuffer buf, int b) {
private static void appendHexCharWithPrefix(StringBuffer buf, int b) {
buf.append("(byte)0x");
appendHexChar(buf, b);
}
private static void appendHexChar(StringBuffer buf, int b) {
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
buf.append(HEX_DIGITS[b & 0xf]);
}

View File

@ -0,0 +1,103 @@
//
// Copyright 2020 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.libsignal.crypto;
import java.io.IOException;
import junit.framework.TestCase;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Hex;
public class Aes256GcmSivTests extends TestCase {
public void testAesGcmSivInvalidInputs() throws Exception {
try {
byte[] invalid_key = new byte[16];
Aes256GcmSiv gcm_siv = new Aes256GcmSiv(invalid_key);
throw new AssertionError("Invalid key length accepted");
} catch (InvalidKeyException e) {
/* good */
}
byte[] key = new byte[32];
Aes256GcmSiv gcm_siv = new Aes256GcmSiv(key);
try {
byte[] ptext = new byte[5];
byte[] ad = new byte[5];
byte[] invalid_nonce = new byte[16];
gcm_siv.encrypt(ptext, invalid_nonce, ad);
throw new AssertionError("Invalid nonce accepted");
} catch (IllegalArgumentException e) {
/* good */
}
}
public void testAesGcmSivKats() throws Exception {
testAesGcmSivKat(
"bae8e37fc83441b16034566b7a806c46bb91c3c5aedb64a6c590bc84d1a5e269",
"671fdd4fbdc66f146545fc880c94a95198",
"9209cfae7372e0a3ec2e5d072d5e26b7b9f3acb73908e54cddf7be1864914e13cf",
"e4b47801afc0577e34699b9e",
"874296d5cc1fd16132");
}
private static void testAesGcmSivKat(
String hex_key,
String hex_plaintext,
String hex_ciphertext,
String hex_nonce,
String hex_associated_data) {
try {
byte[] key = Hex.fromStringCondensed(hex_key);
byte[] plaintext = Hex.fromStringCondensed(hex_plaintext);
byte[] nonce = Hex.fromStringCondensed(hex_nonce);
byte[] ad = Hex.fromStringCondensed(hex_associated_data);
Aes256GcmSiv gcm_siv = new Aes256GcmSiv(key);
byte[] ciphertext = gcm_siv.encrypt(plaintext, nonce, ad);
assertEquals(Hex.toHexString(ciphertext), hex_ciphertext);
byte[] recovered = gcm_siv.decrypt(ciphertext, nonce, ad);
assertEquals(Hex.toHexString(recovered), hex_plaintext);
try {
ciphertext[0] ^= 1;
gcm_siv.decrypt(ciphertext, nonce, ad);
throw new AssertionError("Should not have decrypted");
} catch (InvalidMessageException e) {
/* good */
}
try {
ciphertext[0] ^= 1; // restore ciphertext
nonce[0] ^= 1;
gcm_siv.decrypt(ciphertext, nonce, ad);
throw new AssertionError("Should not have decrypted");
} catch (InvalidMessageException e) {
/* good */
}
try {
nonce[0] ^= 1; // restore nonce
ad[0] ^= 1;
gcm_siv.decrypt(ciphertext, nonce, ad);
throw new AssertionError("Should not have decrypted");
} catch (InvalidMessageException e) {
/* good */
}
} catch (IOException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidMessageException e) {
throw new AssertionError(e);
}
}
}

View File

@ -19,7 +19,7 @@ subtle = "2.3"
cipher = "0.2"
generic-array = "0.14"
[target.'cfg(all(target_arch = "aarch64", any(target_os = "linux", target_os = "android")))'.dependencies]
[target.'cfg(all(target_arch = "aarch64", any(target_os = "linux")))'.dependencies]
libc = "0.2" # for getauxval
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]

View File

@ -47,7 +47,7 @@ impl Aes256 {
pub fn encrypt(&self, buf: &mut [u8]) -> Result<()> {
if buf.len() % 16 != 0 {
return Err(Error::InvalidOutputBuffer);
return Err(Error::InvalidInputSize);
}
fn trait_encrypt<C: BlockCipher>(aes: &C, buf: &mut [u8]) -> Result<()> {

View File

@ -65,7 +65,7 @@ impl Aes256Aarch64 {
#[target_feature(enable = "crypto")]
pub unsafe fn encrypt(&self, buf: &mut [u8]) -> Result<()> {
if buf.len() % 16 != 0 {
return Err(Error::InvalidOutputBuffer);
return Err(Error::InvalidInputSize);
}
let kp = self.ek.as_ptr() as *const u8;

View File

@ -185,4 +185,18 @@ impl Aes256GcmSiv {
}
Ok(())
}
pub fn decrypt_with_appended_tag(
&self,
buffer: &mut Vec<u8>,
nonce: &[u8],
associated_data: &[u8]) -> Result<()> {
if buffer.len() < TAG_SIZE {
return Err(Error::InvalidInputSize);
}
let tag = buffer.split_off(buffer.len() - TAG_SIZE);
self.decrypt(buffer, nonce, associated_data, &tag)
}
}

View File

@ -5,7 +5,7 @@
#[cfg(all(
target_arch = "aarch64",
any(target_os = "linux", target_os = "android")
target_os = "linux"
))]
pub fn has_armv8_crypto() -> bool {
// Require NEON, AES and PMULL
@ -20,6 +20,18 @@ pub fn has_armv8_crypto() -> bool {
true
}
#[cfg(all(target_arch = "aarch64", not(any(target_os = "linux", target_os = "ios"))))]
pub fn has_armv8_crypto() -> bool {
// Detection not available for this platform
false
}
#[cfg(all(target_arch = "aarch64", target_os = "ios"))]
pub fn has_armv8_crypto() -> bool {
// All 64-bit iOS devices have AES/PMUL support
true
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn has_intel_aesni() -> bool {
is_x86_feature_detected!("aes")

View File

@ -3,14 +3,27 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Error {
InvalidKeySize,
InvalidNonceSize,
InvalidInputSize,
InvalidOutputBuffer,
InvalidTag,
CpuidFailure,
}
pub type Result<T> = std::result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let err_msg = match self {
Error::InvalidKeySize => "invalid AES-GCM-SIV key size",
Error::InvalidNonceSize => "invalid AES-GCM-SIV nonce size",
Error::InvalidInputSize => "invalid AES-GCM-SIV input size",
Error::InvalidTag => "invalid AES-GCM-SIV tag",
};
write!(f, "{}", err_msg)
}
}

View File

@ -16,6 +16,7 @@ crate-type = ["staticlib"]
[dependencies]
libsignal-protocol-rust = { path = "../../protocol" }
aes-gcm-siv = { path = "../../aes-gcm-siv" }
async-trait = "0.1.41"
libc = "0.2"
futures = "0.3.7"

View File

@ -38,7 +38,7 @@ sort_by = "None"
[parse]
parse_deps = true
include = ["libsignal-protocol-rust"]
include = ["libsignal-protocol-rust", "aes-gcm-siv"]
[parse.expand]
crates = ["libsignal-ffi"]

View File

@ -13,6 +13,8 @@ use static_assertions::const_assert_eq;
use std::convert::TryFrom;
use std::ffi::{c_void, CString};
use aes_gcm_siv::Aes256GcmSiv;
mod util;
use crate::util::*;
@ -1845,3 +1847,67 @@ pub unsafe extern "C" fn signal_sealed_session_cipher_decrypt(
write_bytearray_to(out, out_len, Ok(decrypted.message))
})
}
#[no_mangle]
pub unsafe extern "C" fn signal_aes256_gcm_siv_new(
obj: *mut *mut Aes256GcmSiv,
key: *const c_uchar,
key_len: size_t) -> *mut SignalFfiError {
run_ffi_safe(|| {
let key = as_slice(key, key_len)?;
let aes_gcm_siv = aes_gcm_siv::Aes256GcmSiv::new(&key)?;
box_object::<Aes256GcmSiv>(obj, Ok(aes_gcm_siv))
})
}
ffi_fn_destroy!(signal_aes256_gcm_siv_destroy destroys Aes256GcmSiv);
#[no_mangle]
pub unsafe extern "C" fn signal_aes256_gcm_siv_encrypt(
aes_gcm_siv: *const Aes256GcmSiv,
ctext: *mut *const c_uchar,
ctext_len: *mut size_t,
ptext: *const c_uchar,
ptext_len: size_t,
nonce: *const c_uchar,
nonce_len: size_t,
associated_data: *const c_uchar,
associated_data_len: size_t) -> *mut SignalFfiError {
run_ffi_safe(|| {
let aes_gcm_siv = native_handle_cast::<Aes256GcmSiv>(aes_gcm_siv)?;
let ptext = as_slice(ptext, ptext_len)?;
let nonce = as_slice(nonce, nonce_len)?;
let associated_data = as_slice(associated_data, associated_data_len)?;
let mut buf = Vec::with_capacity(ptext.len() + 16);
buf.extend_from_slice(ptext);
let gcm_tag = aes_gcm_siv.encrypt(&mut buf, &nonce, &associated_data)?;
buf.extend_from_slice(&gcm_tag);
write_bytearray_to(ctext, ctext_len, Ok(buf))
})
}
#[no_mangle]
pub unsafe extern "C" fn signal_aes256_gcm_siv_decrypt(
aes_gcm_siv: *const Aes256GcmSiv,
ptext: *mut *const c_uchar,
ptext_len: *mut size_t,
ctext: *const c_uchar,
ctext_len: size_t,
nonce: *const c_uchar,
nonce_len: size_t,
associated_data: *const c_uchar,
associated_data_len: size_t) -> *mut SignalFfiError {
run_ffi_safe(|| {
let aes_gcm_siv = native_handle_cast::<Aes256GcmSiv>(aes_gcm_siv)?;
let mut buf = as_slice(ctext, ctext_len)?.to_vec();
let nonce = as_slice(nonce, nonce_len)?;
let associated_data = as_slice(associated_data, associated_data_len)?;
aes_gcm_siv.decrypt_with_appended_tag(&mut buf, &nonce, &associated_data)?;
write_bytearray_to(ptext, ptext_len, Ok(buf))
})
}

View File

@ -12,9 +12,12 @@ use std::fmt;
use std::future::Future;
use std::task::{self, Poll};
use aes_gcm_siv::Error as AesGcmSivError;
#[derive(Debug)]
pub enum SignalFfiError {
Signal(SignalProtocolError),
AesGcmSiv(AesGcmSivError),
InsufficientOutputSize(usize, usize),
NullPointer,
InvalidUtf8String,
@ -92,7 +95,8 @@ impl From<&SignalFfiError> for SignalErrorCode {
SignalFfiError::Signal(SignalProtocolError::NoKeyTypeIdentifier)
| SignalFfiError::Signal(SignalProtocolError::BadKeyType(_))
| SignalFfiError::Signal(SignalProtocolError::BadKeyLength(_, _)) => {
| SignalFfiError::Signal(SignalProtocolError::BadKeyLength(_, _))
| SignalFfiError::AesGcmSiv(AesGcmSivError::InvalidKeySize) => {
SignalErrorCode::InvalidKey
}
@ -109,7 +113,8 @@ impl From<&SignalFfiError> for SignalErrorCode {
}
SignalFfiError::Signal(SignalProtocolError::CiphertextMessageTooShort(_))
| SignalFfiError::Signal(SignalProtocolError::InvalidCiphertext) => {
| SignalFfiError::Signal(SignalProtocolError::InvalidCiphertext)
| SignalFfiError::AesGcmSiv(AesGcmSivError::InvalidTag) => {
SignalErrorCode::InvalidCiphertext
}
@ -142,7 +147,8 @@ impl From<&SignalFfiError> for SignalErrorCode {
SignalErrorCode::InvalidState
}
SignalFfiError::Signal(SignalProtocolError::InvalidArgument(_)) => {
SignalFfiError::Signal(SignalProtocolError::InvalidArgument(_))
| SignalFfiError::AesGcmSiv(_) => {
SignalErrorCode::InvalidArgument
}
@ -158,6 +164,9 @@ impl fmt::Display for SignalFfiError {
SignalFfiError::CallbackError(c) => {
write!(f, "callback invocation returned error code {}", c)
}
SignalFfiError::AesGcmSiv(c) => {
write!(f, "AES-GCM-SIV operation failed {}", c)
}
SignalFfiError::NullPointer => write!(f, "null pointer"),
SignalFfiError::InvalidType => write!(f, "invalid type"),
SignalFfiError::InvalidUtf8String => write!(f, "invalid UTF8 string"),
@ -179,6 +188,12 @@ impl From<SignalProtocolError> for SignalFfiError {
}
}
impl From<AesGcmSivError> for SignalFfiError {
fn from(e: AesGcmSivError) -> SignalFfiError {
SignalFfiError::AesGcmSiv(e)
}
}
pub fn run_ffi_safe<F: FnOnce() -> Result<(), SignalFfiError> + std::panic::UnwindSafe>(
f: F,
) -> *mut SignalFfiError {

View File

@ -18,6 +18,7 @@ crate-type = ["cdylib"]
[dependencies]
libsignal-protocol-rust = { path = "../../protocol" }
aes-gcm-siv = { path = "../../aes-gcm-siv" }
async-trait = "0.1.41"
futures = "0.3.7"
jni = "0.17"

View File

@ -36,7 +36,7 @@ for l in stderr.split('\n'):
if unknown_warning:
sys.exit(1)
java_decl = re.compile(r'([a-zA-Z]+) Java_org_signal_client_internal_Native_([A-Z][a-zA-Z]+)_1([A-Za-z0-9]+)\(JNIEnv .?env, JClass class_(, .*)?\);')
java_decl = re.compile(r'([a-zA-Z]+) Java_org_signal_client_internal_Native_([A-Z][a-zA-Z0-9]+)_1([A-Za-z0-9]+)\(JNIEnv .?env, JClass class_(, .*)?\);')
def translate_to_java(typ):

View File

@ -10,9 +10,11 @@ use async_trait::async_trait;
use jni::objects::{JClass, JObject, JString, JValue};
use jni::sys::{jboolean, jbyteArray, jint, jlong, jobject, jstring};
use jni::JNIEnv;
use libsignal_protocol_rust::*;
use std::convert::TryFrom;
use libsignal_protocol_rust::*;
use aes_gcm_siv::Aes256GcmSiv;
mod util;
use crate::util::*;
@ -1930,3 +1932,58 @@ pub unsafe extern "C" fn Java_org_signal_client_internal_Native_SealedSessionCip
box_object::<UnidentifiedSenderMessageContent>(Ok(usmc))
})
}
#[no_mangle]
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_Aes256GcmSiv_1New(
env: JNIEnv,
_class: JClass,
key: jbyteArray) -> ObjectHandle {
run_ffi_safe(&env, || {
let key = env.convert_byte_array(key)?;
let aes_gcm_siv = aes_gcm_siv::Aes256GcmSiv::new(&key)?;
box_object::<Aes256GcmSiv>(Ok(aes_gcm_siv))
})
}
jni_fn_destroy!(Java_org_signal_client_internal_Native_Aes256GcmSiv_1Destroy destroys Aes256GcmSiv);
#[no_mangle]
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_Aes256GcmSiv_1Encrypt(
env: JNIEnv,
_class: JClass,
aes_gcm_siv: ObjectHandle,
msg: jbyteArray,
nonce: jbyteArray,
associated_data: jbyteArray) -> jbyteArray {
run_ffi_safe(&env, || {
let aes_gcm_siv = native_handle_cast::<Aes256GcmSiv>(aes_gcm_siv)?;
let mut msg = env.convert_byte_array(msg)?;
let nonce = env.convert_byte_array(nonce)?;
let associated_data = env.convert_byte_array(associated_data)?;
let tag = aes_gcm_siv.encrypt(&mut msg, &nonce, &associated_data)?;
msg.extend_from_slice(&tag);
to_jbytearray(&env, Ok(msg))
})
}
#[no_mangle]
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_Aes256GcmSiv_1Decrypt(
env: JNIEnv,
_class: JClass,
aes_gcm_siv: ObjectHandle,
msg: jbyteArray,
nonce: jbyteArray,
associated_data: jbyteArray) -> jbyteArray {
run_ffi_safe(&env, || {
let aes_gcm_siv = native_handle_cast::<Aes256GcmSiv>(aes_gcm_siv)?;
let mut msg = env.convert_byte_array(msg)?;
let nonce = env.convert_byte_array(nonce)?;
let associated_data = env.convert_byte_array(associated_data)?;
aes_gcm_siv.decrypt_with_appended_tag(&mut msg, &nonce, &associated_data)?;
to_jbytearray(&env, Ok(msg))
})
}

View File

@ -8,15 +8,18 @@ use futures::task::noop_waker_ref;
use jni::objects::{JObject, JString, JThrowable, JValue};
use jni::sys::{_jobject, jboolean, jbyteArray, jint, jlong, jobject, jstring};
use jni::JNIEnv;
use libsignal_protocol_rust::SignalProtocolError;
use std::convert::TryFrom;
use std::fmt;
use std::future::Future;
use std::task::{self, Poll};
use libsignal_protocol_rust::SignalProtocolError;
use aes_gcm_siv::Error as AesGcmSivError;
#[derive(Debug)]
pub enum SignalJniError {
Signal(SignalProtocolError),
AesGcmSiv(AesGcmSivError),
Jni(jni::errors::Error),
BadJniParameter(&'static str),
UnexpectedJniResultType(&'static str, &'static str),
@ -43,6 +46,7 @@ impl fmt::Display for SignalJniError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SignalJniError::Signal(s) => write!(f, "{}", s),
SignalJniError::AesGcmSiv(s) => write!(f, "{}", s),
SignalJniError::Jni(s) => write!(f, "JNI error {}", s),
SignalJniError::ExceptionDuringCallback(s) => {
write!(f, "exception recieved during callback {}", s)
@ -69,6 +73,12 @@ impl From<SignalProtocolError> for SignalJniError {
}
}
impl From<AesGcmSivError> for SignalJniError {
fn from(e: AesGcmSivError) -> SignalJniError {
SignalJniError::AesGcmSiv(e)
}
}
impl From<jni::errors::Error> for SignalJniError {
fn from(e: jni::errors::Error) -> SignalJniError {
SignalJniError::Jni(e)
@ -111,7 +121,8 @@ pub fn throw_error(env: &JNIEnv, error: SignalJniError) {
SignalJniError::Signal(SignalProtocolError::NoKeyTypeIdentifier)
| SignalJniError::Signal(SignalProtocolError::SignatureValidationFailed)
| SignalJniError::Signal(SignalProtocolError::BadKeyType(_))
| SignalJniError::Signal(SignalProtocolError::BadKeyLength(_, _)) => {
| SignalJniError::Signal(SignalProtocolError::BadKeyLength(_, _))
| SignalJniError::AesGcmSiv(AesGcmSivError::InvalidKeySize) => {
"org/whispersystems/libsignal/InvalidKeyException"
}
@ -124,7 +135,8 @@ pub fn throw_error(env: &JNIEnv, error: SignalJniError) {
| SignalJniError::Signal(SignalProtocolError::UnrecognizedCiphertextVersion(_))
| SignalJniError::Signal(SignalProtocolError::UnrecognizedMessageVersion(_))
| SignalJniError::Signal(SignalProtocolError::InvalidCiphertext)
| SignalJniError::Signal(SignalProtocolError::InvalidProtobufEncoding) => {
| SignalJniError::Signal(SignalProtocolError::InvalidProtobufEncoding)
| SignalJniError::AesGcmSiv(AesGcmSivError::InvalidTag) => {
"org/whispersystems/libsignal/InvalidMessageException"
}
@ -146,7 +158,8 @@ pub fn throw_error(env: &JNIEnv, error: SignalJniError) {
"org/signal/libsignal/metadata/SelfSendException"
}
SignalJniError::Signal(SignalProtocolError::InvalidArgument(_)) => {
SignalJniError::Signal(SignalProtocolError::InvalidArgument(_))
| SignalJniError::AesGcmSiv(_) => {
"java/lang/IllegalArgumentException"
}

View File

@ -0,0 +1,77 @@
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalFfi
import Foundation
public class Aes256GcmSiv: ClonableHandleOwner {
public init<Bytes: ContiguousBytes>(_ bytes: Bytes) throws {
let handle: OpaquePointer? = try bytes.withUnsafeBytes {
var result: OpaquePointer?
try checkError(signal_aes256_gcm_siv_new(&result, $0.baseAddress?.assumingMemoryBound(to: UInt8.self), $0.count))
return result
}
super.init(owned: handle!)
}
internal override class func destroyNativeHandle(_ handle: OpaquePointer) {
signal_aes256_gcm_siv_destroy(handle)
}
public func encrypt<MessageBytes, NonceBytes, AssociatedDataBytes>(
_ message: MessageBytes,
_ nonce: NonceBytes,
_ associated_data: AssociatedDataBytes) throws -> [UInt8]
where MessageBytes: ContiguousBytes,
NonceBytes: ContiguousBytes,
AssociatedDataBytes: ContiguousBytes {
try message.withUnsafeBytes { messageBytes in
try nonce.withUnsafeBytes { nonceBytes in
try associated_data.withUnsafeBytes { adBytes in
try invokeFnReturningArray {
signal_aes256_gcm_siv_encrypt(nativeHandle,
$0,
$1,
messageBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
messageBytes.count,
nonceBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
nonceBytes.count,
adBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
adBytes.count)
}
}
}
}
}
public func decrypt<MessageBytes, NonceBytes, AssociatedDataBytes> (
_ message: MessageBytes,
_ nonce: NonceBytes,
_ associated_data: AssociatedDataBytes) throws -> [UInt8]
where MessageBytes: ContiguousBytes,
NonceBytes: ContiguousBytes,
AssociatedDataBytes: ContiguousBytes {
try message.withUnsafeBytes { messageBytes in
try nonce.withUnsafeBytes { nonceBytes in
try associated_data.withUnsafeBytes { adBytes in
try invokeFnReturningArray {
signal_aes256_gcm_siv_decrypt(nativeHandle,
$0,
$1,
messageBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
messageBytes.count,
nonceBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
nonceBytes.count,
adBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
adBytes.count)
}
}
}
}
}
}

View File

@ -52,6 +52,26 @@ class PublicAPITests: XCTestCase {
XCTAssertEqual(derived, okm)
}
func testAesGcmSiv() {
let ptext: [UInt8] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
let expected_ctext: [UInt8] = [0x1d, 0xe2, 0x29, 0x67, 0x23, 0x7a, 0x81, 0x32, 0x91, 0x21, 0x3f, 0x26, 0x7e, 0x3b, 0x45, 0x2f, 0x02, 0xd0, 0x1a, 0xe3, 0x3e, 0x4e, 0xc8, 0x54]
let ad: [UInt8] = [0x01]
let key: [UInt8] = [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: [UInt8] = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
let gcm_siv = try! Aes256GcmSiv(key)
let ctext = try! gcm_siv.encrypt(ptext, nonce, ad)
XCTAssertEqual(ctext, expected_ctext)
let recovered = try! gcm_siv.decrypt(ctext, nonce, ad)
XCTAssertEqual(recovered, ptext)
XCTAssertThrowsError(try gcm_siv.decrypt(ptext, nonce, ad))
XCTAssertThrowsError(try gcm_siv.decrypt(ctext, ad, nonce))
}
func testAddress() {
let addr = try! ProtocolAddress(name: "addr1", deviceId: 5)
XCTAssertEqual(addr.name, "addr1")
@ -277,6 +297,7 @@ class PublicAPITests: XCTestCase {
("testPkOperations", testPkOperations),
("testHkdfSimple", testHkdfSimple),
("testHkdfUsingRFCExample", testHkdfUsingRFCExample),
("testAesGcmSiv", testAesGcmSiv),
("testGroupCipher", testGroupCipher),
("testSenderCertifications", testSenderCertificates),
]