Compare commits
61 Commits
b9e7b5af63
...
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
|
|||
|
3c717a8f8d
|
|||
|
a7802a3750
|
|||
|
cc5178d1c1
|
|||
|
b21714cb4b
|
|||
|
6635a0e468
|
|||
|
8b38982e61
|
|||
|
528889bcc7
|
|||
|
338017590f
|
|||
|
08fa61c968
|
|||
|
75740a26a4
|
|||
|
85a7448291
|
|||
|
cd9b3eb624
|
|||
|
0aafaa7586
|
|||
|
408b3e468b
|
|||
|
fd0751d348
|
|||
|
096218fc78
|
|||
|
2a03dd45ce
|
|||
|
3046626691
|
|||
|
f2388b11cf
|
|||
|
90372941be
|
1076
Cargo.lock
generated
1076
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.8.0"
|
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"
|
||||||
@@ -9,11 +9,11 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["decrypt", "macos", "smartcard", "secure-enclave"]
|
default = ["decrypt", "macos", "smartcard"]
|
||||||
|
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"
|
||||||
@@ -24,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"] }
|
||||||
@@ -33,18 +33,17 @@ rand = "0.8"
|
|||||||
# reqwest = { version = "0.11", features = ["blocking", "rustls", "rustls-tls"] }
|
# reqwest = { version = "0.11", features = ["blocking", "rustls", "rustls-tls"] }
|
||||||
rpassword = "7.3"
|
rpassword = "7.3"
|
||||||
rsa = { version = "0.9", features = ["pem"] }
|
rsa = { version = "0.9", features = ["pem"] }
|
||||||
rust-crypto = "0.2"
|
rust-crypto-hatter-fork = "0.2"
|
||||||
rust_util = "0.6"
|
rust_util = "0.6"
|
||||||
security-framework = { version = "3.0", features = ["OSX_10_15"], optional = true }
|
security-framework = { version = "3.0", features = ["OSX_10_15"], optional = true }
|
||||||
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.16"
|
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 = { path = "swift-rs", optional = true }
|
|
||||||
spki = "0.7"
|
spki = "0.7"
|
||||||
pqcrypto-kyber = "0.8"
|
pqcrypto-kyber = "0.8"
|
||||||
pqcrypto-traits = "0.3"
|
pqcrypto-traits = "0.3"
|
||||||
@@ -52,12 +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 = { path = "swift-rs", features = ["build"], optional = true }
|
external-command-rs = "0.1"
|
||||||
|
percent-encoding = "2.3"
|
||||||
[patch.crates-io]
|
ml-kem = { version = "0.2.1", features = ["zeroize"] }
|
||||||
rust-crypto = { git = "https://github.com/jht5945/rust-crypto.git" }
|
zeroizing-alloc = "0.1.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -1,8 +1,10 @@
|
|||||||
# tiny-encrypt-rs
|
# tiny-encrypt-rs
|
||||||
|
|
||||||
**IMPORTANT**: To use tiny-encrypt, a Yubikey(https://www.yubico.com/products/) or MacBook with Secure Enclave get the
|
> [!IMPORTANT]
|
||||||
|
> To use tiny-encrypt, a Yubikey(https://www.yubico.com/products/) or MacBook with Secure Enclave get the
|
||||||
best security effect, the key MUST support PIV or OpenPGP.
|
best security effect, the key MUST support PIV or OpenPGP.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Tiny Encrypt written in Rust Programming Language
|
Tiny Encrypt written in Rust Programming Language
|
||||||
@@ -26,6 +28,12 @@ Compile only encrypt:
|
|||||||
cargo build --release --no-default-features
|
cargo build --release --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Install from git:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install --git https://git.hatter.ink/hatter/tiny-encrypt-rs.git --features full-features
|
||||||
|
```
|
||||||
|
|
||||||
Edit encrypted file:
|
Edit encrypted file:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -106,17 +114,23 @@ 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 |
|
||||||
| static-x25519 | ECDH(X25519) | Key Stored in macOS Keychain Access |
|
| static-x25519 | ECDH(X25519) | Key Stored in macOS Keychain Access |
|
||||||
| 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) |
|
||||||
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
| 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 |
|
||||||
|
|
||||||
Smart Card(Yubikey) protected ECDH Encryption description as below:
|
Smart Card(Yubikey) protected ECDH Encryption description as below:
|
||||||
|
|
||||||
@@ -143,19 +157,24 @@ Smart Card(Yubikey) protected ECDH Encryption description as below:
|
|||||||
|
|
||||||
Environment
|
Environment
|
||||||
|
|
||||||
| KEY | Comment |
|
| KEY | Comment |
|
||||||
|----------------------------------|---------------------------------------------|
|
|----------------------------------|----------------------------------------------------------------|
|
||||||
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
|
| TINY_ENCRYPT_CONFIG_FILE | Config file |
|
||||||
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
|
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
|
||||||
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
|
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
|
||||||
| TINY_ENCRYPT_NO_DEFAULT_PIN_HINT | Do not display default PIN hint |
|
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
|
||||||
| TINY_ENCRYPT_USE_DIALOGUER | Use dialoguer |
|
| TINY_ENCRYPT_NO_DEFAULT_PIN_HINT | Do not display default PIN hint |
|
||||||
| TINY_ENCRYPT_PIN | PIV Card PIN |
|
| TINY_ENCRYPT_USE_DIALOGUER | Use dialoguer |
|
||||||
| TINY_ENCRYPT_KEY_ID | Default Key ID |
|
| TINY_ENCRYPT_PIN | PIV Card PIN |
|
||||||
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
|
| TINY_ENCRYPT_KEY_ID | Default Key ID |
|
||||||
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
|
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
|
||||||
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
|
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
|
||||||
| SECURE_EDITOR | Secure Editor |
|
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
|
||||||
| EDITOR | Editor (Plaintext) |
|
| TINY_ENCRYPT_EXTERNAL_COMMAND | External command cli |
|
||||||
|
| SECURE_EDITOR | Secure Editor [\[OWS RFC6\]](https://openwebstandard.org/rfc6) |
|
||||||
|
| 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();
|
|
||||||
}
|
|
||||||
13
justfile
13
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 .
|
||||||
@@ -13,11 +17,18 @@ build:
|
|||||||
build-no-features:
|
build-no-features:
|
||||||
cargo build --release --no-default-features
|
cargo build --release --no-default-features
|
||||||
|
|
||||||
|
# Build linux musl release without features via zig
|
||||||
|
build-linux-musl-with-zig:
|
||||||
|
cargo zigbuild --release --target x86_64-unknown-linux-musl --no-default-features
|
||||||
|
|
||||||
|
# Lint code
|
||||||
|
lint:
|
||||||
|
cargo clippy
|
||||||
|
|
||||||
# Try build all
|
# Try build all
|
||||||
try-build-all:
|
try-build-all:
|
||||||
cargo build --no-default-features
|
cargo build --no-default-features
|
||||||
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,7 +7,7 @@ use tabled::{Table, Tabled};
|
|||||||
use tabled::settings::Style;
|
use tabled::settings::Style;
|
||||||
|
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::TINY_ENC_CONFIG_FILE;
|
use crate::temporary_key::serialize_config_envelop;
|
||||||
use crate::util_envelop;
|
use crate::util_envelop;
|
||||||
|
|
||||||
#[derive(Tabled, Eq)]
|
#[derive(Tabled, Eq)]
|
||||||
@@ -48,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>,
|
||||||
@@ -56,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(TINY_ENC_CONFIG_FILE)?;
|
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__");
|
||||||
|
}
|
||||||
|
println!("{}", serde_json::to_string_pretty(&config)?);
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
config_profiles(&cmd_version, &config)
|
|
||||||
|
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<()> {
|
||||||
@@ -100,15 +132,17 @@ 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 {
|
||||||
let mut v2 = v.clone();
|
for (p, v) in profiles {
|
||||||
v2.sort();
|
let mut v2 = v.clone();
|
||||||
let vs = v2.join(",");
|
v2.sort();
|
||||||
match reverse_map.get_mut(&vs) {
|
let vs = v2.join(",");
|
||||||
None => { reverse_map.insert(vs, vec![(p, v)]); }
|
match reverse_map.get_mut(&vs) {
|
||||||
Some(vec) => { vec.push((p, v)); }
|
None => { reverse_map.insert(vs, vec![(p, v)]); }
|
||||||
|
Some(vec) => { vec.push((p, v)); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +151,9 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
|
|||||||
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 {
|
||||||
@@ -125,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,16 +29,18 @@ 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,
|
||||||
SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT,
|
SALT_COMMENT, TINY_ENC_FILE_EXT,
|
||||||
};
|
};
|
||||||
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
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(TINY_ENC_CONFIG_FILE).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;
|
||||||
@@ -313,8 +320,8 @@ fn run_file_editor_and_wait_content(editor: &str, temp_file: &PathBuf, secure_ed
|
|||||||
command.arg(temp_file.to_str().expect("Get temp file path failed."));
|
command.arg(temp_file.to_str().expect("Get temp file path failed."));
|
||||||
if secure_editor {
|
if secure_editor {
|
||||||
command.arg("aes-256-gcm");
|
command.arg("aes-256-gcm");
|
||||||
command.arg(&hex::encode(&temp_encryption_key_nonce.0));
|
command.arg(hex::encode(&temp_encryption_key_nonce.0));
|
||||||
command.arg(&hex::encode(&temp_encryption_key_nonce.1));
|
command.arg(hex::encode(&temp_encryption_key_nonce.1));
|
||||||
if readonly { command.env("READONLY", "true"); }
|
if readonly { command.env("READONLY", "true"); }
|
||||||
}
|
}
|
||||||
debugging!("Run cmd: {:?}", command);
|
debugging!("Run cmd: {:?}", command);
|
||||||
@@ -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,18 +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::{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_CONFIG_FILE, TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT, TINY_ENC_PEM_NAME};
|
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};
|
||||||
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 {
|
||||||
@@ -43,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,
|
||||||
@@ -71,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(TINY_ENC_CONFIG_FILE)?;
|
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();
|
||||||
@@ -308,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,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],
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use zeroize::Zeroize;
|
|||||||
use crate::{config, consts, util, util_env};
|
use crate::{config, consts, util, util_env};
|
||||||
use crate::cmd_decrypt::{decrypt_limited_content_to_vec, select_envelop, try_decrypt_key};
|
use crate::cmd_decrypt::{decrypt_limited_content_to_vec, select_envelop, try_decrypt_key};
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::TINY_ENC_CONFIG_FILE;
|
|
||||||
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
use crate::util::SecVec;
|
use crate::util::SecVec;
|
||||||
use crate::util_enc_file;
|
use crate::util_enc_file;
|
||||||
@@ -30,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,
|
||||||
|
|
||||||
@@ -46,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(TINY_ENC_CONFIG_FILE).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.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use simpledateformat::format_human2;
|
|||||||
|
|
||||||
use crate::{config, util, util_enc_file, util_envelop};
|
use crate::{config, util, util_enc_file, util_envelop};
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_CONFIG_FILE};
|
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM};
|
||||||
use crate::util::is_tiny_enc_file;
|
use crate::util::is_tiny_enc_file;
|
||||||
use crate::wrap_key::WrapKey;
|
use crate::wrap_key::WrapKey;
|
||||||
|
|
||||||
@@ -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(TINY_ENC_CONFIG_FILE).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());
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::cmd_decrypt::try_decrypt_key;
|
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::TINY_ENC_CONFIG_FILE;
|
|
||||||
use crate::spec::TinyEncryptEnvelop;
|
use crate::spec::TinyEncryptEnvelop;
|
||||||
use crate::{cmd_encrypt, crypto_cryptor, util, util_env};
|
use crate::{cmd_encrypt, crypto_cryptor, util, util_env};
|
||||||
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||||
@@ -8,7 +6,11 @@ use base64::Engine;
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::{debugging, opt_result, simple_error, XResult};
|
use rust_util::{debugging, opt_result, simple_error, XResult};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::io;
|
||||||
|
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;
|
||||||
|
|
||||||
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
|
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
|
||||||
const SIMPLE_ENCRYPTION_HEADER: &str = "tinyencrypt-dir";
|
const SIMPLE_ENCRYPTION_HEADER: &str = "tinyencrypt-dir";
|
||||||
@@ -24,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,
|
||||||
@@ -39,6 +45,30 @@ pub struct CmdSimpleEncrypt {
|
|||||||
/// Encrypt value in hex
|
/// Encrypt value in hex
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub value_hex: Option<String>,
|
pub value_hex: Option<String>,
|
||||||
|
|
||||||
|
/// With PBKDF encryption
|
||||||
|
#[arg(long, short = 'P')]
|
||||||
|
pub with_pbkdf_encryption: bool,
|
||||||
|
|
||||||
|
/// PBKDF iterations (default: 10000)
|
||||||
|
#[arg(long, short = 'i')]
|
||||||
|
pub pbkdf_iterations: Option<u32>,
|
||||||
|
|
||||||
|
/// PBKDF encryption password
|
||||||
|
#[arg(long, short = 'A')]
|
||||||
|
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
|
||||||
|
#[arg(long)]
|
||||||
|
pub direct_output: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
@@ -63,9 +93,25 @@ pub struct CmdSimpleDecrypt {
|
|||||||
#[arg(long, short = 'v')]
|
#[arg(long, short = 'v')]
|
||||||
pub value: Option<String>,
|
pub value: Option<String>,
|
||||||
|
|
||||||
/// Decrypt result output type (plain, hex, bse64)
|
/// Decrypt result output format (plain, hex, bse64)
|
||||||
#[arg(long, short = 'o')]
|
#[arg(long, short = 'o')]
|
||||||
pub output_type: Option<String>,
|
pub output_format: Option<String>,
|
||||||
|
|
||||||
|
/// PBKDF encryption password
|
||||||
|
#[arg(long, short = 'A')]
|
||||||
|
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
|
||||||
|
#[arg(long)]
|
||||||
|
pub direct_output: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdSimpleEncrypt {
|
impl CmdSimpleEncrypt {
|
||||||
@@ -101,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,45 +157,71 @@ 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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_exit(&self) -> ! {
|
pub fn print_exit(&self, direct_output_value: bool) -> ! {
|
||||||
let result = serde_json::to_string_pretty(self).unwrap();
|
// TODO direct_output_value
|
||||||
println!("{}", result);
|
if direct_output_value {
|
||||||
|
if self.code == 0 {
|
||||||
|
print!("{}", self.result.as_deref().unwrap());
|
||||||
|
} else {
|
||||||
|
println!("{}", self.message.as_deref().unwrap_or("unknown error"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let result = serde_json::to_string_pretty(self).unwrap();
|
||||||
|
println!("{}", result);
|
||||||
|
}
|
||||||
exit(self.code)
|
exit(self.code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
pub fn simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
||||||
|
let direct_output = cmd_simple_encrypt.direct_output;
|
||||||
if let Err(inner_result_error) = inner_simple_encrypt(cmd_simple_encrypt) {
|
if let Err(inner_result_error) = inner_simple_encrypt(cmd_simple_encrypt) {
|
||||||
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit();
|
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit(direct_output);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
|
pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
|
||||||
|
let direct_output = cmd_simple_decrypt.direct_output;
|
||||||
if let Err(inner_result_error) = inner_simple_decrypt(cmd_simple_decrypt) {
|
if let Err(inner_result_error) = inner_simple_decrypt(cmd_simple_decrypt) {
|
||||||
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit();
|
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit(direct_output);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
||||||
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?;
|
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();
|
||||||
@@ -166,30 +240,54 @@ pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()>
|
|||||||
let envelops = cmd_encrypt::encrypt_envelops(cryptor, &value, &envelops)?;
|
let envelops = cmd_encrypt::encrypt_envelops(cryptor, &value, &envelops)?;
|
||||||
|
|
||||||
let envelops_json = serde_json::to_string(&envelops)?;
|
let envelops_json = serde_json::to_string(&envelops)?;
|
||||||
let simple_encrypt_result = format!("{}.{}",
|
let mut simple_encrypt_result = format!("{}.{}",
|
||||||
SIMPLE_ENCRYPTION_HEADER,
|
SIMPLE_ENCRYPTION_HEADER,
|
||||||
URL_SAFE_NO_PAD.encode(envelops_json.as_bytes())
|
URL_SAFE_NO_PAD.encode(envelops_json.as_bytes())
|
||||||
);
|
);
|
||||||
|
|
||||||
CmdResult::success(&simple_encrypt_result).print_exit();
|
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 {
|
||||||
|
let password = util::read_password(&cmd_simple_encrypt.password)?;
|
||||||
|
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, outputs_password).print_exit(cmd_simple_encrypt.direct_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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(TINY_ENC_CONFIG_FILE).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();
|
||||||
|
|
||||||
let output_type = cmd_simple_decrypt.output_type.as_deref().unwrap_or("plain");
|
let output_format = cmd_simple_decrypt.output_format.as_deref().unwrap_or("plain");
|
||||||
match output_type {
|
match output_format {
|
||||||
"plain" | "hex" | "base64" => (),
|
"plain" | "hex" | "base64" => (),
|
||||||
_ => return simple_error!("not supported output type: {}", output_type),
|
_ => return simple_error!("not supported output format: {}", output_format),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = match cmd_simple_decrypt.get_value()? {
|
let mut value = match cmd_simple_decrypt.get_value()? {
|
||||||
None => return simple_error!("--value-stdin/value must assign one"),
|
None => return simple_error!("--value-stdin/value must assign one"),
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut outputs_password = None;
|
||||||
|
if SimplePbkdfEncryptionV1::matches(&value) {
|
||||||
|
let simple_pbkdf_encryption_v1: SimplePbkdfEncryptionV1 = value.as_str().try_into()?;
|
||||||
|
let password = util::read_password(&cmd_simple_decrypt.password)?;
|
||||||
|
let plaintext_bytes = simple_pbkdf_encryption_v1.decrypt(&password)?;
|
||||||
|
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<_>>();
|
||||||
if value_parts.len() != 2 {
|
if value_parts.len() != 2 {
|
||||||
return simple_error!("bad value format: {}", value);
|
return simple_error!("bad value format: {}", value);
|
||||||
@@ -197,7 +295,7 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
|
|||||||
if value_parts[0] != SIMPLE_ENCRYPTION_HEADER {
|
if value_parts[0] != SIMPLE_ENCRYPTION_HEADER {
|
||||||
return simple_error!("bad value format: {}", value);
|
return simple_error!("bad value format: {}", value);
|
||||||
}
|
}
|
||||||
let envelopes_json = opt_result!(URL_SAFE_NO_PAD.decode(&value_parts[1]), "bad value format: {}");
|
let envelopes_json = opt_result!(URL_SAFE_NO_PAD.decode(value_parts[1]), "bad value format: {}");
|
||||||
let envelops: Vec<TinyEncryptEnvelop> = match serde_json::from_slice(&envelopes_json) {
|
let envelops: Vec<TinyEncryptEnvelop> = match serde_json::from_slice(&envelopes_json) {
|
||||||
Err(_) => return simple_error!("bad value format: {}", value),
|
Err(_) => return simple_error!("bad value format: {}", value),
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -213,14 +311,24 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
|
|||||||
return simple_error!("no envelops found: {:?}", cmd_simple_decrypt.key_id);
|
return simple_error!("no envelops found: {:?}", cmd_simple_decrypt.key_id);
|
||||||
}
|
}
|
||||||
if filter_envelops.len() > 1 {
|
if filter_envelops.len() > 1 {
|
||||||
return simple_error!("too many envelops: {:?}, len: {}", cmd_simple_decrypt.key_id, filter_envelops.len());
|
let mut kids = vec![];
|
||||||
|
debugging!("Found {} envelopes", filter_envelops.len());
|
||||||
|
for envelop in &filter_envelops {
|
||||||
|
kids.push(envelop.kid.clone());
|
||||||
|
debugging!("- {} {}", envelop.kid, envelop.r#type.get_name());
|
||||||
|
}
|
||||||
|
return simple_error!("too many envelops: {:?}, len: {}, matched kids: [{}]", cmd_simple_decrypt.key_id, filter_envelops.len(), kids.join(","));
|
||||||
}
|
}
|
||||||
let value = try_decrypt_key(&config, filter_envelops[0], &pin, &slot, false)?;
|
let value = crate::cmd_decrypt::try_decrypt_key(&config, filter_envelops[0], &pin, &slot, false)?;
|
||||||
let value = match output_type {
|
if cmd_simple_decrypt.direct_output && output_format == "plain" {
|
||||||
|
io::stdout().write_all(&value).expect("unable to write to stdout");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
let value = match output_format {
|
||||||
"plain" => opt_result!(String::from_utf8(value), "bad value encoding: {}"),
|
"plain" => opt_result!(String::from_utf8(value), "bad value encoding: {}"),
|
||||||
"hex" => hex::encode(&value),
|
"hex" => hex::encode(&value),
|
||||||
"base64" => STANDARD.encode(&value),
|
"base64" => STANDARD.encode(&value),
|
||||||
_ => return simple_error!("not supported output type: {}", output_type),
|
_ => return simple_error!("not supported output format: {}", output_format),
|
||||||
};
|
};
|
||||||
CmdResult::success(&value).print_exit();
|
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(())
|
||||||
|
|||||||
345
src/config.rs
345
src/config.rs
@@ -1,15 +1,15 @@
|
|||||||
use std::{env, fs};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
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 rust_util::{debugging, opt_result, simple_error, warning, XResult};
|
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, 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_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,36 +72,66 @@ 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(file: &str) -> XResult<Self> {
|
pub fn load_default(config: &Option<String>) -> XResult<Self> {
|
||||||
let resolved_file = resolve_file_path(file);
|
let resolved_file0 = config.clone().or_else(|| rust_util_env::env_var(ENV_TINY_ENC_CONFIG_FILE));
|
||||||
let config_contents = opt_result!(
|
let resolved_file_1 = resolve_file_path(TINY_ENC_CONFIG_FILE);
|
||||||
fs::read_to_string(resolved_file), "Read config file: {}, failed: {}", file
|
let resolved_file_2 = resolve_file_path(TINY_ENC_CONFIG_FILE_2);
|
||||||
);
|
let resolved_file_3 = resolve_file_path(TINY_ENC_CONFIG_FILE_3);
|
||||||
let mut config: TinyEncryptConfig = opt_result!(
|
if let Some(resolved_file) = resolved_file0 {
|
||||||
serde_json::from_str(&config_contents),"Parse config file: {}, failed: {}", file);
|
if resolved_file.starts_with("base64:") {
|
||||||
let mut splited_profiles = HashMap::new();
|
let decoded_resolved_bytes_result = decode_base64(&resolved_file.chars().skip(7).collect::<String>());
|
||||||
for (k, v) in config.profiles.into_iter() {
|
let decoded_resolved_bytes = opt_result!(decoded_resolved_bytes_result, "Decode base64 failed: {}");
|
||||||
if !k.contains(',') {
|
let decoded_resolved_content = opt_result!(String::from_utf8(decoded_resolved_bytes), "Decode UTF-8 string failed: {}");
|
||||||
splited_profiles.insert(k, v);
|
return Self::load_content(&decoded_resolved_content, "<env>");
|
||||||
} else {
|
|
||||||
k.split(',')
|
|
||||||
.map(|k| k.trim())
|
|
||||||
.filter(|k| !k.is_empty())
|
|
||||||
.for_each(|k| {
|
|
||||||
splited_profiles.insert(k.to_string(), v.clone());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
debugging!("Found tiny encrypt config file: {}", &resolved_file);
|
||||||
|
return Self::load_file(&resolved_file)
|
||||||
}
|
}
|
||||||
config.profiles = splited_profiles;
|
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() {
|
||||||
|
debugging!("Load config from: {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 {
|
||||||
|
warning!("Cannot find config file from:\n- {resolved_file_1}\n- {resolved_file_2}\n- {resolved_file_3}");
|
||||||
|
resolved_file_1
|
||||||
|
};
|
||||||
|
Self::load_file(&config_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_file(file: &str) -> XResult<Self> {
|
||||||
|
let resolved_file = resolve_file_path(file);
|
||||||
|
let config_content = opt_result!(
|
||||||
|
fs::read_to_string(resolved_file),
|
||||||
|
"Read config file: {}, failed: {}",
|
||||||
|
file
|
||||||
|
);
|
||||||
|
Self::load_content(&config_content, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {}",
|
||||||
|
file
|
||||||
|
);
|
||||||
|
debugging!("Config: {:#?}", config);
|
||||||
|
let config = load_includes_and_merge(config);
|
||||||
|
debugging!("Final config: {:#?}", config);
|
||||||
|
|
||||||
if let Some(environment) = &config.environment {
|
if let Some(environment) = &config.environment {
|
||||||
for (k, v) in environment {
|
for (k, v) in environment {
|
||||||
let v = match v {
|
let v = match v {
|
||||||
StringOrVecString::String(s) => { s.to_string() }
|
StringOrVecString::String(s) => s.to_string(),
|
||||||
StringOrVecString::Vec(vs) => { vs.join(",") }
|
StringOrVecString::Vec(vs) => vs.join(","),
|
||||||
};
|
};
|
||||||
debugging!("Set env: {}={}", k, v);
|
debugging!("Set env: {}={}", k, v);
|
||||||
env::set_var(k, v);
|
env::set_var(k, v);
|
||||||
@@ -104,10 +144,17 @@ impl TinyEncryptConfig {
|
|||||||
pub fn resolve_path_namespace(&self, path: &Path, append_te: bool) -> PathBuf {
|
pub fn resolve_path_namespace(&self, path: &Path, append_te: bool) -> PathBuf {
|
||||||
if let Some(path_str) = path.to_str() {
|
if let Some(path_str) = path.to_str() {
|
||||||
if path_str.starts_with(':') {
|
if path_str.starts_with(':') {
|
||||||
let namespace = path_str.chars().skip(1)
|
let namespace = path_str
|
||||||
.take_while(|c| *c != ':').collect::<String>();
|
.chars()
|
||||||
let mut filename = path_str.chars().skip(1)
|
.skip(1)
|
||||||
.skip_while(|c| *c != ':').skip(1).collect::<String>();
|
.take_while(|c| *c != ':')
|
||||||
|
.collect::<String>();
|
||||||
|
let mut filename = path_str
|
||||||
|
.chars()
|
||||||
|
.skip(1)
|
||||||
|
.skip_while(|c| *c != ':')
|
||||||
|
.skip(1)
|
||||||
|
.collect::<String>();
|
||||||
if append_te && !filename.ends_with(TINY_ENC_FILE_EXT) {
|
if append_te && !filename.ends_with(TINY_ENC_FILE_EXT) {
|
||||||
filename.push_str(TINY_ENC_FILE_EXT);
|
filename.push_str(TINY_ENC_FILE_EXT);
|
||||||
}
|
}
|
||||||
@@ -145,7 +192,10 @@ impl TinyEncryptConfig {
|
|||||||
}
|
}
|
||||||
if k_filter.ends_with('*') {
|
if k_filter.ends_with('*') {
|
||||||
let new_k_filter = k_filter.chars().collect::<Vec<_>>();
|
let new_k_filter = k_filter.chars().collect::<Vec<_>>();
|
||||||
let new_k_filter = new_k_filter.iter().take(new_k_filter.len() - 1).collect::<String>();
|
let new_k_filter = new_k_filter
|
||||||
|
.iter()
|
||||||
|
.take(new_k_filter.len() - 1)
|
||||||
|
.collect::<String>();
|
||||||
if e.kid.starts_with(&new_k_filter) || envelop_type.starts_with(&new_k_filter) {
|
if e.kid.starts_with(&new_k_filter) || envelop_type.starts_with(&new_k_filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -155,18 +205,30 @@ impl TinyEncryptConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_kid_or_filter<F>(&self, kid: &str, f: F) -> Vec<&TinyEncryptConfigEnvelop>
|
pub fn find_by_kid_or_filter<F>(&self, kid: &str, f: F) -> Vec<&TinyEncryptConfigEnvelop>
|
||||||
where F: Fn(&TinyEncryptConfigEnvelop) -> bool {
|
where
|
||||||
self.envelops.iter().filter(|e| {
|
F: Fn(&TinyEncryptConfigEnvelop) -> bool,
|
||||||
if e.kid == kid { return true; }
|
{
|
||||||
if let Some(sid) = &e.sid {
|
self.envelops
|
||||||
if sid == kid { return true; }
|
.iter()
|
||||||
}
|
.filter(|e| {
|
||||||
f(e)
|
if e.kid == kid {
|
||||||
}).collect()
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(sid) = &e.sid {
|
||||||
|
if sid == kid {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f(e)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_envelops(&self, profile: &Option<String>, key_filter: &Option<String>)
|
pub fn find_envelops(
|
||||||
-> XResult<Vec<&TinyEncryptConfigEnvelop>> {
|
&self,
|
||||||
|
profile: &Option<String>,
|
||||||
|
key_filter: &Option<String>,
|
||||||
|
) -> XResult<Vec<&TinyEncryptConfigEnvelop>> {
|
||||||
debugging!("Profile: {:?}", profile);
|
debugging!("Profile: {:?}", profile);
|
||||||
debugging!("Key filter: {:?}", key_filter);
|
debugging!("Key filter: {:?}", key_filter);
|
||||||
let mut matched_envelops_map = HashMap::new();
|
let mut matched_envelops_map = HashMap::new();
|
||||||
@@ -177,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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,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) {
|
||||||
@@ -200,22 +263,204 @@ 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 { return Ordering::Greater; }
|
if e1.r#type < e2.r#type {
|
||||||
if e1.r#type > e2.r#type { return Ordering::Less; }
|
return Ordering::Greater;
|
||||||
if e1.kid < e2.kid { return Ordering::Greater; }
|
}
|
||||||
if e1.kid > e2.kid { return Ordering::Less; }
|
if e1.r#type > e2.r#type {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
if e1.kid < e2.kid {
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
if e1.kid > e2.kid {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
});
|
});
|
||||||
Ok(envelops)
|
Ok(envelops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_path_namespace(config: &Option<TinyEncryptConfig>, path: &Path, append_te: bool) -> PathBuf {
|
pub fn resolve_path_namespace(
|
||||||
|
config: &Option<TinyEncryptConfig>,
|
||||||
|
path: &Path,
|
||||||
|
append_te: bool,
|
||||||
|
) -> PathBuf {
|
||||||
match config {
|
match config {
|
||||||
None => path.to_path_buf(),
|
None => path.to_path_buf(),
|
||||||
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,15 +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 = "~/.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";
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub use cmd_encrypt::encrypt;
|
|||||||
pub use cmd_encrypt::encrypt_single;
|
pub use cmd_encrypt::encrypt_single;
|
||||||
pub use cmd_encrypt::encrypt_single_file_out;
|
pub use cmd_encrypt::encrypt_single_file_out;
|
||||||
pub use cmd_simple_encrypt_decrypt::simple_encrypt;
|
pub use cmd_simple_encrypt_decrypt::simple_encrypt;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
pub use cmd_simple_encrypt_decrypt::simple_decrypt;
|
pub use cmd_simple_encrypt_decrypt::simple_decrypt;
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
pub use cmd_execenv::CmdExecEnv;
|
pub use cmd_execenv::CmdExecEnv;
|
||||||
@@ -33,6 +34,8 @@ pub use cmd_initpiv::CmdInitPiv;
|
|||||||
pub use cmd_initpiv::init_piv;
|
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 util_log::init_tiny_encrypt_log;
|
||||||
|
|
||||||
mod consts;
|
mod consts;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -71,6 +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_log;
|
||||||
|
mod temporary_key;
|
||||||
|
mod util_mlkem;
|
||||||
|
|
||||||
|
|||||||
15
src/main.rs
15
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,10 +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")]
|
||||||
/// 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)
|
||||||
@@ -63,10 +69,13 @@ 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),
|
||||||
Commands::SimpleEncrypt(cmd_simple_encrypt) => tiny_encrypt::simple_encrypt(cmd_simple_encrypt),
|
Commands::SimpleEncrypt(cmd_simple_encrypt) => tiny_encrypt::simple_encrypt(cmd_simple_encrypt),
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
Commands::SimpleDecrypt(cmd_simple_decrypt) => tiny_encrypt::simple_decrypt(cmd_simple_decrypt),
|
Commands::SimpleDecrypt(cmd_simple_decrypt) => tiny_encrypt::simple_decrypt(cmd_simple_decrypt),
|
||||||
#[cfg(feature = "decrypt")]
|
#[cfg(feature = "decrypt")]
|
||||||
Commands::Decrypt(cmd_decrypt) => tiny_encrypt::decrypt(cmd_decrypt),
|
Commands::Decrypt(cmd_decrypt) => tiny_encrypt::decrypt(cmd_decrypt),
|
||||||
|
|||||||
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())
|
||||||
|
}
|
||||||
59
src/util.rs
59
src/util.rs
@@ -40,28 +40,55 @@ 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 let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
}
|
||||||
let secret = input
|
}
|
||||||
.with_description("Please input your PIN.")
|
if ask_use_default_pin && is_use_default_pin() {
|
||||||
.with_prompt("PIN:")
|
return Ok("123456".into());
|
||||||
.interact();
|
}
|
||||||
opt_result!(secret, "Read PIN from pinentry failed: {}")
|
let rpin = {
|
||||||
.expose_secret()
|
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
||||||
.to_string()
|
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
||||||
} else {
|
let secret = input
|
||||||
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
|
.with_description("Please input your PIN.")
|
||||||
}
|
.with_prompt("PIN:")
|
||||||
|
.interact();
|
||||||
|
opt_result!(secret, "Read PIN from pinentry failed: {}")
|
||||||
|
.expose_secret()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(rpin)
|
Ok(rpin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_password(password: &Option<String>) -> XResult<String> {
|
||||||
|
let rpassword = match password {
|
||||||
|
Some(pin) => pin.to_string(),
|
||||||
|
None => {
|
||||||
|
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) {
|
||||||
|
let secret = input
|
||||||
|
.with_description("Please input your password.")
|
||||||
|
.with_prompt("Password:")
|
||||||
|
.interact();
|
||||||
|
opt_result!(secret, "Read password from pinentry failed: {}")
|
||||||
|
.expose_secret()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
opt_result!(rpassword::prompt_password("Please input password: "), "Read password failed: {}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(rpassword)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_use_default_pin() -> bool {
|
pub fn is_use_default_pin() -> bool {
|
||||||
if util_env::get_no_default_pin_hint() {
|
if util_env::get_no_default_pin_hint() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
}
|
||||||
175
src/util_simple_pbe.rs
Normal file
175
src/util_simple_pbe.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use crate::util_digest;
|
||||||
|
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
||||||
|
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||||
|
use base64::Engine;
|
||||||
|
use rand::random;
|
||||||
|
use rust_util::{opt_result, simple_error, SimpleError, XResult};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
const SIMPLE_PBKDF_ENCRYPTION_PREFIX: &str = "tinyencrypt-pbkdf-encryption-v1";
|
||||||
|
// FORMAT
|
||||||
|
// <PREFIX>.<repeation>.<iterations>.<base64_uri(salt)>.<base64_uri(nonce)>.<base64_uri(ciphertext)>.<base64_uri(tag)>
|
||||||
|
|
||||||
|
pub struct SimplePbkdfEncryptionV1 {
|
||||||
|
pub repetition: u32,
|
||||||
|
pub iterations: u32,
|
||||||
|
pub salt: Vec<u8>,
|
||||||
|
pub nonce: Vec<u8>,
|
||||||
|
pub ciphertext: Vec<u8>,
|
||||||
|
pub tag: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimplePbkdfEncryptionV1 {
|
||||||
|
pub fn matches(enc: &str) -> bool {
|
||||||
|
enc.starts_with(&format!("{SIMPLE_PBKDF_ENCRYPTION_PREFIX}."))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(password: &str, plaintext: &[u8], iterations: &Option<u32>) -> XResult<SimplePbkdfEncryptionV1> {
|
||||||
|
let salt: [u8; 12] = random();
|
||||||
|
let repetition = 1000;
|
||||||
|
let iterations = iterations.unwrap_or(10000);
|
||||||
|
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 nonce: [u8; 12] = random();
|
||||||
|
let mut ciphertext = vec![];
|
||||||
|
|
||||||
|
let mut aes256_gcm_stream_encryptor = Aes256GcmStreamEncryptor::new(key_bytes, &nonce);
|
||||||
|
ciphertext.extend_from_slice(&aes256_gcm_stream_encryptor.update(plaintext));
|
||||||
|
let (last_ciphertext, tag) = aes256_gcm_stream_encryptor.finalize();
|
||||||
|
ciphertext.extend_from_slice(&last_ciphertext);
|
||||||
|
|
||||||
|
Ok(SimplePbkdfEncryptionV1 {
|
||||||
|
repetition,
|
||||||
|
iterations,
|
||||||
|
salt: salt.to_vec(),
|
||||||
|
nonce: nonce.to_vec(),
|
||||||
|
ciphertext,
|
||||||
|
tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, password: &str) -> XResult<Vec<u8>> {
|
||||||
|
let key = simple_pbkdf(
|
||||||
|
password.as_bytes(),
|
||||||
|
&self.salt,
|
||||||
|
self.repetition,
|
||||||
|
self.iterations,
|
||||||
|
);
|
||||||
|
let key_bytes: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {:?}");
|
||||||
|
let mut plaintext = vec![];
|
||||||
|
|
||||||
|
let mut aes256_gcm_stream_decryptor = Aes256GcmStreamDecryptor::new(key_bytes, &self.nonce);
|
||||||
|
plaintext.extend_from_slice(&aes256_gcm_stream_decryptor.update(&self.ciphertext));
|
||||||
|
plaintext.extend_from_slice(&aes256_gcm_stream_decryptor.update(&self.tag));
|
||||||
|
plaintext.extend_from_slice(&opt_result!(
|
||||||
|
aes256_gcm_stream_decryptor.finalize(),
|
||||||
|
"Decrypt failed: {}"
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(plaintext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for SimplePbkdfEncryptionV1 {
|
||||||
|
type Error = SimpleError;
|
||||||
|
|
||||||
|
fn try_from(enc: String) -> Result<Self, Self::Error> {
|
||||||
|
TryFrom::<&str>::try_from(enc.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for SimplePbkdfEncryptionV1 {
|
||||||
|
type Error = SimpleError;
|
||||||
|
|
||||||
|
fn try_from(enc: &str) -> Result<Self, Self::Error> {
|
||||||
|
if !Self::matches(enc) {
|
||||||
|
return simple_error!("Not simple PBKDF encryption: {enc}");
|
||||||
|
}
|
||||||
|
let parts = enc.split(".").collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let repetition: u32 = opt_result!(
|
||||||
|
parts[1].parse(),
|
||||||
|
"Parse simple PBKDF failed, invalid repetition: {}, error: {}",
|
||||||
|
parts[1]
|
||||||
|
);
|
||||||
|
let iterations: u32 = opt_result!(
|
||||||
|
parts[2].parse(),
|
||||||
|
"Parse simple PBKDF failed, invalid iterations: {}, error: {}",
|
||||||
|
parts[2]
|
||||||
|
);
|
||||||
|
let salt = opt_result!(
|
||||||
|
URL_SAFE_NO_PAD.decode(parts[3]),
|
||||||
|
"Parse simple PBKDF failed, invalid salt: {}, error: {}",
|
||||||
|
parts[3]
|
||||||
|
);
|
||||||
|
let nonce = opt_result!(
|
||||||
|
URL_SAFE_NO_PAD.decode(parts[4]),
|
||||||
|
"Parse simple PBKDF failed, invalid nonce: {}, error: {}",
|
||||||
|
parts[4]
|
||||||
|
);
|
||||||
|
let ciphertext = opt_result!(
|
||||||
|
URL_SAFE_NO_PAD.decode(parts[5]),
|
||||||
|
"Parse simple PBKDF failed, invalid ciphertext: {}, error: {}",
|
||||||
|
parts[5]
|
||||||
|
);
|
||||||
|
let tag = opt_result!(
|
||||||
|
URL_SAFE_NO_PAD.decode(parts[6]),
|
||||||
|
"Parse simple PBKDF failed, invalid tag: {}, error: {}",
|
||||||
|
parts[6]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
repetition,
|
||||||
|
iterations,
|
||||||
|
salt,
|
||||||
|
nonce,
|
||||||
|
ciphertext,
|
||||||
|
tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SimplePbkdfEncryptionV1 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut enc = String::with_capacity(1024);
|
||||||
|
enc.push_str(SIMPLE_PBKDF_ENCRYPTION_PREFIX);
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&self.repetition.to_string());
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&self.iterations.to_string());
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&URL_SAFE_NO_PAD.encode(&self.salt));
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&URL_SAFE_NO_PAD.encode(&self.nonce));
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&URL_SAFE_NO_PAD.encode(&self.ciphertext));
|
||||||
|
enc.push('.');
|
||||||
|
enc.push_str(&URL_SAFE_NO_PAD.encode(&self.tag));
|
||||||
|
write!(f, "{}", enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_pbkdf(password: &[u8], salt: &[u8], repetition: u32, iterations: u32) -> Vec<u8> {
|
||||||
|
let mut input = password.to_vec();
|
||||||
|
for it in 0..iterations {
|
||||||
|
let mut message = Vec::with_capacity((input.len() + salt.len() + 4) * repetition as usize);
|
||||||
|
for _ in 0..repetition {
|
||||||
|
message.extend_from_slice(&it.to_be_bytes());
|
||||||
|
message.extend_from_slice(&input);
|
||||||
|
message.extend_from_slice(salt);
|
||||||
|
}
|
||||||
|
input = util_digest::sha256_digest(&message);
|
||||||
|
}
|
||||||
|
input
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let enc = SimplePbkdfEncryptionV1::encrypt("helloworld", "test".as_bytes(), &None).unwrap();
|
||||||
|
let enc_str = enc.to_string();
|
||||||
|
let enc2: SimplePbkdfEncryptionV1 = enc_str.try_into().unwrap();
|
||||||
|
assert_eq!(enc.to_string(), enc2.to_string());
|
||||||
|
let plain = enc2.decrypt("helloworld").unwrap();
|
||||||
|
assert_eq!(b"test", plain.as_slice());
|
||||||
|
}
|
||||||
@@ -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)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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