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:
parent
84daf89942
commit
9f11b256a8
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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<()> {
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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"]
|
||||
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
77
swift/Sources/SignalClient/Aes256GcmSiv.swift
Normal file
77
swift/Sources/SignalClient/Aes256GcmSiv.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user