Skip to content

Commit

Permalink
Allow specifying private key serialization format
Browse files Browse the repository at this point in the history
  • Loading branch information
moiseev-signal committed Feb 10, 2023
1 parent 90c02f3 commit f9317b5
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 60 deletions.
13 changes: 11 additions & 2 deletions rust/bridge/shared/src/device_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//

use ::device_transfer;
use ::device_transfer::{self, KeyFormat};
use libsignal_bridge_macros::*;

// Not used by the Java bridge.
Expand All @@ -14,7 +14,16 @@ use crate::*;
#[bridge_fn_buffer(node = false)]
fn DeviceTransfer_GeneratePrivateKey() -> Result<Vec<u8>, device_transfer::Error> {
const DEVICE_TRANSFER_KEY_BITS: usize = 4096;
device_transfer::create_rsa_private_key(DEVICE_TRANSFER_KEY_BITS)
device_transfer::create_rsa_private_key(DEVICE_TRANSFER_KEY_BITS, KeyFormat::Pkcs8)
}

// TODO: needed for iOS at the moment, but since it is more generic, could bridge it to JNI as well
#[bridge_fn_buffer(node = false, jni = false)]
fn DeviceTransfer_GeneratePrivateKeyWithFormat(
key_format: u8,
) -> Result<Vec<u8>, device_transfer::Error> {
const DEVICE_TRANSFER_KEY_BITS: usize = 4096;
device_transfer::create_rsa_private_key(DEVICE_TRANSFER_KEY_BITS, KeyFormat::from(key_format))
}

#[bridge_fn_buffer(node = false)]
Expand Down
36 changes: 32 additions & 4 deletions rust/device-transfer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,42 @@ impl fmt::Display for Error {
}
}

