Compare commits
41 Commits
3c717a8f8d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
0604745f82
|
|||
|
1f4db9d1b0
|
|||
|
cdec79e4dc
|
|||
|
3812001474
|
|||
| 3708781390 | |||
|
a6397fc45a
|
|||
| 0e6b590f32 | |||
|
b0ee1f2c59
|
|||
|
c176021c81
|
|||
|
d75c589b66
|
|||
|
0c4663f7f0
|
|||
|
c446a52462
|
|||
|
9b0ecef9a0
|
|||
|
783b3e1962
|
|||
|
7b7878e2c1
|
|||
|
403eaf1669
|
|||
|
75ed193d86
|
|||
|
c8175e2654
|
|||
|
7c752eab41
|
|||
|
96ebf7f592
|
|||
|
b18a5ec3e2
|
|||
|
813b8b1b24
|
|||
|
6a84ae7f5c
|
|||
|
ff30fd42dd
|
|||
|
0ad8f83092
|
|||
|
1741c335db
|
|||
|
93cb3309bf
|
|||
|
6a24388a19
|
|||
|
b94acf9c31
|
|||
|
b91b29e22d
|
|||
|
12e828732e
|
|||
|
d98005d799
|
|||
|
e308809b20
|
|||
|
920aa92b0e
|
|||
|
20c54350ee
|
|||
|
044daaad7d
|
|||
|
f0f505bde3
|
|||
|
4ad735d840
|
|||
|
d831b606cd
|
|||
| 84ee9d1b34 | |||
|
1b10c2a0c8
|
984
Cargo.lock
generated
984
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
21
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.8.5"
|
version = "1.9.20"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "A simple and tiny file encrypt tool"
|
description = "A simple and tiny file encrypt tool"
|
||||||
@@ -10,11 +10,10 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["decrypt", "macos", "smartcard"]
|
default = ["decrypt", "macos", "smartcard"]
|
||||||
full-features = ["decrypt", "macos", "smartcard", "secure-enclave"]
|
full-features = ["decrypt", "macos", "smartcard"]
|
||||||
decrypt = ["smartcard"]
|
decrypt = ["smartcard"]
|
||||||
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
|
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
|
||||||
macos = ["security-framework"]
|
macos = ["security-framework"]
|
||||||
secure-enclave = ["macos", "swift-rs"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm-stream = "0.2"
|
aes-gcm-stream = "0.2"
|
||||||
@@ -25,7 +24,7 @@ clap = { version = "4.4", features = ["derive"] }
|
|||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
fs-set-times = "0.20"
|
fs-set-times = "0.20"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
indicatif = "0.17"
|
indicatif = "0.18"
|
||||||
openpgp-card = { version = "0.3", optional = true }
|
openpgp-card = { version = "0.3", optional = true }
|
||||||
openpgp-card-pcsc = { version = "0.3", optional = true }
|
openpgp-card-pcsc = { version = "0.3", optional = true }
|
||||||
p256 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] }
|
p256 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] }
|
||||||
@@ -40,12 +39,11 @@ security-framework = { version = "3.0", features = ["OSX_10_15"], optional = tru
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
simpledateformat = "0.1"
|
simpledateformat = "0.1"
|
||||||
tabled = "0.17"
|
tabled = "0.20"
|
||||||
x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] }
|
x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] }
|
||||||
x509-parser = "0.16"
|
x509-parser = "0.17"
|
||||||
yubikey = { version = "0.8", features = ["untested"], optional = true }
|
yubikey = { version = "0.8", features = ["untested"], optional = true }
|
||||||
zeroize = "1.7"
|
zeroize = "1.7"
|
||||||
swift-rs = { version = "1.0.7", optional = true }
|
|
||||||
spki = "0.7"
|
spki = "0.7"
|
||||||
pqcrypto-kyber = "0.8"
|
pqcrypto-kyber = "0.8"
|
||||||
pqcrypto-traits = "0.3"
|
pqcrypto-traits = "0.3"
|
||||||
@@ -53,9 +51,12 @@ pinentry = "0.6"
|
|||||||
secrecy = "0.10"
|
secrecy = "0.10"
|
||||||
dialoguer = "0.11"
|
dialoguer = "0.11"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
swift-secure-enclave-tool-rs = "1.0"
|
||||||
[build-dependencies]
|
json5 = "0.4"
|
||||||
swift-rs = { version = "1.0.7", features = ["build"], optional = true }
|
external-command-rs = "0.1"
|
||||||
|
percent-encoding = "2.3"
|
||||||
|
ml-kem = { version = "0.2.1", features = ["zeroize"] }
|
||||||
|
zeroizing-alloc = "0.1.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -115,7 +115,7 @@ Last, config key id to profile.
|
|||||||
Supported PKI encryption types:
|
Supported PKI encryption types:
|
||||||
|
|
||||||
| Type | Algorithm | Description |
|
| Type | Algorithm | Description |
|
||||||
|------------------|-----------------|-----------------------------------------|
|
|------------------|---------------------|--------------------------------------------------------|
|
||||||
| pgp-rsa | PKCS1-v1.5 | OpenPGP Encryption Key (Previous `pgp`) |
|
| pgp-rsa | PKCS1-v1.5 | OpenPGP Encryption Key (Previous `pgp`) |
|
||||||
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
|
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
|
||||||
| gpg | OpenPGP | GnuPG Command |
|
| gpg | OpenPGP | GnuPG Command |
|
||||||
@@ -123,7 +123,13 @@ Supported PKI encryption types:
|
|||||||
| static-kyber1024 | Kyber1024 | Key Stored in macOS Keychain Access |
|
| static-kyber1024 | Kyber1024 | Key Stored in macOS Keychain Access |
|
||||||
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
|
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
|
||||||
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
|
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
|
||||||
| key-p256 | ECDH(secp256r1) | Key Stored in macOS Secure Enclave |
|
| key-p256 | ECDH(secp256r1) | Key Stored in macOS Secure Enclave (using P256) |
|
||||||
|
| key-mlkem768 | ML-KEM(ML-KEM-768) | Key Stored in macOS Secure Enclave (using ML-KEM-768) |
|
||||||
|
| key-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Stored in macOS Secure Enclave (using ML-KEM-1024) |
|
||||||
|
| ext-p256 | ECDH(secp256r1) | Key Protected by External Command |
|
||||||
|
| ext-p384 | ECDH(secp384r1) | Key Protected by External Command |
|
||||||
|
| ext-mlkem768 | ML-KEM(ML-KEM-768) | Key Protected by External Command |
|
||||||
|
| ext-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Protected by External Command |
|
||||||
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
||||||
|
|
||||||
Smart Card(Yubikey) protected ECDH Encryption description as below:
|
Smart Card(Yubikey) protected ECDH Encryption description as below:
|
||||||
@@ -152,7 +158,8 @@ Smart Card(Yubikey) protected ECDH Encryption description as below:
|
|||||||
Environment
|
Environment
|
||||||
|
|
||||||
| KEY | Comment |
|
| KEY | Comment |
|
||||||
|----------------------------------|---------------------------------------------|
|
|----------------------------------|----------------------------------------------------------------|
|
||||||
|
| TINY_ENCRYPT_CONFIG_FILE | Config file |
|
||||||
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
|
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
|
||||||
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
|
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
|
||||||
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
|
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
|
||||||
@@ -163,7 +170,11 @@ Environment
|
|||||||
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
|
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
|
||||||
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
|
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
|
||||||
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
|
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
|
||||||
| SECURE_EDITOR | Secure Editor |
|
| TINY_ENCRYPT_EXTERNAL_COMMAND | External command cli |
|
||||||
|
| SECURE_EDITOR | Secure Editor [\[OWS RFC6\]](https://openwebstandard.org/rfc6) |
|
||||||
| EDITOR | Editor (Plaintext) |
|
| EDITOR | Editor (Plaintext) |
|
||||||
|
|
||||||
|
Alternative environment setup:
|
||||||
|
```shell
|
||||||
|
~/.config/envs/ENV_VARIABLE_NAME <--> File Content
|
||||||
|
```
|
||||||
5
USAGE.md
5
USAGE.md
@@ -133,5 +133,10 @@ tiny-encrypt -e [-p Profile] [-x] [-L 6] [-1] [-R] [-c Comment] [-C EncryptedCom
|
|||||||
tiny-encrypt -d [-p PIN] [-s Slot] [-R] FILENAMES
|
tiny-encrypt -d [-p PIN] [-s Slot] [-R] FILENAMES
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 5 Generate key(s)
|
||||||
|
|
||||||
|
### 5.1 Generate SE based key(s)
|
||||||
|
```shell
|
||||||
|
tiny-encrypt init-keychain -S -C biometry-current-set -E --key-name <key-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
11
build.rs
11
build.rs
@@ -1,11 +0,0 @@
|
|||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
use swift_rs::SwiftLinker;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Ensure this matches the versions set in your `Package.swift` file.
|
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
SwiftLinker::new("11")
|
|
||||||
.with_ios("11")
|
|
||||||
.with_package("swift-lib", "./swift-lib/")
|
|
||||||
.link();
|
|
||||||
}
|
|
||||||
5
justfile
5
justfile
@@ -1,6 +1,10 @@
|
|||||||
_:
|
_:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
|
# publish
|
||||||
|
publish:
|
||||||
|
cargo publish --registry crates-io
|
||||||
|
|
||||||
# Install local
|
# Install local
|
||||||
install:
|
install:
|
||||||
cargo install --path .
|
cargo install --path .
|
||||||
@@ -27,5 +31,4 @@ try-build-all:
|
|||||||
cargo build --no-default-features --features smartcard
|
cargo build --no-default-features --features smartcard
|
||||||
cargo build --no-default-features --features decrypt
|
cargo build --no-default-features --features decrypt
|
||||||
cargo build --no-default-features --features macos
|
cargo build --no-default-features --features macos
|
||||||
cargo build --no-default-features --features secure-enclave
|
|
||||||
cargo build
|
cargo build
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use tabled::{Table, Tabled};
|
|||||||
use tabled::settings::Style;
|
use tabled::settings::Style;
|
||||||
|
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
|
use crate::temporary_key::serialize_config_envelop;
|
||||||
use crate::util_envelop;
|
use crate::util_envelop;
|
||||||
|
|
||||||
#[derive(Tabled, Eq)]
|
#[derive(Tabled, Eq)]
|
||||||
@@ -47,6 +48,15 @@ pub struct CmdConfig {
|
|||||||
/// Show KID
|
/// Show KID
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub show_kid: bool,
|
pub show_kid: bool,
|
||||||
|
/// JSON output
|
||||||
|
#[arg(long)]
|
||||||
|
pub json: bool,
|
||||||
|
/// Temporary key output
|
||||||
|
#[arg(long)]
|
||||||
|
pub temporary_key: bool,
|
||||||
|
/// Hide __all__
|
||||||
|
#[arg(long)]
|
||||||
|
pub hide_all: bool,
|
||||||
/// Encryption profile (use default when --key-filter is assigned)
|
/// Encryption profile (use default when --key-filter is assigned)
|
||||||
#[arg(long, short = 'p')]
|
#[arg(long, short = 'p')]
|
||||||
pub profile: Option<String>,
|
pub profile: Option<String>,
|
||||||
@@ -55,13 +65,36 @@ pub struct CmdConfig {
|
|||||||
pub key_filter: Option<String>,
|
pub key_filter: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config(cmd_version: CmdConfig) -> XResult<()> {
|
pub fn config(cmd_config: CmdConfig) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load_default()?;
|
let config = TinyEncryptConfig::load_default(&None)?;
|
||||||
|
|
||||||
if cmd_version.profile.is_some() || cmd_version.key_filter.is_some() {
|
if cmd_config.json {
|
||||||
return config_key_filter(&cmd_version, &config);
|
let mut config = config;
|
||||||
|
config.includes = None;
|
||||||
|
if let Some(profiles) = &mut config.profiles {
|
||||||
|
profiles.remove("__all__");
|
||||||
}
|
}
|
||||||
config_profiles(&cmd_version, &config)
|
println!("{}", serde_json::to_string_pretty(&config)?);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd_config.temporary_key {
|
||||||
|
let envelops = if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
|
||||||
|
config.find_envelops(&cmd_config.profile, &cmd_config.key_filter)?
|
||||||
|
} else {
|
||||||
|
config.find_envelops(&None, &None)?
|
||||||
|
};
|
||||||
|
for envelop in envelops {
|
||||||
|
let k = serialize_config_envelop(envelop);
|
||||||
|
println!("{}", k);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
|
||||||
|
return config_key_filter(&cmd_config, &config);
|
||||||
|
}
|
||||||
|
config_profiles(&cmd_config, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_key_filter(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
|
fn config_key_filter(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
|
||||||
@@ -99,9 +132,10 @@ fn strip_field(kid: &str, max_len: usize) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
|
fn config_profiles(cmd_config: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
|
||||||
let mut reverse_map = HashMap::new();
|
let mut reverse_map = HashMap::new();
|
||||||
for (p, v) in &config.profiles {
|
if let Some(profiles) = &config.profiles {
|
||||||
|
for (p, v) in profiles {
|
||||||
let mut v2 = v.clone();
|
let mut v2 = v.clone();
|
||||||
v2.sort();
|
v2.sort();
|
||||||
let vs = v2.join(",");
|
let vs = v2.join(",");
|
||||||
@@ -110,12 +144,16 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
|
|||||||
Some(vec) => { vec.push((p, v)); }
|
Some(vec) => { vec.push((p, v)); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut config_profiles = vec![];
|
let mut config_profiles = vec![];
|
||||||
for pvs in reverse_map.values() {
|
for pvs in reverse_map.values() {
|
||||||
let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
|
let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
|
||||||
ps.sort();
|
ps.sort();
|
||||||
let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
|
let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
|
||||||
|
if cmd_config.hide_all && pp == "__all__" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let kids = pvs[0].1;
|
let kids = pvs[0].1;
|
||||||
let mut ks = Vec::with_capacity(kids.len());
|
let mut ks = Vec::with_capacity(kids.len());
|
||||||
for kid in kids {
|
for kid in kids {
|
||||||
@@ -124,7 +162,7 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
|
|||||||
ks.push(format!("[ERROR] Key not found: {}", kid));
|
ks.push(format!("[ERROR] Key not found: {}", kid));
|
||||||
}
|
}
|
||||||
Some(envelop) => {
|
Some(envelop) => {
|
||||||
let kid = if cmd_version.show_kid {
|
let kid = if cmd_config.show_kid {
|
||||||
format!("Kid: {}", envelop.kid)
|
format!("Kid: {}", envelop.kid)
|
||||||
} else {
|
} else {
|
||||||
envelop.sid.as_ref()
|
envelop.sid.as_ref()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{env, fs};
|
use std::fs;
|
||||||
use std::env::temp_dir;
|
use std::env::temp_dir;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
@@ -16,6 +16,7 @@ use rust_util::{
|
|||||||
debugging, failure, iff, information, opt_result, opt_value_result, println_ex, simple_error, success,
|
debugging, failure, iff, information, opt_result, opt_value_result, println_ex, simple_error, success,
|
||||||
util_cmd, util_msg, util_size, util_time, warning, XResult,
|
util_cmd, util_msg, util_size, util_time, warning, XResult,
|
||||||
};
|
};
|
||||||
|
use rust_util::util_env as rust_util_env;
|
||||||
use rust_util::util_time::UnixEpochTime;
|
use rust_util::util_time::UnixEpochTime;
|
||||||
use x509_parser::prelude::FromDer;
|
use x509_parser::prelude::FromDer;
|
||||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||||
@@ -28,6 +29,8 @@ use crate::compress::GzStreamDecoder;
|
|||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
DATE_TIME_FORMAT,
|
DATE_TIME_FORMAT,
|
||||||
|
ENC_AES256_GCM_MLKEM768, ENC_AES256_GCM_MLKEM1024,
|
||||||
|
ENC_CHACHA20_POLY1305_MLKEM768, ENC_CHACHA20_POLY1305_MLKEM1024,
|
||||||
ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384,
|
ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384,
|
||||||
ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256,
|
ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256,
|
||||||
ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519,
|
ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519,
|
||||||
@@ -37,7 +40,7 @@ use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
|||||||
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
||||||
use crate::util::SecVec;
|
use crate::util::SecVec;
|
||||||
use crate::util_digest::DigestWrite;
|
use crate::util_digest::DigestWrite;
|
||||||
#[cfg(feature = "secure-enclave")]
|
use crate::util_env::TINY_ENCRYPT_ENV_EXTERNAL_COMMAND;
|
||||||
use crate::util_keychainkey;
|
use crate::util_keychainkey;
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
use crate::util_keychainstatic;
|
use crate::util_keychainstatic;
|
||||||
@@ -92,6 +95,10 @@ pub struct CmdDecrypt {
|
|||||||
#[arg(long, short = 'A')]
|
#[arg(long, short = 'A')]
|
||||||
pub digest_algorithm: Option<String>,
|
pub digest_algorithm: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
/// Files need to be decrypted
|
/// Files need to be decrypted
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
@@ -105,7 +112,7 @@ impl Drop for CmdDecrypt {
|
|||||||
pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
|
pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
|
||||||
if cmd_decrypt.split_print { util_msg::set_logger_std_out(false); }
|
if cmd_decrypt.split_print { util_msg::set_logger_std_out(false); }
|
||||||
debugging!("Cmd decrypt: {:?}", cmd_decrypt);
|
debugging!("Cmd decrypt: {:?}", cmd_decrypt);
|
||||||
let config = TinyEncryptConfig::load_default().ok();
|
let config = TinyEncryptConfig::load_default(&cmd_decrypt.config).ok();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut succeed_count = 0;
|
let mut succeed_count = 0;
|
||||||
@@ -328,12 +335,12 @@ fn run_file_editor_and_wait_content(editor: &str, temp_file: &PathBuf, secure_ed
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_file_editor() -> (bool, String) {
|
fn get_file_editor() -> (bool, String) {
|
||||||
if let Ok(secure_editor) = env::var("SECURE_EDITOR") {
|
if let Some(secure_editor) = rust_util_env::env_var("SECURE_EDITOR") {
|
||||||
// cmd <file-name> "aes-256-gcm" <key-in-hex> <nonce-in-hex>
|
// cmd <file-name> "aes-256-gcm" <key-in-hex> <nonce-in-hex>
|
||||||
information!("Found secure editor: {}", &secure_editor);
|
information!("Found secure editor: {}", &secure_editor);
|
||||||
return (true, secure_editor);
|
return (true, secure_editor);
|
||||||
}
|
}
|
||||||
match env::var("EDITOR").ok() {
|
match rust_util_env::env_var("EDITOR") {
|
||||||
Some(editor) => (false, editor),
|
Some(editor) => (false, editor),
|
||||||
None => {
|
None => {
|
||||||
warning!("EDITOR is not assigned, use default editor vi");
|
warning!("EDITOR is not assigned, use default editor vi");
|
||||||
@@ -466,8 +473,10 @@ pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
|
|||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop),
|
TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop),
|
||||||
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot, silent),
|
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot, silent),
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop),
|
TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop),
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem768 | TinyEncryptEnvelopType::KeyMlKem1024 => try_decrypt_se_key_ecdh(config, envelop),
|
||||||
|
TinyEncryptEnvelopType::ExtP256 | TinyEncryptEnvelopType::ExtP384 |
|
||||||
|
TinyEncryptEnvelopType::ExtMlKem768 | TinyEncryptEnvelopType::ExtMlKem1024 => try_decrypt_ext_key_ecdh(config, envelop),
|
||||||
TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot, silent),
|
TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot, silent),
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
TinyEncryptEnvelopType::StaticKyber1024 => try_decrypt_key_ecdh_static_kyber1204(config, envelop),
|
TinyEncryptEnvelopType::StaticKyber1024 => try_decrypt_key_ecdh_static_kyber1204(config, envelop),
|
||||||
@@ -553,13 +562,12 @@ fn try_decrypt_piv_key_rsa(config: &Option<TinyEncryptConfig>,
|
|||||||
Ok(after_2nd_0_bytes)
|
Ok(after_2nd_0_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
||||||
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
||||||
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
||||||
let cryptor = match wrap_key.header.enc.as_str() {
|
let cryptor = match wrap_key.header.enc.as_str() {
|
||||||
ENC_AES256_GCM_P256 => Cryptor::Aes256Gcm,
|
ENC_AES256_GCM_P256 | ENC_AES256_GCM_MLKEM768 | ENC_AES256_GCM_MLKEM1024 => Cryptor::Aes256Gcm,
|
||||||
ENC_CHACHA20_POLY1305_P256 => Cryptor::ChaCha20Poly1305,
|
ENC_CHACHA20_POLY1305_P256 | ENC_CHACHA20_POLY1305_MLKEM768 | ENC_CHACHA20_POLY1305_MLKEM1024 => Cryptor::ChaCha20Poly1305,
|
||||||
_ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc),
|
_ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc),
|
||||||
};
|
};
|
||||||
let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?;
|
let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?;
|
||||||
@@ -572,14 +580,22 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
|||||||
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
let private_key_base64 = if let Ok(keychain_key) = KeychainKey::parse(&config_envelop_args[0]) {
|
let private_key_base64 = if let Ok(keychain_key) = KeychainKey::parse(&config_envelop_args[0]) {
|
||||||
let key = opt_value_result!(keychain_key.get_password()?, "Key: {} not found", &keychain_key.to_str());
|
let key = opt_value_result!(keychain_key.get_password()?, "Key: {} not found", &keychain_key.to_str());
|
||||||
opt_result!(String::from_utf8(key), "Parse key failed: {}")
|
opt_result!(String::from_utf8(key), "Parse key failed: {}")
|
||||||
} else {
|
} else {
|
||||||
config_envelop_args[0].clone()
|
config_envelop_args[0].clone()
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "macos"))]
|
||||||
|
let private_key_base64 = if config_envelop_args[0].starts_with("keychain:") {
|
||||||
|
return simple_error!("Require macos feature: {}", &config_envelop_args[0]);
|
||||||
|
} else {
|
||||||
|
config_envelop_args[0].clone()
|
||||||
|
};
|
||||||
|
|
||||||
let shared_secret = opt_result!(util_keychainkey::decrypt_data(
|
let shared_secret = opt_result!(util_keychainkey::decrypt_data(
|
||||||
|
envelop.r#type,
|
||||||
&private_key_base64,
|
&private_key_base64,
|
||||||
&e_pub_key_bytes
|
&e_pub_key_bytes
|
||||||
), "Decrypt via secure enclave failed: {}");
|
), "Decrypt via secure enclave failed: {}");
|
||||||
@@ -592,6 +608,48 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
|||||||
Ok(decrypted_key)
|
Ok(decrypted_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_decrypt_ext_key_ecdh(config: &Option<TinyEncryptConfig>,
|
||||||
|
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
||||||
|
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
||||||
|
let cryptor = match wrap_key.header.enc.as_str() {
|
||||||
|
ENC_AES256_GCM_P256 | ENC_AES256_GCM_P384 |
|
||||||
|
ENC_AES256_GCM_MLKEM768 | ENC_AES256_GCM_MLKEM1024 => Cryptor::Aes256Gcm,
|
||||||
|
|
||||||
|
ENC_CHACHA20_POLY1305_P256 | ENC_CHACHA20_POLY1305_P384 |
|
||||||
|
ENC_CHACHA20_POLY1305_MLKEM768 | ENC_CHACHA20_POLY1305_MLKEM1024 => Cryptor::ChaCha20Poly1305,
|
||||||
|
_ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc),
|
||||||
|
};
|
||||||
|
let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?;
|
||||||
|
|
||||||
|
let config = opt_value_result!(config, "Tiny encrypt config is not found");
|
||||||
|
let config_envelop = opt_value_result!(
|
||||||
|
config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid);
|
||||||
|
let config_envelop_args = opt_value_result!(&config_envelop.args, "No arguments found for: {}", &envelop.kid);
|
||||||
|
if config_envelop_args.len() < 2 {
|
||||||
|
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let external_command = if config_envelop_args[0].is_empty() {
|
||||||
|
std::env::var(TINY_ENCRYPT_ENV_EXTERNAL_COMMAND).unwrap_or_else(|_| "card-cli".to_string())
|
||||||
|
} else {
|
||||||
|
config_envelop_args[0].clone()
|
||||||
|
};
|
||||||
|
let external_parameter = &config_envelop_args[1];
|
||||||
|
|
||||||
|
let shared_secret = opt_result!(external_command_rs::external_ecdh(
|
||||||
|
&external_command,
|
||||||
|
external_parameter,
|
||||||
|
&e_pub_key_bytes
|
||||||
|
), "Decrypt via secure enclave failed: {}");
|
||||||
|
let key = util::simple_kdf(shared_secret.as_slice());
|
||||||
|
let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce };
|
||||||
|
let decrypted_key = crypto_simple::decrypt(
|
||||||
|
cryptor, &key_nonce, &wrap_key.encrypted_data)?;
|
||||||
|
util::zeroize(key);
|
||||||
|
util::zeroize(shared_secret);
|
||||||
|
Ok(decrypted_key)
|
||||||
|
}
|
||||||
|
|
||||||
fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
||||||
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
||||||
let cryptor = match wrap_key.header.enc.as_str() {
|
let cryptor = match wrap_key.header.enc.as_str() {
|
||||||
|
|||||||
@@ -12,23 +12,20 @@ use rust_util::{debugging, failure, iff, information, opt_result, simple_error,
|
|||||||
|
|
||||||
use crate::compress::GzStreamEncoder;
|
use crate::compress::GzStreamEncoder;
|
||||||
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
||||||
use crate::consts::{
|
use crate::consts::{ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_MLKEM1024, ENC_AES256_GCM_MLKEM768, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_MLKEM1024, ENC_CHACHA20_POLY1305_MLKEM768, ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT, TINY_ENC_PEM_NAME};
|
||||||
ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519,
|
|
||||||
ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384,
|
|
||||||
ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT,
|
|
||||||
TINY_ENC_PEM_NAME,
|
|
||||||
};
|
|
||||||
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
use crate::spec::{
|
use crate::spec::{
|
||||||
EncEncryptedMeta, EncMetadata,
|
EncEncryptedMeta, EncMetadata,
|
||||||
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
|
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
|
||||||
};
|
};
|
||||||
use crate::util::{is_tiny_enc_file, to_pem};
|
use crate::util::{decode_base64, is_tiny_enc_file, to_pem};
|
||||||
use crate::util_ecdh::{ecdh_kyber1024, ecdh_p256, ecdh_p384, ecdh_x25519};
|
use crate::util_ecdh::{ecdh_kyber1024, ecdh_p256, ecdh_p384, ecdh_x25519};
|
||||||
use crate::util_progress::Progress;
|
use crate::util_progress::Progress;
|
||||||
use crate::util_rsa;
|
use crate::{util_mlkem, util_rsa};
|
||||||
use crate::wrap_key::{WrapKey, WrapKeyHeader};
|
use crate::wrap_key::{WrapKey, WrapKeyHeader};
|
||||||
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_gpg};
|
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_gpg};
|
||||||
|
use crate::temporary_key::parse_temporary_keys;
|
||||||
|
use crate::util_mlkem::MlKemAlgo;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdEncrypt {
|
pub struct CmdEncrypt {
|
||||||
@@ -48,6 +45,10 @@ pub struct CmdEncrypt {
|
|||||||
#[arg(long, short = 'k')]
|
#[arg(long, short = 'k')]
|
||||||
pub key_filter: Option<String>,
|
pub key_filter: Option<String>,
|
||||||
|
|
||||||
|
/// Temporary key
|
||||||
|
#[arg(long)]
|
||||||
|
pub temporary_key: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Compress before encrypt
|
/// Compress before encrypt
|
||||||
#[arg(long, short = 'x')]
|
#[arg(long, short = 'x')]
|
||||||
pub compress: bool,
|
pub compress: bool,
|
||||||
@@ -76,16 +77,29 @@ pub struct CmdEncrypt {
|
|||||||
#[arg(long, short = 'A')]
|
#[arg(long, short = 'A')]
|
||||||
pub encryption_algorithm: Option<String>,
|
pub encryption_algorithm: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
/// Files need to be decrypted
|
/// Files need to be decrypted
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load_default()?;
|
let config = TinyEncryptConfig::load_default(&cmd_encrypt.config)?;
|
||||||
debugging!("Found tiny encrypt config: {:?}", config);
|
debugging!("Found tiny encrypt config: {:?}", config);
|
||||||
let envelops = config.find_envelops(&cmd_encrypt.profile, &cmd_encrypt.key_filter)?;
|
let mut envelops = config.find_envelops(&cmd_encrypt.profile, &cmd_encrypt.key_filter)?;
|
||||||
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
|
|
||||||
debugging!("Found envelops: {:?}", envelops);
|
debugging!("Found envelops: {:?}", envelops);
|
||||||
|
|
||||||
|
let temporary_envelops = parse_temporary_keys(&cmd_encrypt.temporary_key)?;
|
||||||
|
if !temporary_envelops.is_empty() {
|
||||||
|
for t_envelop in &temporary_envelops {
|
||||||
|
envelops.push(t_envelop)
|
||||||
|
}
|
||||||
|
debugging!("Final envelops: {:?}", envelops);
|
||||||
|
}
|
||||||
|
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
|
||||||
|
|
||||||
let envelop_tkids: Vec<_> = envelops.iter()
|
let envelop_tkids: Vec<_> = envelops.iter()
|
||||||
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
|
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -313,15 +327,19 @@ pub fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptCo
|
|||||||
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
|
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 => {
|
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 | TinyEncryptEnvelopType::ExtP256 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::PivP384 => {
|
TinyEncryptEnvelopType::PivP384 | TinyEncryptEnvelopType::ExtP384 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::StaticKyber1024 => {
|
TinyEncryptEnvelopType::StaticKyber1024 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_kyber1204(cryptor, key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_kyber1204(cryptor, key, envelop)?);
|
||||||
}
|
}
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem768 | TinyEncryptEnvelopType::KeyMlKem1024 |
|
||||||
|
TinyEncryptEnvelopType::ExtMlKem768 | TinyEncryptEnvelopType::ExtMlKem1024 => {
|
||||||
|
encrypted_envelops.push(encrypt_envelop_ecdh_ml_kem(cryptor, key, envelop)?);
|
||||||
|
}
|
||||||
_ => return simple_error!("Not supported type: {:?}", envelop.r#type),
|
_ => return simple_error!("Not supported type: {:?}", envelop.r#type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,6 +386,19 @@ fn encrypt_envelop_ecdh_kyber1204(cryptor: Cryptor, key: &[u8], envelop: &TinyEn
|
|||||||
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
|
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encrypt_envelop_ecdh_ml_kem(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
|
let public_key_base64 = &envelop.public_part;
|
||||||
|
let public_key = opt_result!(decode_base64(public_key_base64), "Decode ML-KEM public key from base64 failed: {}");
|
||||||
|
let (shared_secret, ciphertext, ml_kem_algo) = util_mlkem::try_ml_kem_encapsulate(&public_key)?;
|
||||||
|
let enc_type = match (cryptor, ml_kem_algo) {
|
||||||
|
(Cryptor::Aes256Gcm, MlKemAlgo::MlKem768) => ENC_AES256_GCM_MLKEM768,
|
||||||
|
(Cryptor::Aes256Gcm, MlKemAlgo::MlKem1024) => ENC_AES256_GCM_MLKEM1024,
|
||||||
|
(Cryptor::ChaCha20Poly1305, MlKemAlgo::MlKem768) => ENC_CHACHA20_POLY1305_MLKEM768,
|
||||||
|
(Cryptor::ChaCha20Poly1305, MlKemAlgo::MlKem1024) => ENC_CHACHA20_POLY1305_MLKEM1024,
|
||||||
|
};
|
||||||
|
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ciphertext, enc_type, envelop)
|
||||||
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_shared_secret(cryptor: Cryptor,
|
fn encrypt_envelop_shared_secret(cryptor: Cryptor,
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
shared_secret: &[u8],
|
shared_secret: &[u8],
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ pub struct CmdExecEnv {
|
|||||||
#[arg(long, short = 's')]
|
#[arg(long, short = 's')]
|
||||||
pub slot: Option<String>,
|
pub slot: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
/// Tiny encrypt file name
|
/// Tiny encrypt file name
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
|
|
||||||
@@ -45,7 +49,7 @@ impl Drop for CmdExecEnv {
|
|||||||
pub fn exec_env(cmd_exec_env: CmdExecEnv) -> XResult<()> {
|
pub fn exec_env(cmd_exec_env: CmdExecEnv) -> XResult<()> {
|
||||||
util_msg::set_logger_std_out(false);
|
util_msg::set_logger_std_out(false);
|
||||||
debugging!("Cmd exec env: {:?}", cmd_exec_env);
|
debugging!("Cmd exec env: {:?}", cmd_exec_env);
|
||||||
let config = TinyEncryptConfig::load_default().ok();
|
let config = TinyEncryptConfig::load_default(&cmd_exec_env.config).ok();
|
||||||
if cmd_exec_env.command_arguments.is_empty() {
|
if cmd_exec_env.command_arguments.is_empty() {
|
||||||
return simple_error!("No commands assigned.");
|
return simple_error!("No commands assigned.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,16 @@ pub struct CmdInfo {
|
|||||||
#[arg(long, short = 'M', default_value_t = false)]
|
#[arg(long, short = 'M', default_value_t = false)]
|
||||||
pub raw_meta: bool,
|
pub raw_meta: bool,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
/// File
|
/// File
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(cmd_info: CmdInfo) -> XResult<()> {
|
pub fn info(cmd_info: CmdInfo) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load_default().ok();
|
let config = TinyEncryptConfig::load_default(&cmd_info.config).ok();
|
||||||
for (i, path) in cmd_info.paths.iter().enumerate() {
|
for (i, path) in cmd_info.paths.iter().enumerate() {
|
||||||
let path = config::resolve_path_namespace(&config, path, true);
|
let path = config::resolve_path_namespace(&config, path, true);
|
||||||
if i > 0 { println!("{}", "-".repeat(88)); }
|
if i > 0 { println!("{}", "-".repeat(88)); }
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use pqcrypto_traits::kem::PublicKey;
|
use pqcrypto_traits::kem::PublicKey;
|
||||||
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
|
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
|
||||||
|
use swift_secure_enclave_tool_rs::ControlFlag;
|
||||||
use crate::config::TinyEncryptConfigEnvelop;
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
use crate::spec::TinyEncryptEnvelopType;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
use crate::util_keychainkey;
|
use crate::util_keychainkey;
|
||||||
use crate::util_keychainstatic;
|
use crate::util_keychainstatic;
|
||||||
use crate::util_keychainstatic::{KeychainKey, KeychainStaticSecret, KeychainStaticSecretAlgorithm};
|
use crate::util_keychainstatic::{KeychainKey, KeychainStaticSecret, KeychainStaticSecretAlgorithm};
|
||||||
@@ -15,6 +14,10 @@ pub struct CmdInitKeychain {
|
|||||||
#[arg(long, short = 'S')]
|
#[arg(long, short = 'S')]
|
||||||
pub secure_enclave: bool,
|
pub secure_enclave: bool,
|
||||||
|
|
||||||
|
/// Secure Enclave control flag, e.g. none, user-presence, device-passcode, biometry-any, biometry-current-set
|
||||||
|
#[arg(long, short = 'C')]
|
||||||
|
pub secure_enclave_control_flag: Option<String>,
|
||||||
|
|
||||||
/// Expose secure enclave private key data
|
/// Expose secure enclave private key data
|
||||||
#[arg(long, short = 'E')]
|
#[arg(long, short = 'E')]
|
||||||
pub expose_secure_enclave_private_key: bool,
|
pub expose_secure_enclave_private_key: bool,
|
||||||
@@ -40,16 +43,12 @@ const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
|||||||
|
|
||||||
pub fn init_keychain(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
pub fn init_keychain(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||||
if cmd_init_keychain.secure_enclave {
|
if cmd_init_keychain.secure_enclave {
|
||||||
#[cfg(feature = "secure-enclave")]
|
keychain_key_se(cmd_init_keychain)
|
||||||
return keychain_key_se(cmd_init_keychain);
|
|
||||||
#[cfg(not(feature = "secure-enclave"))]
|
|
||||||
return simple_error!("Feature secure-enclave is not built");
|
|
||||||
} else {
|
} else {
|
||||||
keychain_key_static(cmd_init_keychain)
|
keychain_key_static(cmd_init_keychain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||||
if !util_keychainkey::is_support_se() {
|
if !util_keychainkey::is_support_se() {
|
||||||
return simple_error!("Secure enclave is not supported.");
|
return simple_error!("Secure enclave is not supported.");
|
||||||
@@ -59,7 +58,19 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
|||||||
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
||||||
let key_name = &cmd_init_keychain.key_name;
|
let key_name = &cmd_init_keychain.key_name;
|
||||||
|
|
||||||
let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair()?;
|
let control_flag = match &cmd_init_keychain.secure_enclave_control_flag {
|
||||||
|
None => return simple_error!("Parameter --secure-enclave-control-flag required"),
|
||||||
|
Some(control_flag) => match control_flag.as_str() {
|
||||||
|
"none" => ControlFlag::None,
|
||||||
|
"user-presence" | "up" => ControlFlag::UserPresence,
|
||||||
|
"device-passcode" | "passcode" | "pass" => ControlFlag::DevicePasscode,
|
||||||
|
"biometry-any" | "bio-any" => ControlFlag::BiometryAny,
|
||||||
|
"biometry-current-set" | "bio-current" => ControlFlag::BiometryCurrentSet,
|
||||||
|
_ => return simple_error!("Invalid control flag: {}", control_flag),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair(control_flag)?;
|
||||||
let public_key_compressed_hex = public_key_hex.chars()
|
let public_key_compressed_hex = public_key_hex.chars()
|
||||||
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
||||||
let saved_arg0 = if cmd_init_keychain.expose_secure_enclave_private_key {
|
let saved_arg0 = if cmd_init_keychain.expose_secure_enclave_private_key {
|
||||||
@@ -77,6 +88,7 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
|||||||
desc: Some("Keychain Secure Enclave".to_string()),
|
desc: Some("Keychain Secure Enclave".to_string()),
|
||||||
args: Some(vec![saved_arg0]),
|
args: Some(vec![saved_arg0]),
|
||||||
public_part: public_key_hex,
|
public_part: public_key_hex,
|
||||||
|
profiles: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
@@ -164,6 +176,7 @@ pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
|||||||
desc: Some("Keychain static".to_string()),
|
desc: Some("Keychain static".to_string()),
|
||||||
args: Some(vec![keychain_key.to_str()]),
|
args: Some(vec![keychain_key.to_str()]),
|
||||||
public_part: public_key_hex,
|
public_part: public_key_hex,
|
||||||
|
profiles: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
|
|||||||
slot_id_hex.clone()
|
slot_id_hex.clone()
|
||||||
]),
|
]),
|
||||||
public_part: public_key_point_hex,
|
public_part: public_key_point_hex,
|
||||||
|
profiles: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
@@ -84,6 +85,7 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
|
|||||||
slot_id_hex.clone()
|
slot_id_hex.clone()
|
||||||
]),
|
]),
|
||||||
public_part: util::to_pem(&spki, "PUBLIC KEY"),
|
public_part: util::to_pem(&spki, "PUBLIC KEY"),
|
||||||
|
profiles: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use serde::Serialize;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use crate::temporary_key::parse_temporary_keys;
|
||||||
use crate::util_simple_pbe::SimplePbkdfEncryptionV1;
|
use crate::util_simple_pbe::SimplePbkdfEncryptionV1;
|
||||||
|
|
||||||
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
|
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
|
||||||
@@ -25,6 +26,10 @@ pub struct CmdSimpleEncrypt {
|
|||||||
#[arg(long, short = 'k')]
|
#[arg(long, short = 'k')]
|
||||||
pub key_filter: Option<String>,
|
pub key_filter: Option<String>,
|
||||||
|
|
||||||
|
/// Temporary key
|
||||||
|
#[arg(long)]
|
||||||
|
pub temporary_key: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Encrypt value from stdin
|
/// Encrypt value from stdin
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub value_stdin: bool,
|
pub value_stdin: bool,
|
||||||
@@ -45,10 +50,22 @@ pub struct CmdSimpleEncrypt {
|
|||||||
#[arg(long, short = 'P')]
|
#[arg(long, short = 'P')]
|
||||||
pub with_pbkdf_encryption: bool,
|
pub with_pbkdf_encryption: bool,
|
||||||
|
|
||||||
|
/// PBKDF iterations (default: 10000)
|
||||||
|
#[arg(long, short = 'i')]
|
||||||
|
pub pbkdf_iterations: Option<u32>,
|
||||||
|
|
||||||
/// PBKDF encryption password
|
/// PBKDF encryption password
|
||||||
#[arg(long, short = 'A')]
|
#[arg(long, short = 'A')]
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Direct output result value
|
||||||
|
#[arg(long)]
|
||||||
|
pub outputs_password: bool,
|
||||||
|
|
||||||
/// Direct output result value
|
/// Direct output result value
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub direct_output: bool,
|
pub direct_output: bool,
|
||||||
@@ -84,6 +101,14 @@ pub struct CmdSimpleDecrypt {
|
|||||||
#[arg(long, short = 'A')]
|
#[arg(long, short = 'A')]
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Direct output result value
|
||||||
|
#[arg(long)]
|
||||||
|
pub outputs_password: bool,
|
||||||
|
|
||||||
/// Direct output result value
|
/// Direct output result value
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub direct_output: bool,
|
pub direct_output: bool,
|
||||||
@@ -122,6 +147,8 @@ pub struct CmdResult {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub password: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub result: Option<String>,
|
pub result: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,14 +157,16 @@ impl CmdResult {
|
|||||||
Self {
|
Self {
|
||||||
code,
|
code,
|
||||||
message: Some(message.to_string()),
|
message: Some(message.to_string()),
|
||||||
|
password: None,
|
||||||
result: None,
|
result: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn success(result: &str) -> Self {
|
pub fn success(result: &str, password: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
code: 0,
|
code: 0,
|
||||||
message: None,
|
message: None,
|
||||||
|
password,
|
||||||
result: Some(result.to_string()),
|
result: Some(result.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,11 +205,23 @@ pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load_default()?;
|
let config = TinyEncryptConfig::load_default(&cmd_simple_encrypt.config)?;
|
||||||
debugging!("Found tiny encrypt config: {:?}", config);
|
debugging!("Found tiny encrypt config: {:?}", config);
|
||||||
let envelops = config.find_envelops(&cmd_simple_encrypt.profile, &cmd_simple_encrypt.key_filter)?;
|
|
||||||
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
|
let mut envelops = config.find_envelops(
|
||||||
|
&cmd_simple_encrypt.profile,
|
||||||
|
&cmd_simple_encrypt.key_filter)?;
|
||||||
debugging!("Found envelops: {:?}", envelops);
|
debugging!("Found envelops: {:?}", envelops);
|
||||||
|
|
||||||
|
let temporary_envelops = parse_temporary_keys(&cmd_simple_encrypt.temporary_key)?;
|
||||||
|
if !temporary_envelops.is_empty() {
|
||||||
|
for t_envelop in &temporary_envelops {
|
||||||
|
envelops.push(t_envelop)
|
||||||
|
}
|
||||||
|
debugging!("Final envelops: {:?}", envelops);
|
||||||
|
}
|
||||||
|
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
|
||||||
|
|
||||||
let envelop_tkids: Vec<_> = envelops.iter()
|
let envelop_tkids: Vec<_> = envelops.iter()
|
||||||
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
|
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -205,17 +246,22 @@ pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()>
|
|||||||
);
|
);
|
||||||
|
|
||||||
let with_pbkdf_encryption = cmd_simple_encrypt.with_pbkdf_encryption || cmd_simple_encrypt.password.is_some();
|
let with_pbkdf_encryption = cmd_simple_encrypt.with_pbkdf_encryption || cmd_simple_encrypt.password.is_some();
|
||||||
|
let mut outputs_password = None;
|
||||||
if with_pbkdf_encryption {
|
if with_pbkdf_encryption {
|
||||||
let password = util::read_password(&cmd_simple_encrypt.password)?;
|
let password = util::read_password(&cmd_simple_encrypt.password)?;
|
||||||
simple_encrypt_result = SimplePbkdfEncryptionV1::encrypt(&password, simple_encrypt_result.as_bytes())?.to_string();
|
simple_encrypt_result = SimplePbkdfEncryptionV1::encrypt(&password, simple_encrypt_result.as_bytes(),
|
||||||
|
&cmd_simple_encrypt.pbkdf_iterations)?.to_string();
|
||||||
|
if cmd_simple_encrypt.outputs_password {
|
||||||
|
outputs_password = Some(password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CmdResult::success(&simple_encrypt_result).print_exit(cmd_simple_encrypt.direct_output);
|
CmdResult::success(&simple_encrypt_result, outputs_password).print_exit(cmd_simple_encrypt.direct_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
|
pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load_default().ok();
|
let config = TinyEncryptConfig::load_default(&cmd_simple_decrypt.config).ok();
|
||||||
|
|
||||||
let pin = cmd_simple_decrypt.pin.clone().or_else(util_env::get_pin);
|
let pin = cmd_simple_decrypt.pin.clone().or_else(util_env::get_pin);
|
||||||
let slot = cmd_simple_decrypt.slot.clone();
|
let slot = cmd_simple_decrypt.slot.clone();
|
||||||
@@ -231,11 +277,15 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
|
|||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut outputs_password = None;
|
||||||
if SimplePbkdfEncryptionV1::matches(&value) {
|
if SimplePbkdfEncryptionV1::matches(&value) {
|
||||||
let simple_pbkdf_encryption_v1: SimplePbkdfEncryptionV1 = value.as_str().try_into()?;
|
let simple_pbkdf_encryption_v1: SimplePbkdfEncryptionV1 = value.as_str().try_into()?;
|
||||||
let password = util::read_password(&cmd_simple_decrypt.password)?;
|
let password = util::read_password(&cmd_simple_decrypt.password)?;
|
||||||
let plaintext_bytes = simple_pbkdf_encryption_v1.decrypt(&password)?;
|
let plaintext_bytes = simple_pbkdf_encryption_v1.decrypt(&password)?;
|
||||||
value = opt_result!(String::from_utf8(plaintext_bytes), "Decrypt PBKDF encryption failed: {}");
|
value = opt_result!(String::from_utf8(plaintext_bytes), "Decrypt PBKDF encryption failed: {}");
|
||||||
|
if cmd_simple_decrypt.outputs_password {
|
||||||
|
outputs_password = Some(password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let value_parts = value.trim().split(SIMPLE_ENCRYPTION_DOT).collect::<Vec<_>>();
|
let value_parts = value.trim().split(SIMPLE_ENCRYPTION_DOT).collect::<Vec<_>>();
|
||||||
@@ -280,5 +330,5 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
|
|||||||
"base64" => STANDARD.encode(&value),
|
"base64" => STANDARD.encode(&value),
|
||||||
_ => return simple_error!("not supported output format: {}", output_format),
|
_ => return simple_error!("not supported output format: {}", output_format),
|
||||||
};
|
};
|
||||||
CmdResult::success(&value).print_exit(cmd_simple_decrypt.direct_output);
|
CmdResult::success(&value, outputs_password).print_exit(cmd_simple_decrypt.direct_output);
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::XResult;
|
use rust_util::{iff, XResult};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
use crate::util_keychainkey;
|
use crate::util_keychainkey;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
@@ -16,13 +15,12 @@ pub fn version(_cmd_version: CmdVersion) -> XResult<()> {
|
|||||||
features.push("macos".to_string());
|
features.push("macos".to_string());
|
||||||
#[cfg(feature = "smartcard")]
|
#[cfg(feature = "smartcard")]
|
||||||
features.push("smartcard".to_string());
|
features.push("smartcard".to_string());
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
features.push(format!("secure-enclave{}", rust_util::iff!(util_keychainkey::is_support_se(), "*", "")));
|
|
||||||
if features.is_empty() { features.push("-".to_string()); }
|
if features.is_empty() { features.push("-".to_string()); }
|
||||||
println!(
|
println!(
|
||||||
"User-Agent: {} [with features: {}]\n{}",
|
"User-Agent: {} [with features: {}]{}\n{}",
|
||||||
util::get_user_agent(),
|
util::get_user_agent(),
|
||||||
features.join(", "),
|
features.join(", "),
|
||||||
|
iff!(util_keychainkey::is_support_se(), " with Secure Enclave Supported", ""),
|
||||||
env!("CARGO_PKG_DESCRIPTION")
|
env!("CARGO_PKG_DESCRIPTION")
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
258
src/config.rs
258
src/config.rs
@@ -3,13 +3,13 @@ use std::collections::HashMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
use rust_util::{util_env as rust_util_env};
|
||||||
use rust_util::util_file::resolve_file_path;
|
use rust_util::util_file::resolve_file_path;
|
||||||
use rust_util::{debugging, opt_result, simple_error, warning, XResult};
|
use rust_util::{debugging, opt_result, warning, XResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::consts::{ENV_TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_CONFIG_FILE_3, TINY_ENC_FILE_EXT};
|
||||||
use crate::consts::{TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_FILE_EXT};
|
|
||||||
use crate::spec::TinyEncryptEnvelopType;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
|
use crate::util::decode_base64;
|
||||||
|
|
||||||
/// Config file sample:
|
/// Config file sample:
|
||||||
/// ~/.tinyencrypt/config-rs.json
|
/// ~/.tinyencrypt/config-rs.json
|
||||||
@@ -39,8 +39,18 @@ use crate::spec::TinyEncryptEnvelopType;
|
|||||||
pub struct TinyEncryptConfig {
|
pub struct TinyEncryptConfig {
|
||||||
pub environment: Option<HashMap<String, StringOrVecString>>,
|
pub environment: Option<HashMap<String, StringOrVecString>>,
|
||||||
pub namespaces: Option<HashMap<String, String>>,
|
pub namespaces: Option<HashMap<String, String>>,
|
||||||
|
pub includes: Option<String>, // find all *.tinyencrypt.json
|
||||||
pub envelops: Vec<TinyEncryptConfigEnvelop>,
|
pub envelops: Vec<TinyEncryptConfigEnvelop>,
|
||||||
pub profiles: HashMap<String, Vec<String>>,
|
pub profiles: Option<HashMap<String, Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TinyEncryptConfig {
|
||||||
|
fn get_profile(&self, profile: &str) -> Option<&Vec<String>> {
|
||||||
|
match &self.profiles {
|
||||||
|
Some(profiles) => profiles.get(profile),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -62,51 +72,60 @@ pub struct TinyEncryptConfigEnvelop {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub args: Option<Vec<String>>,
|
pub args: Option<Vec<String>>,
|
||||||
pub public_part: String,
|
pub public_part: String,
|
||||||
|
pub profiles: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TinyEncryptConfig {
|
impl TinyEncryptConfig {
|
||||||
pub fn load_default() -> XResult<Self> {
|
pub fn load_default(config: &Option<String>) -> XResult<Self> {
|
||||||
let resolved_file = resolve_file_path(TINY_ENC_CONFIG_FILE);
|
let resolved_file0 = config.clone().or_else(|| rust_util_env::env_var(ENV_TINY_ENC_CONFIG_FILE));
|
||||||
|
let resolved_file_1 = resolve_file_path(TINY_ENC_CONFIG_FILE);
|
||||||
let resolved_file_2 = resolve_file_path(TINY_ENC_CONFIG_FILE_2);
|
let resolved_file_2 = resolve_file_path(TINY_ENC_CONFIG_FILE_2);
|
||||||
let config_file = if fs::metadata(&resolved_file).is_ok() {
|
let resolved_file_3 = resolve_file_path(TINY_ENC_CONFIG_FILE_3);
|
||||||
debugging!("Load config from: {resolved_file}");
|
if let Some(resolved_file) = resolved_file0 {
|
||||||
resolved_file
|
if resolved_file.starts_with("base64:") {
|
||||||
|
let decoded_resolved_bytes_result = decode_base64(&resolved_file.chars().skip(7).collect::<String>());
|
||||||
|
let decoded_resolved_bytes = opt_result!(decoded_resolved_bytes_result, "Decode base64 failed: {}");
|
||||||
|
let decoded_resolved_content = opt_result!(String::from_utf8(decoded_resolved_bytes), "Decode UTF-8 string failed: {}");
|
||||||
|
return Self::load_content(&decoded_resolved_content, "<env>");
|
||||||
|
}
|
||||||
|
debugging!("Found tiny encrypt config file: {}", &resolved_file);
|
||||||
|
return Self::load_file(&resolved_file)
|
||||||
|
}
|
||||||
|
let config_file = if fs::metadata(&resolved_file_1).is_ok() {
|
||||||
|
debugging!("Load config from: {resolved_file_1}");
|
||||||
|
resolved_file_1
|
||||||
} else if fs::metadata(&resolved_file_2).is_ok() {
|
} else if fs::metadata(&resolved_file_2).is_ok() {
|
||||||
debugging!("Load config from: {resolved_file_2}");
|
debugging!("Load config from: {resolved_file_2}");
|
||||||
resolved_file_2
|
resolved_file_2
|
||||||
|
} else if fs::metadata(&resolved_file_3).is_ok() {
|
||||||
|
debugging!("Load config from: {resolved_file_3}");
|
||||||
|
resolved_file_3
|
||||||
} else {
|
} else {
|
||||||
warning!("Cannot find config file from:\n- {resolved_file}\n- {resolved_file_2}");
|
warning!("Cannot find config file from:\n- {resolved_file_1}\n- {resolved_file_2}\n- {resolved_file_3}");
|
||||||
resolved_file
|
resolved_file_1
|
||||||
};
|
};
|
||||||
Self::load(&config_file)
|
Self::load_file(&config_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(file: &str) -> XResult<Self> {
|
pub fn load_file(file: &str) -> XResult<Self> {
|
||||||
let resolved_file = resolve_file_path(file);
|
let resolved_file = resolve_file_path(file);
|
||||||
let config_contents = opt_result!(
|
let config_content = opt_result!(
|
||||||
fs::read_to_string(resolved_file),
|
fs::read_to_string(resolved_file),
|
||||||
"Read config file: {}, failed: {}",
|
"Read config file: {}, failed: {}",
|
||||||
file
|
file
|
||||||
);
|
);
|
||||||
let mut config: TinyEncryptConfig = opt_result!(
|
Self::load_content(&config_content, file)
|
||||||
serde_json::from_str(&config_contents),
|
}
|
||||||
|
|
||||||
|
pub fn load_content(config_content: &str, file: &str) -> XResult<Self> {
|
||||||
|
let config: TinyEncryptConfig = opt_result!(
|
||||||
|
serde_json::from_str(&config_content),
|
||||||
"Parse config file: {}, failed: {}",
|
"Parse config file: {}, failed: {}",
|
||||||
file
|
file
|
||||||
);
|
);
|
||||||
let mut splited_profiles = HashMap::new();
|
debugging!("Config: {:#?}", config);
|
||||||
for (k, v) in config.profiles.into_iter() {
|
let config = load_includes_and_merge(config);
|
||||||
if !k.contains(',') {
|
debugging!("Final config: {:#?}", config);
|
||||||
splited_profiles.insert(k, v);
|
|
||||||
} else {
|
|
||||||
k.split(',')
|
|
||||||
.map(|k| k.trim())
|
|
||||||
.filter(|k| !k.is_empty())
|
|
||||||
.for_each(|k| {
|
|
||||||
splited_profiles.insert(k.to_string(), v.clone());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.profiles = splited_profiles;
|
|
||||||
|
|
||||||
if let Some(environment) = &config.environment {
|
if let Some(environment) = &config.environment {
|
||||||
for (k, v) in environment {
|
for (k, v) in environment {
|
||||||
@@ -220,7 +239,7 @@ impl TinyEncryptConfig {
|
|||||||
self.envelops.iter().for_each(|e| {
|
self.envelops.iter().for_each(|e| {
|
||||||
key_ids.push(e.kid.to_string());
|
key_ids.push(e.kid.to_string());
|
||||||
});
|
});
|
||||||
} else if let Some(kids) = self.profiles.get(profile) {
|
} else if let Some(kids) = self.get_profile(profile) {
|
||||||
kids.iter().for_each(|k| key_ids.push(k.to_string()));
|
kids.iter().for_each(|k| key_ids.push(k.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +252,8 @@ impl TinyEncryptConfig {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if key_ids.is_empty() {
|
if key_ids.is_empty() {
|
||||||
return simple_error!("Profile or key filter cannot find any valid envelopes");
|
// return simple_error!("Profile or key filter cannot find any valid envelopes");
|
||||||
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
for key_id in &key_ids {
|
for key_id in &key_ids {
|
||||||
for envelop in self.find_by_kid_or_type(key_id) {
|
for envelop in self.find_by_kid_or_type(key_id) {
|
||||||
@@ -243,7 +263,8 @@ impl TinyEncryptConfig {
|
|||||||
|
|
||||||
let mut envelops: Vec<_> = matched_envelops_map.values().copied().collect();
|
let mut envelops: Vec<_> = matched_envelops_map.values().copied().collect();
|
||||||
if envelops.is_empty() {
|
if envelops.is_empty() {
|
||||||
return simple_error!("Profile or key filter cannot find any valid envelopes");
|
// return simple_error!("Profile or key filter cannot find any valid envelopes");
|
||||||
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
envelops.sort_by(|e1, e2| {
|
envelops.sort_by(|e1, e2| {
|
||||||
if e1.r#type < e2.r#type {
|
if e1.r#type < e2.r#type {
|
||||||
@@ -274,3 +295,172 @@ pub fn resolve_path_namespace(
|
|||||||
Some(config) => config.resolve_path_namespace(path, append_te),
|
Some(config) => config.resolve_path_namespace(path, append_te),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_includes_and_merge(mut config: TinyEncryptConfig) -> TinyEncryptConfig {
|
||||||
|
debugging!("Config includes: {:?}", &config.includes);
|
||||||
|
if let Some(includes) = &config.includes {
|
||||||
|
let sub_configs = search_include_configs(includes);
|
||||||
|
debugging!(
|
||||||
|
"Found {} sub configs, detail {:?}",
|
||||||
|
sub_configs.len(),
|
||||||
|
sub_configs
|
||||||
|
);
|
||||||
|
for sub_config in &sub_configs {
|
||||||
|
// merge environment
|
||||||
|
if let Some(sub_environment) = &sub_config.environment {
|
||||||
|
match &mut config.environment {
|
||||||
|
None => {
|
||||||
|
config.environment = Some(sub_environment.clone());
|
||||||
|
}
|
||||||
|
Some(env) => {
|
||||||
|
for (k, v) in sub_environment {
|
||||||
|
match env.get_mut(k) {
|
||||||
|
None => {
|
||||||
|
env.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
Some(env_val) => {
|
||||||
|
match (env_val, v) {
|
||||||
|
(StringOrVecString::Vec(env_value_vec), StringOrVecString::Vec(v_vec)) => {
|
||||||
|
for vv in v_vec {
|
||||||
|
if !env_value_vec.contains(vv) {
|
||||||
|
env_value_vec.push(vv.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warning!("Duplicate or mis-match environment value, key: {}", k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// merge profiles
|
||||||
|
for sub_envelop in &sub_config.envelops {
|
||||||
|
let filter_envelops = config.envelops.iter().filter(|e| {
|
||||||
|
e.kid == sub_envelop.kid || (e.sid.is_some() && e.sid == sub_envelop.sid)
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
if !filter_envelops.is_empty() {
|
||||||
|
warning!("Duplication kid: {} or sid: {:?}", sub_envelop.kid, sub_envelop.sid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
config.envelops.push(sub_envelop.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// deal with envelop profiles
|
||||||
|
let mut sub_profiles: HashMap<String, Vec<String>> = match &sub_config.profiles {
|
||||||
|
None => HashMap::new(),
|
||||||
|
Some(sub_profiles) => sub_profiles.clone(),
|
||||||
|
};
|
||||||
|
for envelop in &sub_config.envelops {
|
||||||
|
if let Some(profiles) = &envelop.profiles {
|
||||||
|
let kid = envelop.kid.clone();
|
||||||
|
for profile in profiles {
|
||||||
|
match sub_profiles.get_mut(profile) {
|
||||||
|
None => {
|
||||||
|
sub_profiles.insert(profile.clone(), vec![kid.clone()]);
|
||||||
|
}
|
||||||
|
Some(kids) => {
|
||||||
|
if !kids.contains(&kid) {
|
||||||
|
kids.push(kid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge profiles
|
||||||
|
match &mut config.profiles {
|
||||||
|
None => {
|
||||||
|
config.profiles = Some(sub_profiles.clone());
|
||||||
|
}
|
||||||
|
Some(profiles) => {
|
||||||
|
for (k, v) in &sub_profiles {
|
||||||
|
match profiles.get_mut(k) {
|
||||||
|
None => {
|
||||||
|
profiles.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
Some(env_val) => {
|
||||||
|
for vv in v {
|
||||||
|
env_val.push(vv.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(profiles) = &mut config.profiles {
|
||||||
|
let all_key_ids = config.envelops.iter().map(|e| e.kid.clone()).collect::<Vec<_>>();
|
||||||
|
if profiles.contains_key("__all__") {
|
||||||
|
warning!("Key __all__ in profiles exists")
|
||||||
|
} else {
|
||||||
|
profiles.insert("__all__".to_string(), all_key_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_include_configs(includes_path: &str) -> Vec<TinyEncryptConfig> {
|
||||||
|
let includes_path = if includes_path.starts_with("$") {
|
||||||
|
let includes_path_env_var = includes_path.chars().skip(1).collect::<String>();
|
||||||
|
match rust_util_env::env_var(&includes_path_env_var) {
|
||||||
|
Some(includes_path) => includes_path,
|
||||||
|
None => {
|
||||||
|
warning!("Cannot find env var: {}", &includes_path_env_var);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
includes_path.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let includes_path = &includes_path;
|
||||||
|
let mut sub_configs = vec![];
|
||||||
|
let read_dir = match fs::read_dir(includes_path) {
|
||||||
|
Ok(read_dir) => read_dir,
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Read dir: {}, failed: {}", includes_path, e);
|
||||||
|
return sub_configs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for entry in read_dir {
|
||||||
|
let entry = match entry {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Read dir: {} entry, failed: {}", includes_path, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let file_name = file_name.to_str();
|
||||||
|
let file_name = match file_name {
|
||||||
|
Some(file_name) => file_name,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if file_name.ends_with(".tinyencrypt.json") {
|
||||||
|
debugging!("Matches config file: {}", file_name);
|
||||||
|
let file_path = entry.path();
|
||||||
|
let content = match fs::read_to_string(entry.path()) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Read config file: {:?}, failed: {}", file_path, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let config = match serde_json::from_str::<TinyEncryptConfig>(&content) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Parse config file: {:?}, failed: {}", file_path, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sub_configs.push(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sub_configs
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,16 +3,22 @@ pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256";
|
|||||||
pub const ENC_AES256_GCM_P384: &str = "aes256-gcm-p384";
|
pub const ENC_AES256_GCM_P384: &str = "aes256-gcm-p384";
|
||||||
pub const ENC_AES256_GCM_X25519: &str = "aes256-gcm-x25519";
|
pub const ENC_AES256_GCM_X25519: &str = "aes256-gcm-x25519";
|
||||||
pub const ENC_AES256_GCM_KYBER1204: &str = "aes256-gcm-kyber1204";
|
pub const ENC_AES256_GCM_KYBER1204: &str = "aes256-gcm-kyber1204";
|
||||||
|
pub const ENC_AES256_GCM_MLKEM768: &str = "aes256-gcm-mlkem768";
|
||||||
|
pub const ENC_AES256_GCM_MLKEM1024: &str = "aes256-gcm-mlkem1024";
|
||||||
pub const ENC_CHACHA20_POLY1305_P256: &str = "chacha20-poly1305-p256";
|
pub const ENC_CHACHA20_POLY1305_P256: &str = "chacha20-poly1305-p256";
|
||||||
pub const ENC_CHACHA20_POLY1305_P384: &str = "chacha20-poly1305-p384";
|
pub const ENC_CHACHA20_POLY1305_P384: &str = "chacha20-poly1305-p384";
|
||||||
pub const ENC_CHACHA20_POLY1305_X25519: &str = "chacha20-poly1305-x25519";
|
pub const ENC_CHACHA20_POLY1305_X25519: &str = "chacha20-poly1305-x25519";
|
||||||
pub const ENC_CHACHA20_POLY1305_KYBER1204: &str = "chacha20-poly1305-kyber1204";
|
pub const ENC_CHACHA20_POLY1305_KYBER1204: &str = "chacha20-poly1305-kyber1204";
|
||||||
|
pub const ENC_CHACHA20_POLY1305_MLKEM768: &str = "chacha20-poly1305-mlkem768";
|
||||||
|
pub const ENC_CHACHA20_POLY1305_MLKEM1024: &str = "chacha20-poly1305-mlkem1024";
|
||||||
|
|
||||||
// Extend and config file
|
// Extend and config file
|
||||||
pub const TINY_ENC_FILE_EXT: &str = ".tinyenc";
|
pub const TINY_ENC_FILE_EXT: &str = ".tinyenc";
|
||||||
pub const TINY_ENC_PEM_FILE_EXT: &str = ".tinyenc.pem";
|
pub const TINY_ENC_PEM_FILE_EXT: &str = ".tinyenc.pem";
|
||||||
|
pub const ENV_TINY_ENC_CONFIG_FILE: &str = "TINY_ENCRYPT_CONFIG_FILE";
|
||||||
pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json";
|
pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json";
|
||||||
pub const TINY_ENC_CONFIG_FILE_2: &str = "/etc/tinyencrypt/config-rs.json";
|
pub const TINY_ENC_CONFIG_FILE_2: &str = "~/.config/tinyencrypt-rs.json";
|
||||||
|
pub const TINY_ENC_CONFIG_FILE_3: &str = "/etc/tinyencrypt/config-rs.json";
|
||||||
|
|
||||||
pub const TINY_ENC_PEM_NAME: &str = "TINY ENCRYPT";
|
pub const TINY_ENC_PEM_NAME: &str = "TINY ENCRYPT";
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub use cmd_initpiv::init_piv;
|
|||||||
pub use cmd_version::CmdVersion;
|
pub use cmd_version::CmdVersion;
|
||||||
pub use cmd_version::version;
|
pub use cmd_version::version;
|
||||||
pub use config::TinyEncryptConfig;
|
pub use config::TinyEncryptConfig;
|
||||||
|
pub use util_log::init_tiny_encrypt_log;
|
||||||
|
|
||||||
mod consts;
|
mod consts;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -73,7 +74,9 @@ mod cmd_initpiv;
|
|||||||
mod util_keychainstatic;
|
mod util_keychainstatic;
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
mod cmd_execenv;
|
mod cmd_execenv;
|
||||||
#[cfg(feature = "secure-enclave")]
|
|
||||||
mod util_keychainkey;
|
mod util_keychainkey;
|
||||||
mod util_simple_pbe;
|
mod util_simple_pbe;
|
||||||
|
mod util_log;
|
||||||
|
mod temporary_key;
|
||||||
|
mod util_mlkem;
|
||||||
|
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@@ -11,7 +11,12 @@ use tiny_encrypt::CmdExecEnv;
|
|||||||
use tiny_encrypt::CmdInitKeychain;
|
use tiny_encrypt::CmdInitKeychain;
|
||||||
#[cfg(feature = "smartcard")]
|
#[cfg(feature = "smartcard")]
|
||||||
use tiny_encrypt::CmdInitPiv;
|
use tiny_encrypt::CmdInitPiv;
|
||||||
use tiny_encrypt::{CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdSimpleDecrypt, CmdSimpleEncrypt, CmdVersion};
|
use tiny_encrypt::{init_tiny_encrypt_log, CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdSimpleDecrypt, CmdSimpleEncrypt, CmdVersion};
|
||||||
|
|
||||||
|
use zeroizing_alloc::ZeroAlloc;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: ZeroAlloc<std::alloc::System> = ZeroAlloc(std::alloc::System);
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "tiny-encrypt-rs")]
|
#[command(name = "tiny-encrypt-rs")]
|
||||||
@@ -27,11 +32,11 @@ enum Commands {
|
|||||||
#[command(arg_required_else_help = true, short_flag = 'e')]
|
#[command(arg_required_else_help = true, short_flag = 'e')]
|
||||||
Encrypt(CmdEncrypt),
|
Encrypt(CmdEncrypt),
|
||||||
/// Simple encrypt message
|
/// Simple encrypt message
|
||||||
#[command(arg_required_else_help = true)]
|
#[command(arg_required_else_help = true, short_flag = 'E')]
|
||||||
SimpleEncrypt(CmdSimpleEncrypt),
|
SimpleEncrypt(CmdSimpleEncrypt),
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
/// Simple decrypt message
|
/// Simple decrypt message
|
||||||
#[command(arg_required_else_help = true)]
|
#[command(arg_required_else_help = true, short_flag = 'D')]
|
||||||
SimpleDecrypt(CmdSimpleDecrypt),
|
SimpleDecrypt(CmdSimpleDecrypt),
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
/// Decrypt file(s)
|
/// Decrypt file(s)
|
||||||
@@ -64,6 +69,8 @@ enum Commands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> XResult<()> {
|
fn main() -> XResult<()> {
|
||||||
|
init_tiny_encrypt_log();
|
||||||
|
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Encrypt(cmd_encrypt) => tiny_encrypt::encrypt(cmd_encrypt),
|
Commands::Encrypt(cmd_encrypt) => tiny_encrypt::encrypt(cmd_encrypt),
|
||||||
|
|||||||
61
src/spec.rs
61
src/spec.rs
@@ -86,12 +86,30 @@ pub enum TinyEncryptEnvelopType {
|
|||||||
// Secure Enclave ECDH P256
|
// Secure Enclave ECDH P256
|
||||||
#[serde(rename = "key-p256")]
|
#[serde(rename = "key-p256")]
|
||||||
KeyP256,
|
KeyP256,
|
||||||
|
// Secure Enclave ML-KEM 768
|
||||||
|
#[serde(rename = "key-mlkem768")]
|
||||||
|
KeyMlKem768,
|
||||||
|
// Secure Enclave ML-KEM 1024
|
||||||
|
#[serde(rename = "key-mlkem1024")]
|
||||||
|
KeyMlKem1024,
|
||||||
// PIV ECDH P256
|
// PIV ECDH P256
|
||||||
#[serde(rename = "piv-p256", alias = "ecdh")]
|
#[serde(rename = "piv-p256", alias = "ecdh")]
|
||||||
PivP256,
|
PivP256,
|
||||||
// PIV ECDH P384
|
// PIV ECDH P384
|
||||||
#[serde(rename = "piv-p384", alias = "ecdh-p384")]
|
#[serde(rename = "piv-p384", alias = "ecdh-p384")]
|
||||||
PivP384,
|
PivP384,
|
||||||
|
// External ECDH P256
|
||||||
|
#[serde(rename = "ext-p256")]
|
||||||
|
ExtP256,
|
||||||
|
// External ECDH P384
|
||||||
|
#[serde(rename = "ext-p384")]
|
||||||
|
ExtP384,
|
||||||
|
// External ML-KEM 768
|
||||||
|
#[serde(rename = "ext-mlkem768")]
|
||||||
|
ExtMlKem768,
|
||||||
|
// External ML-KEM 1024
|
||||||
|
#[serde(rename = "ext-mlkem1024")]
|
||||||
|
ExtMlKem1024,
|
||||||
// PIV RSA
|
// PIV RSA
|
||||||
#[serde(rename = "piv-rsa")]
|
#[serde(rename = "piv-rsa")]
|
||||||
PivRsa,
|
PivRsa,
|
||||||
@@ -116,6 +134,12 @@ impl TinyEncryptEnvelopType {
|
|||||||
TinyEncryptEnvelopType::StaticX25519 => "static-x25519",
|
TinyEncryptEnvelopType::StaticX25519 => "static-x25519",
|
||||||
TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024",
|
TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024",
|
||||||
TinyEncryptEnvelopType::KeyP256 => "key-p256",
|
TinyEncryptEnvelopType::KeyP256 => "key-p256",
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem768 => "key-mlkem768",
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem1024 => "key-mlkem1024",
|
||||||
|
TinyEncryptEnvelopType::ExtP256 => "ext-p256",
|
||||||
|
TinyEncryptEnvelopType::ExtP384 => "ext-p384",
|
||||||
|
TinyEncryptEnvelopType::ExtMlKem768 => "ext-mlkem768",
|
||||||
|
TinyEncryptEnvelopType::ExtMlKem1024 => "ext-mlkem1024",
|
||||||
TinyEncryptEnvelopType::PivP256 => "piv-p256",
|
TinyEncryptEnvelopType::PivP256 => "piv-p256",
|
||||||
TinyEncryptEnvelopType::PivP384 => "piv-p384",
|
TinyEncryptEnvelopType::PivP384 => "piv-p384",
|
||||||
TinyEncryptEnvelopType::PivRsa => "piv-rsa",
|
TinyEncryptEnvelopType::PivRsa => "piv-rsa",
|
||||||
@@ -124,15 +148,44 @@ impl TinyEncryptEnvelopType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_name(name: &str) -> Option<Self> {
|
||||||
|
match name {
|
||||||
|
"pgp-rsa" => Some(TinyEncryptEnvelopType::PgpRsa),
|
||||||
|
"pgp-x25519" => Some(TinyEncryptEnvelopType::PgpX25519),
|
||||||
|
"gpg" => Some(TinyEncryptEnvelopType::Gpg),
|
||||||
|
"static-x25519" => Some(TinyEncryptEnvelopType::StaticX25519),
|
||||||
|
"static-kyber1024" => Some(TinyEncryptEnvelopType::StaticKyber1024),
|
||||||
|
"key-p256" => Some(TinyEncryptEnvelopType::KeyP256),
|
||||||
|
"key-mlkem768" => Some(TinyEncryptEnvelopType::KeyMlKem768),
|
||||||
|
"key-mlkem1024" => Some(TinyEncryptEnvelopType::KeyMlKem1024),
|
||||||
|
"ext-p256" => Some(TinyEncryptEnvelopType::ExtP256),
|
||||||
|
"ext-p384" => Some(TinyEncryptEnvelopType::ExtP384),
|
||||||
|
"ext-mlkem768" => Some(TinyEncryptEnvelopType::ExtMlKem768),
|
||||||
|
"ext-mlkem1024" => Some(TinyEncryptEnvelopType::ExtMlKem1024),
|
||||||
|
"piv-p256" => Some(TinyEncryptEnvelopType::PivP256),
|
||||||
|
"piv-p384" => Some(TinyEncryptEnvelopType::PivP384),
|
||||||
|
"piv-rsa" => Some(TinyEncryptEnvelopType::PivRsa),
|
||||||
|
"age" => Some(TinyEncryptEnvelopType::Age),
|
||||||
|
"kms" => Some(TinyEncryptEnvelopType::Kms),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn auto_select(&self) -> bool {
|
pub fn auto_select(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
TinyEncryptEnvelopType::StaticX25519
|
TinyEncryptEnvelopType::StaticX25519
|
||||||
| TinyEncryptEnvelopType::StaticKyber1024
|
| TinyEncryptEnvelopType::StaticKyber1024
|
||||||
| TinyEncryptEnvelopType::KeyP256
|
| TinyEncryptEnvelopType::KeyP256
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem768
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem1024
|
||||||
| TinyEncryptEnvelopType::Gpg
|
| TinyEncryptEnvelopType::Gpg
|
||||||
| TinyEncryptEnvelopType::Kms => true,
|
| TinyEncryptEnvelopType::Kms => true,
|
||||||
TinyEncryptEnvelopType::PgpRsa
|
TinyEncryptEnvelopType::PgpRsa
|
||||||
| TinyEncryptEnvelopType::PgpX25519
|
| TinyEncryptEnvelopType::PgpX25519
|
||||||
|
| TinyEncryptEnvelopType::ExtP256
|
||||||
|
| TinyEncryptEnvelopType::ExtP384
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem768
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem1024
|
||||||
| TinyEncryptEnvelopType::PivP256
|
| TinyEncryptEnvelopType::PivP256
|
||||||
| TinyEncryptEnvelopType::PivP384
|
| TinyEncryptEnvelopType::PivP384
|
||||||
| TinyEncryptEnvelopType::PivRsa
|
| TinyEncryptEnvelopType::PivRsa
|
||||||
@@ -145,6 +198,8 @@ impl TinyEncryptEnvelopType {
|
|||||||
TinyEncryptEnvelopType::PgpRsa
|
TinyEncryptEnvelopType::PgpRsa
|
||||||
| TinyEncryptEnvelopType::PgpX25519
|
| TinyEncryptEnvelopType::PgpX25519
|
||||||
| TinyEncryptEnvelopType::KeyP256
|
| TinyEncryptEnvelopType::KeyP256
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem768
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem1024
|
||||||
| TinyEncryptEnvelopType::PivP256
|
| TinyEncryptEnvelopType::PivP256
|
||||||
| TinyEncryptEnvelopType::PivP384
|
| TinyEncryptEnvelopType::PivP384
|
||||||
| TinyEncryptEnvelopType::PivRsa
|
| TinyEncryptEnvelopType::PivRsa
|
||||||
@@ -153,7 +208,11 @@ impl TinyEncryptEnvelopType {
|
|||||||
| TinyEncryptEnvelopType::StaticKyber1024
|
| TinyEncryptEnvelopType::StaticKyber1024
|
||||||
| TinyEncryptEnvelopType::Kms => Some(false),
|
| TinyEncryptEnvelopType::Kms => Some(false),
|
||||||
// GPG is unknown(hardware/software)
|
// GPG is unknown(hardware/software)
|
||||||
TinyEncryptEnvelopType::Gpg => None,
|
TinyEncryptEnvelopType::Gpg
|
||||||
|
| TinyEncryptEnvelopType::ExtP256
|
||||||
|
| TinyEncryptEnvelopType::ExtP384
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem768
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem1024 => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
69
src/temporary_key.rs
Normal file
69
src/temporary_key.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// syntax
|
||||||
|
// tiny-encrypt-key:type:sid:key_id:public_part[?key=value]
|
||||||
|
// e.g.
|
||||||
|
// tiny-encrypt-key:ext-p256:ext-key-1:02536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835b:04536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835bf3f95d932c22a74a91859bd7fdd8829a02d38cf4ec598b1cf6e02fa09f707a6f
|
||||||
|
|
||||||
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
|
use rust_util::{debugging, iff, opt_result, opt_value_result, simple_error, XResult};
|
||||||
|
|
||||||
|
const TINY_ENCRYPT_KEY_PREFIX: &str = "tiny-encrypt-key:";
|
||||||
|
|
||||||
|
pub fn serialize_config_envelop(config_envelop: &TinyEncryptConfigEnvelop) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push_str(TINY_ENCRYPT_KEY_PREFIX);
|
||||||
|
s.push_str(config_envelop.r#type.get_name());
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&encode(config_envelop.sid.as_deref().unwrap_or("")));
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&encode(&config_envelop.kid));
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&encode(&config_envelop.public_part));
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_temporary_keys(temporary_keys: &Option<Vec<String>>) -> XResult<Vec<TinyEncryptConfigEnvelop>> {
|
||||||
|
let mut temporary_envelops = vec![];
|
||||||
|
if let Some(temporary_key) = temporary_keys {
|
||||||
|
for t_key in temporary_key {
|
||||||
|
let envelop = opt_result!(deserialize_config_envelop(t_key), "Parse temporary key: {} failed: {}", t_key);
|
||||||
|
temporary_envelops.push(envelop);
|
||||||
|
}
|
||||||
|
debugging!("Temporary envelops: {:?}", temporary_envelops);
|
||||||
|
}
|
||||||
|
Ok(temporary_envelops)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_config_envelop(k: &str) -> XResult<TinyEncryptConfigEnvelop> {
|
||||||
|
if !k.starts_with(TINY_ENCRYPT_KEY_PREFIX) {
|
||||||
|
return simple_error!("invalid temporary key");
|
||||||
|
}
|
||||||
|
let k_parts = k.split(":").collect::<Vec<_>>();
|
||||||
|
if k_parts.len() != 5 {
|
||||||
|
return simple_error!("invalid temporary key (parts)");
|
||||||
|
}
|
||||||
|
let envelop_type = opt_value_result!(
|
||||||
|
TinyEncryptEnvelopType::from_name(k_parts[1]), "Unknown envelop type: {}", k_parts[1]);
|
||||||
|
Ok(TinyEncryptConfigEnvelop {
|
||||||
|
r#type: envelop_type,
|
||||||
|
sid: iff!(k_parts[2].is_empty(), None, Some(decode(k_parts[2])?)),
|
||||||
|
kid: decode(k_parts[3])?,
|
||||||
|
desc: None,
|
||||||
|
args: None,
|
||||||
|
public_part: decode(k_parts[4])?,
|
||||||
|
profiles: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(s: &str) -> String {
|
||||||
|
percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(s: &str) -> XResult<String> {
|
||||||
|
Ok(opt_result!(
|
||||||
|
percent_encoding::percent_decode_str(s).decode_utf8(),
|
||||||
|
"decode: {} failed: {}",
|
||||||
|
s
|
||||||
|
)
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
20
src/util.rs
20
src/util.rs
@@ -40,12 +40,19 @@ pub fn read_stdin() -> XResult<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_pin(pin: &Option<String>) -> XResult<String> {
|
pub fn read_pin(pin: &Option<String>) -> XResult<String> {
|
||||||
let rpin = match pin {
|
let mut ask_use_default_pin = true;
|
||||||
Some(pin) => pin.to_string(),
|
if let Some(pin) = pin {
|
||||||
None => if is_use_default_pin() {
|
if pin == "#INPUT#" {
|
||||||
"123456".into()
|
ask_use_default_pin = false;
|
||||||
} else {
|
} else {
|
||||||
let pin_entry = util_env::get_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
return Ok(pin.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ask_use_default_pin && is_use_default_pin() {
|
||||||
|
return Ok("123456".into());
|
||||||
|
}
|
||||||
|
let rpin = {
|
||||||
|
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
||||||
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
||||||
let secret = input
|
let secret = input
|
||||||
.with_description("Please input your PIN.")
|
.with_description("Please input your PIN.")
|
||||||
@@ -57,7 +64,6 @@ pub fn read_pin(pin: &Option<String>) -> XResult<String> {
|
|||||||
} else {
|
} else {
|
||||||
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
|
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Ok(rpin)
|
Ok(rpin)
|
||||||
}
|
}
|
||||||
@@ -66,7 +72,7 @@ pub fn read_password(password: &Option<String>) -> XResult<String> {
|
|||||||
let rpassword = match password {
|
let rpassword = match password {
|
||||||
Some(pin) => pin.to_string(),
|
Some(pin) => pin.to_string(),
|
||||||
None => {
|
None => {
|
||||||
let pin_entry = util_env::get_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
||||||
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
||||||
let secret = input
|
let secret = input
|
||||||
.with_description("Please input your password.")
|
.with_description("Please input your password.")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod ecdh_p256 {
|
pub mod ecdh_p256 {
|
||||||
|
use std::ops::Deref;
|
||||||
use p256::{EncodedPoint, PublicKey};
|
use p256::{EncodedPoint, PublicKey};
|
||||||
use p256::ecdh::EphemeralSecret;
|
use p256::ecdh::EphemeralSecret;
|
||||||
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
||||||
@@ -15,11 +16,12 @@ pub mod ecdh_p256 {
|
|||||||
let epk = esk.public_key();
|
let epk = esk.public_key();
|
||||||
let shared_secret = esk.diffie_hellman(&public_key);
|
let shared_secret = esk.diffie_hellman(&public_key);
|
||||||
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
|
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
|
||||||
Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec()))
|
Ok((shared_secret.raw_secret_bytes().deref().to_vec(), epk_public_key_der.to_vec()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod ecdh_p384 {
|
pub mod ecdh_p384 {
|
||||||
|
use std::ops::Deref;
|
||||||
use p384::{EncodedPoint, PublicKey};
|
use p384::{EncodedPoint, PublicKey};
|
||||||
use p384::ecdh::EphemeralSecret;
|
use p384::ecdh::EphemeralSecret;
|
||||||
use p384::elliptic_curve::sec1::FromEncodedPoint;
|
use p384::elliptic_curve::sec1::FromEncodedPoint;
|
||||||
@@ -36,7 +38,7 @@ pub mod ecdh_p384 {
|
|||||||
let epk = esk.public_key();
|
let epk = esk.public_key();
|
||||||
let shared_secret = esk.diffie_hellman(&public_key);
|
let shared_secret = esk.diffie_hellman(&public_key);
|
||||||
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
|
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
|
||||||
Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec()))
|
Ok((shared_secret.raw_secret_bytes().deref().to_vec(), epk_public_key_der.to_vec()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::env;
|
use std::{env, fs};
|
||||||
|
|
||||||
use rust_util::util_env as rust_util_env;
|
use rust_util::util_env as rust_util_env;
|
||||||
use rust_util::{debugging, util_env, warning};
|
use rust_util::{debugging, util_env, warning};
|
||||||
@@ -16,9 +16,10 @@ pub const TINY_ENCRYPT_EVN_AUTO_COMPRESS_EXTS: &str = "TINY_ENCRYPT_AUTO_COMPRES
|
|||||||
pub const TINY_ENCRYPT_ENV_GPG_COMMAND: &str = "TINY_ENCRYPT_GPG_COMMAND";
|
pub const TINY_ENCRYPT_ENV_GPG_COMMAND: &str = "TINY_ENCRYPT_GPG_COMMAND";
|
||||||
pub const TINY_ENCRYPT_ENV_NO_DEFAULT_PIN_HINT: &str = "TINY_ENCRYPT_NO_DEFAULT_PIN_HINT";
|
pub const TINY_ENCRYPT_ENV_NO_DEFAULT_PIN_HINT: &str = "TINY_ENCRYPT_NO_DEFAULT_PIN_HINT";
|
||||||
pub const TINY_ENCRYPT_ENV_PIN_ENTRY: &str = "TINY_ENCRYPT_PIN_ENTRY";
|
pub const TINY_ENCRYPT_ENV_PIN_ENTRY: &str = "TINY_ENCRYPT_PIN_ENTRY";
|
||||||
|
pub const TINY_ENCRYPT_ENV_EXTERNAL_COMMAND: &str = "TINY_ENCRYPT_EXTERNAL_COMMAND";
|
||||||
|
|
||||||
pub fn get_default_encryption_algorithm() -> Option<&'static str> {
|
pub fn get_default_encryption_algorithm() -> Option<&'static str> {
|
||||||
let env_default_algorithm = env::var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM).ok();
|
let env_default_algorithm = rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM);
|
||||||
debugging!("Environment variable {} = {:?}", TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM, env_default_algorithm);
|
debugging!("Environment variable {} = {:?}", TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM, env_default_algorithm);
|
||||||
if let Some(env_algorithm) = env_default_algorithm {
|
if let Some(env_algorithm) = env_default_algorithm {
|
||||||
let lower_default_algorithm = env_algorithm.to_lowercase();
|
let lower_default_algorithm = env_algorithm.to_lowercase();
|
||||||
@@ -36,15 +37,25 @@ pub fn get_pin() -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_key_id() -> Option<String> {
|
pub fn get_key_id() -> Option<String> {
|
||||||
env::var(TINY_ENCRYPT_ENV_KEY_ID).ok()
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_KEY_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_gpg_cmd() -> Option<String> {
|
pub fn get_gpg_cmd() -> Option<String> {
|
||||||
env::var(TINY_ENCRYPT_ENV_GPG_COMMAND).ok()
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_GPG_COMMAND)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_pin_entry() -> Option<String> {
|
||||||
|
let default_pin_entry = "/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac";
|
||||||
|
if let Ok(meta) = fs::metadata(default_pin_entry) {
|
||||||
|
if meta.is_file() {
|
||||||
|
return Some(default_pin_entry.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_pin_entry()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pin_entry() -> Option<String> {
|
pub fn get_pin_entry() -> Option<String> {
|
||||||
env::var(TINY_ENCRYPT_ENV_PIN_ENTRY).ok()
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_PIN_ENTRY)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_auto_select_key_ids() -> Option<Vec<String>> {
|
pub fn get_auto_select_key_ids() -> Option<Vec<String>> {
|
||||||
@@ -56,7 +67,7 @@ pub fn get_auto_compress_file_exts() -> Option<Vec<String>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_default_compress() -> Option<bool> {
|
pub fn get_default_compress() -> Option<bool> {
|
||||||
env::var(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS).ok().map(|val| util_env::is_on(&val))
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS).map(|val| util_env::is_on(&val))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_no_progress() -> bool {
|
pub fn get_no_progress() -> bool {
|
||||||
@@ -72,7 +83,7 @@ pub fn get_use_dialoguer() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_env_with_split(env_name: &str) -> Option<Vec<String>> {
|
fn get_env_with_split(env_name: &str) -> Option<Vec<String>> {
|
||||||
let val = env::var(env_name).ok();
|
let val = rust_util_env::env_var(env_name);
|
||||||
debugging!("Environment variable {} = {:?}", env_name, &val);
|
debugging!("Environment variable {} = {:?}", env_name, &val);
|
||||||
val.map(|env_values| {
|
val.map(|env_values| {
|
||||||
env_values.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
env_values.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult<String> {
|
|||||||
let stderr = String::from_utf8_lossy(&encrypt_output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&encrypt_output.stderr).to_string();
|
||||||
if !encrypt_output.status.success() {
|
if !encrypt_output.status.success() {
|
||||||
return simple_error!(
|
return simple_error!(
|
||||||
"GPG encrypt failed: {:?}\n- stdout: {}\n- stderr: {}",
|
"GPG encrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
|
||||||
encrypt_output.status.code(), stdout, stderr
|
encrypt_output.status.code(), stdout, stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ pub fn gpg_decrypt(message: &str) -> XResult<Vec<u8>> {
|
|||||||
let stderr = String::from_utf8_lossy(&decrypt_output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&decrypt_output.stderr).to_string();
|
||||||
if !decrypt_output.status.success() {
|
if !decrypt_output.status.success() {
|
||||||
return simple_error!(
|
return simple_error!(
|
||||||
"GPG decrypt failed: {:?}\n- stdout: {}\n- stderr: {}",
|
"GPG decrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
|
||||||
decrypt_output.status.code(), stdout, stderr
|
decrypt_output.status.code(), stdout, stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,47 @@
|
|||||||
use rust_util::{opt_result, simple_error, XResult};
|
use base64::engine::general_purpose::STANDARD;
|
||||||
use swift_rs::{Bool, SRString};
|
use base64::Engine;
|
||||||
use swift_rs::swift;
|
use rust_util::{simple_error, XResult};
|
||||||
|
use swift_secure_enclave_tool_rs::{ControlFlag, KeyMlKem, KeyPurpose};
|
||||||
use crate::util;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
|
|
||||||
swift!(fn is_support_secure_enclave() -> Bool);
|
|
||||||
swift!(fn generate_secure_enclave_p256_keypair() -> SRString);
|
|
||||||
swift!(fn compute_secure_enclave_p256_ecdh(private_key_base64: SRString, ephemera_public_key_base64: SRString) -> SRString);
|
|
||||||
|
|
||||||
pub fn is_support_se() -> bool {
|
pub fn is_support_se() -> bool {
|
||||||
unsafe { is_support_secure_enclave() }
|
swift_secure_enclave_tool_rs::is_secure_enclave_supported().unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_data(
|
||||||
pub fn decrypt_data(private_key_base64: &str, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
envelop_type: TinyEncryptEnvelopType,
|
||||||
let ephemera_public_key_base64 = util::encode_base64(ephemeral_public_key_bytes);
|
private_key_base64: &str,
|
||||||
let result = unsafe {
|
ephemeral_public_key_bytes: &[u8],
|
||||||
compute_secure_enclave_p256_ecdh(
|
) -> XResult<Vec<u8>> {
|
||||||
SRString::from(private_key_base64), SRString::from(ephemera_public_key_base64.as_str()),
|
let private_key_representation = STANDARD.decode(private_key_base64)?;
|
||||||
)
|
let shared_secret = match envelop_type {
|
||||||
|
TinyEncryptEnvelopType::KeyP256 => swift_secure_enclave_tool_rs::private_key_ecdh(
|
||||||
|
&private_key_representation,
|
||||||
|
ephemeral_public_key_bytes,
|
||||||
|
)?,
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem768 => swift_secure_enclave_tool_rs::private_key_mlkem_ecdh(
|
||||||
|
KeyMlKem::MlKem768,
|
||||||
|
&private_key_representation,
|
||||||
|
ephemeral_public_key_bytes,
|
||||||
|
)?,
|
||||||
|
TinyEncryptEnvelopType::KeyMlKem1024 => swift_secure_enclave_tool_rs::private_key_mlkem_ecdh(
|
||||||
|
KeyMlKem::MlKem1024,
|
||||||
|
&private_key_representation,
|
||||||
|
ephemeral_public_key_bytes,
|
||||||
|
)?,
|
||||||
|
_ => return simple_error!("Invalid envelop type: {:?}", envelop_type),
|
||||||
};
|
};
|
||||||
let result = result.as_str();
|
Ok(shared_secret)
|
||||||
if !result.starts_with("ok:SharedSecret:") {
|
|
||||||
return simple_error!("ECDH P256 in secure enclave failed: {}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
let shared_secret_hex = result.chars().skip("ok:SharedSecret:".len()).collect::<String>();
|
|
||||||
let shared_secret_hex = shared_secret_hex.trim();
|
|
||||||
|
|
||||||
Ok(opt_result!(hex::decode(shared_secret_hex), "Decrypt shared secret hex: {}, failed: {}", shared_secret_hex))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_se_p256_keypair() -> XResult<(String, String)> {
|
pub fn generate_se_p256_keypair(control_flag: ControlFlag) -> XResult<(String, String)> {
|
||||||
if !is_support_se() {
|
if !is_support_se() {
|
||||||
return simple_error!("Secure enclave is not supported.");
|
return simple_error!("Secure enclave is not supported.");
|
||||||
}
|
}
|
||||||
let result = unsafe { generate_secure_enclave_p256_keypair() };
|
let key_material =
|
||||||
let result = result.as_str();
|
swift_secure_enclave_tool_rs::generate_keypair(KeyPurpose::KeyAgreement, control_flag)?;
|
||||||
if !result.starts_with("ok:") {
|
Ok((
|
||||||
return simple_error!("Generate P256 in secure enclave failed: {}", result);
|
hex::encode(&key_material.public_key_point),
|
||||||
}
|
STANDARD.encode(&key_material.private_key_representation),
|
||||||
let public_key_and_private_key = result.chars().skip(3).collect::<String>();
|
))
|
||||||
let public_key_and_private_keys = public_key_and_private_key.split(',').collect::<Vec<_>>();
|
|
||||||
if public_key_and_private_keys.len() != 2 {
|
|
||||||
return simple_error!("Generate P256 in secure enclave result is bad: {}", public_key_and_private_key);
|
|
||||||
}
|
|
||||||
let public_key = hex::encode(
|
|
||||||
opt_result!(util::decode_base64(public_key_and_private_keys[0]), "Public key is not base64 encoded: {}"));
|
|
||||||
let private_key = public_key_and_private_keys[1].to_string();
|
|
||||||
|
|
||||||
Ok((public_key, private_key))
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/util_log.rs
Normal file
25
src/util_log.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use rust_util::{util_env, util_msg, util_time};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::{env, thread};
|
||||||
|
|
||||||
|
pub fn init_tiny_encrypt_log() {
|
||||||
|
if let Some(file_log) = util_env::env_var("TINY_ENCRYPT_FILE_LOG") {
|
||||||
|
let log_file_name = format!("{}-{}.log", file_log, util_time::get_current_millis());
|
||||||
|
if let Ok(mut log_file) = File::create(&log_file_name) {
|
||||||
|
env::set_var("LOGGER_LEVEL", "*"); // set logger to debug
|
||||||
|
|
||||||
|
log_file.write_all("Start logging...\n".as_bytes()).ok();
|
||||||
|
let (sender, receiver) = channel::<String>();
|
||||||
|
util_msg::set_logger_sender(sender);
|
||||||
|
thread::spawn(move || loop {
|
||||||
|
let m = match receiver.recv() {
|
||||||
|
Ok(msg) => format!("{}\n", msg),
|
||||||
|
Err(e) => format!("{}\n", e),
|
||||||
|
};
|
||||||
|
log_file.write_all(m.as_bytes()).ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/util_mlkem.rs
Normal file
49
src/util_mlkem.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use ml_kem::kem::Encapsulate;
|
||||||
|
use ml_kem::{Encoded, EncodedSizeUser, KemCore, MlKem1024, MlKem768};
|
||||||
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum MlKemAlgo {
|
||||||
|
MlKem768,
|
||||||
|
MlKem1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ml_kem_768_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let encapsulation_key_encoded: Encoded<<MlKem768 as KemCore>::EncapsulationKey> = opt_result!(
|
||||||
|
public_key.try_into(),
|
||||||
|
"Parse ML-KEM 768 encapsulation key failed: {}"
|
||||||
|
);
|
||||||
|
let encapsulation_key =
|
||||||
|
<MlKem768 as KemCore>::EncapsulationKey::from_bytes(&encapsulation_key_encoded);
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let (ciphertext, shared_key) = opt_result!(
|
||||||
|
encapsulation_key.encapsulate(&mut rng),
|
||||||
|
"Encapsulate shared key failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok((shared_key.0.to_vec(), ciphertext.0.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ml_kem_1024_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let encapsulation_key_encoded: Encoded<<MlKem1024 as KemCore>::EncapsulationKey> = opt_result!(
|
||||||
|
public_key.try_into(),
|
||||||
|
"Parse ML-KEM 1024 encapsulation key failed: {}"
|
||||||
|
);
|
||||||
|
let encapsulation_key =
|
||||||
|
<MlKem1024 as KemCore>::EncapsulationKey::from_bytes(&encapsulation_key_encoded);
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let (ciphertext, shared_key) = opt_result!(
|
||||||
|
encapsulation_key.encapsulate(&mut rng),
|
||||||
|
"Encapsulate shared key failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok((shared_key.0.to_vec(), ciphertext.0.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_ml_kem_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>, MlKemAlgo)> {
|
||||||
|
if let Ok((shared_key, ciphertext)) = ml_kem_768_encapsulate(public_key) {
|
||||||
|
return Ok((shared_key, ciphertext, MlKemAlgo::MlKem768));
|
||||||
|
}
|
||||||
|
if let Ok((shared_key, ciphertext)) = ml_kem_1024_encapsulate(public_key) {
|
||||||
|
return Ok((shared_key, ciphertext, MlKemAlgo::MlKem1024));
|
||||||
|
}
|
||||||
|
simple_error!("Only supports ML-KEM 768 or ML-KEM 1024.")
|
||||||
|
}
|
||||||
@@ -24,10 +24,10 @@ impl SimplePbkdfEncryptionV1 {
|
|||||||
enc.starts_with(&format!("{SIMPLE_PBKDF_ENCRYPTION_PREFIX}."))
|
enc.starts_with(&format!("{SIMPLE_PBKDF_ENCRYPTION_PREFIX}."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encrypt(password: &str, plaintext: &[u8]) -> XResult<SimplePbkdfEncryptionV1> {
|
pub fn encrypt(password: &str, plaintext: &[u8], iterations: &Option<u32>) -> XResult<SimplePbkdfEncryptionV1> {
|
||||||
let salt: [u8; 12] = random();
|
let salt: [u8; 12] = random();
|
||||||
let repetition = 1000;
|
let repetition = 1000;
|
||||||
let iterations = 10000;
|
let iterations = iterations.unwrap_or(10000);
|
||||||
let key = simple_pbkdf(password.as_bytes(), &salt, repetition, iterations);
|
let key = simple_pbkdf(password.as_bytes(), &salt, repetition, iterations);
|
||||||
|
|
||||||
let key_bytes: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {:?}");
|
let key_bytes: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {:?}");
|
||||||
@@ -166,7 +166,7 @@ fn simple_pbkdf(password: &[u8], salt: &[u8], repetition: u32, iterations: u32)
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let enc = SimplePbkdfEncryptionV1::encrypt("helloworld", "test".as_bytes()).unwrap();
|
let enc = SimplePbkdfEncryptionV1::encrypt("helloworld", "test".as_bytes(), &None).unwrap();
|
||||||
let enc_str = enc.to_string();
|
let enc_str = enc.to_string();
|
||||||
let enc2: SimplePbkdfEncryptionV1 = enc_str.try_into().unwrap();
|
let enc2: SimplePbkdfEncryptionV1 = enc_str.try_into().unwrap();
|
||||||
assert_eq!(enc.to_string(), enc2.to_string());
|
assert_eq!(enc.to_string(), enc2.to_string());
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
// swift-tools-version:5.3
|
|
||||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "swift-lib",
|
|
||||||
platforms: [
|
|
||||||
.macOS(.v11), // macOS Catalina. Earliest version that is officially supported by Apple.
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
|
||||||
.library(
|
|
||||||
name: "swift-lib",
|
|
||||||
type: .static,
|
|
||||||
targets: ["swift-lib"]),
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
// Dependencies declare other packages that this package depends on.
|
|
||||||
.package(name: "SwiftRs", path: "../swift-rs")
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
|
||||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
|
||||||
.target(
|
|
||||||
name: "swift-lib",
|
|
||||||
dependencies: [.product(name: "SwiftRs", package: "SwiftRs")],
|
|
||||||
path: "src")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import SwiftRs
|
|
||||||
import CryptoKit
|
|
||||||
import LocalAuthentication
|
|
||||||
|
|
||||||
// reference:
|
|
||||||
// https://zenn.dev/iceman/scraps/380f69137c7ea2
|
|
||||||
// https://www.andyibanez.com/posts/cryptokit-secure-enclave/
|
|
||||||
@_cdecl("is_support_secure_enclave")
|
|
||||||
func isSupportSecureEnclave() -> Bool {
|
|
||||||
return SecureEnclave.isAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("generate_secure_enclave_p256_keypair")
|
|
||||||
func generateSecureEnclaveP256KeyPair() -> SRString {
|
|
||||||
var error: Unmanaged<CFError>? = nil;
|
|
||||||
guard let accessCtrl = SecAccessControlCreateWithFlags(
|
|
||||||
nil,
|
|
||||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
||||||
[.privateKeyUsage, .biometryCurrentSet],
|
|
||||||
&error
|
|
||||||
) else {
|
|
||||||
return SRString("err:\(error.debugDescription)")
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let privateKeyReference = try SecureEnclave.P256.KeyAgreement.PrivateKey.init(
|
|
||||||
accessControl: accessCtrl
|
|
||||||
);
|
|
||||||
let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString()
|
|
||||||
let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString()
|
|
||||||
return SRString("ok:\(publicKeyBase64),\(dataRepresentationBase64)")
|
|
||||||
} catch {
|
|
||||||
return SRString("err:\(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("compute_secure_enclave_p256_ecdh")
|
|
||||||
func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString {
|
|
||||||
guard let privateKeyDataRepresentation = Data(
|
|
||||||
base64Encoded: privateKeyDataRepresentation.toString()
|
|
||||||
) else {
|
|
||||||
return SRString("err:private key base64 decode failed")
|
|
||||||
}
|
|
||||||
guard let ephemeralPublicKeyRepresentation = Data(
|
|
||||||
base64Encoded: ephemeraPublicKey.toString()
|
|
||||||
) else {
|
|
||||||
return SRString("err:ephemeral public key base64 decode failed")
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let context = LAContext();
|
|
||||||
let p = try SecureEnclave.P256.KeyAgreement.PrivateKey(
|
|
||||||
dataRepresentation: privateKeyDataRepresentation,
|
|
||||||
authenticationContext: context
|
|
||||||
)
|
|
||||||
|
|
||||||
let ephemeralPublicKey = try P256.KeyAgreement.PublicKey.init(derRepresentation: ephemeralPublicKeyRepresentation)
|
|
||||||
|
|
||||||
let sharedSecret = try p.sharedSecretFromKeyAgreement(
|
|
||||||
with: ephemeralPublicKey)
|
|
||||||
|
|
||||||
return SRString("ok:\(sharedSecret.description)")
|
|
||||||
} catch {
|
|
||||||
return SRString("err:\(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
417
swift-rs/Cargo.lock
generated
417
swift-rs/Cargo.lock
generated
@@ -1,417 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.22.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dashmap"
|
|
||||||
version = "5.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"hashbrown",
|
|
||||||
"lock_api",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-executor",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-channel"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-executor"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-io"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"memchr",
|
|
||||||
"pin-project-lite",
|
|
||||||
"pin-utils",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.14.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.164"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.20.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.9.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-lite"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.89"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.5.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.215"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.215"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.87",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.133"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serial_test"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2"
|
|
||||||
dependencies = [
|
|
||||||
"dashmap",
|
|
||||||
"futures",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"parking_lot",
|
|
||||||
"serial_test_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serial_test_derive"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smallvec"
|
|
||||||
version = "1.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "swift-rs-hatter-fork"
|
|
||||||
version = "1.0.6"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serial_test",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.87"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_gnullvm",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "swift-rs"
|
|
||||||
version = "1.0.6"
|
|
||||||
description = "Call Swift from Rust with ease!"
|
|
||||||
authors = ["The swift-rs contributors"]
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
repository = "https://github.com/Brendonovich/swift-rs"
|
|
||||||
edition = "2021"
|
|
||||||
exclude=["/src-swift", "*.swift"]
|
|
||||||
build = "src-rs/test-build.rs"
|
|
||||||
|
|
||||||
# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
|
||||||
[package.metadata."docs.rs"]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src-rs/lib.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
base64 = "0.22"
|
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true}
|
|
||||||
serde_json = { version = "1.0", optional = true }
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
serde = { version = "1.0", features = ["derive"]}
|
|
||||||
serde_json = { version = "1.0" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
serial_test = "0.10"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
build = ["serde", "serde_json"]
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2023 The swift-rs developers
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2023 The swift-rs Developers
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// swift-tools-version:5.3
|
|
||||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "SwiftRs",
|
|
||||||
platforms: [
|
|
||||||
.macOS(.v10_13),
|
|
||||||
.iOS(.v11),
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
|
||||||
.library(
|
|
||||||
name: "SwiftRs",
|
|
||||||
targets: ["SwiftRs"]),
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
// Dependencies declare other packages that this package depends on.
|
|
||||||
// .package(url: /* package url */, from: "1.0.0"),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
|
||||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
|
||||||
.target(
|
|
||||||
name: "SwiftRs",
|
|
||||||
dependencies: [],
|
|
||||||
path: "src-swift")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,483 +0,0 @@
|
|||||||
# swift-rs
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Call Swift functions from Rust with ease!
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Add `swift-rs` to your project's `dependencies` and `build-dependencies`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
swift-rs = "1.0.5"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
swift-rs = { version = "1.0.5", features = ["build"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, some setup work must be done:
|
|
||||||
|
|
||||||
1. Ensure your swift code is organized into a Swift Package.
|
|
||||||
This can be done in XCode by selecting File -> New -> Project -> Multiplatform -> Swift Package and importing your existing code.
|
|
||||||
2. Add `SwiftRs` as a dependency to your Swift package and make the build type `.static`.
|
|
||||||
```swift
|
|
||||||
let package = Package(
|
|
||||||
dependencies: [
|
|
||||||
.package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5")
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
.library(
|
|
||||||
type: .static,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.target(
|
|
||||||
// Must specify swift-rs as a dependency of your target
|
|
||||||
dependencies: [
|
|
||||||
.product(
|
|
||||||
name: "SwiftRs",
|
|
||||||
package: "swift-rs"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
```
|
|
||||||
3. Create a `build.rs` file in your project's root folder, if you don't have one already.
|
|
||||||
4. Use `SwiftLinker` in your `build.rs` file to link both the Swift runtime and your Swift package.
|
|
||||||
The package name should be the same as is specified in your `Package.swift` file,
|
|
||||||
and the path should point to your Swift project's root folder relative to your crate's root folder.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::SwiftLinker;
|
|
||||||
|
|
||||||
fn build() {
|
|
||||||
// swift-rs has a minimum of macOS 10.13
|
|
||||||
// Ensure the same minimum supported macOS version is specified as in your `Package.swift` file.
|
|
||||||
SwiftLinker::new("10.13")
|
|
||||||
// Only if you are also targetting iOS
|
|
||||||
// Ensure the same minimum supported iOS version is specified as in your `Package.swift` file
|
|
||||||
.with_ios("11")
|
|
||||||
.with_package(PACKAGE_NAME, PACKAGE_PATH)
|
|
||||||
.link();
|
|
||||||
|
|
||||||
// Other build steps
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
With those steps completed, you should be ready to start using Swift code from Rust!
|
|
||||||
|
|
||||||
If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib`
|
|
||||||
when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your
|
|
||||||
[Tauri minimum system version](https://tauri.app/v1/guides/building/macos#setting-a-minimum-system-version)
|
|
||||||
to `10.15` or higher in your `tauri.config.json`.
|
|
||||||
|
|
||||||
## Calling basic functions
|
|
||||||
|
|
||||||
To allow calling a Swift function from Rust, it must follow some rules:
|
|
||||||
|
|
||||||
1. It must be global
|
|
||||||
2. It must be annotated with `@_cdecl`, so that it is callable from C
|
|
||||||
3. It must only use types that can be represented in Objective-C,
|
|
||||||
so only classes that derive `NSObject`, as well as scalars such as Int and Bool.
|
|
||||||
This excludes strings, arrays, generics (though all of these can be sent with workarounds)
|
|
||||||
and structs (which are strictly forbidden).
|
|
||||||
|
|
||||||
For this example we will use a function that simply squares a number:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
public func squareNumber(number: Int) -> Int {
|
|
||||||
return number * number
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So far, this function meets requirements 1 and 3: it is global and public, and only uses the Int type, which is Objective-C compatible.
|
|
||||||
However, it is not annotated with `@_cdecl`.
|
|
||||||
To fix this, we must call `@_cdecl` before the function's declaration and specify the name that the function is exposed to Rust with as its only argument.
|
|
||||||
To keep with Rust's naming conventions, we will export this function in snake case as `square_number`.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
@_cdecl("square_number")
|
|
||||||
public func squareNumber(number: Int) -> Int {
|
|
||||||
return number * number
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it.
|
|
||||||
This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::swift;
|
|
||||||
|
|
||||||
swift!(fn square_number(number: Int) -> Int);
|
|
||||||
```
|
|
||||||
|
|
||||||
Lastly, you can call the function from regular Rust functions.
|
|
||||||
Note that <b>all</b> calls to a Swift function are unsafe,
|
|
||||||
and require wrapping in an `unsafe {}` block or `unsafe fn`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
let input: Int = 4;
|
|
||||||
let output = unsafe { square_number(input) };
|
|
||||||
|
|
||||||
println!("Input: {}, Squared: {}", input, output);
|
|
||||||
// Prints "Input: 4, Squared: 16"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Check [the documentation](TODO) for all available helper types.
|
|
||||||
|
|
||||||
## Returning objects from Swift
|
|
||||||
|
|
||||||
Let's say that we want our `squareNumber` function to return not only the result, but also the original input.
|
|
||||||
A standard way to do this in Swift would be with a struct:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
struct SquareNumberResult {
|
|
||||||
var input: Int
|
|
||||||
var output: Int
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We are not allowed to do this, though, since structs cannot be represented in Objective-C.
|
|
||||||
Instead, we must use a class that extends `NSObject`:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
class SquareNumberResult: NSObject {
|
|
||||||
var input: Int
|
|
||||||
var output: Int
|
|
||||||
|
|
||||||
init(_ input: Int, _ output: Int) {
|
|
||||||
self.input = input;
|
|
||||||
self.output = output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<sub><sup>Yes, this class could contain the squaring logic too, but that is irrelevant for this example
|
|
||||||
|
|
||||||
An instance of this class can then be returned from `squareNumber`:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
@_cdecl("square_number")
|
|
||||||
public func squareNumber(input: Int) -> SquareNumberResult {
|
|
||||||
let output = input * input
|
|
||||||
return SquareNumberResult(input, output)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can see, returning an `NSObject` from Swift isn't too difficult.
|
|
||||||
The same can't be said for the Rust implementation, though.
|
|
||||||
`squareNumber` doesn't actually return a struct containing `input` and `output`,
|
|
||||||
but instead a pointer to a `SquareNumberResult` stored somewhere in memory.
|
|
||||||
Additionally, this value contains more data than just `input` and `output`:
|
|
||||||
Since it is an `NSObject`, it contains extra data that must be accounted for when using it in Rust.
|
|
||||||
|
|
||||||
This may sound daunting, but it's not actually a problem thanks to `SRObject<T>`.
|
|
||||||
This type manages the pointer internally, and takes a generic argument for a struct that we can access the data through.
|
|
||||||
Let's see how we'd implement `SquareNumberResult` in Rust:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{swift, Int, SRObject};
|
|
||||||
|
|
||||||
// Any struct that is used in a C function must be annotated
|
|
||||||
// with this, and since our Swift function is exposed as a
|
|
||||||
// C function with @_cdecl, this is necessary here
|
|
||||||
#[repr(C)]
|
|
||||||
// Struct matches the class declaration in Swift
|
|
||||||
struct SquareNumberResult {
|
|
||||||
input: Int,
|
|
||||||
output: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// SRObject abstracts away the underlying pointer and will automatically deref to
|
|
||||||
// &SquareNumberResult through the Deref trait
|
|
||||||
swift!(fn square_number(input: Int) -> SRObject<SquareNumberResult>);
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, using the new return value is just like using `SquareNumberResult` directly:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn main() {
|
|
||||||
let input = 4;
|
|
||||||
let result = unsafe { square_number(input) };
|
|
||||||
|
|
||||||
let result_input = result.input; // 4
|
|
||||||
let result_output = result.output; // 16
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Creating objects in Rust and then passing them to Swift is not supported.
|
|
||||||
|
|
||||||
## Optionals
|
|
||||||
|
|
||||||
`swift-rs` also supports Swift's `nil` type, but only for functions that return optional `NSObject`s.
|
|
||||||
Functions returning optional primitives cannot be represented in Objective C, and thus are not supported.
|
|
||||||
|
|
||||||
Let's say we have a function returning an optional `SRString`:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
@_cdecl("optional_string")
|
|
||||||
func optionalString(returnNil: Bool) -> SRString? {
|
|
||||||
if (returnNil) return nil
|
|
||||||
else return SRString("lorem ipsum")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Thanks to Rust's [null pointer optimisation](https://doc.rust-lang.org/std/option/index.html#representation),
|
|
||||||
the optional nature of `SRString?` can be represented by wrapping `SRString` in Rust's `Option<T>` type!
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{swift, Bool, SRString};
|
|
||||||
|
|
||||||
swift!(optional_string(return_nil: Bool) -> Option<SRString>)
|
|
||||||
```
|
|
||||||
|
|
||||||
Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C.
|
|
||||||
If this were to be supported, how could a `nil` be differentiated from a number? It can't!
|
|
||||||
|
|
||||||
## Complex types
|
|
||||||
|
|
||||||
So far we have only looked at using primitive types and structs/classes,
|
|
||||||
but this leaves out some of the most important data structures: arrays (`SRArray<T>`) and strings (`SRString`).
|
|
||||||
These types must be treated with caution, however, and are not as flexible as their native Swift & Rust counterparts.
|
|
||||||
|
|
||||||
### Strings
|
|
||||||
|
|
||||||
Strings can be passed between Rust and Swift through `SRString`, which can be created from native strings in either language.
|
|
||||||
|
|
||||||
**As an argument**
|
|
||||||
|
|
||||||
```swift
|
|
||||||
import SwiftRs
|
|
||||||
|
|
||||||
@_cdecl("swift_print")
|
|
||||||
public func swiftPrint(value: SRString) {
|
|
||||||
// .to_string() converts the SRString to a Swift String
|
|
||||||
print(value.to_string())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{swift, SRString, SwiftRef};
|
|
||||||
|
|
||||||
swift!(fn swift_print(value: &SRString));
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// SRString can be created by simply calling .into() on any string reference.
|
|
||||||
// This will allocate memory in Swift and copy the string
|
|
||||||
let value: SRString = "lorem ipsum".into();
|
|
||||||
|
|
||||||
unsafe { swift_print(&value) }; // Will print "lorem ipsum" to the console
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**As a return value**
|
|
||||||
|
|
||||||
```swift
|
|
||||||
import SwiftRs
|
|
||||||
|
|
||||||
@_cdecl("get_string")
|
|
||||||
public func getString() -> SRString {
|
|
||||||
let value = "lorem ipsum"
|
|
||||||
|
|
||||||
// SRString can be created from a regular String
|
|
||||||
return SRString(value)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{swift, SRString};
|
|
||||||
|
|
||||||
swift!(fn get_string() -> SRString);
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let value_srstring = unsafe { get_string() };
|
|
||||||
|
|
||||||
// SRString can be converted to an &str using as_str()...
|
|
||||||
let value_str: &str = value_srstring.as_str();
|
|
||||||
// or though the Deref trait
|
|
||||||
let value_str: &str = &*value_srstring;
|
|
||||||
|
|
||||||
// SRString also implements Display
|
|
||||||
println!("{}", value_srstring); // Will print "lorem ipsum" to the console
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrays
|
|
||||||
|
|
||||||
**Primitive Arrays**
|
|
||||||
|
|
||||||
Representing arrays properly is tricky, since we cannot use generics as Swift arguments or return values according to rule 3.
|
|
||||||
Instead, `swift-rs` provides a generic `SRArray<T>` that can be embedded inside another class that extends `NSObject` that is not generic,
|
|
||||||
but is restricted to a single element type.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
import SwiftRs
|
|
||||||
|
|
||||||
// Argument/Return values can contain generic types, but cannot be generic themselves.
|
|
||||||
// This includes extending generic types.
|
|
||||||
class IntArray: NSObject {
|
|
||||||
var data: SRArray<Int>
|
|
||||||
|
|
||||||
init(_ data: [Int]) {
|
|
||||||
self.data = SRArray(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("get_numbers")
|
|
||||||
public func getNumbers() -> IntArray {
|
|
||||||
let numbers = [1, 2, 3, 4]
|
|
||||||
|
|
||||||
return IntArray(numbers)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{Int, SRArray, SRObject};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct IntArray {
|
|
||||||
data: SRArray<Int>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since IntArray extends NSObject in its Swift implementation,
|
|
||||||
// it must be wrapped in SRObject on the Rust side
|
|
||||||
swift!(fn get_numbers() -> SRObject<IntArray>);
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let numbers = unsafe { get_numbers() };
|
|
||||||
|
|
||||||
// SRArray can be accessed as a slice via as_slice
|
|
||||||
let numbers_slice: &[Int] = numbers.data.as_slice();
|
|
||||||
|
|
||||||
assert_eq!(numbers_slice, &[1, 2, 3, 4]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To simplify things on the rust side, we can actually do away with the `IntArray` struct.
|
|
||||||
Since `IntArray` only has one field, its memory layout is identical to that of `SRArray<usize>`,
|
|
||||||
so our Rust implementation can be simplified at the cost of equivalence with our Swift code:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// We still need to wrap the array in SRObject since
|
|
||||||
// the wrapper class in Swift is an NSObject
|
|
||||||
swift!(fn get_numbers() -> SRObject<SRArray<Int>>);
|
|
||||||
```
|
|
||||||
|
|
||||||
**NSObject Arrays**
|
|
||||||
|
|
||||||
What if we want to return an `NSObject` array? There are two options on the Swift side:
|
|
||||||
|
|
||||||
1. Continue using `SRArray` and a custom wrapper type, or
|
|
||||||
2. Use `SRObjectArray`, a wrapper type provided by `swift-rs` that accepts any `NSObject` as its elements.
|
|
||||||
This can be easier than continuing to create wrapper types, but sacrifices some type safety.
|
|
||||||
|
|
||||||
There is also `SRObjectArray<T>` for Rust, which is compatible with any single-element Swift wrapper type (and of course `SRObjectArray` in Swift),
|
|
||||||
and automatically wraps its elements in `SRObject<T>`, so there's very little reason to not use it unless you _really_ like custom wrapper types.
|
|
||||||
|
|
||||||
Using `SRObjectArray` in both Swift and Rust with a basic custom class/struct can be done like this:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
import SwiftRs
|
|
||||||
|
|
||||||
class IntTuple: NSObject {
|
|
||||||
var item1: Int
|
|
||||||
var item2: Int
|
|
||||||
|
|
||||||
init(_ item1: Int, _ item2: Int) {
|
|
||||||
self.item1 = item1
|
|
||||||
self.item2 = item2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("get_tuples")
|
|
||||||
public func getTuples() -> SRObjectArray {
|
|
||||||
let tuple1 = IntTuple(0,1),
|
|
||||||
tuple2 = IntTuple(2,3),
|
|
||||||
tuple3 = IntTuple(4,5)
|
|
||||||
|
|
||||||
let tupleArray: [IntTuple] = [
|
|
||||||
tuple1,
|
|
||||||
tuple2,
|
|
||||||
tuple3
|
|
||||||
]
|
|
||||||
|
|
||||||
// Type safety is only lost when the Swift array is converted to an SRObjectArray
|
|
||||||
return SRObjectArray(tupleArray)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::{swift, Int, SRObjectArray};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct IntTuple {
|
|
||||||
item1: Int,
|
|
||||||
item2: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// No need to wrap IntTuple in SRObject<T> since
|
|
||||||
// SRObjectArray<T> does it automatically
|
|
||||||
swift!(fn get_tuples() -> SRObjectArray<IntTuple>);
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let tuples = unsafe { get_tuples() };
|
|
||||||
|
|
||||||
for tuple in tuples.as_slice() {
|
|
||||||
// Will print each tuple's contents to the console
|
|
||||||
println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Complex types can contain whatever combination of primitives and `SRObject<T>` you like, just remember to follow the 3 rules!
|
|
||||||
|
|
||||||
## Bonuses
|
|
||||||
|
|
||||||
### SRData
|
|
||||||
|
|
||||||
A wrapper type for `SRArray<T>` designed for storing `u8`s - essentially just a byte buffer.
|
|
||||||
|
|
||||||
### Tighter Memory Control with `autoreleasepool!`
|
|
||||||
|
|
||||||
If you've come to Swift from an Objective-C background, you likely know the utility of `@autoreleasepool` blocks.
|
|
||||||
`swift-rs` has your back on this too, just wrap your block of code with a `autoreleasepool!`, and that block of code now executes with its own autorelease pool!
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use swift_rs::autoreleasepool;
|
|
||||||
|
|
||||||
for _ in 0..10000 {
|
|
||||||
autoreleasepool!({
|
|
||||||
// do some memory intensive thing here
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
Currently, the only types that can be created from Rust are number types, boolean, `SRString`, and `SRData`.
|
|
||||||
This is because those types are easy to allocate memory for, either on the stack or on the heap via calling out to swift,
|
|
||||||
whereas other types are not. This may be implemented in the future, though.
|
|
||||||
|
|
||||||
Mutating values across Swift and Rust is not currently an aim for this library, it is purely for providing arguments and returning values.
|
|
||||||
Besides, this would go against Rust's programming model, potentially allowing for multiple shared references to a value instead of interior mutability via something like a Mutex.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Licensed under either of
|
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
at your option.
|
|
||||||
|
|
||||||
### Contribution
|
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally
|
|
||||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
|
||||||
license, shall be dual licensed as above, without any additional terms or
|
|
||||||
conditions.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/// Run code with its own autorelease pool. Semantically, this is identical
|
|
||||||
/// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html)
|
|
||||||
/// in Objective-C
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// use swift_rs::autoreleasepool;
|
|
||||||
///
|
|
||||||
/// autoreleasepool!({
|
|
||||||
/// // do something memory intensive stuff
|
|
||||||
/// })
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! autoreleasepool {
|
|
||||||
( $expr:expr ) => {{
|
|
||||||
extern "C" {
|
|
||||||
fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void;
|
|
||||||
fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void);
|
|
||||||
}
|
|
||||||
|
|
||||||
let pool = unsafe { objc_autoreleasePoolPush() };
|
|
||||||
let r = { $expr };
|
|
||||||
unsafe { objc_autoreleasePoolPop(pool) };
|
|
||||||
r
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command};
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct SwiftTarget {
|
|
||||||
triple: String,
|
|
||||||
unversioned_triple: String,
|
|
||||||
module_triple: String,
|
|
||||||
//pub swift_runtime_compatibility_version: String,
|
|
||||||
#[serde(rename = "librariesRequireRPath")]
|
|
||||||
libraries_require_rpath: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct SwiftPaths {
|
|
||||||
runtime_library_paths: Vec<String>,
|
|
||||||
runtime_library_import_paths: Vec<String>,
|
|
||||||
runtime_resource_path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct SwiftEnv {
|
|
||||||
target: SwiftTarget,
|
|
||||||
paths: SwiftPaths,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwiftEnv {
|
|
||||||
fn new(minimum_macos_version: &str, minimum_ios_version: Option<&str>) -> Self {
|
|
||||||
let rust_target = RustTarget::from_env();
|
|
||||||
let target = rust_target.swift_target_triple(minimum_macos_version, minimum_ios_version);
|
|
||||||
|
|
||||||
let swift_target_info_str = Command::new("swift")
|
|
||||||
.args(["-target", &target, "-print-target-info"])
|
|
||||||
.output()
|
|
||||||
.unwrap()
|
|
||||||
.stdout;
|
|
||||||
|
|
||||||
serde_json::from_slice(&swift_target_info_str).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
enum RustTargetOS {
|
|
||||||
MacOS,
|
|
||||||
IOS,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustTargetOS {
|
|
||||||
fn from_env() -> Self {
|
|
||||||
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
|
|
||||||
"macos" => RustTargetOS::MacOS,
|
|
||||||
"ios" => RustTargetOS::IOS,
|
|
||||||
_ => panic!("unexpected target operating system"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_swift(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::MacOS => "macosx",
|
|
||||||
Self::IOS => "ios",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for RustTargetOS {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::MacOS => write!(f, "macos"),
|
|
||||||
Self::IOS => write!(f, "ios"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
enum SwiftSDK {
|
|
||||||
MacOS,
|
|
||||||
IOS,
|
|
||||||
IOSSimulator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwiftSDK {
|
|
||||||
fn from_os(os: &RustTargetOS) -> Self {
|
|
||||||
let target = env::var("TARGET").unwrap();
|
|
||||||
let simulator = target.ends_with("ios-sim")
|
|
||||||
|| (target.starts_with("x86_64") && target.ends_with("ios"));
|
|
||||||
|
|
||||||
match os {
|
|
||||||
RustTargetOS::MacOS => Self::MacOS,
|
|
||||||
RustTargetOS::IOS if simulator => Self::IOSSimulator,
|
|
||||||
RustTargetOS::IOS => Self::IOS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clang_lib_extension(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::MacOS => "osx",
|
|
||||||
Self::IOS => "ios",
|
|
||||||
Self::IOSSimulator => "iossim",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SwiftSDK {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::MacOS => write!(f, "macosx"),
|
|
||||||
Self::IOSSimulator => write!(f, "iphonesimulator"),
|
|
||||||
Self::IOS => write!(f, "iphoneos"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RustTarget {
|
|
||||||
arch: String,
|
|
||||||
os: RustTargetOS,
|
|
||||||
sdk: SwiftSDK,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustTarget {
|
|
||||||
fn from_env() -> Self {
|
|
||||||
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
|
||||||
let os = RustTargetOS::from_env();
|
|
||||||
let sdk = SwiftSDK::from_os(&os);
|
|
||||||
|
|
||||||
Self { arch, os, sdk }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn swift_target_triple(
|
|
||||||
&self,
|
|
||||||
minimum_macos_version: &str,
|
|
||||||
minimum_ios_version: Option<&str>,
|
|
||||||
) -> String {
|
|
||||||
let unversioned = self.unversioned_swift_target_triple();
|
|
||||||
format!(
|
|
||||||
"{unversioned}{}{}",
|
|
||||||
match (&self.os, minimum_ios_version) {
|
|
||||||
(RustTargetOS::MacOS, _) => minimum_macos_version,
|
|
||||||
(RustTargetOS::IOS, Some(version)) => version,
|
|
||||||
_ => "",
|
|
||||||
},
|
|
||||||
// simulator suffix
|
|
||||||
matches!(self.sdk, SwiftSDK::IOSSimulator)
|
|
||||||
.then(|| "-simulator".to_string())
|
|
||||||
.unwrap_or_default()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unversioned_swift_target_triple(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}-apple-{}",
|
|
||||||
match self.arch.as_str() {
|
|
||||||
"aarch64" => "arm64",
|
|
||||||
a => a,
|
|
||||||
},
|
|
||||||
self.os.to_swift(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SwiftPackage {
|
|
||||||
name: String,
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder for linking the Swift runtime and custom packages.
|
|
||||||
#[cfg(feature = "build")]
|
|
||||||
pub struct SwiftLinker {
|
|
||||||
packages: Vec<SwiftPackage>,
|
|
||||||
macos_min_version: String,
|
|
||||||
ios_min_version: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwiftLinker {
|
|
||||||
/// Creates a new [`SwiftLinker`] with a minimum macOS verison.
|
|
||||||
///
|
|
||||||
/// Minimum macOS version must be at least 10.13.
|
|
||||||
pub fn new(macos_min_version: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
packages: vec![],
|
|
||||||
macos_min_version: macos_min_version.to_string(),
|
|
||||||
ios_min_version: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instructs the [`SwiftLinker`] to also compile for iOS
|
|
||||||
/// using the specified minimum iOS version.
|
|
||||||
///
|
|
||||||
/// Minimum iOS version must be at least 11.
|
|
||||||
pub fn with_ios(mut self, min_version: &str) -> Self {
|
|
||||||
self.ios_min_version = Some(min_version.to_string());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a package to be linked against.
|
|
||||||
/// `name` should match the `name` field in your `Package.swift`,
|
|
||||||
/// and `path` should point to the root of your Swift package relative
|
|
||||||
/// to your crate's root.
|
|
||||||
pub fn with_package(mut self, name: &str, path: impl AsRef<Path>) -> Self {
|
|
||||||
self.packages.extend([SwiftPackage {
|
|
||||||
name: name.to_string(),
|
|
||||||
path: path.as_ref().into(),
|
|
||||||
}]);
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Links the Swift runtime, then builds and links the provided packages.
|
|
||||||
/// This does not (yet) automatically rebuild your Swift files when they are modified,
|
|
||||||
/// you'll need to modify/save your `build.rs` file for that.
|
|
||||||
pub fn link(self) {
|
|
||||||
let swift_env = SwiftEnv::new(&self.macos_min_version, self.ios_min_version.as_deref());
|
|
||||||
|
|
||||||
#[allow(clippy::uninlined_format_args)]
|
|
||||||
for path in swift_env.paths.runtime_library_paths {
|
|
||||||
println!("cargo:rustc-link-search=native={path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let debug = env::var("DEBUG").unwrap() == "true";
|
|
||||||
let configuration = if debug { "debug" } else { "release" };
|
|
||||||
let rust_target = RustTarget::from_env();
|
|
||||||
|
|
||||||
link_clang_rt(&rust_target);
|
|
||||||
|
|
||||||
for package in self.packages {
|
|
||||||
let package_path =
|
|
||||||
Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&package.path);
|
|
||||||
let out_path = Path::new(&env::var("OUT_DIR").unwrap())
|
|
||||||
.join("swift-rs")
|
|
||||||
.join(&package.name);
|
|
||||||
|
|
||||||
let sdk_path_output = Command::new("xcrun")
|
|
||||||
.args(["--sdk", &rust_target.sdk.to_string(), "--show-sdk-path"])
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
if !sdk_path_output.status.success() {
|
|
||||||
panic!(
|
|
||||||
"Failed to get SDK path with `xcrun --sdk {} --show-sdk-path`",
|
|
||||||
rust_target.sdk
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sdk_path = String::from_utf8_lossy(&sdk_path_output.stdout);
|
|
||||||
|
|
||||||
let mut command = Command::new("swift");
|
|
||||||
command.current_dir(&package.path);
|
|
||||||
|
|
||||||
let arch = match std::env::consts::ARCH {
|
|
||||||
"aarch64" => "arm64",
|
|
||||||
arch => arch,
|
|
||||||
};
|
|
||||||
|
|
||||||
command
|
|
||||||
// Build the package (duh)
|
|
||||||
.args(["build"])
|
|
||||||
// SDK path for regular compilation (idk)
|
|
||||||
.args(["--sdk", sdk_path.trim()])
|
|
||||||
// Release/Debug configuration
|
|
||||||
.args(["-c", configuration])
|
|
||||||
.args(["--arch", arch])
|
|
||||||
// Where the artifacts will be generated to
|
|
||||||
.args(["--build-path", &out_path.display().to_string()])
|
|
||||||
// Override SDK path for each swiftc instance.
|
|
||||||
// Necessary for iOS compilation.
|
|
||||||
.args(["-Xswiftc", "-sdk"])
|
|
||||||
.args(["-Xswiftc", sdk_path.trim()])
|
|
||||||
// Override target triple for each swiftc instance.
|
|
||||||
// Necessary for iOS compilation.
|
|
||||||
.args(["-Xswiftc", "-target"])
|
|
||||||
.args([
|
|
||||||
"-Xswiftc",
|
|
||||||
&rust_target.swift_target_triple(
|
|
||||||
&self.macos_min_version,
|
|
||||||
self.ios_min_version.as_deref(),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if !command.status().unwrap().success() {
|
|
||||||
panic!("Failed to compile swift package {}", package.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let search_path = out_path
|
|
||||||
// swift build uses this output folder no matter what is the target
|
|
||||||
.join(format!(
|
|
||||||
"{}-apple-macosx",
|
|
||||||
arch
|
|
||||||
))
|
|
||||||
.join(configuration);
|
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed={}", package_path.display());
|
|
||||||
println!("cargo:rustc-link-search=native={}", search_path.display());
|
|
||||||
println!("cargo:rustc-link-lib=static={}", package.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn link_clang_rt(rust_target: &RustTarget) {
|
|
||||||
println!(
|
|
||||||
"cargo:rustc-link-lib=clang_rt.{}",
|
|
||||||
rust_target.sdk.clang_lib_extension()
|
|
||||||
);
|
|
||||||
println!("cargo:rustc-link-search={}", clang_link_search_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clang_link_search_path() -> String {
|
|
||||||
let output = std::process::Command::new(
|
|
||||||
std::env::var("SWIFT_RS_CLANG").unwrap_or_else(|_| "/usr/bin/clang".to_string()),
|
|
||||||
)
|
|
||||||
.arg("--print-search-dirs")
|
|
||||||
.output()
|
|
||||||
.unwrap();
|
|
||||||
if !output.status.success() {
|
|
||||||
panic!("Can't get search paths from clang");
|
|
||||||
}
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
for line in stdout.lines() {
|
|
||||||
if line.contains("libraries: =") {
|
|
||||||
let path = line.split('=').nth(1).unwrap();
|
|
||||||
return format!("{}/lib/darwin", path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!("clang is missing search paths");
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/// This retain-balancing algorithm is cool but likely isn't required.
|
|
||||||
/// I'm keeping it around in case it's necessary one day.
|
|
||||||
|
|
||||||
// #[derive(Clone, Copy, Debug)]
|
|
||||||
// enum ValueArity {
|
|
||||||
// Reference,
|
|
||||||
// Value,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) {
|
|
||||||
// fn collect_references(
|
|
||||||
// v: Vec<(*const c_void, bool)>,
|
|
||||||
// ) -> BTreeMap<*const c_void, Vec<ValueArity>> {
|
|
||||||
// v.into_iter().fold(
|
|
||||||
// BTreeMap::<_, Vec<ValueArity>>::new(),
|
|
||||||
// |mut map, (ptr, is_ref)| {
|
|
||||||
// map.entry(ptr).or_default().push(if is_ref {
|
|
||||||
// ValueArity::Reference
|
|
||||||
// } else {
|
|
||||||
// ValueArity::Value
|
|
||||||
// });
|
|
||||||
// map
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut args = collect_references(args);
|
|
||||||
// let mut ret = collect_references(ret);
|
|
||||||
|
|
||||||
// let both_counts = args
|
|
||||||
// .clone()
|
|
||||||
// .into_iter()
|
|
||||||
// .flat_map(|(arg, values)| {
|
|
||||||
// ret.remove(&arg).map(|ret| {
|
|
||||||
// args.remove(&arg);
|
|
||||||
|
|
||||||
// let ret_values = ret
|
|
||||||
// .iter()
|
|
||||||
// .filter(|v| matches!(v, ValueArity::Value))
|
|
||||||
// .count() as isize;
|
|
||||||
|
|
||||||
// let arg_references = values
|
|
||||||
// .iter()
|
|
||||||
// .filter(|v| matches!(v, ValueArity::Reference))
|
|
||||||
// .count() as isize;
|
|
||||||
|
|
||||||
// let ref_in_value_out_retains = min(ret_values, arg_references);
|
|
||||||
|
|
||||||
// (arg, ref_in_value_out_retains)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// let arg_counts = args.into_iter().map(|(ptr, values)| {
|
|
||||||
// let count = values
|
|
||||||
// .into_iter()
|
|
||||||
// .filter(|v| matches!(v, ValueArity::Value))
|
|
||||||
// .count() as isize;
|
|
||||||
// (ptr, count)
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let ret_counts = ret
|
|
||||||
// .into_iter()
|
|
||||||
// .map(|(ptr, values)| {
|
|
||||||
// let count = values
|
|
||||||
// .into_iter()
|
|
||||||
// .filter(|v| matches!(v, ValueArity::Value))
|
|
||||||
// .count() as isize;
|
|
||||||
// (ptr, count)
|
|
||||||
// })
|
|
||||||
// .collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// both_counts
|
|
||||||
// .into_iter()
|
|
||||||
// .chain(arg_counts)
|
|
||||||
// .chain(ret_counts)
|
|
||||||
// .for_each(|(ptr, count)| match count {
|
|
||||||
// 0 => {}
|
|
||||||
// n if n > 0 => {
|
|
||||||
// for _ in 0..n {
|
|
||||||
// retain_object(ptr)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// n => {
|
|
||||||
// for _ in n..0 {
|
|
||||||
// release_object(ptr)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//! Call Swift functions from Rust with ease!
|
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
||||||
|
|
||||||
mod autorelease;
|
|
||||||
mod swift;
|
|
||||||
mod swift_arg;
|
|
||||||
mod swift_ret;
|
|
||||||
mod types;
|
|
||||||
|
|
||||||
// pub use autorelease::*;
|
|
||||||
pub use swift::*;
|
|
||||||
pub use swift_arg::*;
|
|
||||||
pub use swift_ret::*;
|
|
||||||
pub use types::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "build")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "build")))]
|
|
||||||
mod build;
|
|
||||||
#[cfg(feature = "build")]
|
|
||||||
pub use build::*;
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Reference to an `NSObject` for internal use by [`swift!`].
|
|
||||||
#[must_use = "A Ref MUST be sent over to the Swift side"]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl<T::Shape>);
|
|
||||||
|
|
||||||
impl<'a, T: SwiftObject> SwiftRef<'a, T> {
|
|
||||||
pub(crate) unsafe fn retain(&self) {
|
|
||||||
retain_object(self.0 as *const _ as *const c_void)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type that is represented as an `NSObject` in Swift.
|
|
||||||
pub trait SwiftObject {
|
|
||||||
type Shape;
|
|
||||||
|
|
||||||
/// Gets a reference to the `SRObject` at the root of a `SwiftObject`
|
|
||||||
fn get_object(&self) -> &SRObject<Self::Shape>;
|
|
||||||
|
|
||||||
/// Creates a [`SwiftRef`] for an object which can be used when calling a Swift function.
|
|
||||||
/// This function should never be called manually,
|
|
||||||
/// instead you should rely on the [`swift!`] macro to call it for you.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// This function converts the [`NonNull`](std::ptr::NonNull)
|
|
||||||
/// inside an [`SRObject`] into a reference,
|
|
||||||
/// implicitly assuming that the pointer is still valid.
|
|
||||||
/// The inner pointer is private,
|
|
||||||
/// and the returned [`SwiftRef`] is bound to the lifetime of the original [`SRObject`],
|
|
||||||
/// so if you use `swift-rs` as normal this function should be safe.
|
|
||||||
unsafe fn swift_ref(&self) -> SwiftRef<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
SwiftRef(self.get_object().0.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a retain to an object.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Just don't call this, let [`swift!`] handle it for you.
|
|
||||||
unsafe fn retain(&self)
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.swift_ref().retain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
swift!(pub(crate) fn retain_object(obj: *const c_void));
|
|
||||||
swift!(pub(crate) fn release_object(obj: *const c_void));
|
|
||||||
swift!(pub(crate) fn data_from_bytes(data: *const u8, size: Int) -> SRData);
|
|
||||||
swift!(pub(crate) fn string_from_bytes(data: *const u8, size: Int) -> SRString);
|
|
||||||
|
|
||||||
/// Declares a function defined in a swift library.
|
|
||||||
/// As long as this macro is used, retain counts of arguments
|
|
||||||
/// and return values will be correct.
|
|
||||||
///
|
|
||||||
/// Use this macro as if the contents were going directly
|
|
||||||
/// into an `extern "C"` block.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use swift_rs::*;
|
|
||||||
///
|
|
||||||
/// swift!(fn echo(string: &SRString) -> SRString);
|
|
||||||
///
|
|
||||||
/// let string: SRString = "test".into();
|
|
||||||
/// let result = unsafe { echo(&string) };
|
|
||||||
///
|
|
||||||
/// assert_eq!(result.as_str(), string.as_str())
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Details
|
|
||||||
///
|
|
||||||
/// Internally this macro creates a wrapping function around an `extern "C"` block
|
|
||||||
/// that represents the actual Swift function. This is done in order to restrict the types
|
|
||||||
/// that can be used as arguments and return types, and to ensure that retain counts of returned
|
|
||||||
/// values are appropriately balanced.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! swift {
|
|
||||||
($vis:vis fn $name:ident $(<$($lt:lifetime),+>)? ($($arg:ident: $arg_ty:ty),*) $(-> $ret:ty)?) => {
|
|
||||||
$vis unsafe fn $name $(<$($lt),*>)? ($($arg: $arg_ty),*) $(-> $ret)? {
|
|
||||||
extern "C" {
|
|
||||||
fn $name $(<$($lt),*>)? ($($arg: <$arg_ty as $crate::SwiftArg>::ArgType),*) $(-> $ret)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = {
|
|
||||||
$(let $arg = $crate::SwiftArg::as_arg(&$arg);)*
|
|
||||||
|
|
||||||
$name($($arg),*)
|
|
||||||
};
|
|
||||||
|
|
||||||
$crate::SwiftRet::retain(&res);
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
use crate::{swift::SwiftObject, *};
|
|
||||||
|
|
||||||
/// Identifies a type as being a valid argument in a Swift function.
|
|
||||||
pub trait SwiftArg<'a> {
|
|
||||||
type ArgType;
|
|
||||||
|
|
||||||
/// Creates a swift-compatible version of the argument.
|
|
||||||
/// For primitives this just returns `self`,
|
|
||||||
/// but for [`SwiftObject`] types it wraps them in [`SwiftRef`].
|
|
||||||
///
|
|
||||||
/// This function is called within the [`swift!`] macro.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Creating a [`SwiftRef`] is inherently unsafe,
|
|
||||||
/// but is reliable if using the [`swift!`] macro,
|
|
||||||
/// so it is not advised to call this function manually.
|
|
||||||
unsafe fn as_arg(&'a self) -> Self::ArgType;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitive_impl {
|
|
||||||
($($t:ty),+) => {
|
|
||||||
$(impl<'a> SwiftArg<'a> for $t {
|
|
||||||
type ArgType = $t;
|
|
||||||
|
|
||||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
})+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive_impl!(
|
|
||||||
Bool,
|
|
||||||
Int,
|
|
||||||
Int8,
|
|
||||||
Int16,
|
|
||||||
Int32,
|
|
||||||
Int64,
|
|
||||||
UInt,
|
|
||||||
UInt8,
|
|
||||||
UInt16,
|
|
||||||
UInt32,
|
|
||||||
UInt64,
|
|
||||||
Float32,
|
|
||||||
Float64,
|
|
||||||
*const c_void,
|
|
||||||
*mut c_void,
|
|
||||||
*const u8,
|
|
||||||
()
|
|
||||||
);
|
|
||||||
|
|
||||||
macro_rules! ref_impl {
|
|
||||||
($($t:ident $(<$($gen:ident),+>)?),+) => {
|
|
||||||
$(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? {
|
|
||||||
type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>;
|
|
||||||
|
|
||||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
|
||||||
self.swift_ref()
|
|
||||||
}
|
|
||||||
})+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ref_impl!(SRObject<T>, SRArray<T>, SRData, SRString);
|
|
||||||
|
|
||||||
impl<'a, T: SwiftArg<'a>> SwiftArg<'a> for &T {
|
|
||||||
type ArgType = T::ArgType;
|
|
||||||
|
|
||||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
|
||||||
(*self).as_arg()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
use crate::{swift::SwiftObject, *};
|
|
||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
/// Identifies a type as being a valid return type from a Swift function.
|
|
||||||
/// For types that are objects which need extra retains,
|
|
||||||
/// the [`retain`](SwiftRet::retain) function will be re-implemented.
|
|
||||||
pub trait SwiftRet {
|
|
||||||
/// Adds a retain to the value if possible
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Just don't use this.
|
|
||||||
/// Let [`swift!`] handle it.
|
|
||||||
unsafe fn retain(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitive_impl {
|
|
||||||
($($t:ty),+) => {
|
|
||||||
$(impl SwiftRet for $t {
|
|
||||||
})+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive_impl!(
|
|
||||||
Bool,
|
|
||||||
Int,
|
|
||||||
Int8,
|
|
||||||
Int16,
|
|
||||||
Int32,
|
|
||||||
Int64,
|
|
||||||
UInt,
|
|
||||||
UInt8,
|
|
||||||
UInt16,
|
|
||||||
UInt32,
|
|
||||||
UInt64,
|
|
||||||
Float32,
|
|
||||||
Float64,
|
|
||||||
*const c_void,
|
|
||||||
*mut c_void,
|
|
||||||
*const u8,
|
|
||||||
()
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<T: SwiftObject> SwiftRet for Option<T> {
|
|
||||||
unsafe fn retain(&self) {
|
|
||||||
if let Some(v) = self {
|
|
||||||
v.retain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: SwiftObject> SwiftRet for T {
|
|
||||||
unsafe fn retain(&self) {
|
|
||||||
(*self).retain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//! Build script for swift-rs that is a no-op for normal builds, but can be enabled
|
|
||||||
//! to include test swift library based on env var `TEST_SWIFT_RS=true` with the
|
|
||||||
//! `build` feature being enabled.
|
|
||||||
|
|
||||||
#[cfg(feature = "build")]
|
|
||||||
mod build;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS");
|
|
||||||
|
|
||||||
#[cfg(feature = "build")]
|
|
||||||
if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" {
|
|
||||||
use build::SwiftLinker;
|
|
||||||
|
|
||||||
SwiftLinker::new("10.15")
|
|
||||||
.with_ios("11")
|
|
||||||
.with_package("test-swift", "tests/swift-pkg")
|
|
||||||
.link();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
use std::{ops::Deref, ptr::NonNull};
|
|
||||||
|
|
||||||
use crate::swift::SwiftObject;
|
|
||||||
|
|
||||||
use super::SRObject;
|
|
||||||
|
|
||||||
/// Wrapper of [`SRArray`] exclusively for arrays of objects.
|
|
||||||
/// Equivalent to `SRObjectArray` in Swift.
|
|
||||||
// SRArray is wrapped in SRObject since the Swift implementation extends NSObject
|
|
||||||
pub type SRObjectArray<T> = SRObject<SRArray<SRObject<T>>>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct SRArrayImpl<T> {
|
|
||||||
data: NonNull<T>,
|
|
||||||
length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// General array type for objects and scalars.
|
|
||||||
///
|
|
||||||
/// ## Returning Directly
|
|
||||||
///
|
|
||||||
/// When returning an `SRArray` from a Swift function,
|
|
||||||
/// you will need to wrap it in an `NSObject` class since
|
|
||||||
/// Swift doesn't permit returning generic types from `@_cdecl` functions.
|
|
||||||
/// To account for the wrapping `NSObject`, the array must be wrapped
|
|
||||||
/// in `SRObject` on the Rust side.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use swift_rs::{swift, SRArray, SRObject, Int};
|
|
||||||
///
|
|
||||||
/// swift!(fn get_int_array() -> SRObject<SRArray<Int>>);
|
|
||||||
///
|
|
||||||
/// let array = unsafe { get_int_array() };
|
|
||||||
///
|
|
||||||
/// assert_eq!(array.as_slice(), &[1, 2, 3])
|
|
||||||
/// ```
|
|
||||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L19)
|
|
||||||
///
|
|
||||||
/// ## Returning in a Struct fIeld
|
|
||||||
///
|
|
||||||
/// When returning an `SRArray` from a custom struct that is itself an `NSObject`,
|
|
||||||
/// the above work is already done for you.
|
|
||||||
/// Assuming your custom struct is already wrapped in `SRObject` in Rust,
|
|
||||||
/// `SRArray` will work normally.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use swift_rs::{swift, SRArray, SRObject, Int};
|
|
||||||
///
|
|
||||||
/// #[repr(C)]
|
|
||||||
/// struct ArrayStruct {
|
|
||||||
/// array: SRArray<Int>
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// swift!(fn get_array_struct() -> SRObject<ArrayStruct>);
|
|
||||||
///
|
|
||||||
/// let data = unsafe { get_array_struct() };
|
|
||||||
///
|
|
||||||
/// assert_eq!(data.array.as_slice(), &[4, 5, 6]);
|
|
||||||
/// ```
|
|
||||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L32)
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SRArray<T>(SRObject<SRArrayImpl<T>>);
|
|
||||||
|
|
||||||
impl<T> SRArray<T> {
|
|
||||||
pub fn as_slice(&self) -> &[T] {
|
|
||||||
self.0.as_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> SwiftObject for SRArray<T> {
|
|
||||||
type Shape = SRArrayImpl<T>;
|
|
||||||
|
|
||||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for SRArray<T> {
|
|
||||||
type Target = [T];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.0.as_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> SRArrayImpl<T> {
|
|
||||||
pub fn as_slice(&self) -> &[T] {
|
|
||||||
unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<T> serde::Serialize for SRArray<T>
|
|
||||||
where
|
|
||||||
T: serde::Serialize,
|
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
use serde::ser::SerializeSeq;
|
|
||||||
|
|
||||||
let mut seq = serializer.serialize_seq(Some(self.len()))?;
|
|
||||||
for item in self.iter() {
|
|
||||||
seq.serialize_element(item)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
swift::{self, SwiftObject},
|
|
||||||
Int,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{array::SRArray, SRObject};
|
|
||||||
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
type Data = SRArray<u8>;
|
|
||||||
|
|
||||||
/// Convenience type for working with byte buffers,
|
|
||||||
/// analagous to `SRData` in Swift.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use swift_rs::{swift, SRData};
|
|
||||||
///
|
|
||||||
/// swift!(fn get_data() -> SRData);
|
|
||||||
///
|
|
||||||
/// let data = unsafe { get_data() };
|
|
||||||
///
|
|
||||||
/// assert_eq!(data.as_ref(), &[1, 2, 3])
|
|
||||||
/// ```
|
|
||||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L68)
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SRData(SRObject<Data>);
|
|
||||||
|
|
||||||
impl SRData {
|
|
||||||
///
|
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
|
||||||
self.as_slice().to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwiftObject for SRData {
|
|
||||||
type Shape = Data;
|
|
||||||
|
|
||||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for SRData {
|
|
||||||
type Target = [u8];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<[u8]> for SRData {
|
|
||||||
fn as_ref(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&[u8]> for SRData {
|
|
||||||
fn from(value: &[u8]) -> Self {
|
|
||||||
unsafe { swift::data_from_bytes(value.as_ptr(), value.len() as Int) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl serde::Serialize for SRData {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_bytes(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
mod array;
|
|
||||||
mod data;
|
|
||||||
mod object;
|
|
||||||
mod scalars;
|
|
||||||
mod string;
|
|
||||||
|
|
||||||
pub use array::*;
|
|
||||||
pub use data::*;
|
|
||||||
pub use object::*;
|
|
||||||
pub use scalars::*;
|
|
||||||
pub use string::*;
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
use crate::swift::{self, SwiftObject};
|
|
||||||
use std::{ffi::c_void, ops::Deref, ptr::NonNull};
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct SRObjectImpl<T> {
|
|
||||||
_nsobject_offset: u8,
|
|
||||||
data: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for arbitrary `NSObject` types.
|
|
||||||
///
|
|
||||||
/// When returning an `NSObject`, its Rust type must be wrapped in `SRObject`.
|
|
||||||
/// The type must also be annotated with `#[repr(C)]` to ensure its memory layout
|
|
||||||
/// is identical to its Swift counterpart's.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use swift_rs::{swift, SRObject, Int, Bool};
|
|
||||||
///
|
|
||||||
/// #[repr(C)]
|
|
||||||
/// struct CustomObject {
|
|
||||||
/// a: Int,
|
|
||||||
/// b: Bool
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// swift!(fn get_custom_object() -> SRObject<CustomObject>);
|
|
||||||
///
|
|
||||||
/// let value = unsafe { get_custom_object() };
|
|
||||||
///
|
|
||||||
/// let reference: &CustomObject = value.as_ref();
|
|
||||||
/// ```
|
|
||||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L49)
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SRObject<T>(pub(crate) NonNull<SRObjectImpl<T>>);
|
|
||||||
|
|
||||||
impl<T> SwiftObject for SRObject<T> {
|
|
||||||
type Shape = T;
|
|
||||||
|
|
||||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for SRObject<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
unsafe { &self.0.as_ref().data }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> AsRef<T> for SRObject<T> {
|
|
||||||
fn as_ref(&self) -> &T {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for SRObject<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<T> serde::Serialize for SRObject<T>
|
|
||||||
where
|
|
||||||
T: serde::Serialize,
|
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
self.deref().serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type
|
|
||||||
pub type Bool = bool;
|
|
||||||
|
|
||||||
/// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type
|
|
||||||
pub type Int = isize;
|
|
||||||
/// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type
|
|
||||||
pub type Int8 = i8;
|
|
||||||
/// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type
|
|
||||||
pub type Int16 = i16;
|
|
||||||
/// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type
|
|
||||||
pub type Int32 = i32;
|
|
||||||
/// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type
|
|
||||||
pub type Int64 = i64;
|
|
||||||
|
|
||||||
/// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type
|
|
||||||
pub type UInt = usize;
|
|
||||||
/// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type
|
|
||||||
pub type UInt8 = u8;
|
|
||||||
/// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type
|
|
||||||
pub type UInt16 = u16;
|
|
||||||
/// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type
|
|
||||||
pub type UInt32 = u32;
|
|
||||||
/// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type
|
|
||||||
pub type UInt64 = u64;
|
|
||||||
|
|
||||||
/// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type
|
|
||||||
pub type Float = f32;
|
|
||||||
/// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type
|
|
||||||
pub type Double = f64;
|
|
||||||
|
|
||||||
/// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type
|
|
||||||
pub type Float32 = f32;
|
|
||||||
/// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type
|
|
||||||
pub type Float64 = f64;
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
use std::{
|
|
||||||
fmt::{Display, Error, Formatter},
|
|
||||||
ops::Deref,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
swift::{self, SwiftObject},
|
|
||||||
Int, SRData, SRObject,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// String type that can be shared between Swift and Rust.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use swift_rs::{swift, SRString};
|
|
||||||
///
|
|
||||||
/// swift!(fn get_greeting(name: &SRString) -> SRString);
|
|
||||||
///
|
|
||||||
/// let greeting = unsafe { get_greeting(&"Brendan".into()) };
|
|
||||||
///
|
|
||||||
/// assert_eq!(greeting.as_str(), "Hello Brendan!");
|
|
||||||
/// ```
|
|
||||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56)
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SRString(SRData);
|
|
||||||
|
|
||||||
impl SRString {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
unsafe { std::str::from_utf8_unchecked(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwiftObject for SRString {
|
|
||||||
type Shape = <SRData as SwiftObject>::Shape;
|
|
||||||
|
|
||||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
|
||||||
self.0.get_object()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for SRString {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<[u8]> for SRString {
|
|
||||||
fn as_ref(&self) -> &[u8] {
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for SRString {
|
|
||||||
fn from(string: &str) -> Self {
|
|
||||||
unsafe { swift::string_from_bytes(string.as_ptr(), string.len() as Int) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SRString {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
|
||||||
self.as_str().fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl serde::Serialize for SRString {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
impl<'a> serde::Deserialize<'a> for SRString {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'a>,
|
|
||||||
{
|
|
||||||
let string = String::deserialize(deserializer)?;
|
|
||||||
Ok(SRString::from(string.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public class SRArray<T>: NSObject {
|
|
||||||
// Used by Rust
|
|
||||||
let pointer: UnsafePointer<T>
|
|
||||||
let length: Int;
|
|
||||||
|
|
||||||
// Actual array, deallocates objects inside automatically
|
|
||||||
let array: [T];
|
|
||||||
|
|
||||||
public override init() {
|
|
||||||
self.array = [];
|
|
||||||
self.pointer = UnsafePointer(self.array);
|
|
||||||
self.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ data: [T]) {
|
|
||||||
self.array = data;
|
|
||||||
self.pointer = UnsafePointer(self.array)
|
|
||||||
self.length = data.count
|
|
||||||
}
|
|
||||||
|
|
||||||
public func toArray() -> [T] {
|
|
||||||
return Array(self.array)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SRObjectArray: NSObject {
|
|
||||||
let data: SRArray<NSObject>
|
|
||||||
|
|
||||||
public init(_ data: [NSObject]) {
|
|
||||||
self.data = SRArray(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SRData: NSObject {
|
|
||||||
let data: SRArray<UInt8>
|
|
||||||
|
|
||||||
public override init() {
|
|
||||||
self.data = SRArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ data: [UInt8]) {
|
|
||||||
self.data = SRArray(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
public init (_ srArray: SRArray<UInt8>) {
|
|
||||||
self.data = srArray
|
|
||||||
}
|
|
||||||
|
|
||||||
public func toArray() -> [UInt8] {
|
|
||||||
return self.data.toArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SRString: SRData {
|
|
||||||
public override init() {
|
|
||||||
super.init([])
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ string: String) {
|
|
||||||
super.init(Array(string.utf8))
|
|
||||||
}
|
|
||||||
|
|
||||||
init(_ data: SRData) {
|
|
||||||
super.init(data.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func toString() -> String {
|
|
||||||
return String(bytes: self.data.array, encoding: .utf8)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("retain_object")
|
|
||||||
func retainObject(ptr: UnsafeMutableRawPointer) {
|
|
||||||
let _ = Unmanaged<AnyObject>.fromOpaque(ptr).retain()
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("release_object")
|
|
||||||
func releaseObject(ptr: UnsafeMutableRawPointer) {
|
|
||||||
let _ = Unmanaged<AnyObject>.fromOpaque(ptr).release()
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("data_from_bytes")
|
|
||||||
func dataFromBytes(data: UnsafePointer<UInt8>, size: Int) -> SRData {
|
|
||||||
let buffer = UnsafeBufferPointer(start: data, count: size)
|
|
||||||
return SRData(Array(buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("string_from_bytes")
|
|
||||||
func stringFromBytes(data: UnsafePointer<UInt8>, size: Int) -> SRString {
|
|
||||||
let data = dataFromBytes(data: data, size: size);
|
|
||||||
return SRString(data)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user