/// Generate a private key of size `bits` and export to PKCS8 format.
pub fn create_rsa_private_key(bits: usize) -> Result<Vec<u8>, Error> {
/// Key serialization format.
#[derive(Copy, Clone, Debug)]
pub enum KeyFormat {
/// DER-encoded PKCS8 PrivateKeyInfo format
Pkcs8,
/// DER-encoded key type specific format
KeySpecific,
}

impl From<u8> for KeyFormat {
fn from(value: u8) -> Self {
match value {
0u8 => KeyFormat::Pkcs8,
_ => KeyFormat::KeySpecific,
}
}
}

/// Generate a private key of size `bits` and export to a specified format.
pub fn create_rsa_private_key(bits: usize, key_format: KeyFormat) -> Result<Vec<u8>, Error> {
let rsa = Rsa::generate(bits as u32)
.map_err(|_| Error::InternalError("RSA key generation failed"))?;
let key =
PKey::from_rsa(rsa).map_err(|_| Error::InternalError("Private key generation failed"))?;
key.private_key_to_der_pkcs8()
.map_err(|_| Error::InternalError("Exporting to PKCS8 failed"))
private_key_to_der(key, key_format)
}

fn private_key_to_der(key: PKey<Private>, format: KeyFormat) -> Result<Vec<u8>, Error> {
match format {
KeyFormat::KeySpecific => key
.private_key_to_der()
.map_err(|_| Error::InternalError("Exporting to a key specific format failed")),
KeyFormat::Pkcs8 => key
.private_key_to_der_pkcs8()
.map_err(|_| Error::InternalError("Exporting to PKCS8 failed")),
}
}

/// Generate a self-signed certificate of name `name`, expiring in `days_to_expire`.
Expand Down
83 changes: 44 additions & 39 deletions rust/device-transfer/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,60 @@ use device_transfer::*;

#[test]
fn test_generate_and_parse() -> Result<(), Error> {
let bit_size = 4096;
let key = create_rsa_private_key(bit_size)?;
let days_to_expire = 10;
let cert = create_self_signed_cert(&key, "test", days_to_expire)?;
for key_format in [KeyFormat::KeySpecific, KeyFormat::Pkcs8] {
let bit_size = 4096;
let key = create_rsa_private_key(bit_size, key_format)?;
let days_to_expire = 10;
let cert = create_self_signed_cert(&key, "test", days_to_expire)?;

println!("key = {}", hex::encode(&key));
println!("cert = {}", hex::encode(&cert));
println!("Key format: {:?}", key_format);
println!("key = {}", hex::encode(&key));
println!("cert = {}", hex::encode(&cert));

let boring_key = PKey::private_key_from_der(&key).expect("BoringSSL can parse our PKCS8 key");
// Try to use the key
let boring_key =
PKey::private_key_from_der(&key).expect("BoringSSL can parse our private key");
// Try to use the key

let boring_rsa = boring_key.rsa().expect("This is a RSA key");
let boring_rsa = boring_key.rsa().expect("This is a RSA key");

let mut signature = vec![0; bit_size / 8];
let digest = vec![0x23; 20];
let sig_len = boring_rsa
.private_encrypt(&digest, &mut signature, Padding::PKCS1)
.unwrap();
assert_eq!(sig_len, bit_size / 8);
let mut signature = vec![0; bit_size / 8];
let digest = vec![0x23; 20];
let sig_len = boring_rsa
.private_encrypt(&digest, &mut signature, Padding::PKCS1)
.unwrap();
assert_eq!(sig_len, bit_size / 8);

assert!(boring_rsa.check_key().unwrap());
assert!(boring_rsa.check_key().unwrap());

let boring_cert = X509::from_der(&cert).expect("BoringSSL can parse our certificate");
let pubkey = boring_cert.public_key().expect("Can extract public key");
let boring_cert = X509::from_der(&cert).expect("BoringSSL can parse our certificate");
let pubkey = boring_cert.public_key().expect("Can extract public key");

// Self-signature verifies:
assert!(boring_cert.verify(&pubkey).unwrap());
// Self-signature verifies:
assert!(boring_cert.verify(&pubkey).unwrap());

// Cert should be valid an hour ago, to allow for clock skew
let one_hour_ago: libc::time_t = (SystemTime::now() - Duration::from_secs(60 * 60))
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Valid duration")
.as_secs()
.try_into()
.expect("Duration seconds should fit in i64");
let one_hour_ago = Asn1Time::from_unix(one_hour_ago).expect("Valid timestamp");
// Cert should be valid an hour ago, to allow for clock skew
let one_hour_ago: libc::time_t = (SystemTime::now() - Duration::from_secs(60 * 60))
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Valid duration")
.as_secs()
.try_into()
.expect("Duration seconds should fit in i64");
let one_hour_ago = Asn1Time::from_unix(one_hour_ago).expect("Valid timestamp");

let start = boring_cert.not_before();
let start_ordering = start
.compare(&one_hour_ago)
.expect("comparison should not fail");
assert_eq!(Ordering::Less, start_ordering);
let start = boring_cert.not_before();
let start_ordering = start
.compare(&one_hour_ago)
.expect("comparison should not fail");
assert_eq!(Ordering::Less, start_ordering);

let after_expiration = Asn1Time::days_from_now(days_to_expire + 1).expect("Should not fail");
let expires = boring_cert.not_after();
let expires_ordering = expires
.compare(&after_expiration)
.expect("comparison should not fail");
assert_eq!(Ordering::Less, expires_ordering);
let after_expiration =
Asn1Time::days_from_now(days_to_expire + 1).expect("Should not fail");
let expires = boring_cert.not_after();
let expires_ordering = expires
.compare(&after_expiration)
.expect("comparison should not fail");
assert_eq!(Ordering::Less, expires_ordering);
}

Ok(())
}
10 changes: 8 additions & 2 deletions swift/Sources/LibSignalClient/DeviceTransfer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
import SignalFfi
import Foundation

public enum KeyFormat: UInt8, CaseIterable {
// PKCS#8 is the default for backward compatibility
case pkcs8 = 0
case keySpecific = 1
}

public struct DeviceTransferKey {
public let privateKey: [UInt8]

public static func generate() -> Self {
public static func generate(formattedAs keyFormat: KeyFormat = .pkcs8) -> Self {
let privateKey = failOnError {
try invokeFnReturningArray {
signal_device_transfer_generate_private_key($0, $1)
signal_device_transfer_generate_private_key_with_format($0, $1, keyFormat.rawValue)
}
}

Expand Down
4 changes: 4 additions & 0 deletions swift/Sources/SignalFfi/signal_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,10 @@ SignalFfiError *signal_group_decrypt_message(const unsigned char **out,
SignalFfiError *signal_device_transfer_generate_private_key(const unsigned char **out,
size_t *out_len);

SignalFfiError *signal_device_transfer_generate_private_key_with_format(const unsigned char **out,
size_t *out_len,
uint8_t key_format);

SignalFfiError *signal_device_transfer_generate_certificate(const unsigned char **out,
size_t *out_len,
SignalBorrowedBuffer private_key,
Expand Down
28 changes: 15 additions & 13 deletions swift/Tests/LibSignalClientTests/PublicAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,19 +298,21 @@ class PublicAPITests: TestCaseBase {
}

func testDeviceTransferKey() {
let deviceKey = DeviceTransferKey.generate()

/*
Anything encoded in an ASN.1 SEQUENCE starts with 0x30 when encoded
as DER. (This test could be better.)
*/
let key = deviceKey.privateKeyMaterial()
XCTAssert(key.count > 0)
XCTAssertEqual(key[0], 0x30)

let cert = deviceKey.generateCertificate("name", 30)
XCTAssert(cert.count > 0)
XCTAssertEqual(cert[0], 0x30)
for keyFormat in KeyFormat.allCases {
let deviceKey = DeviceTransferKey.generate(formattedAs: keyFormat)

/*
Anything encoded in an ASN.1 SEQUENCE starts with 0x30 when encoded
as DER. (This test could be better.)
*/
let key = deviceKey.privateKeyMaterial()
XCTAssert(key.count > 0)
XCTAssertEqual(key[0], 0x30)

let cert = deviceKey.generateCertificate("name", 30)
XCTAssert(cert.count > 0)
XCTAssertEqual(cert[0], 0x30)
}
}

func testSignAlternateIdentity() {
Expand Down

0 comments on commit f9317b5

Please sign in to comment.