Compare commits
240 Commits
03ec9a0fe0
...
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
|
|||
| b9e7b5af63 | |||
|
8e6e708c73
|
|||
|
6a07360dc1
|
|||
|
d6e1f96207
|
|||
|
228c69731a
|
|||
|
568022e655
|
|||
|
ac727f082e
|
|||
|
623e657211
|
|||
|
598dbc38c7
|
|||
|
f74a81d4ca
|
|||
|
0309f77ed7
|
|||
|
1340f73bad
|
|||
|
2067faec8e
|
|||
|
de675d215a
|
|||
|
3127dfab0a
|
|||
|
0022181bd6
|
|||
|
af1678f67a
|
|||
|
80ce9fcece
|
|||
|
62feb2e246
|
|||
|
e31f6d49b7
|
|||
|
784765fbb9
|
|||
|
6283c28fff
|
|||
|
c093f865ab
|
|||
|
27c6bee4ea
|
|||
|
2bf0614854
|
|||
|
2bcb0fe5c4
|
|||
|
2e53130ead
|
|||
|
3ce150db72
|
|||
|
3e2a3cc7f3
|
|||
|
61853889ca
|
|||
|
b7d3f77385
|
|||
|
004df06df2
|
|||
|
e60f491801
|
|||
|
5f04aa5783
|
|||
|
0c5dbc7cc3
|
|||
|
d19c9a48f1
|
|||
|
def76d1957
|
|||
|
171cd2d2a4
|
|||
|
a3d101a405
|
|||
|
9b73b4ecc1
|
|||
|
e7b4f022bf
|
|||
|
df5ba3c53d
|
|||
|
0b09630635
|
|||
|
343a6f5fac
|
|||
|
a7398a2578
|
|||
|
9de9af0cde
|
|||
|
5bdc4c69e6
|
|||
|
c30d061f53
|
|||
|
bafb12bdf7
|
|||
|
5822cf9b7f
|
|||
|
7f1abf7dea
|
|||
|
f9a5f39330
|
|||
|
fc3275d978
|
|||
|
26c5a25759
|
|||
|
e7a9877cad
|
|||
|
fd98c27462
|
|||
|
0189b5a765
|
|||
|
90ecfe76ba
|
|||
|
2c2f623df4
|
|||
|
c597a87557
|
|||
|
2ccb4f213b
|
|||
|
33e08e657d
|
|||
|
880b5be94e
|
|||
|
7ee38b8ac5
|
|||
|
c393302d6c
|
|||
|
72d89b32ab
|
|||
|
c1debe5262
|
|||
|
54ac065bd3
|
|||
|
f5bd348d5b
|
|||
|
c49dfcf261
|
|||
|
d0218ee233
|
|||
|
b15b9a5b32
|
|||
|
2845e5155e
|
|||
|
34b60f6929
|
|||
|
68d3c07767
|
|||
|
0a815dbbdf
|
|||
|
993f10ba71
|
|||
|
af7bf60869
|
|||
|
9b735f8e48
|
|||
|
2d29e6eaed
|
|||
|
baafa7aa31
|
|||
|
df36022203
|
|||
|
5e73466400
|
|||
|
115e0f7094
|
|||
|
d75a88c90c
|
|||
|
36fcaab6be
|
|||
|
42bc09fe07
|
|||
|
a8b2fc62b8
|
|||
|
2034db589a
|
|||
|
fa1cd80fc8
|
|||
|
215b75d6da
|
|||
|
d36c06f461
|
|||
|
6b132b800a
|
|||
|
a60bf5c74a
|
|||
|
43b43ba055
|
|||
|
be65cfb06c
|
|||
|
24c6d3bbad
|
|||
|
638e40448a
|
|||
|
bd586130fb
|
|||
|
bae6c13582
|
|||
|
92e6808820
|
|||
|
fd7e8d35a6
|
|||
|
883ed0918b
|
|||
|
fc4ec8d41c
|
|||
|
76b8f93ddd
|
|||
|
0bf4ef2e6b
|
|||
|
9e36725908
|
|||
|
1e2f13e0b8
|
|||
|
ae84bbd13b
|
|||
|
4966ddf72b
|
|||
|
4b426f7b13
|
|||
|
9c5b28eb2a
|
|||
|
4306825706
|
|||
|
42a7a0fb28
|
|||
|
5ec12237d9
|
|||
|
699474cad4
|
|||
|
e6b1ec8a86
|
|||
|
6279e8e16b
|
|||
|
d8ae0ad9db
|
|||
|
3a3ebd64bf
|
|||
|
25de8ea682
|
|||
|
9e46d11e89
|
|||
|
fa5d01a7d5
|
|||
|
a6d78928d9
|
|||
|
e478e43e12
|
|||
|
231bf28240
|
|||
|
8aec9ad9a3
|
|||
|
6161e2d293
|
|||
|
67465d584e
|
|||
|
e355d0f03d
|
|||
|
9d3de9c8f0
|
|||
|
84eb5c789b
|
|||
|
20b0fe1ea2
|
|||
|
89ed3c26ed
|
|||
|
373b3bcefc
|
|||
|
9ec0dba5b3
|
|||
|
3d59680f0c
|
|||
|
80e3a3540e
|
|||
|
19269bf6de
|
|||
|
c05cf1a7cf
|
|||
|
d5cb25cc6a
|
|||
|
f039c183b2
|
|||
|
dcda185512
|
|||
|
0fa22f7f39
|
|||
|
0f02edc6f6
|
|||
|
2a7d28372e
|
|||
|
5dc1927cc9
|
|||
|
1012b33f31
|
|||
|
bb07aec896
|
|||
|
83464dfb28
|
|||
|
b3359d205b
|
|||
|
a66babb828
|
|||
|
94a6cf18b3
|
|||
|
aa6c4f325d
|
|||
|
452cb2b666
|
|||
|
6e00247bf6
|
|||
|
186330fe2c
|
|||
|
ddd3ac3b2d
|
|||
|
17fae72d91
|
|||
|
e0b6df7c46
|
|||
|
90040bbf8e
|
|||
|
9db0cec508
|
|||
|
b4d9b4692b
|
|||
|
ab2e850435
|
|||
|
f281cc15a2
|
|||
|
7d0115541f
|
|||
|
47a0625dcc
|
|||
|
eda2d94d21
|
|||
|
0ab41c87fb
|
|||
|
cb72baebee
|
|||
|
6f70e7c83e
|
|||
|
bec7306918
|
|||
|
dda040a1b3
|
|||
|
bae6ca828d
|
|||
|
66e93106fe
|
|||
|
a37d5b8d62
|
|||
|
93b957a4c3
|
|||
|
b0af535aa3
|
|||
|
cd359bb6ac
|
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
.swiftpm/
|
||||||
|
.build/
|
||||||
_tinyencrypt_config-rs.json
|
_tinyencrypt_config-rs.json
|
||||||
*.tinyenc
|
*.tinyenc
|
||||||
# ---> Rust
|
# ---> Rust
|
||||||
@@ -7,6 +9,11 @@ debug/
|
|||||||
target/
|
target/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
libse.a
|
||||||
|
se.swiftdoc
|
||||||
|
se.swiftmodule
|
||||||
|
se.swiftsourceinfo
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
@@ -22,6 +29,7 @@ target/
|
|||||||
# Icon must end with two \r
|
# Icon must end with two \r
|
||||||
Icon
|
Icon
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
._*
|
._*
|
||||||
|
|
||||||
|
|||||||
2249
Cargo.lock
generated
2249
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
48
Cargo.toml
48
Cargo.toml
@@ -1,38 +1,62 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "0.3.3"
|
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"
|
||||||
|
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]
|
||||||
|
default = ["decrypt", "macos", "smartcard"]
|
||||||
|
full-features = ["decrypt", "macos", "smartcard"]
|
||||||
|
decrypt = ["smartcard"]
|
||||||
|
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
|
||||||
|
macos = ["security-framework"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm-stream = "0.2"
|
aes-gcm-stream = "0.2"
|
||||||
base64 = "0.21"
|
base64 = "0.22"
|
||||||
|
chacha20-poly1305-stream = "0.1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
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"
|
||||||
openpgp-card = "0.3"
|
indicatif = "0.18"
|
||||||
openpgp-card-pcsc = "0.3"
|
openpgp-card = { 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"] }
|
||||||
p384 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] }
|
p384 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.11", features = ["blocking", "rustls", "rustls-tls"] }
|
# reqwest = { version = "0.11", features = ["blocking", "rustls", "rustls-tls"] }
|
||||||
rpassword = "7.2"
|
rpassword = "7.3"
|
||||||
rsa = { version = "0.9", features = ["pem"] }
|
rsa = { version = "0.9", features = ["pem"] }
|
||||||
|
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 }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha256 = "1.4"
|
|
||||||
simpledateformat = "0.1"
|
simpledateformat = "0.1"
|
||||||
tabled = "0.14"
|
tabled = "0.20"
|
||||||
x25519-dalek = "2.0"
|
x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] }
|
||||||
x509-parser = "0.15"
|
x509-parser = "0.17"
|
||||||
yubikey = { version = "0.8", features = ["untested"] }
|
yubikey = { version = "0.8", features = ["untested"], optional = true }
|
||||||
zeroize = "1.6"
|
zeroize = "1.7"
|
||||||
|
spki = "0.7"
|
||||||
|
pqcrypto-kyber = "0.8"
|
||||||
|
pqcrypto-traits = "0.3"
|
||||||
|
pinentry = "0.6"
|
||||||
|
secrecy = "0.10"
|
||||||
|
dialoguer = "0.11"
|
||||||
|
ctrlc = "3.4"
|
||||||
|
swift-secure-enclave-tool-rs = "1.0"
|
||||||
|
json5 = "0.4"
|
||||||
|
external-command-rs = "0.1"
|
||||||
|
percent-encoding = "2.3"
|
||||||
|
ml-kem = { version = "0.2.1", features = ["zeroize"] }
|
||||||
|
zeroizing-alloc = "0.1.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
120
README.md
120
README.md
@@ -1,11 +1,13 @@
|
|||||||
# tiny-encrypt-rs
|
# tiny-encrypt-rs
|
||||||
|
|
||||||
**IMPORTANT**: To use tiny-encrypt, a Yubikey(https://www.yubico.com/products/) is
|
> [!IMPORTANT]
|
||||||
required, the key MUST support PIV or OpenPGP.
|
> 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.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Tiny encrypt for Rust
|
Tiny Encrypt written in Rust Programming Language
|
||||||
|
|
||||||
Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md)
|
Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md)
|
||||||
|
|
||||||
@@ -14,21 +16,59 @@ Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-
|
|||||||
|
|
||||||
Repository address: https://git.hatter.ink/hatter/tiny-encrypt-rs mirror https://github.com/jht5945/tiny-encrypt-rs
|
Repository address: https://git.hatter.ink/hatter/tiny-encrypt-rs mirror https://github.com/jht5945/tiny-encrypt-rs
|
||||||
|
|
||||||
|
Set default encryption algorithm:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export TINY_ENCRYPT_DEFAULT_ALGORITHM='AES' # or CHACHA20
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile only encrypt:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
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:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
tiny-encrypt decrypt --edit-file sample.txt.tinyenc
|
||||||
|
```
|
||||||
|
|
||||||
|
Read environment `EDITOR` or `SECURE_EDITOR` to edit file, `SECURE_EDITOR` write encrypted file to temp file.
|
||||||
|
|
||||||
|
Secure editor command format:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$SECURE_EDITOR <temp-file-name> "aes-256-gcm" <temp-key-hex> <temp-nonce-hex>
|
||||||
|
```
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Encrypt config `~/.tinyencrypt/config-rs.json`:
|
Encrypt config `~/.tinyencrypt/config-rs.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"environment": {
|
||||||
|
"TINY_ENCRYPT_DEFAULT_ALGORITHM": "AES or CHACHA20"
|
||||||
|
},
|
||||||
|
"namespaces": {
|
||||||
|
"name": "/Users/example/.name"
|
||||||
|
},
|
||||||
"envelops": [
|
"envelops": [
|
||||||
{
|
{
|
||||||
"type": "pgp",
|
"type": "pgp-rsa",
|
||||||
"kid": "KID-1",
|
"kid": "KID-1",
|
||||||
"desc": "this is key 001",
|
"desc": "this is key 001",
|
||||||
"publicPart": "----- BEGIN PUBLIC KEY ..."
|
"publicPart": "----- BEGIN PUBLIC KEY ..."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "ecdh",
|
"type": "piv-p256",
|
||||||
"kid": "KID-2",
|
"kid": "KID-2",
|
||||||
"desc": "this is key 002",
|
"desc": "this is key 002",
|
||||||
"publicPart": "04..."
|
"publicPart": "04..."
|
||||||
@@ -46,16 +86,53 @@ Encrypt config `~/.tinyencrypt/config-rs.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Kyber1024 usage:
|
||||||
|
Generate `static-kyber1024` keypair:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -K -a kyber1024 -n keyname
|
||||||
|
[OK ] Keychain name: keyname
|
||||||
|
[OK ] Public key : a731b5032194c3d2ad01f36d64e859ca9738595c21aa19c852dac22f4...
|
||||||
|
[INFO ] Config envelop:
|
||||||
|
{
|
||||||
|
"type": "static-kyber1024",
|
||||||
|
"sid": "keyname",
|
||||||
|
"kid": "keychain:a731b5032194c3d2ad01f36d64e859ca9738595c21aa19c852dac22f411036c7",
|
||||||
|
"desc": "Keychain static",
|
||||||
|
"args": [
|
||||||
|
"keychain::tiny-encrypt:keyname"
|
||||||
|
],
|
||||||
|
"publicPart": "a731b5032194c3d2ad01f36d64e859ca9738595c21aa19c852dac22f411036c..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then write file `~/.tinyencrypt/config-rs.json`.
|
||||||
|
|
||||||
|
Last, config key id to profile.
|
||||||
|
|
||||||
Supported PKI encryption types:
|
Supported PKI encryption types:
|
||||||
|
|
||||||
| Type | Algorithm | Description |
|
| Type | Algorithm | Description |
|
||||||
|------------|-----------------|------------------------|
|
|------------------|---------------------|--------------------------------------------------------|
|
||||||
| pgp | PKCS1-v1.5 | OpenPGP Encryption Key |
|
| pgp-rsa | PKCS1-v1.5 | OpenPGP Encryption Key (Previous `pgp`) |
|
||||||
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
|
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
|
||||||
| ecdh | ECDH(secp256r1) | PIV Slot |
|
| gpg | OpenPGP | GnuPG Command |
|
||||||
| ecdh-p384 | ECDH(secp384r1) | PIV Slot |
|
| static-x25519 | ECDH(X25519) | Key Stored in macOS Keychain Access |
|
||||||
|
| static-kyber1024 | Kyber1024 | Key Stored in macOS Keychain Access |
|
||||||
|
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
|
||||||
|
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
|
||||||
|
| key-p256 | ECDH(secp256r1) | Key Stored in macOS Secure Enclave (using P256) |
|
||||||
|
| key-mlkem768 | ML-KEM(ML-KEM-768) | Key Stored in macOS Secure Enclave (using ML-KEM-768) |
|
||||||
|
| key-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Stored in macOS Secure Enclave (using ML-KEM-1024) |
|
||||||
|
| ext-p256 | ECDH(secp256r1) | Key Protected by External Command |
|
||||||
|
| ext-p384 | ECDH(secp384r1) | Key Protected by External Command |
|
||||||
|
| ext-mlkem768 | ML-KEM(ML-KEM-768) | Key Protected by External Command |
|
||||||
|
| ext-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Protected by External Command |
|
||||||
|
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
||||||
|
|
||||||
Smart Card(Yubikey) protected ECDH Encryption description:
|
Smart Card(Yubikey) protected ECDH Encryption description as below:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
┌───────────────────┐ ┌───────────────────────────┐
|
┌───────────────────┐ ┌───────────────────────────┐
|
||||||
@@ -78,3 +155,26 @@ Smart Card(Yubikey) protected ECDH Encryption description:
|
|||||||
Decrypt using derived key from restored Shared Secret
|
Decrypt using derived key from restored Shared Secret
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Environment
|
||||||
|
|
||||||
|
| KEY | Comment |
|
||||||
|
|----------------------------------|----------------------------------------------------------------|
|
||||||
|
| TINY_ENCRYPT_CONFIG_FILE | Config file |
|
||||||
|
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
|
||||||
|
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
|
||||||
|
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
|
||||||
|
| TINY_ENCRYPT_NO_DEFAULT_PIN_HINT | Do not display default PIN hint |
|
||||||
|
| TINY_ENCRYPT_USE_DIALOGUER | Use dialoguer |
|
||||||
|
| TINY_ENCRYPT_PIN | PIV Card PIN |
|
||||||
|
| TINY_ENCRYPT_KEY_ID | Default Key ID |
|
||||||
|
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
|
||||||
|
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
|
||||||
|
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
|
||||||
|
| 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
34
justfile
Normal file
34
justfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
_:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# publish
|
||||||
|
publish:
|
||||||
|
cargo publish --registry crates-io
|
||||||
|
|
||||||
|
# Install local
|
||||||
|
install:
|
||||||
|
cargo install --path .
|
||||||
|
|
||||||
|
# Default build release
|
||||||
|
build:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Build release without features
|
||||||
|
build-no-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:
|
||||||
|
cargo build --no-default-features
|
||||||
|
cargo build --no-default-features --features smartcard
|
||||||
|
cargo build --no-default-features --features decrypt
|
||||||
|
cargo build --no-default-features --features macos
|
||||||
|
cargo build
|
||||||
@@ -2,12 +2,13 @@ use std::cmp::Ordering;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::XResult;
|
use rust_util::{iff, information, warning, XResult};
|
||||||
use tabled::{Table, Tabled};
|
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;
|
||||||
|
|
||||||
#[derive(Tabled, Eq)]
|
#[derive(Tabled, Eq)]
|
||||||
struct ConfigProfile {
|
struct ConfigProfile {
|
||||||
@@ -29,19 +30,112 @@ impl Ord for ConfigProfile {
|
|||||||
|
|
||||||
impl PartialOrd for ConfigProfile {
|
impl PartialOrd for ConfigProfile {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.profiles.partial_cmp(&other.profiles)
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Tabled)]
|
||||||
|
pub struct ConfigEnvelop {
|
||||||
|
pub r#type: String,
|
||||||
|
pub sid: String,
|
||||||
|
pub kid: String,
|
||||||
|
pub desc: String,
|
||||||
|
pub args: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdConfig {}
|
pub struct CmdConfig {
|
||||||
|
/// Show KID
|
||||||
|
#[arg(long)]
|
||||||
|
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)
|
||||||
|
#[arg(long, short = 'p')]
|
||||||
|
pub profile: Option<String>,
|
||||||
|
/// Encryption key filter (key_id or type:TYPE(e.g. type:piv-p256, type:piv-p384, type:pgp-*), multiple joined by ',', ALL for all)
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
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_config.json {
|
||||||
|
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(());
|
||||||
|
}
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
let envelops = config.find_envelops(&cmd_version.profile, &cmd_version.key_filter)?;
|
||||||
|
if envelops.is_empty() { warning!("Found no envelops"); }
|
||||||
|
information!("Found {} envelops", envelops.len());
|
||||||
|
let mut config_envelops = vec![];
|
||||||
|
for envelop in envelops {
|
||||||
|
let hardware_security_mark = match envelop.r#type.is_hardware_security() {
|
||||||
|
None => " ?",
|
||||||
|
Some(hardware_security) => iff!(hardware_security, " *", "")
|
||||||
|
};
|
||||||
|
config_envelops.push(ConfigEnvelop {
|
||||||
|
r#type: format!("{}{}", envelop.r#type.get_name(), hardware_security_mark),
|
||||||
|
sid: strip_field(&envelop.sid.as_ref().map(ToString::to_string).unwrap_or_else(|| "-".to_string()), 25),
|
||||||
|
kid: strip_field(&envelop.kid, 40),
|
||||||
|
desc: strip_field(&envelop.desc.as_ref().map(ToString::to_string).unwrap_or_else(|| "-".to_string()), 40),
|
||||||
|
args: strip_field(&envelop.args.as_ref().map(|a| format!("[{}]", a.join(", "))).unwrap_or_else(|| "-".to_string()), 20),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut table = Table::new(config_envelops);
|
||||||
|
table.with(Style::sharp());
|
||||||
|
println!("{}", table);
|
||||||
|
println!("> Type with * is hardware security");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_field(kid: &str, max_len: usize) -> String {
|
||||||
|
if kid.len() <= max_len {
|
||||||
|
kid.to_string()
|
||||||
|
} else {
|
||||||
|
kid.chars().enumerate()
|
||||||
|
.filter(|(i, _c)| *i < max_len)
|
||||||
|
.map(|(i, c)| iff!(i >= (max_len - 3), '.', c)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 p = p;
|
for (p, v) in profiles {
|
||||||
let mut v2 = v.clone();
|
let mut v2 = v.clone();
|
||||||
v2.sort();
|
v2.sort();
|
||||||
let vs = v2.join(",");
|
let vs = v2.join(",");
|
||||||
@@ -50,12 +144,16 @@ pub fn config(_cmd_version: CmdConfig) -> XResult<()> {
|
|||||||
Some(vec) => { vec.push((p, v)); }
|
Some(vec) => { vec.push((p, v)); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut config_profiles = vec![];
|
let mut config_profiles = vec![];
|
||||||
for pvs in reverse_map.values() {
|
for pvs in reverse_map.values() {
|
||||||
let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
|
let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
|
||||||
ps.sort();
|
ps.sort();
|
||||||
let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
|
let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
|
||||||
|
if cmd_config.hide_all && pp == "__all__" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let kids = pvs[0].1;
|
let kids = pvs[0].1;
|
||||||
let mut ks = Vec::with_capacity(kids.len());
|
let mut ks = Vec::with_capacity(kids.len());
|
||||||
for kid in kids {
|
for kid in kids {
|
||||||
@@ -64,10 +162,20 @@ pub fn config(_cmd_version: CmdConfig) -> XResult<()> {
|
|||||||
ks.push(format!("[ERROR] Key not found: {}", kid));
|
ks.push(format!("[ERROR] Key not found: {}", kid));
|
||||||
}
|
}
|
||||||
Some(envelop) => {
|
Some(envelop) => {
|
||||||
|
let kid = if cmd_config.show_kid {
|
||||||
|
format!("Kid: {}", envelop.kid)
|
||||||
|
} else {
|
||||||
|
envelop.sid.as_ref()
|
||||||
|
.map(|sid| format!("Sid: {}", sid))
|
||||||
|
.unwrap_or_else(|| format!("Kid: {}", envelop.kid))
|
||||||
|
};
|
||||||
let desc = envelop.desc.as_ref()
|
let desc = envelop.desc.as_ref()
|
||||||
.map(|desc| format!(", Desc: {}", desc))
|
.map(|desc| format!(", Desc: {}", desc))
|
||||||
.unwrap_or_else(|| "".to_string());
|
.unwrap_or_else(|| "".to_string());
|
||||||
ks.push(format!("{}{}", envelop.kid, desc));
|
ks.push(format!(
|
||||||
|
"{}, {}{}",
|
||||||
|
util_envelop::with_width_type(envelop.r#type.get_name()), kid, desc
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::env::temp_dir;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
use std::time::{Instant, SystemTime};
|
use std::time::{Instant, SystemTime};
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use fs_set_times::SystemTimeSpec;
|
use dialoguer::console::Term;
|
||||||
|
use dialoguer::Select;
|
||||||
|
use dialoguer::theme::ColorfulTheme;
|
||||||
|
use flate2::Compression;
|
||||||
use openpgp_card::crypto_data::Cryptogram;
|
use openpgp_card::crypto_data::Cryptogram;
|
||||||
use rust_util::{
|
use rust_util::{
|
||||||
debugging, failure, iff, information, opt_result, simple_error, success,
|
debugging, failure, iff, information, opt_result, opt_value_result, println_ex, simple_error, success,
|
||||||
util_msg, 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;
|
||||||
@@ -17,53 +24,122 @@ use yubikey::piv::{AlgorithmId, decrypt_data};
|
|||||||
use yubikey::YubiKey;
|
use yubikey::YubiKey;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::{file, util, util_pgp, util_piv};
|
use crate::{cmd_encrypt, config, consts, crypto_simple, util, util_enc_file, util_env, util_envelop, util_file, util_gpg, util_pgp, util_piv};
|
||||||
use crate::compress::GzStreamDecoder;
|
use crate::compress::GzStreamDecoder;
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
use crate::consts::{DATE_TIME_FORMAT, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
|
use crate::consts::{
|
||||||
use crate::crypto_aes::{aes_gcm_decrypt, try_aes_gcm_decrypt_with_salt};
|
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_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256,
|
||||||
|
ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519,
|
||||||
|
SALT_COMMENT, TINY_ENC_FILE_EXT,
|
||||||
|
};
|
||||||
|
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_digest::DigestWrite;
|
||||||
|
use crate::util_env::TINY_ENCRYPT_ENV_EXTERNAL_COMMAND;
|
||||||
|
use crate::util_keychainkey;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
use crate::util_keychainstatic;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
use crate::util_keychainstatic::KeychainKey;
|
||||||
|
use crate::util_progress::Progress;
|
||||||
use crate::wrap_key::WrapKey;
|
use crate::wrap_key::WrapKey;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdDecrypt {
|
pub struct CmdDecrypt {
|
||||||
/// Files need to be decrypted
|
/// PGP or PIV PIN
|
||||||
pub paths: Vec<PathBuf>,
|
|
||||||
/// PIN
|
|
||||||
#[arg(long, short = 'p')]
|
#[arg(long, short = 'p')]
|
||||||
pub pin: Option<String>,
|
pub pin: Option<String>,
|
||||||
/// Slot
|
|
||||||
|
/// KeyID
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
pub key_id: Option<String>,
|
||||||
|
|
||||||
|
/// PIV slot
|
||||||
#[arg(long, short = 's')]
|
#[arg(long, short = 's')]
|
||||||
pub slot: Option<String>,
|
pub slot: Option<String>,
|
||||||
|
|
||||||
/// Remove source file
|
/// Remove source file
|
||||||
#[arg(long, short = 'R')]
|
#[arg(long, short = 'R')]
|
||||||
pub remove_file: bool,
|
pub remove_file: bool,
|
||||||
|
|
||||||
/// Skip decrypt file
|
/// Skip decrypt file
|
||||||
#[arg(long)]
|
#[arg(long, short = 'S')]
|
||||||
pub skip_decrypt_file: bool,
|
pub skip_decrypt_file: bool,
|
||||||
|
|
||||||
|
/// Direct print to the console, file must less than 10K
|
||||||
|
#[arg(long, short = 'P')]
|
||||||
|
pub direct_print: bool,
|
||||||
|
|
||||||
|
/// Split std out and std err
|
||||||
|
#[arg(long)]
|
||||||
|
pub split_print: bool,
|
||||||
|
|
||||||
|
/// Digest file
|
||||||
|
#[arg(long, short = 'D')]
|
||||||
|
pub digest_file: bool,
|
||||||
|
|
||||||
|
/// Edit file
|
||||||
|
#[arg(long, short = 'E')]
|
||||||
|
pub edit_file: bool,
|
||||||
|
|
||||||
|
/// Readonly mode
|
||||||
|
#[arg(long)]
|
||||||
|
pub readonly: bool,
|
||||||
|
|
||||||
|
/// Digest algorithm (sha1, sha256[default], sha384, sha512 ...)
|
||||||
|
#[arg(long, short = 'A')]
|
||||||
|
pub digest_algorithm: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Files need to be decrypted
|
||||||
|
pub paths: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CmdDecrypt {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(p) = self.pin.as_mut() { p.zeroize(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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); }
|
||||||
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;
|
||||||
let mut failed_count = 0;
|
let mut failed_count = 0;
|
||||||
let mut total_len = 0_u64;
|
let mut total_len = 0_u64;
|
||||||
|
if cmd_decrypt.edit_file && (cmd_decrypt.paths.len() != 1) {
|
||||||
|
return simple_error!("Edit mode only allows one file assigned.");
|
||||||
|
}
|
||||||
|
let pin = cmd_decrypt.pin.clone().or_else(util_env::get_pin);
|
||||||
|
let key_id = cmd_decrypt.key_id.clone().or_else(util_env::get_key_id);
|
||||||
|
|
||||||
for path in &cmd_decrypt.paths {
|
for path in &cmd_decrypt.paths {
|
||||||
|
let path = config::resolve_path_namespace(&config, path, true);
|
||||||
let start_decrypt_single = Instant::now();
|
let start_decrypt_single = Instant::now();
|
||||||
match decrypt_single(&config, path, &cmd_decrypt.pin, &cmd_decrypt.slot, &cmd_decrypt) {
|
match decrypt_single(&config, &path, &pin, &key_id, &cmd_decrypt.slot, &cmd_decrypt) {
|
||||||
Ok(len) => {
|
Ok(len) => {
|
||||||
succeed_count += 1;
|
succeed_count += 1;
|
||||||
|
if len > 0 {
|
||||||
total_len += len;
|
total_len += len;
|
||||||
success!(
|
success!(
|
||||||
"Decrypt {} succeed, cost {} ms, file size {} byte(s)",
|
"Decrypt {} succeed, cost {} ms{}",
|
||||||
path.to_str().unwrap_or("N/A"),
|
path.to_str().unwrap_or("N/A"),
|
||||||
start_decrypt_single.elapsed().as_millis(),
|
start_decrypt_single.elapsed().as_millis(),
|
||||||
len
|
iff!(len == 0, "".to_string(), format!(", file size {}", util_size::get_display_size(len as i64)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failed_count += 1;
|
failed_count += 1;
|
||||||
failure!("Decrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e);
|
failure!("Decrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e);
|
||||||
@@ -72,9 +148,9 @@ pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
|
|||||||
}
|
}
|
||||||
if (succeed_count + failed_count) > 1 {
|
if (succeed_count + failed_count) > 1 {
|
||||||
success!(
|
success!(
|
||||||
"Decrypt succeed {} file(s) {} byte(s), failed {} file(s), total cost {} ms",
|
"Decrypt succeed {} file(s) {}, failed {} file(s), total cost {} ms",
|
||||||
succeed_count,
|
succeed_count,
|
||||||
total_len,
|
util_size::get_display_size(total_len as i64),
|
||||||
failed_count,
|
failed_count,
|
||||||
start.elapsed().as_millis(),
|
start.elapsed().as_millis(),
|
||||||
);
|
);
|
||||||
@@ -85,60 +161,235 @@ pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
|
|||||||
pub fn decrypt_single(config: &Option<TinyEncryptConfig>,
|
pub fn decrypt_single(config: &Option<TinyEncryptConfig>,
|
||||||
path: &PathBuf,
|
path: &PathBuf,
|
||||||
pin: &Option<String>,
|
pin: &Option<String>,
|
||||||
|
key_id: &Option<String>,
|
||||||
slot: &Option<String>,
|
slot: &Option<String>,
|
||||||
cmd_decrypt: &CmdDecrypt) -> XResult<u64> {
|
cmd_decrypt: &CmdDecrypt) -> XResult<u64> {
|
||||||
let path_display = format!("{}", path.display());
|
let path_display = format!("{}", path.display());
|
||||||
util::require_tiny_enc_file_and_exists(path)?;
|
util::require_tiny_enc_file_and_exists(path)?;
|
||||||
|
|
||||||
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
||||||
let meta = opt_result!(file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
let (_, is_meta_compressed, meta) = opt_result!(
|
||||||
|
util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
||||||
|
util_msg::when_debug(|| {
|
||||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||||
|
});
|
||||||
|
|
||||||
|
let encryption_algorithm = meta.encryption_algorithm.as_deref()
|
||||||
|
.unwrap_or(consts::TINY_ENC_AES_GCM);
|
||||||
|
let cryptor = Cryptor::from(encryption_algorithm)?;
|
||||||
|
|
||||||
|
let do_skip_file_out = cmd_decrypt.skip_decrypt_file || cmd_decrypt.direct_print
|
||||||
|
|| cmd_decrypt.digest_file || cmd_decrypt.edit_file;
|
||||||
let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()];
|
let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()];
|
||||||
util::require_file_not_exists(path_out)?;
|
if !do_skip_file_out { util::require_file_not_exists(path_out)?; }
|
||||||
|
|
||||||
let selected_envelop = select_envelop(&meta)?;
|
let digest_algorithm = cmd_decrypt.digest_algorithm.as_deref().unwrap_or("sha256");
|
||||||
|
if cmd_decrypt.digest_file { DigestWrite::from_algo(digest_algorithm)?; } // FAST CHECK
|
||||||
|
|
||||||
let key = try_decrypt_key(config, selected_envelop, pin, slot)?;
|
let selected_envelop = select_envelop(&meta, key_id, config, false)?;
|
||||||
let nonce = opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}");
|
|
||||||
|
|
||||||
// debugging!("Decrypt key: {}", hex::encode(&key));
|
let key = SecVec(try_decrypt_key(config, selected_envelop, pin, slot, false)?);
|
||||||
debugging!("Decrypt nonce: {}", hex::encode(&nonce));
|
let nonce = SecVec(opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}"));
|
||||||
|
let key_nonce = KeyNonce { k: key.as_ref(), n: nonce.as_ref() };
|
||||||
|
|
||||||
let enc_meta = parse_encrypted_meta(&meta, &key, &nonce)?;
|
// debugging!("Decrypt key: {}", hex::encode(&key.0));
|
||||||
parse_encrypted_comment(&meta, &key, &nonce)?;
|
util_msg::when_debug(|| debugging!("Decrypt nonce: {}", hex::encode(nonce.as_ref())));
|
||||||
|
|
||||||
if cmd_decrypt.skip_decrypt_file {
|
let enc_meta = parse_encrypted_meta(&meta, cryptor, &key_nonce)?;
|
||||||
information!("Decrypt file is skipped.");
|
parse_encrypted_comment(&meta, cryptor, &key_nonce)?;
|
||||||
|
|
||||||
|
// Decrypt to output
|
||||||
|
if cmd_decrypt.direct_print {
|
||||||
|
if let Some(output) = decrypt_limited_content_to_vec(&mut file_in, &meta, cryptor, &key_nonce)? {
|
||||||
|
if cmd_decrypt.split_print {
|
||||||
|
print!("{}", &output)
|
||||||
} else {
|
} else {
|
||||||
let compressed_desc = iff!(meta.compress, " [compressed]", "");
|
println!(">>>>> BEGIN CONTENT >>>>>\n{}\n<<<<< END CONTENT <<<<<", &output)
|
||||||
let start = Instant::now();
|
}
|
||||||
util_msg::print_lastline(
|
return Ok(meta.file_length);
|
||||||
&format!("Decrypting file: {}{} ...", path_display, compressed_desc)
|
}
|
||||||
);
|
return Ok(0);
|
||||||
|
|
||||||
let mut file_out = File::create(path_out)?;
|
|
||||||
let _ = decrypt_file(&mut file_in, &mut file_out, &key, &nonce, meta.compress)?;
|
|
||||||
drop(file_out);
|
|
||||||
util_msg::clear_lastline();
|
|
||||||
|
|
||||||
update_out_file_time(enc_meta, path_out);
|
|
||||||
let encrypt_duration = start.elapsed();
|
|
||||||
debugging!("Inner decrypt file{}: {} elapsed: {} ms", compressed_desc, path_display, encrypt_duration.as_millis());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
util::zeroize(key);
|
// Edit file
|
||||||
util::zeroize(nonce);
|
if cmd_decrypt.edit_file {
|
||||||
|
let file_content = match decrypt_limited_content_to_vec(&mut file_in, &meta, cryptor, &key_nonce)? {
|
||||||
|
None => return Ok(0),
|
||||||
|
Some(output) => output,
|
||||||
|
};
|
||||||
|
let (secure_editor, editor) = get_file_editor();
|
||||||
|
let temp_cryptor = Cryptor::Aes256Gcm;
|
||||||
|
let temp_encryption_key_nonce = util::make_key256_and_nonce();
|
||||||
|
let temp_key_nonce = KeyNonce { k: temp_encryption_key_nonce.0.as_ref(), n: temp_encryption_key_nonce.1.as_ref() };
|
||||||
|
let write_file_content = if secure_editor {
|
||||||
|
let mut encryptor = temp_cryptor.encryptor(&temp_key_nonce)?;
|
||||||
|
encryptor.encrypt(file_content.as_bytes())
|
||||||
|
} else {
|
||||||
|
file_content.as_bytes().to_vec()
|
||||||
|
};
|
||||||
|
let temp_file = create_edit_temp_file(&write_file_content, path_out)?;
|
||||||
|
|
||||||
|
let do_edit_file = || -> XResult<()> {
|
||||||
|
let temp_file_content_bytes = run_file_editor_and_wait_content(
|
||||||
|
&editor, &temp_file, secure_editor, cmd_decrypt.readonly, &temp_encryption_key_nonce)?;
|
||||||
|
if cmd_decrypt.readonly {
|
||||||
|
information!("Readonly, do not check temp file is changed.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let temp_file_content_bytes = if secure_editor {
|
||||||
|
let mut decryptor = temp_cryptor.decryptor(&temp_key_nonce)?;
|
||||||
|
decryptor.decrypt(&temp_file_content_bytes)?
|
||||||
|
} else {
|
||||||
|
temp_file_content_bytes
|
||||||
|
};
|
||||||
|
let temp_file_content = opt_result!(String::from_utf8(temp_file_content_bytes), "Read temp file failed: {}");
|
||||||
|
if temp_file_content == file_content {
|
||||||
|
information!("Temp file is not changed.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
success!("Temp file is changed, save file ...");
|
||||||
drop(file_in);
|
drop(file_in);
|
||||||
if cmd_decrypt.remove_file { util::remove_file_with_msg(path); }
|
let mut meta = meta;
|
||||||
|
meta.latest_user_agent = Some(util::get_user_agent());
|
||||||
|
meta.file_length = temp_file_content.len() as u64;
|
||||||
|
meta.file_last_modified = util_time::get_current_millis() as u64;
|
||||||
|
match &mut meta.file_edit_count {
|
||||||
|
None => { meta.file_edit_count = Some(1); }
|
||||||
|
Some(file_edit_count) => { *file_edit_count += 1; }
|
||||||
|
}
|
||||||
|
let mut file_out = File::create(path)?;
|
||||||
|
let _ = util_enc_file::write_tiny_encrypt_meta(&mut file_out, &meta, is_meta_compressed)?;
|
||||||
|
let compress_level = iff!(meta.compress, Some(Compression::default().level()), None);
|
||||||
|
cmd_encrypt::encrypt_file(
|
||||||
|
&mut temp_file_content.as_bytes(), meta.file_length, &mut file_out, cryptor,
|
||||||
|
&key_nonce, &compress_level,
|
||||||
|
)?;
|
||||||
|
drop(file_out);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
let do_edit_file_result = do_edit_file();
|
||||||
|
if let Err(e) = fs::remove_file(&temp_file) {
|
||||||
|
warning!("Remove temp file: {} failed: {}", temp_file.display(), e)
|
||||||
|
}
|
||||||
|
do_edit_file_result?;
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digest file
|
||||||
|
if cmd_decrypt.digest_file {
|
||||||
|
let mut digest_write = DigestWrite::from_algo(digest_algorithm)?;
|
||||||
|
let _ = decrypt_file(
|
||||||
|
&mut file_in, meta.file_length, &mut digest_write, cryptor, &key_nonce, meta.compress,
|
||||||
|
)?;
|
||||||
|
let digest = digest_write.digest();
|
||||||
|
success!("File digest {}: {}", digest_algorithm.to_uppercase(), hex::encode(digest));
|
||||||
|
return Ok(meta.file_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd_decrypt.skip_decrypt_file {
|
||||||
|
information!("Decrypt file content is skipped.");
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt to file
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let mut file_out = File::create(path_out)?;
|
||||||
|
let _ = decrypt_file(
|
||||||
|
&mut file_in, meta.file_length, &mut file_out, cryptor, &key_nonce, meta.compress,
|
||||||
|
)?;
|
||||||
|
drop(file_out);
|
||||||
|
util_file::update_file_time(enc_meta, path_out);
|
||||||
|
|
||||||
|
let encrypt_duration = start.elapsed();
|
||||||
|
debugging!("Inner decrypt file{}: {} elapsed: {} ms",
|
||||||
|
iff!(meta.compress, " [compressed]", ""),
|
||||||
|
path_display,
|
||||||
|
encrypt_duration.as_millis()
|
||||||
|
);
|
||||||
|
|
||||||
|
if cmd_decrypt.remove_file {
|
||||||
|
util::remove_file_with_msg(path);
|
||||||
|
}
|
||||||
Ok(meta.file_length)
|
Ok(meta.file_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8], compress: bool) -> XResult<usize> {
|
fn run_file_editor_and_wait_content(editor: &str, temp_file: &PathBuf, secure_editor: bool, readonly: bool, temp_encryption_key_nonce: &(SecVec, SecVec)) -> XResult<Vec<u8>> {
|
||||||
let mut total_len = 0;
|
let mut command = Command::new(editor);
|
||||||
|
command.arg(temp_file.to_str().expect("Get temp file path failed."));
|
||||||
|
if secure_editor {
|
||||||
|
command.arg("aes-256-gcm");
|
||||||
|
command.arg(hex::encode(&temp_encryption_key_nonce.0));
|
||||||
|
command.arg(hex::encode(&temp_encryption_key_nonce.1));
|
||||||
|
if readonly { command.env("READONLY", "true"); }
|
||||||
|
}
|
||||||
|
debugging!("Run cmd: {:?}", command);
|
||||||
|
let run_cmd_result = util_cmd::run_command_and_wait(&mut command);
|
||||||
|
debugging!("Run cmd result: {:?}", run_cmd_result);
|
||||||
|
let run_cmd_exit_status = opt_result!(run_cmd_result, "Run cmd {} failed: {}", editor);
|
||||||
|
if !run_cmd_exit_status.success() {
|
||||||
|
return simple_error!("Run cmd {} failed: {:?}", editor, run_cmd_exit_status.code());
|
||||||
|
}
|
||||||
|
Ok(opt_result!(fs::read(temp_file), "Read file failed: {}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_editor() -> (bool, String) {
|
||||||
|
if let Some(secure_editor) = rust_util_env::env_var("SECURE_EDITOR") {
|
||||||
|
// cmd <file-name> "aes-256-gcm" <key-in-hex> <nonce-in-hex>
|
||||||
|
information!("Found secure editor: {}", &secure_editor);
|
||||||
|
return (true, secure_editor);
|
||||||
|
}
|
||||||
|
match rust_util_env::env_var("EDITOR") {
|
||||||
|
Some(editor) => (false, editor),
|
||||||
|
None => {
|
||||||
|
warning!("EDITOR is not assigned, use default editor vi");
|
||||||
|
(false, "vi".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_edit_temp_file(file_content: &[u8], path_out: &str) -> XResult<PathBuf> {
|
||||||
|
let temp_dir = temp_dir();
|
||||||
|
let current_millis = util_time::get_current_millis();
|
||||||
|
let file_name = if path_out.contains('/') {
|
||||||
|
path_out.split('/').last().unwrap().to_string()
|
||||||
|
} else {
|
||||||
|
path_out.to_string()
|
||||||
|
};
|
||||||
|
let temp_file = temp_dir.join(format!("tmp_file_{}_{}", current_millis, file_name));
|
||||||
|
information!("Temp file: {}", temp_file.display());
|
||||||
|
opt_result!(fs::write(&temp_file, file_content), "Write temp file failed: {}");
|
||||||
|
Ok(temp_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_limited_content_to_vec(mut file_in: &mut File,
|
||||||
|
meta: &TinyEncryptMeta, cryptor: Cryptor, key_nonce: &KeyNonce) -> XResult<Option<String>> {
|
||||||
|
if meta.file_length > 100 * 1024 {
|
||||||
|
failure!("File too large(more than 100K) cannot direct print on console.");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if meta.file_length > 10 * 1024 {
|
||||||
|
warning!("File is large(more than 10K) print on console.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output: Vec<u8> = Vec::with_capacity(10 * 1024);
|
||||||
|
let _ = decrypt_file(
|
||||||
|
&mut file_in, meta.file_length, &mut output, cryptor, key_nonce, meta.compress,
|
||||||
|
)?;
|
||||||
|
match String::from_utf8(output) {
|
||||||
|
Err(_) => failure!("File content is not UTF-8 encoded."),
|
||||||
|
Ok(output) => return Ok(Some(output)),
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt_file(file_in: &mut impl Read, file_len: u64, file_out: &mut impl Write,
|
||||||
|
cryptor: Cryptor, key_nonce: &KeyNonce, compress: bool) -> XResult<u64> {
|
||||||
|
let mut total_len = 0_u64;
|
||||||
let mut buffer = [0u8; 1024 * 8];
|
let mut buffer = [0u8; 1024 * 8];
|
||||||
let key = opt_result!(key.try_into(), "Key is not 32 bytes: {}");
|
let progress = Progress::new(file_len);
|
||||||
let mut decryptor = aes_gcm_stream::Aes256GcmStreamDecryptor::new(key, nonce);
|
let mut decryptor = cryptor.decryptor(key_nonce)?;
|
||||||
let mut gz_decoder = GzStreamDecoder::new();
|
let mut gz_decoder = GzStreamDecoder::new();
|
||||||
loop {
|
loop {
|
||||||
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
||||||
@@ -153,10 +404,11 @@ fn decrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8
|
|||||||
last_block
|
last_block
|
||||||
};
|
};
|
||||||
opt_result!(file_out.write_all(&last_block), "Write file failed: {}");
|
opt_result!(file_out.write_all(&last_block), "Write file failed: {}");
|
||||||
debugging!("Decrypt finished, total bytes: {}", total_len);
|
progress.finish();
|
||||||
|
debugging!("Decrypt finished, total: {} byte(s)", total_len);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
total_len += len;
|
total_len += len as u64;
|
||||||
let decrypted = decryptor.update(&buffer[0..len]);
|
let decrypted = decryptor.update(&buffer[0..len]);
|
||||||
let decrypted = if compress {
|
let decrypted = if compress {
|
||||||
opt_result!(gz_decoder.update(&decrypted), "Decompress file failed: {}")
|
opt_result!(gz_decoder.update(&decrypted), "Decompress file failed: {}")
|
||||||
@@ -164,36 +416,17 @@ fn decrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8
|
|||||||
decrypted
|
decrypted
|
||||||
};
|
};
|
||||||
opt_result!(file_out.write_all(&decrypted), "Write file failed: {}");
|
opt_result!(file_out.write_all(&decrypted), "Write file failed: {}");
|
||||||
|
progress.position(total_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut key = key;
|
|
||||||
key.zeroize();
|
|
||||||
Ok(total_len)
|
Ok(total_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_out_file_time(enc_meta: Option<EncEncryptedMeta>, path_out: &str) {
|
fn parse_encrypted_comment(meta: &TinyEncryptMeta, crypto: Cryptor, key_nonce: &KeyNonce) -> XResult<()> {
|
||||||
if let Some(enc_meta) = &enc_meta {
|
|
||||||
let create_time = enc_meta.c_time.map(SystemTime::from_millis);
|
|
||||||
let modify_time = enc_meta.m_time.map(SystemTime::from_millis);
|
|
||||||
if create_time.is_some() || modify_time.is_some() {
|
|
||||||
let set_times_result = fs_set_times::set_times(
|
|
||||||
path_out,
|
|
||||||
create_time.map(SystemTimeSpec::Absolute),
|
|
||||||
modify_time.map(SystemTimeSpec::Absolute),
|
|
||||||
);
|
|
||||||
match set_times_result {
|
|
||||||
Ok(_) => information!("Set file time succeed."),
|
|
||||||
Err(e) => warning!("Set file time failed: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_encrypted_comment(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) -> XResult<()> {
|
|
||||||
if let Some(encrypted_comment) = &meta.encrypted_comment {
|
if let Some(encrypted_comment) = &meta.encrypted_comment {
|
||||||
match util::decode_base64(encrypted_comment) {
|
match util::decode_base64(encrypted_comment) {
|
||||||
Err(e) => warning!("Decode encrypted comment failed: {}", e),
|
Err(e) => warning!("Decode encrypted comment failed: {}", e),
|
||||||
Ok(ec_bytes) => match try_aes_gcm_decrypt_with_salt(key, nonce, SALT_COMMENT, &ec_bytes) {
|
Ok(ec_bytes) => match crypto_simple::try_decrypt_with_salt(crypto, key_nonce, SALT_COMMENT, &ec_bytes) {
|
||||||
Err(e) => warning!("Decrypt encrypted comment failed: {}", e),
|
Err(e) => warning!("Decrypt encrypted comment failed: {}", e),
|
||||||
Ok(decrypted_comment_bytes) => match String::from_utf8(decrypted_comment_bytes.clone()) {
|
Ok(decrypted_comment_bytes) => match String::from_utf8(decrypted_comment_bytes.clone()) {
|
||||||
Err(_) => success!("Encrypted message hex: {}", hex::encode(&decrypted_comment_bytes)),
|
Err(_) => success!("Encrypted message hex: {}", hex::encode(&decrypted_comment_bytes)),
|
||||||
@@ -205,14 +438,16 @@ fn parse_encrypted_comment(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_encrypted_meta(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) -> XResult<Option<EncEncryptedMeta>> {
|
fn parse_encrypted_meta(meta: &TinyEncryptMeta, cryptor: Cryptor, key_nonce: &KeyNonce) -> XResult<Option<EncEncryptedMeta>> {
|
||||||
Ok(match &meta.encrypted_meta {
|
let enc_encrypted_meta = match &meta.encrypted_meta {
|
||||||
None => None,
|
None => return Ok(None),
|
||||||
Some(enc_encrypted_meta) => {
|
Some(enc_encrypted_meta) => enc_encrypted_meta,
|
||||||
|
};
|
||||||
let enc_encrypted_meta_bytes = opt_result!(
|
let enc_encrypted_meta_bytes = opt_result!(
|
||||||
util::decode_base64(enc_encrypted_meta), "Decode enc-encrypted-meta failed: {}");
|
util::decode_base64(enc_encrypted_meta), "Decode enc-encrypted-meta failed: {}");
|
||||||
let enc_meta = opt_result!(
|
let enc_meta = opt_result!(
|
||||||
EncEncryptedMeta::unseal(key, nonce, &enc_encrypted_meta_bytes), "Unseal enc-encrypted-meta failed: {}");
|
EncEncryptedMeta::unseal(cryptor, key_nonce, &enc_encrypted_meta_bytes), "Unseal enc-encrypted-meta failed: {}");
|
||||||
|
debugging!("Encrypted meta: {:?}", enc_meta);
|
||||||
if let Some(filename) = &enc_meta.filename {
|
if let Some(filename) = &enc_meta.filename {
|
||||||
information!("Source filename: {}", filename);
|
information!("Source filename: {}", filename);
|
||||||
}
|
}
|
||||||
@@ -223,47 +458,57 @@ fn parse_encrypted_meta(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) -> XRe
|
|||||||
if let Some(m_time) = &enc_meta.c_time {
|
if let Some(m_time) = &enc_meta.c_time {
|
||||||
information!("Source file modified time: {}", fmt.format_local(SystemTime::from_millis(*m_time)));
|
information!("Source file modified time: {}", fmt.format_local(SystemTime::from_millis(*m_time)));
|
||||||
}
|
}
|
||||||
Some(enc_meta)
|
Ok(Some(enc_meta))
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
|
pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
|
||||||
envelop: &TinyEncryptEnvelop,
|
envelop: &TinyEncryptEnvelop,
|
||||||
pin: &Option<String>,
|
pin: &Option<String>,
|
||||||
slot: &Option<String>) -> XResult<Vec<u8>> {
|
slot: &Option<String>,
|
||||||
|
silent: bool) -> XResult<Vec<u8>> {
|
||||||
match envelop.r#type {
|
match envelop.r#type {
|
||||||
TinyEncryptEnvelopType::Pgp => try_decrypt_key_pgp(envelop, pin),
|
TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp_rsa(envelop, pin),
|
||||||
TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin),
|
TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin),
|
||||||
TinyEncryptEnvelopType::Ecdh => try_decrypt_key_ecdh(config, envelop, pin, ENC_AES256_GCM_P256, slot),
|
TinyEncryptEnvelopType::Gpg => try_decrypt_key_gpg(envelop),
|
||||||
TinyEncryptEnvelopType::EcdhP384 => try_decrypt_key_ecdh(config, envelop, pin, ENC_AES256_GCM_P384, slot),
|
#[cfg(feature = "macos")]
|
||||||
|
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::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),
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
TinyEncryptEnvelopType::StaticKyber1024 => try_decrypt_key_ecdh_static_kyber1204(config, envelop),
|
||||||
unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()),
|
unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
|
fn try_decrypt_piv_key_ecdh(config: &Option<TinyEncryptConfig>,
|
||||||
envelop: &TinyEncryptEnvelop,
|
envelop: &TinyEncryptEnvelop,
|
||||||
pin: &Option<String>,
|
pin: &Option<String>,
|
||||||
expected_enc_type: &str,
|
slot: &Option<String>,
|
||||||
slot: &Option<String>) -> XResult<Vec<u8>> {
|
silent: bool) -> XResult<Vec<u8>> {
|
||||||
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
|
||||||
if wrap_key.header.enc.as_str() != expected_enc_type {
|
let (cryptor, algo_id) = match wrap_key.header.enc.as_str() {
|
||||||
return simple_error!("Unsupported header, requires: {} actual: {}", expected_enc_type, &wrap_key.header.enc);
|
ENC_AES256_GCM_P256 => (Cryptor::Aes256Gcm, AlgorithmId::EccP256),
|
||||||
}
|
ENC_AES256_GCM_P384 => (Cryptor::Aes256Gcm, AlgorithmId::EccP384),
|
||||||
let e_pub_key = &wrap_key.header.e_pub_key;
|
ENC_CHACHA20_POLY1305_P256 => (Cryptor::ChaCha20Poly1305, AlgorithmId::EccP256),
|
||||||
let e_pub_key_bytes = opt_result!(util::decode_base64_url_no_pad(e_pub_key), "Invalid envelop: {}");
|
ENC_CHACHA20_POLY1305_P384 => (Cryptor::ChaCha20Poly1305, AlgorithmId::EccP384),
|
||||||
let (_, subject_public_key_info) = opt_result!(SubjectPublicKeyInfo::from_der(&e_pub_key_bytes), "Invalid envelop: {}");
|
_ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc),
|
||||||
|
};
|
||||||
|
let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?;
|
||||||
|
let (_, subject_public_key_info) = opt_result!(
|
||||||
|
SubjectPublicKeyInfo::from_der(&e_pub_key_bytes), "Invalid envelop: {}");
|
||||||
|
|
||||||
let slot = util_piv::read_piv_slot(config, &envelop.kid, slot)?;
|
let slot = util_piv::read_piv_slot(config, &envelop.kid, slot, silent)?;
|
||||||
let pin = util::read_pin(pin);
|
let pin = util::read_pin(pin)?;
|
||||||
let epk_bytes = subject_public_key_info.subject_public_key.as_ref();
|
let epk_bytes = subject_public_key_info.subject_public_key.as_ref();
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
||||||
let slot_id = util_piv::get_slot_id(&slot)?;
|
let slot_id = util_piv::get_slot_id(&slot)?;
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
let algo_id = iff!(
|
|
||||||
expected_enc_type == ENC_AES256_GCM_P256, AlgorithmId::EccP256, AlgorithmId::EccP384
|
|
||||||
);
|
|
||||||
let shared_secret = opt_result!(decrypt_data(
|
let shared_secret = opt_result!(decrypt_data(
|
||||||
&mut yk,
|
&mut yk,
|
||||||
epk_bytes,
|
epk_bytes,
|
||||||
@@ -271,7 +516,135 @@ fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
|
|||||||
slot_id,
|
slot_id,
|
||||||
), "Decrypt via PIV card failed: {}");
|
), "Decrypt via PIV card failed: {}");
|
||||||
let key = util::simple_kdf(shared_secret.as_slice());
|
let key = util::simple_kdf(shared_secret.as_slice());
|
||||||
let decrypted_key = aes_gcm_decrypt(&key, &wrap_key.nonce, &wrap_key.encrypted_data)?;
|
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(pin);
|
||||||
|
util::zeroize(key);
|
||||||
|
util::zeroize(shared_secret);
|
||||||
|
Ok(decrypted_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_decrypt_piv_key_rsa(config: &Option<TinyEncryptConfig>,
|
||||||
|
envelop: &TinyEncryptEnvelop,
|
||||||
|
pin: &Option<String>,
|
||||||
|
slot: &Option<String>,
|
||||||
|
silent: bool) -> XResult<Vec<u8>> {
|
||||||
|
let encrypted_key_bytes = opt_result!(util::decode_base64(&envelop.encrypted_key), "Decode encrypt key failed: {}");
|
||||||
|
|
||||||
|
let slot = util_piv::read_piv_slot(config, &envelop.kid, slot, silent)?;
|
||||||
|
let pin = util::read_pin(pin)?;
|
||||||
|
|
||||||
|
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
||||||
|
let slot_id = util_piv::get_slot_id(&slot)?;
|
||||||
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
|
|
||||||
|
let key = opt_result!(decrypt_data(
|
||||||
|
&mut yk,
|
||||||
|
&encrypted_key_bytes,
|
||||||
|
AlgorithmId::Rsa2048,
|
||||||
|
slot_id,
|
||||||
|
), "Decrypt via PIV card failed: {}");
|
||||||
|
let key_bytes = key.as_slice();
|
||||||
|
if !key_bytes.starts_with(&[0x00, 0x02]) {
|
||||||
|
return simple_error!("RSA decrypted in error format: {}", hex::encode(key_bytes));
|
||||||
|
}
|
||||||
|
let after_2nd_0_bytes = key_bytes.iter()
|
||||||
|
.skip(1)
|
||||||
|
.skip_while(|b| **b != 0x00)
|
||||||
|
.skip(1)
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
information!(">>>>>>>> {:?}", &after_2nd_0_bytes);
|
||||||
|
util::zeroize(pin);
|
||||||
|
util::zeroize(key);
|
||||||
|
Ok(after_2nd_0_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_decrypt_se_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_MLKEM768 | ENC_AES256_GCM_MLKEM1024 => Cryptor::Aes256Gcm,
|
||||||
|
ENC_CHACHA20_POLY1305_P256 | 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.is_empty() {
|
||||||
|
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 key = opt_value_result!(keychain_key.get_password()?, "Key: {} not found", &keychain_key.to_str());
|
||||||
|
opt_result!(String::from_utf8(key), "Parse key failed: {}")
|
||||||
|
} else {
|
||||||
|
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(
|
||||||
|
envelop.r#type,
|
||||||
|
&private_key_base64,
|
||||||
|
&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_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(key);
|
||||||
util::zeroize(shared_secret);
|
util::zeroize(shared_secret);
|
||||||
Ok(decrypted_key)
|
Ok(decrypted_key)
|
||||||
@@ -279,41 +652,118 @@ fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
|
|||||||
|
|
||||||
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)?;
|
||||||
if wrap_key.header.enc.as_str() != ENC_AES256_GCM_X25519 {
|
let cryptor = match wrap_key.header.enc.as_str() {
|
||||||
return simple_error!("Unsupported header, requires: {} actual: {}", ENC_AES256_GCM_X25519, &wrap_key.header.enc);
|
ENC_AES256_GCM_X25519 => Cryptor::Aes256Gcm,
|
||||||
}
|
ENC_CHACHA20_POLY1305_X25519 => Cryptor::ChaCha20Poly1305,
|
||||||
let e_pub_key = &wrap_key.header.e_pub_key;
|
_ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc),
|
||||||
let epk_bytes = opt_result!(util::decode_base64_url_no_pad(e_pub_key), "Invalid envelop: {}");
|
};
|
||||||
|
let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?;
|
||||||
|
|
||||||
let mut pgp = util_pgp::get_openpgp()?;
|
let mut pgp = util_pgp::get_openpgp()?;
|
||||||
let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}");
|
let mut trans = opt_result!(pgp.transaction(), "Connect PIV card failed: {}");
|
||||||
|
|
||||||
util_pgp::read_and_verify_openpgp_pin(&mut trans, pin)?;
|
util_pgp::read_and_verify_openpgp_pin(&mut trans, pin)?;
|
||||||
|
|
||||||
let shared_secret = trans.decipher(Cryptogram::ECDH(&epk_bytes))?;
|
let shared_secret = trans.decipher(Cryptogram::ECDH(&e_pub_key_bytes))?;
|
||||||
|
|
||||||
let key = util::simple_kdf(shared_secret.as_slice());
|
let key = util::simple_kdf(shared_secret.as_slice());
|
||||||
let decrypted_key = aes_gcm_decrypt(&key, &wrap_key.nonce, &wrap_key.encrypted_data)?;
|
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(key);
|
||||||
util::zeroize(shared_secret);
|
util::zeroize(shared_secret);
|
||||||
Ok(decrypted_key)
|
Ok(decrypted_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_decrypt_key_pgp(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
fn try_decrypt_key_gpg(envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
||||||
|
util_gpg::gpg_decrypt(&envelop.encrypted_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
fn try_decrypt_key_ecdh_static_x25519(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_X25519 => Cryptor::Aes256Gcm,
|
||||||
|
ENC_CHACHA20_POLY1305_X25519 => 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() != 1 && config_envelop_args.len() != 3 {
|
||||||
|
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain_key = if config_envelop_args.len() == 1 {
|
||||||
|
KeychainKey::parse(&config_envelop_args[0])?
|
||||||
|
} else {
|
||||||
|
KeychainKey::from(&config_envelop_args[0], &config_envelop_args[1], &config_envelop_args[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
let shared_secret = opt_result!(
|
||||||
|
util_keychainstatic::decrypt_x25519_data(&keychain_key, &e_pub_key_bytes), "Decrypt static x25519 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
fn try_decrypt_key_ecdh_static_kyber1204(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_KYBER1204 => Cryptor::Aes256Gcm,
|
||||||
|
ENC_CHACHA20_POLY1305_KYBER1204 => 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() != 1 && config_envelop_args.len() != 3 {
|
||||||
|
return simple_error!("Not enough arguments for: {}", &envelop.kid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain_key = if config_envelop_args.len() == 1 {
|
||||||
|
KeychainKey::parse(&config_envelop_args[0])?
|
||||||
|
} else {
|
||||||
|
KeychainKey::from(&config_envelop_args[0], &config_envelop_args[1], &config_envelop_args[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
let shared_secret = opt_result!(
|
||||||
|
util_keychainstatic::decrypt_kyber1204_data(&keychain_key, &e_pub_key_bytes), "Decrypt static kyber1204 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_pgp_rsa(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
||||||
let mut pgp = util_pgp::get_openpgp()?;
|
let mut pgp = util_pgp::get_openpgp()?;
|
||||||
let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}");
|
let mut trans = opt_result!(pgp.transaction(), "Connect OpenPGP card failed: {}");
|
||||||
|
|
||||||
util_pgp::read_and_verify_openpgp_pin(&mut trans, pin)?;
|
util_pgp::read_and_verify_openpgp_pin(&mut trans, pin)?;
|
||||||
|
|
||||||
let pgp_envelop = &envelop.encrypted_key;
|
let pgp_envelop = &envelop.encrypted_key;
|
||||||
debugging!("PGP envelop: {}", &pgp_envelop);
|
debugging!("PGP envelop: {}", pgp_envelop);
|
||||||
let pgp_envelop_bytes = opt_result!(util::decode_base64(pgp_envelop), "Decode PGP envelop failed: {}");
|
let pgp_envelop_bytes = opt_result!(util::decode_base64(pgp_envelop), "Decode PGP envelop failed: {}");
|
||||||
|
|
||||||
let key = trans.decipher(Cryptogram::RSA(&pgp_envelop_bytes))?;
|
let key = trans.decipher(Cryptogram::RSA(&pgp_envelop_bytes))?;
|
||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> {
|
pub fn select_envelop<'a>(meta: &'a TinyEncryptMeta, key_id: &Option<String>, config: &Option<TinyEncryptConfig>, silent: bool) -> XResult<&'a TinyEncryptEnvelop> {
|
||||||
let envelops = match &meta.envelops {
|
let envelops = match &meta.envelops {
|
||||||
None => return simple_error!("No envelops found"),
|
None => return simple_error!("No envelops found"),
|
||||||
Some(envelops) => if envelops.is_empty() {
|
Some(envelops) => if envelops.is_empty() {
|
||||||
@@ -323,28 +773,88 @@ fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if silent {
|
||||||
|
debugging!("Found {} envelops:", envelops.len());
|
||||||
|
} else {
|
||||||
success!("Found {} envelops:", envelops.len());
|
success!("Found {} envelops:", envelops.len());
|
||||||
|
}
|
||||||
|
if let Some(envelop) = match_envelop_by_key_id(envelops, key_id, config, silent) {
|
||||||
|
return Ok(envelop);
|
||||||
|
}
|
||||||
|
|
||||||
if envelops.len() == 1 {
|
if envelops.len() == 1 {
|
||||||
let selected_envelop = &envelops[0];
|
let selected_envelop = &envelops[0];
|
||||||
success!("Auto selected envelop: #{} {}", 1, selected_envelop.r#type.get_upper_name());
|
if silent {
|
||||||
|
debugging!("Auto selected envelop: #{} {}", 1, util_envelop::format_envelop(selected_envelop, config));
|
||||||
|
} else {
|
||||||
|
success!("Auto selected envelop: #{} {}", 1, util_envelop::format_envelop(selected_envelop, config));
|
||||||
|
}
|
||||||
|
if !selected_envelop.r#type.auto_select() {
|
||||||
util::read_line("Press enter to continue: ");
|
util::read_line("Press enter to continue: ");
|
||||||
|
}
|
||||||
return Ok(selected_envelop);
|
return Ok(selected_envelop);
|
||||||
}
|
}
|
||||||
|
|
||||||
envelops.iter().enumerate().for_each(|(i, envelop)| {
|
// auto select
|
||||||
let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid));
|
if let Some(auto_select_key_ids) = util_env::get_auto_select_key_ids() {
|
||||||
let desc = envelop.desc.as_ref()
|
for auto_select_key_id in auto_select_key_ids {
|
||||||
.map(|desc| format!(", Desc: {}", desc))
|
if let Some(envelop) = match_envelop_by_key_id(envelops, &Some(auto_select_key_id), config, silent) {
|
||||||
.unwrap_or_else(|| "".to_string());
|
return Ok(envelop);
|
||||||
println!("#{} {}{}{}", i + 1,
|
}
|
||||||
envelop.r#type.get_upper_name(),
|
}
|
||||||
kid,
|
}
|
||||||
desc,
|
|
||||||
);
|
let use_dialoguer = util_env::get_use_dialoguer();
|
||||||
});
|
let envelop_number = if use_dialoguer {
|
||||||
|
let format_envelops = envelops.iter().map(|envelop| {
|
||||||
|
format!("#{}", util_envelop::format_envelop(envelop, config))
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
util::register_ctrlc();
|
||||||
|
let select_result = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Please select envelop: ")
|
||||||
|
.items(&format_envelops[..])
|
||||||
|
.default(0)
|
||||||
|
.report(!silent)
|
||||||
|
.clear(true)
|
||||||
|
.interact();
|
||||||
|
if select_result.is_err() {
|
||||||
|
let _ = Term::stderr().show_cursor();
|
||||||
|
}
|
||||||
|
opt_result!(select_result, "Select envelop error: {}") + 1
|
||||||
|
} else {
|
||||||
|
envelops.iter().enumerate().for_each(|(i, envelop)| {
|
||||||
|
println_ex!("#{} {}", i + 1, util_envelop::format_envelop(envelop, config));
|
||||||
|
});
|
||||||
|
util::read_number("Please select an envelop:", 1, envelops.len())
|
||||||
|
};
|
||||||
|
|
||||||
let envelop_number = util::read_number("Please select an envelop:", 1, envelops.len());
|
|
||||||
let selected_envelop = &envelops[envelop_number - 1];
|
let selected_envelop = &envelops[envelop_number - 1];
|
||||||
|
if silent {
|
||||||
|
debugging!("Selected envelop: #{} {}", envelop_number, selected_envelop.r#type.get_upper_name());
|
||||||
|
} else {
|
||||||
success!("Selected envelop: #{} {}", envelop_number, selected_envelop.r#type.get_upper_name());
|
success!("Selected envelop: #{} {}", envelop_number, selected_envelop.r#type.get_upper_name());
|
||||||
|
}
|
||||||
Ok(selected_envelop)
|
Ok(selected_envelop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_envelop_by_key_id<'a>(envelops: &'a Vec<TinyEncryptEnvelop>, key_id: &Option<String>, config: &Option<TinyEncryptConfig>, silent: bool) -> Option<&'a TinyEncryptEnvelop> {
|
||||||
|
if let Some(key_id) = key_id {
|
||||||
|
for envelop in envelops {
|
||||||
|
let is_sid_matched = config.as_ref().and_then(|config| {
|
||||||
|
config.find_by_kid(&envelop.kid).and_then(|config_envelop| {
|
||||||
|
config_envelop.sid.as_ref().map(|sid| sid == key_id)
|
||||||
|
})
|
||||||
|
}).unwrap_or(false);
|
||||||
|
|
||||||
|
if is_sid_matched || (&envelop.kid == key_id) {
|
||||||
|
if silent {
|
||||||
|
debugging!("Matched envelop: {}", util_envelop::format_envelop(envelop, config));
|
||||||
|
} else {
|
||||||
|
information!("Matched envelop: {}", util_envelop::format_envelop(envelop, config));
|
||||||
|
}
|
||||||
|
return Some(envelop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|||||||
125
src/cmd_directdecrypt.rs
Normal file
125
src/cmd_directdecrypt.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use clap::Args;
|
||||||
|
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{util, util_digest};
|
||||||
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
|
use crate::util_progress::Progress;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CmdDirectDecrypt {
|
||||||
|
/// File input
|
||||||
|
#[arg(long, short = 'i')]
|
||||||
|
pub file_in: PathBuf,
|
||||||
|
|
||||||
|
/// File output
|
||||||
|
#[arg(long, short = 'o')]
|
||||||
|
pub file_out: PathBuf,
|
||||||
|
|
||||||
|
/// Remove source file
|
||||||
|
#[arg(long, short = 'R')]
|
||||||
|
pub remove_file: bool,
|
||||||
|
|
||||||
|
/// Key in HEX (32 bytes)
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CmdDirectDecrypt {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.key.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DIRECT_ENCRYPT_MAGIC: &str = "e2c50001";
|
||||||
|
|
||||||
|
// Direct decrypt file format:
|
||||||
|
// [4 bytes] - magic 0xe2 0xc5 0x00 0x01
|
||||||
|
// [32 bytes] - key digest
|
||||||
|
// [12 bytes] - nonce
|
||||||
|
// [n bytes] - ciphertext
|
||||||
|
// [16 bytes] - tag
|
||||||
|
pub fn direct_decrypt(cmd_direct_decrypt: CmdDirectDecrypt) -> XResult<()> {
|
||||||
|
let key = opt_result!(hex::decode(&cmd_direct_decrypt.key), "Parse encryption key failed: {}");
|
||||||
|
if key.len() != 32 {
|
||||||
|
return simple_error!("Key length error, must be AES256.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_in = opt_result!(File::open(&cmd_direct_decrypt.file_in), "Open in file failed: {}");
|
||||||
|
let file_in_len = file_in.metadata().map(|m| m.len()).unwrap_or(0);
|
||||||
|
if fs::metadata(&cmd_direct_decrypt.file_out).is_ok() {
|
||||||
|
return simple_error!("Out file exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut magic = [0_u8; 4];
|
||||||
|
opt_result!(file_in.read_exact(&mut magic), "Read magic failed: {}");
|
||||||
|
if hex::encode(magic) != DIRECT_ENCRYPT_MAGIC {
|
||||||
|
return simple_error!("In file magic mismatch.");
|
||||||
|
}
|
||||||
|
let mut key_digest = [0_u8; 32];
|
||||||
|
opt_result!(file_in.read_exact(&mut key_digest), "Read encryption key digest failed: {}");
|
||||||
|
if hex::encode(util_digest::sha256_digest(&key)) != hex::encode(key_digest) {
|
||||||
|
debugging!("Encryption key digest mismatch: {} vs {}",
|
||||||
|
hex::encode(util_digest::sha256_digest(&key)), hex::encode(key_digest));
|
||||||
|
return simple_error!("Encryption key digest mismatch.");
|
||||||
|
}
|
||||||
|
let mut nonce = [0_u8; 12];
|
||||||
|
opt_result!(file_in.read_exact(&mut nonce), "Read nonce failed: {}");
|
||||||
|
|
||||||
|
let mut file_out = opt_result!(File::create(&cmd_direct_decrypt.file_out), "Create out file failed: {}");
|
||||||
|
let key_nonce = KeyNonce { k: &key, n: &nonce };
|
||||||
|
let instant = Instant::now();
|
||||||
|
let decrypted_len = opt_result!(
|
||||||
|
decrypt_file(&mut file_in, file_in_len, &mut file_out, Cryptor::Aes256Gcm, &key_nonce),
|
||||||
|
"Decrypt file {} -> {}, failed: {}",
|
||||||
|
cmd_direct_decrypt.file_in.display(),
|
||||||
|
cmd_direct_decrypt.file_out.display()
|
||||||
|
);
|
||||||
|
let elapsed_millis = instant.elapsed().as_millis();
|
||||||
|
success!("Decrypt file succeed: {}, file size: {} byte(s), elapsed: {} ms",
|
||||||
|
cmd_direct_decrypt.file_out.display(), decrypted_len, elapsed_millis);
|
||||||
|
|
||||||
|
util::zeroize(key);
|
||||||
|
nonce.zeroize();
|
||||||
|
drop(file_in);
|
||||||
|
drop(file_out);
|
||||||
|
|
||||||
|
if cmd_direct_decrypt.remove_file {
|
||||||
|
information!("Remove in file: {}", cmd_direct_decrypt.file_in.display());
|
||||||
|
if let Err(e) = fs::remove_file(&cmd_direct_decrypt.file_in) {
|
||||||
|
warning!("Remove in file failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt_file(file_in: &mut impl Read, file_len: u64, file_out: &mut impl Write,
|
||||||
|
cryptor: Cryptor, key_nonce: &KeyNonce) -> XResult<u64> {
|
||||||
|
let mut total_len = 0_u64;
|
||||||
|
let mut buffer = [0u8; 1024 * 8];
|
||||||
|
let progress = Progress::new(file_len);
|
||||||
|
let mut decryptor = cryptor.decryptor(key_nonce)?;
|
||||||
|
loop {
|
||||||
|
let len = opt_result!(file_in.read(&mut buffer), "Read in file failed: {}");
|
||||||
|
if len == 0 {
|
||||||
|
let last_block = opt_result!(decryptor.finalize(), "Decrypt in file failed: {}");
|
||||||
|
opt_result!(file_out.write_all(&last_block), "Write out file failed: {}");
|
||||||
|
progress.finish();
|
||||||
|
debugging!("Decrypt finished, total: {} byte(s)", total_len);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
total_len += len as u64;
|
||||||
|
let decrypted = decryptor.update(&buffer[0..len]);
|
||||||
|
opt_result!(file_out.write_all(&decrypted), "Write out file failed: {}");
|
||||||
|
progress.position(total_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(total_len)
|
||||||
|
}
|
||||||
@@ -7,55 +7,99 @@ use std::time::Instant;
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use rsa::Pkcs1v15Encrypt;
|
use rsa::Pkcs1v15Encrypt;
|
||||||
use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_msg, XResult};
|
|
||||||
use rust_util::util_time::UnixEpochTime;
|
use rust_util::util_time::UnixEpochTime;
|
||||||
use zeroize::Zeroize;
|
use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_size, warning, XResult};
|
||||||
|
|
||||||
use crate::{file, util, util_ecdh, util_p384, util_x25519};
|
|
||||||
use crate::compress::GzStreamEncoder;
|
use crate::compress::GzStreamEncoder;
|
||||||
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
||||||
use crate::consts::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
|
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_aes::{aes_gcm_encrypt, aes_gcm_encrypt_with_salt};
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
use crate::crypto_rsa::parse_spki;
|
use crate::spec::{
|
||||||
use crate::spec::{EncEncryptedMeta, EncMetadata, TINY_ENCRYPT_VERSION_10, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
EncEncryptedMeta, EncMetadata,
|
||||||
|
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
|
||||||
|
};
|
||||||
|
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_progress::Progress;
|
||||||
|
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::temporary_key::parse_temporary_keys;
|
||||||
|
use crate::util_mlkem::MlKemAlgo;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdEncrypt {
|
pub struct CmdEncrypt {
|
||||||
/// Files need to be decrypted
|
/// Plaintext comment
|
||||||
pub paths: Vec<PathBuf>,
|
|
||||||
/// Comment
|
|
||||||
#[arg(long, short = 'c')]
|
#[arg(long, short = 'c')]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
|
|
||||||
/// Encrypted comment
|
/// Encrypted comment
|
||||||
#[arg(long, short = 'C')]
|
#[arg(long, short = 'C')]
|
||||||
pub encrypted_comment: Option<String>,
|
pub encrypted_comment: Option<String>,
|
||||||
/// Encryption profile
|
|
||||||
|
/// 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>,
|
||||||
|
|
||||||
|
/// Encryption key filter (key_id or type:TYPE(e.g. ecdh, pgp, ecdh-p384, pgp-ed25519), multiple joined by ',', ALL for all)
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
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,
|
||||||
|
|
||||||
/// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best])
|
/// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best])
|
||||||
#[arg(long, short = 'L')]
|
#[arg(long, short = 'L')]
|
||||||
pub compress_level: Option<u32>,
|
pub compress_level: Option<u32>,
|
||||||
/// Compatible with 1.0
|
|
||||||
#[arg(long, short = '1')]
|
|
||||||
pub compatible_with_1_0: bool,
|
|
||||||
/// Remove source file
|
/// Remove source file
|
||||||
#[arg(long, short = 'R')]
|
#[arg(long, short = 'R')]
|
||||||
pub remove_file: bool,
|
pub remove_file: bool,
|
||||||
|
|
||||||
|
/// Create file (create a empty encrypted file)
|
||||||
|
#[arg(long, short = 'a')]
|
||||||
|
pub create: bool,
|
||||||
|
|
||||||
/// Disable compress meta
|
/// Disable compress meta
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub disable_compress_meta: bool,
|
pub disable_compress_meta: bool,
|
||||||
|
|
||||||
|
/// Output file in PEM format (alias --pem)
|
||||||
|
#[arg(long, alias = "pem")]
|
||||||
|
pub pem_output: bool,
|
||||||
|
|
||||||
|
/// Encryption algorithm (AES/GCM, CHACHA20/POLY1305 or AES, CHACHA20, default AES/GCM)
|
||||||
|
#[arg(long, short = 'A')]
|
||||||
|
pub encryption_algorithm: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Files need to be decrypted
|
||||||
|
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)?;
|
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();
|
||||||
@@ -68,8 +112,9 @@ pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
|||||||
let mut failed_count = 0;
|
let mut failed_count = 0;
|
||||||
let mut total_len = 0_u64;
|
let mut total_len = 0_u64;
|
||||||
for path in &cmd_encrypt.paths {
|
for path in &cmd_encrypt.paths {
|
||||||
|
let path = config.resolve_path_namespace(path, false);
|
||||||
let start_encrypt_single = Instant::now();
|
let start_encrypt_single = Instant::now();
|
||||||
match encrypt_single(path, &envelops, &cmd_encrypt) {
|
match encrypt_single(&path, &envelops, &cmd_encrypt) {
|
||||||
Ok(len) => {
|
Ok(len) => {
|
||||||
total_len += len;
|
total_len += len;
|
||||||
if len > 0 { succeed_count += 1; } else { skipped_count += 1; }
|
if len > 0 { succeed_count += 1; } else { skipped_count += 1; }
|
||||||
@@ -99,27 +144,55 @@ pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop], cmd_encrypt: &CmdEncrypt) -> XResult<u64> {
|
pub fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop], cmd_encrypt: &CmdEncrypt) -> XResult<u64> {
|
||||||
let path_display = format!("{}", path.display());
|
let path_display = format!("{}", path.display());
|
||||||
if path_display.ends_with(TINY_ENC_FILE_EXT) {
|
let path_out = if cmd_encrypt.pem_output {
|
||||||
|
format!("{}{}", path_display, TINY_ENC_PEM_FILE_EXT)
|
||||||
|
} else {
|
||||||
|
format!("{}{}", path_display, TINY_ENC_FILE_EXT)
|
||||||
|
};
|
||||||
|
let encrypt_single_result = encrypt_single_file_out(path, &path_out, envelops, cmd_encrypt);
|
||||||
|
if cmd_encrypt.create {
|
||||||
|
if let Ok(content) = fs::read_to_string(path) {
|
||||||
|
if content == "\n" {
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encrypt_single_result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_single_file_out(path: &PathBuf, path_out: &str, envelops: &[&TinyEncryptConfigEnvelop], cmd_encrypt: &CmdEncrypt) -> XResult<u64> {
|
||||||
|
let path_display = format!("{}", path.display());
|
||||||
|
if is_tiny_enc_file(&path_display) {
|
||||||
information!("Tiny enc file skipped: {}", path_display);
|
information!("Tiny enc file skipped: {}", path_display);
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cryptor = crypto_cryptor::get_cryptor_by_encryption_algorithm(&cmd_encrypt.encryption_algorithm)?;
|
||||||
|
information!("Using encryption algorithm: {}", cryptor.get_name());
|
||||||
|
|
||||||
|
if cmd_encrypt.create {
|
||||||
|
util::require_file_not_exists(path)?;
|
||||||
|
opt_result!(fs::write(path, "\n"), "Write empty file failed: {}");
|
||||||
|
} else {
|
||||||
util::require_file_exists(path)?;
|
util::require_file_exists(path)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
||||||
|
|
||||||
let path_out = format!("{}{}", path_display, TINY_ENC_FILE_EXT);
|
util::require_file_not_exists(path_out)?;
|
||||||
util::require_file_not_exists(path_out.as_str())?;
|
|
||||||
|
|
||||||
let (key, nonce) = util::make_key256_and_nonce();
|
let (key, nonce) = util::make_key256_and_nonce();
|
||||||
let envelops = encrypt_envelops(&key, envelops)?;
|
let key_nonce = KeyNonce { k: key.as_ref(), n: nonce.as_ref() };
|
||||||
|
// Encrypt session key to envelops
|
||||||
|
let envelops = encrypt_envelops(cryptor, key.as_ref(), envelops)?;
|
||||||
|
|
||||||
let encrypted_comment = match &cmd_encrypt.encrypted_comment {
|
let encrypted_comment = match &cmd_encrypt.encrypted_comment {
|
||||||
None => None,
|
None => None,
|
||||||
Some(encrypted_comment) => Some(util::encode_base64(
|
Some(encrypted_comment) => Some(util::encode_base64(
|
||||||
&aes_gcm_encrypt_with_salt(&key, &nonce, SALT_COMMENT, encrypted_comment.as_bytes())?))
|
&crypto_simple::encrypt_with_salt(
|
||||||
|
cryptor, &key_nonce, SALT_COMMENT, encrypted_comment.as_bytes())?))
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_metadata = opt_result!(fs::metadata(path), "Read file: {} meta failed: {}", path.display());
|
let file_metadata = opt_result!(fs::metadata(path), "Read file: {} meta failed: {}", path.display());
|
||||||
@@ -128,132 +201,144 @@ fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop], cmd_en
|
|||||||
c_time: file_metadata.created().ok().and_then(|t| t.to_millis()),
|
c_time: file_metadata.created().ok().and_then(|t| t.to_millis()),
|
||||||
m_time: file_metadata.modified().ok().and_then(|t| t.to_millis()),
|
m_time: file_metadata.modified().ok().and_then(|t| t.to_millis()),
|
||||||
};
|
};
|
||||||
let enc_encrypted_meta_bytes = opt_result!(enc_encrypted_meta.seal(&key, &nonce), "Seal enc-encrypted-meta failed: {}");
|
let enc_encrypted_meta_bytes = opt_result!(enc_encrypted_meta.seal(
|
||||||
|
cryptor, &key_nonce), "Seal enc-encrypted-meta failed: {}");
|
||||||
|
|
||||||
|
let compress_level = get_compress_level(cmd_encrypt, &path_display, cmd_encrypt.pem_output);
|
||||||
|
debugging!("Compress level: {:?}", compress_level);
|
||||||
|
|
||||||
let enc_metadata = EncMetadata {
|
let enc_metadata = EncMetadata {
|
||||||
comment: cmd_encrypt.comment.clone(),
|
comment: cmd_encrypt.comment.clone(),
|
||||||
encrypted_comment,
|
encrypted_comment,
|
||||||
encrypted_meta: Some(util::encode_base64(&enc_encrypted_meta_bytes)),
|
encrypted_meta: Some(util::encode_base64(&enc_encrypted_meta_bytes)),
|
||||||
compress: cmd_encrypt.compress,
|
compress: compress_level.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut encrypt_meta = TinyEncryptMeta::new(&file_metadata, &enc_metadata, &nonce, envelops);
|
let encrypt_meta = TinyEncryptMeta::new(
|
||||||
|
&file_metadata, &enc_metadata, cryptor, nonce.as_ref(), envelops);
|
||||||
debugging!("Encrypted meta: {:?}", encrypt_meta);
|
debugging!("Encrypted meta: {:?}", encrypt_meta);
|
||||||
|
|
||||||
if cmd_encrypt.compatible_with_1_0 {
|
let start = Instant::now();
|
||||||
encrypt_meta = process_compatible_with_1_0(cmd_encrypt, encrypt_meta)?;
|
|
||||||
|
let mut file_out = File::create(path_out)?;
|
||||||
|
let compress_meta = !cmd_encrypt.disable_compress_meta;
|
||||||
|
|
||||||
|
if cmd_encrypt.pem_output {
|
||||||
|
let temp_output_len = file_in.metadata().map(|m| m.len()).unwrap_or(0) + 1024 * 8;
|
||||||
|
if temp_output_len > 8 * 1024 * 1028 {
|
||||||
|
warning!("Input file is more than 8 MiB.");
|
||||||
|
}
|
||||||
|
if temp_output_len > 32 * 1024 * 1028 {
|
||||||
|
return simple_error!("Input file is too large, file is {} bytes", temp_output_len);
|
||||||
|
}
|
||||||
|
let mut temp_output = Vec::with_capacity(temp_output_len as usize);
|
||||||
|
let _ = util_enc_file::write_tiny_encrypt_meta(&mut temp_output, &encrypt_meta, compress_meta)?;
|
||||||
|
encrypt_file(&mut file_in, file_metadata.len(), &mut temp_output,
|
||||||
|
cryptor, &key_nonce, &compress_level,
|
||||||
|
)?;
|
||||||
|
let temp_output_pem = to_pem(&temp_output, TINY_ENC_PEM_NAME);
|
||||||
|
file_out.write_all(temp_output_pem.as_bytes())?;
|
||||||
|
} else {
|
||||||
|
let _ = util_enc_file::write_tiny_encrypt_meta(&mut file_out, &encrypt_meta, compress_meta)?;
|
||||||
|
encrypt_file(&mut file_in, file_metadata.len(), &mut file_out,
|
||||||
|
cryptor, &key_nonce, &compress_level,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file_out = File::create(&path_out)?;
|
|
||||||
let compress_meta = !cmd_encrypt.disable_compress_meta;
|
|
||||||
let _ = file::write_tiny_encrypt_meta(&mut file_out, &encrypt_meta, compress_meta)?;
|
|
||||||
|
|
||||||
let compress_desc = iff!(cmd_encrypt.compress, " [with compress]", "");
|
|
||||||
let start = Instant::now();
|
|
||||||
util_msg::print_lastline(
|
|
||||||
&format!("Encrypting file: {}{} ...", path_display, compress_desc)
|
|
||||||
);
|
|
||||||
encrypt_file(&mut file_in, &mut file_out, &key, &nonce, cmd_encrypt.compress, &cmd_encrypt.compress_level)?;
|
|
||||||
util_msg::clear_lastline();
|
|
||||||
let encrypt_duration = start.elapsed();
|
|
||||||
debugging!("Inner encrypt file{}: {} elapsed: {} ms", compress_desc, path_display, encrypt_duration.as_millis());
|
|
||||||
|
|
||||||
util::zeroize(key);
|
|
||||||
util::zeroize(nonce);
|
|
||||||
drop(file_in);
|
|
||||||
drop(file_out);
|
drop(file_out);
|
||||||
|
let encrypt_duration = start.elapsed();
|
||||||
|
let compress_desc = iff!(compress_level.is_some(), " [with compress]", "");
|
||||||
|
debugging!("Inner encrypt file{}: {} elapsed: {} ms", compress_desc, path_display, encrypt_duration.as_millis());
|
||||||
if cmd_encrypt.remove_file { util::remove_file_with_msg(path); }
|
if cmd_encrypt.remove_file { util::remove_file_with_msg(path); }
|
||||||
Ok(file_metadata.len())
|
Ok(file_metadata.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_compatible_with_1_0(cmd_encrypt: &CmdEncrypt, mut encrypt_meta: TinyEncryptMeta) -> XResult<TinyEncryptMeta> {
|
pub(crate) fn encrypt_file(file_in: &mut impl Read, file_len: u64, file_out: &mut impl Write, cryptor: Cryptor,
|
||||||
if !cmd_encrypt.disable_compress_meta {
|
key_nonce: &KeyNonce, compress_level: &Option<u32>) -> XResult<u64> {
|
||||||
return simple_error!("Compatible with 1.0 mode must turns --disable-compress-meta on.");
|
let compress = compress_level.is_some();
|
||||||
}
|
let mut total_len = 0_u64;
|
||||||
if let Some(envelops) = encrypt_meta.envelops {
|
let mut write_len = 0_u64;
|
||||||
let mut filter_envelops = vec![];
|
|
||||||
for envelop in envelops {
|
|
||||||
if (envelop.r#type == TinyEncryptEnvelopType::Pgp) && encrypt_meta.pgp_envelop.is_none() {
|
|
||||||
encrypt_meta.pgp_fingerprint = Some(format!("KID:{}", envelop.kid));
|
|
||||||
encrypt_meta.pgp_envelop = Some(envelop.encrypted_key.clone());
|
|
||||||
} else if (envelop.r#type == TinyEncryptEnvelopType::Ecdh) && encrypt_meta.ecdh_envelop.is_none() {
|
|
||||||
encrypt_meta.ecdh_point = Some(format!("KID:{}", envelop.kid));
|
|
||||||
encrypt_meta.ecdh_envelop = Some(envelop.encrypted_key.clone());
|
|
||||||
} else {
|
|
||||||
filter_envelops.push(envelop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encrypt_meta.envelops = if filter_envelops.is_empty() { None } else { Some(filter_envelops) };
|
|
||||||
if encrypt_meta.envelops.is_none() {
|
|
||||||
encrypt_meta.version = TINY_ENCRYPT_VERSION_10.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(encrypt_meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8], compress: bool, compress_level: &Option<u32>) -> XResult<usize> {
|
|
||||||
let mut total_len = 0;
|
|
||||||
let mut buffer = [0u8; 1024 * 8];
|
let mut buffer = [0u8; 1024 * 8];
|
||||||
let key = opt_result!(key.try_into(), "Key is not 32 bytes: {}");
|
|
||||||
let mut gz_encoder = match compress_level {
|
let mut gz_encoder = match compress_level {
|
||||||
None => GzStreamEncoder::new_default(),
|
None => GzStreamEncoder::new_default(),
|
||||||
Some(compress_level) => {
|
Some(compress_level) => {
|
||||||
if *compress_level > 9 {
|
if *compress_level > 9 {
|
||||||
return simple_error!("Compress level must in range [0, 9]");
|
return simple_error!("Compress level must be in range [0, 9]");
|
||||||
}
|
}
|
||||||
GzStreamEncoder::new(Compression::new(*compress_level))
|
GzStreamEncoder::new(Compression::new(*compress_level))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut encryptor = aes_gcm_stream::Aes256GcmStreamEncryptor::new(key, nonce);
|
let progress = Progress::new(file_len);
|
||||||
|
let mut encryptor = cryptor.encryptor(key_nonce)?;
|
||||||
loop {
|
loop {
|
||||||
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
let last_block = if compress {
|
let last_block_and_tag = if compress {
|
||||||
let last_compressed_buffer = opt_result!(gz_encoder.finalize(), "Decompress file failed: {}");
|
let last_compressed_buffer = opt_result!(gz_encoder.finalize(), "Decompress file failed: {}");
|
||||||
let mut encrypted_block = encryptor.update(&last_compressed_buffer);
|
let mut encrypted_block = encryptor.update(&last_compressed_buffer);
|
||||||
let (last_block, tag) = encryptor.finalize();
|
let (last_block, tag) = encryptor.finalize();
|
||||||
|
write_len += encrypted_block.len() as u64;
|
||||||
|
write_len += last_block.len() as u64;
|
||||||
encrypted_block.extend_from_slice(&last_block);
|
encrypted_block.extend_from_slice(&last_block);
|
||||||
encrypted_block.extend_from_slice(&tag);
|
encrypted_block.extend_from_slice(&tag);
|
||||||
encrypted_block
|
encrypted_block
|
||||||
} else {
|
} else {
|
||||||
let (mut last_block, tag) = encryptor.finalize();
|
let (mut last_block, tag) = encryptor.finalize();
|
||||||
|
write_len += last_block.len() as u64;
|
||||||
last_block.extend_from_slice(&tag);
|
last_block.extend_from_slice(&tag);
|
||||||
last_block
|
last_block
|
||||||
};
|
};
|
||||||
opt_result!(file_out.write_all(&last_block), "Write file failed: {}");
|
opt_result!(file_out.write_all(&last_block_and_tag), "Write file failed: {}");
|
||||||
debugging!("Encrypt finished, total bytes: {}", total_len);
|
progress.finish();
|
||||||
|
debugging!("Encrypt finished, total bytes: {} byte(s)", total_len);
|
||||||
|
if compress {
|
||||||
|
information!("File is compressed: {} -> {}, ratio: {}%",
|
||||||
|
util_size::get_display_size(total_len as i64),
|
||||||
|
util_size::get_display_size(write_len as i64),
|
||||||
|
util::ratio(write_len, total_len));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
total_len += len;
|
total_len += len as u64;
|
||||||
let encrypted = if compress {
|
let encrypted = if compress {
|
||||||
let compressed = opt_result!(gz_encoder.update(&buffer[0..len]), "Decompress file failed: {}");
|
let compressed = opt_result!(gz_encoder.update(&buffer[0..len]), "Decompress file failed: {}");
|
||||||
encryptor.update(&compressed)
|
encryptor.update(&compressed)
|
||||||
} else {
|
} else {
|
||||||
encryptor.update(&buffer[0..len])
|
encryptor.update(&buffer[0..len])
|
||||||
};
|
};
|
||||||
|
write_len += encrypted.len() as u64;
|
||||||
opt_result!(file_out.write_all(&encrypted), "Write file failed: {}");
|
opt_result!(file_out.write_all(&encrypted), "Write file failed: {}");
|
||||||
|
progress.position(total_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut key = key;
|
|
||||||
key.zeroize();
|
|
||||||
Ok(total_len)
|
Ok(total_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelops(key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<Vec<TinyEncryptEnvelop>> {
|
pub fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<Vec<TinyEncryptEnvelop>> {
|
||||||
let mut encrypted_envelops = vec![];
|
let mut encrypted_envelops = vec![];
|
||||||
for envelop in envelops {
|
for envelop in envelops {
|
||||||
match envelop.r#type {
|
match envelop.r#type {
|
||||||
TinyEncryptEnvelopType::Pgp => {
|
TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PivRsa => {
|
||||||
encrypted_envelops.push(encrypt_envelop_pgp(key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_rsa(key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::PgpX25519 => {
|
TinyEncryptEnvelopType::Gpg => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_gpg(key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::Ecdh => {
|
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh(key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::EcdhP384 => {
|
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 | TinyEncryptEnvelopType::ExtP256 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_p384(key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?);
|
||||||
|
}
|
||||||
|
TinyEncryptEnvelopType::PivP384 | TinyEncryptEnvelopType::ExtP384 => {
|
||||||
|
encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?);
|
||||||
|
}
|
||||||
|
TinyEncryptEnvelopType::StaticKyber1024 => {
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
@@ -261,44 +346,75 @@ fn encrypt_envelops(key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResu
|
|||||||
Ok(encrypted_envelops)
|
Ok(encrypted_envelops)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_ecdh(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
fn encrypt_envelop_ecdh_p256(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
let public_key_point_hex = &envelop.public_part;
|
let public_key_point_hex = &envelop.public_part;
|
||||||
let (shared_secret, ephemeral_spki) = util_ecdh::compute_shared_secret(public_key_point_hex)?;
|
let (shared_secret, ephemeral_spki) = ecdh_p256::compute_p256_shared_secret(public_key_point_hex)?;
|
||||||
|
let enc_type = match cryptor {
|
||||||
encrypt_envelop_shared_secret(key, &shared_secret, &ephemeral_spki, ENC_AES256_GCM_P256, envelop)
|
Cryptor::Aes256Gcm => ENC_AES256_GCM_P256,
|
||||||
|
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_P256,
|
||||||
|
};
|
||||||
|
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_ecdh_p384(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
fn encrypt_envelop_ecdh_p384(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
let public_key_point_hex = &envelop.public_part;
|
let public_key_point_hex = &envelop.public_part;
|
||||||
let (shared_secret, ephemeral_spki) = util_p384::compute_p384_shared_secret(public_key_point_hex)?;
|
let (shared_secret, ephemeral_spki) = ecdh_p384::compute_p384_shared_secret(public_key_point_hex)?;
|
||||||
|
let enc_type = match cryptor {
|
||||||
encrypt_envelop_shared_secret(key, &shared_secret, &ephemeral_spki, ENC_AES256_GCM_P384, envelop)
|
Cryptor::Aes256Gcm => ENC_AES256_GCM_P384,
|
||||||
|
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_P384,
|
||||||
|
};
|
||||||
|
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_ecdh_x25519(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
fn encrypt_envelop_ecdh_x25519(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
let public_key_point_hex = &envelop.public_part;
|
let public_key_point_hex = &envelop.public_part;
|
||||||
let (shared_secret, ephemeral_spki) = util_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
|
let (shared_secret, ephemeral_spki) = ecdh_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
|
||||||
|
let enc_type = match cryptor {
|
||||||
encrypt_envelop_shared_secret(key, &shared_secret, &ephemeral_spki, ENC_AES256_GCM_X25519, envelop)
|
Cryptor::Aes256Gcm => ENC_AES256_GCM_X25519,
|
||||||
|
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_X25519,
|
||||||
|
};
|
||||||
|
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_shared_secret(key: &[u8],
|
fn encrypt_envelop_ecdh_kyber1204(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
|
let public_key_point_hex = &envelop.public_part;
|
||||||
|
let (shared_secret, ephemeral_spki) = ecdh_kyber1024::compute_kyber1024_shared_secret(public_key_point_hex)?;
|
||||||
|
let enc_type = match cryptor {
|
||||||
|
Cryptor::Aes256Gcm => ENC_AES256_GCM_KYBER1204,
|
||||||
|
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_KYBER1204,
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
key: &[u8],
|
||||||
shared_secret: &[u8],
|
shared_secret: &[u8],
|
||||||
ephemeral_spki: &[u8],
|
ephemeral_spki: &[u8],
|
||||||
enc_type: &str,
|
enc_type: &str,
|
||||||
envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
let shared_key = util::simple_kdf(shared_secret);
|
let shared_key = util::simple_kdf(shared_secret);
|
||||||
let (_, nonce) = util::make_key256_and_nonce();
|
let nonce = util::make_nonce();
|
||||||
|
let key_nonce = KeyNonce { k: &shared_key, n: nonce.as_ref() };
|
||||||
|
|
||||||
let encrypted_key = aes_gcm_encrypt(&shared_key, &nonce, key)?;
|
let encrypted_key = crypto_simple::encrypt(
|
||||||
|
cryptor, &key_nonce, key)?;
|
||||||
|
|
||||||
let wrap_key = WrapKey {
|
let wrap_key = WrapKey {
|
||||||
header: WrapKeyHeader {
|
header: WrapKeyHeader::from(enc_type, ephemeral_spki),
|
||||||
kid: None, // Some(envelop.kid.clone()),
|
nonce: nonce.0.clone(),
|
||||||
enc: enc_type.to_string(),
|
|
||||||
e_pub_key: util::encode_base64_url_no_pad(ephemeral_spki),
|
|
||||||
},
|
|
||||||
nonce,
|
|
||||||
encrypted_data: encrypted_key,
|
encrypted_data: encrypted_key,
|
||||||
};
|
};
|
||||||
let encoded_wrap_key = wrap_key.encode()?;
|
let encoded_wrap_key = wrap_key.encode()?;
|
||||||
@@ -306,19 +422,47 @@ fn encrypt_envelop_shared_secret(key: &[u8],
|
|||||||
Ok(TinyEncryptEnvelop {
|
Ok(TinyEncryptEnvelop {
|
||||||
r#type: envelop.r#type,
|
r#type: envelop.r#type,
|
||||||
kid: envelop.kid.clone(),
|
kid: envelop.kid.clone(),
|
||||||
desc: envelop.desc.clone(),
|
desc: None,
|
||||||
encrypted_key: encoded_wrap_key,
|
encrypted_key: encoded_wrap_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
fn encrypt_envelop_rsa(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
let pgp_public_key = opt_result!(parse_spki(&envelop.public_part), "Parse PGP public key failed: {}");
|
let rsa_public_key = opt_result!(util_rsa::parse_spki(&envelop.public_part), "Parse RSA public key failed: {}");
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let encrypted_key = opt_result!(pgp_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "PGP public key encrypt failed: {}");
|
let encrypted_key = opt_result!(rsa_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "RSA public key encrypt failed: {}");
|
||||||
Ok(TinyEncryptEnvelop {
|
Ok(TinyEncryptEnvelop {
|
||||||
r#type: envelop.r#type,
|
r#type: envelop.r#type,
|
||||||
kid: envelop.kid.clone(),
|
kid: envelop.kid.clone(),
|
||||||
desc: envelop.desc.clone(),
|
desc: None,
|
||||||
encrypted_key: util::encode_base64(&encrypted_key),
|
encrypted_key: util::encode_base64(&encrypted_key),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encrypt_envelop_gpg(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
|
||||||
|
let encrypted_key = opt_result!(util_gpg::gpg_encrypt(&envelop.public_part, key), "GPG encrypt failed: {}");
|
||||||
|
Ok(TinyEncryptEnvelop {
|
||||||
|
r#type: envelop.r#type,
|
||||||
|
kid: envelop.kid.clone(),
|
||||||
|
desc: None,
|
||||||
|
encrypted_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_compress_level(cmd_encrypt: &CmdEncrypt, path: &str, pem_output: bool) -> Option<u32> {
|
||||||
|
let mut auto_compress = false;
|
||||||
|
let path_parts = path.split(".").collect::<Vec<_>>();
|
||||||
|
let path_ext = path_parts[path_parts.len() - 1].to_lowercase();
|
||||||
|
if let Some(auto_compress_file_exts) = util_env::get_auto_compress_file_exts() {
|
||||||
|
auto_compress = auto_compress_file_exts.contains(&path_ext);
|
||||||
|
debugging!("File ext: {} matches auto compress exts: {:?}", path_ext, auto_compress_file_exts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if auto_compress || cmd_encrypt.compress || util_env::get_default_compress().unwrap_or(false) {
|
||||||
|
Some(cmd_encrypt.compress_level.unwrap_or_else(|| Compression::default().level()))
|
||||||
|
} else if pem_output {
|
||||||
|
Some(Compression::best().level())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
165
src/cmd_execenv.rs
Normal file
165
src/cmd_execenv.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use clap::Args;
|
||||||
|
use rust_util::{debugging, iff, opt_result, simple_error, util_cmd, util_msg, warning, XResult};
|
||||||
|
use serde_json::Value;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{config, consts, util, util_env};
|
||||||
|
use crate::cmd_decrypt::{decrypt_limited_content_to_vec, select_envelop, try_decrypt_key};
|
||||||
|
use crate::config::TinyEncryptConfig;
|
||||||
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
|
use crate::util::SecVec;
|
||||||
|
use crate::util_enc_file;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CmdExecEnv {
|
||||||
|
/// PGP or PIV PIN
|
||||||
|
#[arg(long, short = 'p')]
|
||||||
|
pub pin: Option<String>,
|
||||||
|
|
||||||
|
/// KeyID
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
pub key_id: Option<String>,
|
||||||
|
|
||||||
|
/// PIV slot
|
||||||
|
#[arg(long, short = 's')]
|
||||||
|
pub slot: Option<String>,
|
||||||
|
|
||||||
|
/// Config file or based64 encoded (starts with: base64:)
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Tiny encrypt file name
|
||||||
|
pub file_name: String,
|
||||||
|
|
||||||
|
/// Command and arguments
|
||||||
|
pub command_arguments: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CmdExecEnv {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(p) = self.pin.as_mut() { p.zeroize(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec_env(cmd_exec_env: CmdExecEnv) -> XResult<()> {
|
||||||
|
util_msg::set_logger_std_out(false);
|
||||||
|
debugging!("Cmd exec env: {:?}", cmd_exec_env);
|
||||||
|
let config = TinyEncryptConfig::load_default(&cmd_exec_env.config).ok();
|
||||||
|
if cmd_exec_env.command_arguments.is_empty() {
|
||||||
|
return simple_error!("No commands assigned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let pin = cmd_exec_env.pin.clone().or_else(util_env::get_pin);
|
||||||
|
let key_id = cmd_exec_env.key_id.clone().or_else(util_env::get_key_id);
|
||||||
|
|
||||||
|
let path = PathBuf::from(&cmd_exec_env.file_name);
|
||||||
|
let path = config::resolve_path_namespace(&config, &path, true);
|
||||||
|
let path_display = format!("{}", &path.display());
|
||||||
|
util::require_tiny_enc_file_and_exists(&path)?;
|
||||||
|
|
||||||
|
let mut file_in = opt_result!(File::open(&path), "Open file: {} failed: {}", &path_display);
|
||||||
|
let (_, _, meta) = opt_result!(
|
||||||
|
util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
||||||
|
util_msg::when_debug(|| {
|
||||||
|
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||||
|
});
|
||||||
|
|
||||||
|
let encryption_algorithm = meta.encryption_algorithm.as_deref()
|
||||||
|
.unwrap_or(consts::TINY_ENC_AES_GCM);
|
||||||
|
let cryptor = Cryptor::from(encryption_algorithm)?;
|
||||||
|
|
||||||
|
let selected_envelop = select_envelop(&meta, &key_id, &config, true)?;
|
||||||
|
|
||||||
|
let key = SecVec(try_decrypt_key(&config, selected_envelop, &pin, &cmd_exec_env.slot, true)?);
|
||||||
|
let nonce = SecVec(opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}"));
|
||||||
|
let key_nonce = KeyNonce { k: key.as_ref(), n: nonce.as_ref() };
|
||||||
|
|
||||||
|
let decrypted_content = decrypt_limited_content_to_vec(&mut file_in, &meta, cryptor, &key_nonce)?;
|
||||||
|
let exit_code = if let Some(output) = decrypted_content {
|
||||||
|
debugging!("Outputs: {}", output);
|
||||||
|
let arguments = &cmd_exec_env.command_arguments;
|
||||||
|
let envs = parse_output_to_env(&output);
|
||||||
|
|
||||||
|
let mut command = Command::new(&arguments[0]);
|
||||||
|
arguments.iter().skip(1).for_each(|a| { command.arg(a); });
|
||||||
|
envs.iter().for_each(|(k, v)| { command.env(k, v); });
|
||||||
|
|
||||||
|
debugging!("Run cmd: {:?}", command);
|
||||||
|
let run_cmd_result = util_cmd::run_command_and_wait(&mut command)?;
|
||||||
|
debugging!("Run cmd result: {}", run_cmd_result);
|
||||||
|
iff!(run_cmd_result.success(), 0, run_cmd_result.code().unwrap_or(-2))
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
};
|
||||||
|
|
||||||
|
debugging!("Finished, cost: {}ms", start.elapsed().as_millis());
|
||||||
|
std::process::exit(exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// supports format:
|
||||||
|
// JSON:
|
||||||
|
// {
|
||||||
|
// "KEY": "value",
|
||||||
|
// "KEY2": "value2"
|
||||||
|
// }
|
||||||
|
// ----OR----
|
||||||
|
// [
|
||||||
|
// "KEY": "value",
|
||||||
|
// "KEY2": "value2"
|
||||||
|
// ]
|
||||||
|
// ENV:
|
||||||
|
// KEY=value
|
||||||
|
// KEY2=value2
|
||||||
|
fn parse_output_to_env(output: &str) -> Vec<(String, String)> {
|
||||||
|
let mut env = vec![];
|
||||||
|
if let Ok(json) = serde_json::from_str::<Value>(output) {
|
||||||
|
match &json {
|
||||||
|
Value::Array(array) => {
|
||||||
|
for a in array {
|
||||||
|
match a {
|
||||||
|
Value::String(s) => { env.push((s.to_string(), "".to_string())); }
|
||||||
|
Value::Array(a2) => if a2.len() == 2 {
|
||||||
|
env.push((a2[0].to_string(), a2[1].to_string()));
|
||||||
|
} else {
|
||||||
|
warning!("Invalid array object: {:?}", a2);
|
||||||
|
}
|
||||||
|
Value::Object(object) => {
|
||||||
|
object.iter().for_each(|(k, v)| {
|
||||||
|
env.push((k.to_string(), v.to_string()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => { warning!("Invalid array object: {}", a); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Object(object) => {
|
||||||
|
object.iter().for_each(|(k, v)| {
|
||||||
|
env.push((k.to_string(), v.to_string()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => { warning!("Parse to env failed: {}", json); }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let lines = output.split('\n');
|
||||||
|
lines.filter(|ln| !ln.trim().is_empty()).for_each(|ln| {
|
||||||
|
if ln.starts_with('#') {
|
||||||
|
debugging!("Found comment: {}", ln);
|
||||||
|
} else if ln.contains('=') {
|
||||||
|
let k = ln.chars().take_while(|c| c != &'=').collect::<String>();
|
||||||
|
let v = ln.chars().skip_while(|c| c != &'=').skip(1).collect::<String>();
|
||||||
|
env.push((k, v));
|
||||||
|
} else {
|
||||||
|
env.push((ln.to_string(), "".to_string()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debugging!("Parsed env: {:?}", env);
|
||||||
|
env
|
||||||
|
}
|
||||||
101
src/cmd_info.rs
101
src/cmd_info.rs
@@ -4,46 +4,61 @@ use std::path::PathBuf;
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::{util_time, iff, opt_result, simple_error, success, warning, XResult};
|
use rust_util::{
|
||||||
|
debugging, failure, iff, opt_result, simple_error, success,
|
||||||
|
util_msg, util_size, util_time, XResult,
|
||||||
|
};
|
||||||
use rust_util::util_time::UnixEpochTime;
|
use rust_util::util_time::UnixEpochTime;
|
||||||
use simpledateformat::format_human2;
|
use simpledateformat::format_human2;
|
||||||
|
|
||||||
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_FILE_EXT};
|
use crate::{config, util, util_enc_file, util_envelop};
|
||||||
use crate::file;
|
use crate::config::TinyEncryptConfig;
|
||||||
|
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM};
|
||||||
|
use crate::util::is_tiny_enc_file;
|
||||||
|
use crate::wrap_key::WrapKey;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdInfo {
|
pub struct CmdInfo {
|
||||||
|
/// Show raw meta
|
||||||
|
#[arg(long, short = 'M', default_value_t = false)]
|
||||||
|
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>,
|
||||||
/// Show raw meta
|
|
||||||
#[arg(long, default_value_t = false)]
|
|
||||||
pub raw_meta: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(cmd_info: CmdInfo) -> XResult<()> {
|
pub fn info(cmd_info: CmdInfo) -> XResult<()> {
|
||||||
|
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);
|
||||||
if i > 0 { println!("{}", "-".repeat(88)); }
|
if i > 0 { println!("{}", "-".repeat(88)); }
|
||||||
if let Err(e) = info_single(path, &cmd_info) {
|
if let Err(e) = info_single(&path, &cmd_info, &config) {
|
||||||
warning!("Parse Tiny Encrypt file info failed: {}", e);
|
failure!("Parse Tiny Encrypt file info failed: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> {
|
pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo, config: &Option<TinyEncryptConfig>) -> XResult<()> {
|
||||||
let path_display = format!("{}", path.display());
|
let path_display = format!("{}", path.display());
|
||||||
if !path_display.ends_with(TINY_ENC_FILE_EXT) {
|
if !is_tiny_enc_file(&path_display) {
|
||||||
return simple_error!("Not a Tiny Encrypt file: {}", path_display);
|
return simple_error!("Not a Tiny Encrypt file: {}", path_display);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
||||||
let meta = opt_result!(
|
let file_in_len = file_in.metadata().map(|m| m.len()).unwrap_or(0);
|
||||||
file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display
|
|
||||||
|
let (meta_len, _, meta) = opt_result!(
|
||||||
|
util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display
|
||||||
);
|
);
|
||||||
|
|
||||||
if cmd_info.raw_meta {
|
if cmd_info.raw_meta {
|
||||||
success!("Meta data:\n{}", serde_json::to_string_pretty(&meta).expect("SHOULD NOT HAPPEN"));
|
println!("{}", serde_json::to_string_pretty(&meta).expect("SHOULD NOT HAPPEN"));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,34 +66,61 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> {
|
|||||||
infos.push("Tiny Encrypt File Info".to_string());
|
infos.push("Tiny Encrypt File Info".to_string());
|
||||||
let compressed = if meta.compress { " [compressed]" } else { "" };
|
let compressed = if meta.compress { " [compressed]" } else { "" };
|
||||||
infos.push(format!("{}: {}{}", header("File name"), path_display, compressed));
|
infos.push(format!("{}: {}{}", header("File name"), path_display, compressed));
|
||||||
infos.push(format!("{}: {} bytes", header("File size"), meta.file_length));
|
|
||||||
|
if meta.compress && file_in_len > (2 + 4 + meta_len as u64) {
|
||||||
|
let actual_file_in_len = file_in_len - 2 - 4 - meta_len as u64;
|
||||||
|
infos.push(format!("{}: {}, after compressed {}, ratio: {}%",
|
||||||
|
header("File size"),
|
||||||
|
util_size::get_display_size(meta.file_length as i64),
|
||||||
|
util_size::get_display_size(actual_file_in_len as i64),
|
||||||
|
util::ratio(actual_file_in_len, meta.file_length)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
infos.push(format!("{}: {}", header("File size"),
|
||||||
|
util_size::get_display_size(meta.file_length as i64)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
infos.push(format!("{}: {}",
|
||||||
|
header("Meta size"), util_size::get_display_size(meta_len as i64))
|
||||||
|
);
|
||||||
|
|
||||||
infos.push(format!("{}: Version: {}, Agent: {}",
|
infos.push(format!("{}: Version: {}, Agent: {}",
|
||||||
header("File summary"), meta.version, meta.user_agent)
|
header("File summary"), meta.version, meta.user_agent)
|
||||||
);
|
);
|
||||||
|
if let Some(latest_user_agent) = meta.latest_user_agent {
|
||||||
|
infos.push(format!("{}: {}", header("Latest user agent"), latest_user_agent))
|
||||||
|
}
|
||||||
|
|
||||||
let now_millis = util_time::get_current_millis() as u64;
|
let now_millis = util_time::get_current_millis() as u64;
|
||||||
let fmt = simpledateformat::fmt(DATE_TIME_FORMAT).unwrap();
|
let fmt = simpledateformat::fmt(DATE_TIME_FORMAT).unwrap();
|
||||||
infos.push(format!("{}: {}, {} ago",
|
|
||||||
header("Last modified"),
|
|
||||||
fmt.format_local(SystemTime::from_millis(meta.file_last_modified)),
|
|
||||||
format_human2(Duration::from_millis(now_millis - meta.file_last_modified))
|
|
||||||
));
|
|
||||||
infos.push(format!("{}: {}, {} ago",
|
infos.push(format!("{}: {}, {} ago",
|
||||||
header("Created"),
|
header("Created"),
|
||||||
fmt.format_local(SystemTime::from_millis(meta.created)),
|
fmt.format_local(SystemTime::from_millis(meta.created)),
|
||||||
format_human2(Duration::from_millis(now_millis - meta.created))
|
format_human2(Duration::from_millis(now_millis - meta.created))
|
||||||
));
|
));
|
||||||
|
infos.push(format!("{}: {}, {} ago",
|
||||||
|
header("Last modified"),
|
||||||
|
fmt.format_local(SystemTime::from_millis(meta.file_last_modified)),
|
||||||
|
format_human2(Duration::from_millis(now_millis - meta.file_last_modified))
|
||||||
|
));
|
||||||
|
if let Some(file_edit_count) = meta.file_edit_count {
|
||||||
|
infos.push(format!("{}: {} time(s)",
|
||||||
|
header("Edit count"),
|
||||||
|
file_edit_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(envelops) = meta.envelops.as_ref() {
|
if let Some(envelops) = meta.envelops.as_ref() {
|
||||||
envelops.iter().enumerate().for_each(|(i, envelop)| {
|
envelops.iter().enumerate().for_each(|(i, envelop)| {
|
||||||
let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid));
|
infos.push(format!("{}: {}",
|
||||||
let desc = envelop.desc.as_ref().map(|desc| format!(", Desc: {}", desc)).unwrap_or_else(|| "".to_string());
|
|
||||||
infos.push(format!("{}: {}{}{}",
|
|
||||||
header(&format!("Envelop #{}", i + 1)),
|
header(&format!("Envelop #{}", i + 1)),
|
||||||
envelop.r#type.get_upper_name(),
|
util_envelop::format_envelop(envelop, config)
|
||||||
kid,
|
|
||||||
desc
|
|
||||||
));
|
));
|
||||||
|
util_msg::when_debug(|| {
|
||||||
|
if let Ok(wrap_key) = WrapKey::parse(&envelop.encrypted_key) {
|
||||||
|
debugging!("Wrap key: {}", serde_json::to_string(&wrap_key).expect("SHOULD NOT HAPPEN"));
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +132,8 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> {
|
|||||||
}
|
}
|
||||||
infos.push(format!("{}: {}", header("Encrypted comment"), to_yes_or_no(&meta.encrypted_comment)));
|
infos.push(format!("{}: {}", header("Encrypted comment"), to_yes_or_no(&meta.encrypted_comment)));
|
||||||
infos.push(format!("{}: {}", header("Encrypted meta"), to_yes_or_no(&meta.encrypted_meta)));
|
infos.push(format!("{}: {}", header("Encrypted meta"), to_yes_or_no(&meta.encrypted_meta)));
|
||||||
let encryption_algorithm = if let Some(encryption_algorithm) = &meta.encryption_algorithm {
|
let encryption_algorithm = meta.encryption_algorithm.clone()
|
||||||
encryption_algorithm.to_string()
|
.unwrap_or_else(|| format!("{} (default)", TINY_ENC_AES_GCM));
|
||||||
} else {
|
|
||||||
format!("{} (default)", TINY_ENC_AES_GCM)
|
|
||||||
};
|
|
||||||
infos.push(format!("{}: {}", header("Encryption algorithm"), encryption_algorithm));
|
infos.push(format!("{}: {}", header("Encryption algorithm"), encryption_algorithm));
|
||||||
|
|
||||||
success!("{}", infos.join("\n"));
|
success!("{}", infos.join("\n"));
|
||||||
@@ -107,5 +146,5 @@ fn header(h: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_yes_or_no(opt: &Option<String>) -> String {
|
fn to_yes_or_no(opt: &Option<String>) -> String {
|
||||||
opt.as_ref().map(|_| "YES".to_string()).unwrap_or_else(|| "NO".to_string())
|
iff!(opt.is_some(), "YES", "NO").to_string()
|
||||||
}
|
}
|
||||||
185
src/cmd_initkeychain.rs
Normal file
185
src/cmd_initkeychain.rs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
use clap::Args;
|
||||||
|
use pqcrypto_traits::kem::PublicKey;
|
||||||
|
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::spec::TinyEncryptEnvelopType;
|
||||||
|
use crate::util_keychainkey;
|
||||||
|
use crate::util_keychainstatic;
|
||||||
|
use crate::util_keychainstatic::{KeychainKey, KeychainStaticSecret, KeychainStaticSecretAlgorithm};
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CmdInitKeychain {
|
||||||
|
/// Secure Enclave
|
||||||
|
#[arg(long, short = 'S')]
|
||||||
|
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
|
||||||
|
#[arg(long, short = 'E')]
|
||||||
|
pub expose_secure_enclave_private_key: bool,
|
||||||
|
|
||||||
|
/// Keychain name, or default [--keychain-name not works yet]
|
||||||
|
#[arg(long, short = 'c')]
|
||||||
|
pub keychain_name: Option<String>,
|
||||||
|
|
||||||
|
/// Service name, or default: tiny-encrypt
|
||||||
|
#[arg(long, short = 's')]
|
||||||
|
pub server_name: Option<String>,
|
||||||
|
|
||||||
|
/// Key name
|
||||||
|
#[arg(long, short = 'n')]
|
||||||
|
pub key_name: String,
|
||||||
|
|
||||||
|
/// Algorithm (x25519, or kyber1024, default x25519)
|
||||||
|
#[arg(long, short = 'a')]
|
||||||
|
pub algorithm: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
||||||
|
|
||||||
|
pub fn init_keychain(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||||
|
if cmd_init_keychain.secure_enclave {
|
||||||
|
keychain_key_se(cmd_init_keychain)
|
||||||
|
} else {
|
||||||
|
keychain_key_static(cmd_init_keychain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||||
|
if !util_keychainkey::is_support_se() {
|
||||||
|
return simple_error!("Secure enclave is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain_name = cmd_init_keychain.keychain_name.as_deref().unwrap_or("");
|
||||||
|
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
||||||
|
let key_name = &cmd_init_keychain.key_name;
|
||||||
|
|
||||||
|
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()
|
||||||
|
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
|
||||||
|
let saved_arg0 = if cmd_init_keychain.expose_secure_enclave_private_key {
|
||||||
|
private_key_base64
|
||||||
|
} else {
|
||||||
|
let keychain_key = KeychainKey::from(keychain_name, service_name, key_name);
|
||||||
|
keychain_key.set_password(private_key_base64.as_bytes())?;
|
||||||
|
keychain_key.to_str()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
|
r#type: TinyEncryptEnvelopType::KeyP256,
|
||||||
|
sid: Some(cmd_init_keychain.key_name.clone()),
|
||||||
|
kid: format!("keychain:02{}", &public_key_compressed_hex),
|
||||||
|
desc: Some("Keychain Secure Enclave".to_string()),
|
||||||
|
args: Some(vec![saved_arg0]),
|
||||||
|
public_part: public_key_hex,
|
||||||
|
profiles: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
|
||||||
|
let keychain_name = cmd_init_keychain.keychain_name.as_deref().unwrap_or("");
|
||||||
|
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
|
||||||
|
let key_name = &cmd_init_keychain.key_name;
|
||||||
|
let keychain_key = KeychainKey::from(keychain_name, service_name, key_name);
|
||||||
|
|
||||||
|
let mut envelop_type = match &cmd_init_keychain.algorithm {
|
||||||
|
None => TinyEncryptEnvelopType::StaticX25519,
|
||||||
|
Some(algorithm) => {
|
||||||
|
let a_lower = algorithm.to_lowercase();
|
||||||
|
if &a_lower == "kyber" || &a_lower == "kyber1024" {
|
||||||
|
TinyEncryptEnvelopType::StaticKyber1024
|
||||||
|
} else if &a_lower == "25519" || &a_lower == "x25519" || &a_lower == "cv25519" || &a_lower == "curve25519" {
|
||||||
|
TinyEncryptEnvelopType::StaticX25519
|
||||||
|
} else {
|
||||||
|
return simple_error!("Unknown algorithm: {}", algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_key_hex = match keychain_key.get_password()? {
|
||||||
|
Some(static_key) => {
|
||||||
|
warning!("Key already exists: {}.{}", service_name, key_name);
|
||||||
|
let keychain_static_secret = KeychainStaticSecret::parse_bytes(static_key.as_ref())?;
|
||||||
|
match keychain_static_secret.algo {
|
||||||
|
KeychainStaticSecretAlgorithm::X25519 => {
|
||||||
|
envelop_type = TinyEncryptEnvelopType::StaticX25519;
|
||||||
|
}
|
||||||
|
KeychainStaticSecretAlgorithm::Kyber1024 => {
|
||||||
|
envelop_type = TinyEncryptEnvelopType::StaticKyber1024;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match keychain_static_secret.algo {
|
||||||
|
KeychainStaticSecretAlgorithm::X25519 => {
|
||||||
|
let public_key = keychain_static_secret.to_x25519_public_key()?;
|
||||||
|
hex::encode(public_key.as_bytes())
|
||||||
|
}
|
||||||
|
KeychainStaticSecretAlgorithm::Kyber1024 => {
|
||||||
|
let (_, public_key) = keychain_static_secret.to_kyber1204_static_secret()?;
|
||||||
|
hex::encode(public_key.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let (keychain_key_bytes, public_key_hex) = match envelop_type {
|
||||||
|
TinyEncryptEnvelopType::StaticX25519 => {
|
||||||
|
let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
||||||
|
(keychain_key_bytes, hex::encode(public_key.as_bytes()))
|
||||||
|
}
|
||||||
|
TinyEncryptEnvelopType::StaticKyber1024 => {
|
||||||
|
let (keychain_key_bytes, public_key) = util_keychainstatic::generate_static_kyber1024_secret();
|
||||||
|
(keychain_key_bytes, hex::encode(public_key.as_bytes()))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
debugging!("Keychain key : {}", keychain_key_bytes);
|
||||||
|
opt_result!(
|
||||||
|
keychain_key.set_password(keychain_key_bytes.as_bytes()),
|
||||||
|
"Write static key failed: {}"
|
||||||
|
);
|
||||||
|
public_key_hex
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
success!("Keychain name: {}", &key_name);
|
||||||
|
success!("Public key : {}", &public_key_hex);
|
||||||
|
|
||||||
|
let kid_part2 = if public_key_hex.len() <= 64 {
|
||||||
|
public_key_hex.clone()
|
||||||
|
} else {
|
||||||
|
public_key_hex.chars().take(64).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
|
r#type: envelop_type,
|
||||||
|
sid: Some(key_name.clone()),
|
||||||
|
kid: format!("keychain:{}", &kid_part2),
|
||||||
|
desc: Some("Keychain static".to_string()),
|
||||||
|
args: Some(vec![keychain_key.to_str()]),
|
||||||
|
public_part: public_key_hex,
|
||||||
|
profiles: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
145
src/cmd_initpiv.rs
Normal file
145
src/cmd_initpiv.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use clap::Args;
|
||||||
|
use p256::pkcs8::der::Decode;
|
||||||
|
use rust_util::{failure, iff, information, opt_result, simple_error, warning, XResult};
|
||||||
|
use spki::{ObjectIdentifier, SubjectPublicKeyInfoOwned};
|
||||||
|
use spki::der::Encode;
|
||||||
|
use x509_parser::prelude::FromDer;
|
||||||
|
use x509_parser::public_key::RSAPublicKey;
|
||||||
|
use yubikey::Certificate;
|
||||||
|
use yubikey::Key;
|
||||||
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
|
use yubikey::YubiKey;
|
||||||
|
|
||||||
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
|
use crate::{util, util_piv};
|
||||||
|
use crate::util_digest::sha256_digest;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CmdInitPiv {
|
||||||
|
/// PIV slot
|
||||||
|
#[arg(long, short = 's')]
|
||||||
|
pub slot: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
|
||||||
|
const ECC: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
|
||||||
|
|
||||||
|
const ECC_P256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
|
||||||
|
const ECC_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34");
|
||||||
|
|
||||||
|
pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
|
||||||
|
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
||||||
|
let slot_id = util_piv::get_slot_id(&cmd_init_piv.slot)?;
|
||||||
|
let slot_id_hex = to_slot_hex(&slot_id);
|
||||||
|
let keys = opt_result!(Key::list(&mut yk), "List keys failed: {}");
|
||||||
|
|
||||||
|
let find_key = || {
|
||||||
|
for k in &keys {
|
||||||
|
let key_slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
|
||||||
|
if slot_equals(&slot_id, &key_slot_str) {
|
||||||
|
return Some(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let key = match find_key() {
|
||||||
|
None => {
|
||||||
|
warning!("Key not found.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Some(key) => key,
|
||||||
|
};
|
||||||
|
let cert = &key.certificate().cert.tbs_certificate;
|
||||||
|
if let Ok(algorithm_id) = get_algorithm_id_by_certificate(key.certificate()) {
|
||||||
|
let public_key_bit_string = &cert.subject_public_key_info.subject_public_key;
|
||||||
|
match algorithm_id {
|
||||||
|
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
|
||||||
|
let pk_point_hex = public_key_bit_string.raw_bytes();
|
||||||
|
let public_key_point_hex = hex::encode(pk_point_hex);
|
||||||
|
let compressed_public_key_point_hex = format!("02{}", hex::encode(&pk_point_hex[1..(pk_point_hex.len() / 2) + 1]));
|
||||||
|
|
||||||
|
let is_p256 = algorithm_id == AlgorithmId::EccP256;
|
||||||
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
|
r#type: iff!(is_p256, TinyEncryptEnvelopType::PivP256, TinyEncryptEnvelopType::PivP384),
|
||||||
|
sid: Some(format!("piv-{}-ecdh-{}", &slot_id_hex, iff!(is_p256, "p256", "p384"))),
|
||||||
|
kid: compressed_public_key_point_hex.clone(),
|
||||||
|
desc: Some(format!("PIV --slot {}", &slot_id_hex)),
|
||||||
|
args: Some(vec![
|
||||||
|
slot_id_hex.clone()
|
||||||
|
]),
|
||||||
|
public_part: public_key_point_hex,
|
||||||
|
profiles: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
}
|
||||||
|
AlgorithmId::Rsa2048 => {
|
||||||
|
let spki = opt_result!(cert.subject_public_key_info.to_der(), "Generate SPKI DER failed: {}");
|
||||||
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
|
r#type: TinyEncryptEnvelopType::PivRsa,
|
||||||
|
sid: Some(format!("piv-{}-rsa2048", &slot_id_hex)),
|
||||||
|
kid: format!("piv:{}", hex::encode(sha256_digest(&spki))),
|
||||||
|
desc: Some(format!("PIV --slot {}", &slot_id_hex)),
|
||||||
|
args: Some(vec![
|
||||||
|
slot_id_hex.clone()
|
||||||
|
]),
|
||||||
|
public_part: util::to_pem(&spki, "PUBLIC KEY"),
|
||||||
|
profiles: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
failure!("Only support P256, P384 or RSA2048, actual: {:?}", algorithm_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_algorithm_id_by_certificate(certificate: &Certificate) -> XResult<AlgorithmId> {
|
||||||
|
let tbs_certificate = &certificate.cert.tbs_certificate;
|
||||||
|
get_algorithm_id(&tbs_certificate.subject_public_key_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<AlgorithmId> {
|
||||||
|
if public_key_info.algorithm.oid == RSA {
|
||||||
|
let rsa_public_key = opt_result!(
|
||||||
|
RSAPublicKey::from_der(public_key_info.subject_public_key.raw_bytes()), "Parse public key failed: {}");
|
||||||
|
let starts_with_0 = rsa_public_key.1.modulus.starts_with(&[0]);
|
||||||
|
let public_key_bits = (rsa_public_key.1.modulus.len() - iff!(starts_with_0, 1, 0)) * 8;
|
||||||
|
if public_key_bits == 1024 {
|
||||||
|
return Ok(AlgorithmId::Rsa1024);
|
||||||
|
}
|
||||||
|
if public_key_bits == 2048 {
|
||||||
|
return Ok(AlgorithmId::Rsa2048);
|
||||||
|
}
|
||||||
|
return simple_error!("Unknown rsa bits: {}", public_key_bits);
|
||||||
|
}
|
||||||
|
if public_key_info.algorithm.oid == ECC {
|
||||||
|
if let Some(any) = &public_key_info.algorithm.parameters {
|
||||||
|
let any_parameter_der = opt_result!(any.to_der(), "Bad any parameter: {}");
|
||||||
|
let any_parameter_oid = opt_result!(ObjectIdentifier::from_der(&any_parameter_der), "Bad any parameter der: {}");
|
||||||
|
if any_parameter_oid == ECC_P256 {
|
||||||
|
return Ok(AlgorithmId::EccP256);
|
||||||
|
}
|
||||||
|
if any_parameter_oid == ECC_P384 {
|
||||||
|
return Ok(AlgorithmId::EccP384);
|
||||||
|
}
|
||||||
|
return simple_error!("Unknown any parameter oid: {}", any_parameter_oid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
simple_error!("Unknown algorithm: {}", public_key_info.algorithm.oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slot_equals(slot_id: &SlotId, slot: &str) -> bool {
|
||||||
|
util_piv::get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_slot_hex(slot: &SlotId) -> String {
|
||||||
|
let slot_id: u8 = (*slot).into();
|
||||||
|
format!("{:x}", slot_id)
|
||||||
|
}
|
||||||
334
src/cmd_simple_encrypt_decrypt.rs
Normal file
334
src/cmd_simple_encrypt_decrypt.rs
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
use crate::config::TinyEncryptConfig;
|
||||||
|
use crate::spec::TinyEncryptEnvelop;
|
||||||
|
use crate::{cmd_encrypt, crypto_cryptor, util, util_env};
|
||||||
|
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||||
|
use base64::Engine;
|
||||||
|
use clap::Args;
|
||||||
|
use rust_util::{debugging, opt_result, simple_error, XResult};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
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
|
||||||
|
const SIMPLE_ENCRYPTION_HEADER: &str = "tinyencrypt-dir";
|
||||||
|
const SIMPLE_ENCRYPTION_DOT: &str = ".";
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CmdSimpleEncrypt {
|
||||||
|
/// Encryption profile (use default when --key-filter is assigned)
|
||||||
|
#[arg(long, short = 'p')]
|
||||||
|
pub profile: Option<String>,
|
||||||
|
|
||||||
|
/// Encryption key filter (key_id or type:TYPE(e.g. ecdh, pgp, ecdh-p384, pgp-ed25519), multiple joined by ',', ALL for all)
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
pub key_filter: Option<String>,
|
||||||
|
|
||||||
|
/// Temporary key
|
||||||
|
#[arg(long)]
|
||||||
|
pub temporary_key: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Encrypt value from stdin
|
||||||
|
#[arg(long)]
|
||||||
|
pub value_stdin: bool,
|
||||||
|
|
||||||
|
/// Encrypt value
|
||||||
|
#[arg(long, short = 'v')]
|
||||||
|
pub value: Option<String>,
|
||||||
|
|
||||||
|
/// Encrypt value in bse64
|
||||||
|
#[arg(long)]
|
||||||
|
pub value_base64: Option<String>,
|
||||||
|
|
||||||
|
/// Encrypt value in hex
|
||||||
|
#[arg(long)]
|
||||||
|
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)]
|
||||||
|
pub struct CmdSimpleDecrypt {
|
||||||
|
/// PGP or PIV PIN
|
||||||
|
#[arg(long, short = 'p')]
|
||||||
|
pub pin: Option<String>,
|
||||||
|
|
||||||
|
/// Decrypt key ID
|
||||||
|
#[arg(long, short = 'k')]
|
||||||
|
pub key_id: Option<String>,
|
||||||
|
|
||||||
|
/// PIV slot
|
||||||
|
#[arg(long, short = 's')]
|
||||||
|
pub slot: Option<String>,
|
||||||
|
|
||||||
|
/// Decrypt value from stdin
|
||||||
|
#[arg(long)]
|
||||||
|
pub value_stdin: bool,
|
||||||
|
|
||||||
|
/// Decrypt value
|
||||||
|
#[arg(long, short = 'v')]
|
||||||
|
pub value: Option<String>,
|
||||||
|
|
||||||
|
/// Decrypt result output format (plain, hex, bse64)
|
||||||
|
#[arg(long, short = 'o')]
|
||||||
|
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 {
|
||||||
|
pub fn get_value(&self) -> XResult<Option<Vec<u8>>> {
|
||||||
|
if self.value_stdin {
|
||||||
|
return Ok(Some(util::read_stdin()?));
|
||||||
|
}
|
||||||
|
if let Some(value) = &self.value {
|
||||||
|
return Ok(Some(value.as_bytes().to_vec()));
|
||||||
|
}
|
||||||
|
if let Some(value_base64) = &self.value_base64 {
|
||||||
|
return Ok(Some(opt_result!(STANDARD.decode(value_base64), "Parse value base64 failed: {}")));
|
||||||
|
}
|
||||||
|
if let Some(value_hex) = &self.value_hex {
|
||||||
|
return Ok(Some(opt_result!(hex::decode(value_hex), "Parse value hex failed: {}")));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdSimpleDecrypt {
|
||||||
|
pub fn get_value(&self) -> XResult<Option<String>> {
|
||||||
|
if self.value_stdin {
|
||||||
|
return Ok(Some(opt_result!(String::from_utf8(util::read_stdin()?), "Read stdin value failed: {}")));
|
||||||
|
}
|
||||||
|
Ok(self.value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CmdResult {
|
||||||
|
pub code: i32,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub password: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub result: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdResult {
|
||||||
|
pub fn fail(code: i32, message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
code,
|
||||||
|
message: Some(message.to_string()),
|
||||||
|
password: None,
|
||||||
|
result: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success(result: &str, password: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
code: 0,
|
||||||
|
message: None,
|
||||||
|
password,
|
||||||
|
result: Some(result.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_exit(&self, direct_output_value: bool) -> ! {
|
||||||
|
// TODO direct_output_value
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit(direct_output);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
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) {
|
||||||
|
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit(direct_output);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
|
||||||
|
let config = TinyEncryptConfig::load_default(&cmd_simple_encrypt.config)?;
|
||||||
|
debugging!("Found tiny encrypt config: {:?}", config);
|
||||||
|
|
||||||
|
let mut envelops = config.find_envelops(
|
||||||
|
&cmd_simple_encrypt.profile,
|
||||||
|
&cmd_simple_encrypt.key_filter)?;
|
||||||
|
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()
|
||||||
|
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
|
||||||
|
.collect();
|
||||||
|
debugging!("Matched {} envelop(s): \n- {}", envelops.len(), envelop_tkids.join("\n- "));
|
||||||
|
|
||||||
|
if envelop_tkids.is_empty() {
|
||||||
|
return simple_error!("no matched envelops found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = match cmd_simple_encrypt.get_value()? {
|
||||||
|
None => return simple_error!("--value-stdin/value/value-base64/value-hex must assign one"),
|
||||||
|
Some(value) => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cryptor = crypto_cryptor::get_cryptor_by_encryption_algorithm(&None)?;
|
||||||
|
let envelops = cmd_encrypt::encrypt_envelops(cryptor, &value, &envelops)?;
|
||||||
|
|
||||||
|
let envelops_json = serde_json::to_string(&envelops)?;
|
||||||
|
let mut simple_encrypt_result = format!("{}.{}",
|
||||||
|
SIMPLE_ENCRYPTION_HEADER,
|
||||||
|
URL_SAFE_NO_PAD.encode(envelops_json.as_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
let config = TinyEncryptConfig::load_default(&cmd_simple_decrypt.config).ok();
|
||||||
|
|
||||||
|
let pin = cmd_simple_decrypt.pin.clone().or_else(util_env::get_pin);
|
||||||
|
let slot = cmd_simple_decrypt.slot.clone();
|
||||||
|
|
||||||
|
let output_format = cmd_simple_decrypt.output_format.as_deref().unwrap_or("plain");
|
||||||
|
match output_format {
|
||||||
|
"plain" | "hex" | "base64" => (),
|
||||||
|
_ => return simple_error!("not supported output format: {}", output_format),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut value = match cmd_simple_decrypt.get_value()? {
|
||||||
|
None => return simple_error!("--value-stdin/value must assign one"),
|
||||||
|
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<_>>();
|
||||||
|
if value_parts.len() != 2 {
|
||||||
|
return simple_error!("bad value format: {}", value);
|
||||||
|
}
|
||||||
|
if value_parts[0] != SIMPLE_ENCRYPTION_HEADER {
|
||||||
|
return simple_error!("bad value format: {}", value);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
Err(_) => return simple_error!("bad value format: {}", value),
|
||||||
|
Ok(value) => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter_envelops = envelops.iter().filter(|e| {
|
||||||
|
match &cmd_simple_decrypt.key_id {
|
||||||
|
None => true,
|
||||||
|
Some(key_id) => &e.kid == key_id,
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
if filter_envelops.is_empty() {
|
||||||
|
return simple_error!("no envelops found: {:?}", cmd_simple_decrypt.key_id);
|
||||||
|
}
|
||||||
|
if filter_envelops.len() > 1 {
|
||||||
|
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 = crate::cmd_decrypt::try_decrypt_key(&config, filter_envelops[0], &pin, &slot, false)?;
|
||||||
|
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: {}"),
|
||||||
|
"hex" => hex::encode(&value),
|
||||||
|
"base64" => STANDARD.encode(&value),
|
||||||
|
_ => return simple_error!("not supported output format: {}", output_format),
|
||||||
|
};
|
||||||
|
CmdResult::success(&value, outputs_password).print_exit(cmd_simple_decrypt.direct_output);
|
||||||
|
}
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
use rust_util::XResult;
|
use rust_util::{iff, XResult};
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
|
use crate::util_keychainkey;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdVersion {}
|
pub struct CmdVersion {}
|
||||||
|
|
||||||
pub fn version(_cmd_version: CmdVersion) -> XResult<()> {
|
pub fn version(_cmd_version: CmdVersion) -> XResult<()> {
|
||||||
|
let mut features: Vec<String> = vec![];
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
features.push("decrypt".to_string());
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
features.push("macos".to_string());
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
features.push("smartcard".to_string());
|
||||||
|
if features.is_empty() { features.push("-".to_string()); }
|
||||||
println!(
|
println!(
|
||||||
"{} - {}\n{}\n",
|
"User-Agent: {} [with features: {}]{}\n{}",
|
||||||
env!("CARGO_PKG_NAME"),
|
util::get_user_agent(),
|
||||||
env!("CARGO_PKG_VERSION"),
|
features.join(", "),
|
||||||
|
iff!(util_keychainkey::is_support_se(), " with Secure Enclave Supported", ""),
|
||||||
env!("CARGO_PKG_DESCRIPTION")
|
env!("CARGO_PKG_DESCRIPTION")
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ use std::io::Write;
|
|||||||
|
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use flate2::write::{GzDecoder, GzEncoder};
|
use flate2::write::{GzDecoder, GzEncoder};
|
||||||
use rust_util::{simple_error, XResult};
|
use rust_util::{opt_result, XResult};
|
||||||
|
|
||||||
|
const BUFFER_SIZE: usize = 8 * 1024;
|
||||||
|
|
||||||
|
pub fn compress_default(message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
compress(Compression::default(), message)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compress(compression: Compression, message: &[u8]) -> XResult<Vec<u8>> {
|
pub fn compress(compression: Compression, message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
let mut encoder = GzStreamEncoder::new(compression);
|
let mut encoder = GzStreamEncoder::new(compression);
|
||||||
@@ -28,13 +34,13 @@ impl GzStreamEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(compression: Compression) -> Self {
|
pub fn new(compression: Compression) -> Self {
|
||||||
let buffer = Vec::with_capacity(1024 * 8);
|
let buffer = Vec::with_capacity(BUFFER_SIZE);
|
||||||
let gz_encoder = GzEncoder::new(buffer, compression);
|
let gz_encoder = GzEncoder::new(buffer, compression);
|
||||||
Self { gz_encoder }
|
Self { gz_encoder }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, buff: &[u8]) -> XResult<Vec<u8>> {
|
pub fn update(&mut self, buff: &[u8]) -> XResult<Vec<u8>> {
|
||||||
self.gz_encoder.write_all(buff)?;
|
opt_result!(self.gz_encoder.write_all(buff), "Encode Gz stream failed: {}");
|
||||||
let inner = self.gz_encoder.get_mut();
|
let inner = self.gz_encoder.get_mut();
|
||||||
let result = inner.clone();
|
let result = inner.clone();
|
||||||
inner.clear();
|
inner.clear();
|
||||||
@@ -42,10 +48,7 @@ impl GzStreamEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(self) -> XResult<Vec<u8>> {
|
pub fn finalize(self) -> XResult<Vec<u8>> {
|
||||||
match self.gz_encoder.finish() {
|
Ok(opt_result!(self.gz_encoder.finish(), "Encode Gz stream failed: {}"))
|
||||||
Ok(last_buffer) => Ok(last_buffer),
|
|
||||||
Err(e) => simple_error!("Decode stream failed: {}", e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +58,13 @@ pub struct GzStreamDecoder {
|
|||||||
|
|
||||||
impl GzStreamDecoder {
|
impl GzStreamDecoder {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let buffer = Vec::with_capacity(1024 * 8);
|
let buffer = Vec::with_capacity(BUFFER_SIZE);
|
||||||
let gz_decoder = GzDecoder::new(buffer);
|
let gz_decoder = GzDecoder::new(buffer);
|
||||||
Self { gz_decoder }
|
Self { gz_decoder }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, buff: &[u8]) -> XResult<Vec<u8>> {
|
pub fn update(&mut self, buff: &[u8]) -> XResult<Vec<u8>> {
|
||||||
self.gz_decoder.write_all(buff)?;
|
opt_result!(self.gz_decoder.write_all(buff), "Decode Gz stream failed: {}");
|
||||||
let inner = self.gz_decoder.get_mut();
|
let inner = self.gz_decoder.get_mut();
|
||||||
let result = inner.clone();
|
let result = inner.clone();
|
||||||
inner.clear();
|
inner.clear();
|
||||||
@@ -69,10 +72,7 @@ impl GzStreamDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(self) -> XResult<Vec<u8>> {
|
pub fn finalize(self) -> XResult<Vec<u8>> {
|
||||||
match self.gz_decoder.finish() {
|
Ok(opt_result!(self.gz_decoder.finish(), "Decode Gz stream failed: {}"))
|
||||||
Ok(last_buffer) => Ok(last_buffer),
|
|
||||||
Err(e) => simple_error!("Decode stream failed: {}", e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,8 @@ fn test_gzip_compress() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gzip_compress_multi_blocks() {
|
fn test_gzip_compress_multi_blocks() {
|
||||||
let compressed = hex::decode("1f8b0800000000000000f348cdc9c95708cf2fca49f12081090044f4575937000000").unwrap();
|
let compressed = hex::decode(
|
||||||
|
"1f8b0800000000000000f348cdc9c95708cf2fca49f12081090044f4575937000000").unwrap();
|
||||||
let decompressed = "Hello WorldHello WorldHello WorldHello WorldHello World";
|
let decompressed = "Hello WorldHello WorldHello WorldHello WorldHello World";
|
||||||
let mut decoder = GzStreamDecoder::new();
|
let mut decoder = GzStreamDecoder::new();
|
||||||
let mut decompressed_bytes = vec![];
|
let mut decompressed_bytes = vec![];
|
||||||
|
|||||||
436
src/config.rs
436
src/config.rs
@@ -1,12 +1,15 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
use rust_util::{debugging, opt_result, simple_error, XResult};
|
use std::{env, fs};
|
||||||
|
use rust_util::{util_env as rust_util_env};
|
||||||
use rust_util::util_file::resolve_file_path;
|
use rust_util::util_file::resolve_file_path;
|
||||||
|
use rust_util::{debugging, opt_result, 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::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
|
||||||
@@ -20,6 +23,7 @@ use crate::spec::TinyEncryptEnvelopType;
|
|||||||
/// },
|
/// },
|
||||||
/// {
|
/// {
|
||||||
/// "type": "ecdh",
|
/// "type": "ecdh",
|
||||||
|
/// "sid": "SHORT-ID-1",
|
||||||
/// "kid": "KID-2",
|
/// "kid": "KID-2",
|
||||||
/// "desc": "this is key 002",
|
/// "desc": "this is key 002",
|
||||||
/// "publicPart": "04..."
|
/// "publicPart": "04..."
|
||||||
@@ -33,45 +37,141 @@ use crate::spec::TinyEncryptEnvelopType;
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TinyEncryptConfig {
|
pub struct TinyEncryptConfig {
|
||||||
|
pub environment: Option<HashMap<String, StringOrVecString>>,
|
||||||
|
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)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum StringOrVecString {
|
||||||
|
String(String),
|
||||||
|
Vec(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TinyEncryptConfigEnvelop {
|
pub struct TinyEncryptConfigEnvelop {
|
||||||
pub r#type: TinyEncryptEnvelopType,
|
pub r#type: TinyEncryptEnvelopType,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sid: Option<String>,
|
||||||
pub kid: String,
|
pub kid: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
|
#[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 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 file: {}, failed: {}", file);
|
if resolved_file.starts_with("base64:") {
|
||||||
let mut splitted_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: {}");
|
||||||
splitted_profiles.insert(k, v);
|
return Self::load_content(&decoded_resolved_content, "<env>");
|
||||||
|
}
|
||||||
|
debugging!("Found tiny encrypt config file: {}", &resolved_file);
|
||||||
|
return Self::load_file(&resolved_file)
|
||||||
|
}
|
||||||
|
let config_file = if fs::metadata(&resolved_file_1).is_ok() {
|
||||||
|
debugging!("Load config from: {resolved_file_1}");
|
||||||
|
resolved_file_1
|
||||||
|
} else if fs::metadata(&resolved_file_2).is_ok() {
|
||||||
|
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 {
|
} else {
|
||||||
k.split(',')
|
warning!("Cannot find config file from:\n- {resolved_file_1}\n- {resolved_file_2}\n- {resolved_file_3}");
|
||||||
.map(|k| k.trim())
|
resolved_file_1
|
||||||
.filter(|k| !k.is_empty())
|
};
|
||||||
.for_each(|k| {
|
Self::load_file(&config_file)
|
||||||
splitted_profiles.insert(k.to_string(), v.clone());
|
}
|
||||||
});
|
|
||||||
|
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 {
|
||||||
|
for (k, v) in environment {
|
||||||
|
let v = match v {
|
||||||
|
StringOrVecString::String(s) => s.to_string(),
|
||||||
|
StringOrVecString::Vec(vs) => vs.join(","),
|
||||||
|
};
|
||||||
|
debugging!("Set env: {}={}", k, v);
|
||||||
|
env::set_var(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.profiles = splitted_profiles;
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_path_namespace(&self, path: &Path, append_te: bool) -> PathBuf {
|
||||||
|
if let Some(path_str) = path.to_str() {
|
||||||
|
if path_str.starts_with(':') {
|
||||||
|
let namespace = path_str
|
||||||
|
.chars()
|
||||||
|
.skip(1)
|
||||||
|
.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) {
|
||||||
|
filename.push_str(TINY_ENC_FILE_EXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.find_namespace(&namespace) {
|
||||||
|
None => warning!("Namespace: {} not found", &namespace),
|
||||||
|
Some(dir) => return PathBuf::from(dir).join(&filename),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_namespace(&self, prefix: &str) -> Option<&String> {
|
||||||
|
self.namespaces.as_ref().and_then(|m| m.get(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_first_arg_by_kid(&self, kid: &str) -> Option<&String> {
|
pub fn find_first_arg_by_kid(&self, kid: &str) -> Option<&String> {
|
||||||
self.find_args_by_kid(kid).and_then(|a| a.iter().next())
|
self.find_args_by_kid(kid).and_then(|a| a.iter().next())
|
||||||
}
|
}
|
||||||
@@ -81,40 +181,286 @@ impl TinyEncryptConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_kid(&self, kid: &str) -> Option<&TinyEncryptConfigEnvelop> {
|
pub fn find_by_kid(&self, kid: &str) -> Option<&TinyEncryptConfigEnvelop> {
|
||||||
self.envelops.iter().find(|e| e.kid == kid)
|
self.find_by_kid_or_filter(kid, |_| false).first().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_envelops(&self, profile: &Option<String>) -> XResult<Vec<&TinyEncryptConfigEnvelop>> {
|
pub fn find_by_kid_or_type(&self, k_filter: &str) -> Vec<&TinyEncryptConfigEnvelop> {
|
||||||
let profile = profile.as_ref().map(String::as_str).unwrap_or("default");
|
self.find_by_kid_or_filter(k_filter, |e| {
|
||||||
debugging!("Profile: {}", profile);
|
let envelop_type = format!("type:{}", &e.r#type.get_name());
|
||||||
let mut matched_envelops_map = HashMap::new();
|
if k_filter == "ALL" || k_filter == "*" || k_filter == envelop_type {
|
||||||
if let Some(key_ids) = self.profiles.get(profile) {
|
return true;
|
||||||
if key_ids.is_empty() {
|
|
||||||
return simple_error!("Profile: {} contains no valid envelopes", profile);
|
|
||||||
}
|
}
|
||||||
for key_id in key_ids {
|
if k_filter.ends_with('*') {
|
||||||
self.envelops.iter().for_each(|envelop| {
|
let new_k_filter = k_filter.chars().collect::<Vec<_>>();
|
||||||
let is_matched = (&envelop.kid == key_id)
|
let new_k_filter = new_k_filter
|
||||||
|| key_id == &format!("type:{}", &envelop.r#type.get_name());
|
.iter()
|
||||||
if is_matched {
|
.take(new_k_filter.len() - 1)
|
||||||
matched_envelops_map.insert(&envelop.kid, envelop);
|
.collect::<String>();
|
||||||
|
if e.kid.starts_with(&new_k_filter) || envelop_type.starts_with(&new_k_filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_by_kid_or_filter<F>(&self, kid: &str, f: F) -> Vec<&TinyEncryptConfigEnvelop>
|
||||||
|
where
|
||||||
|
F: Fn(&TinyEncryptConfigEnvelop) -> bool,
|
||||||
|
{
|
||||||
|
self.envelops
|
||||||
|
.iter()
|
||||||
|
.filter(|e| {
|
||||||
|
if e.kid == kid {
|
||||||
|
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>,
|
||||||
|
) -> XResult<Vec<&TinyEncryptConfigEnvelop>> {
|
||||||
|
debugging!("Profile: {:?}", profile);
|
||||||
|
debugging!("Key filter: {:?}", key_filter);
|
||||||
|
let mut matched_envelops_map = HashMap::new();
|
||||||
|
let mut key_ids = vec![];
|
||||||
|
if key_filter.is_none() || profile.is_some() {
|
||||||
|
let profile = profile.as_ref().map(String::as_str).unwrap_or("default");
|
||||||
|
if profile == "ALL" {
|
||||||
|
self.envelops.iter().for_each(|e| {
|
||||||
|
key_ids.push(e.kid.to_string());
|
||||||
|
});
|
||||||
|
} else if let Some(kids) = self.get_profile(profile) {
|
||||||
|
kids.iter().for_each(|k| key_ids.push(k.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(key_filter) = key_filter {
|
||||||
|
key_filter.split(',').for_each(|k| {
|
||||||
|
let k = k.trim();
|
||||||
|
if !k.is_empty() {
|
||||||
|
key_ids.push(k.to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if key_ids.is_empty() {
|
||||||
|
// return simple_error!("Profile or key filter cannot find any valid envelopes");
|
||||||
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
let mut envelops: Vec<_> = matched_envelops_map.values()
|
for key_id in &key_ids {
|
||||||
.copied()
|
for envelop in self.find_by_kid_or_type(key_id) {
|
||||||
.collect();
|
matched_envelops_map.insert(&envelop.kid, envelop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut envelops: Vec<_> = matched_envelops_map.values().copied().collect();
|
||||||
if envelops.is_empty() {
|
if envelops.is_empty() {
|
||||||
return simple_error!("Profile: {} has no valid envelopes found", profile);
|
// 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 {
|
||||||
|
match config {
|
||||||
|
None => path.to_path_buf(),
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,28 @@
|
|||||||
pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256";
|
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_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_P384: &str = "chacha20-poly1305-p384";
|
||||||
|
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_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 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_AES_GCM: &str = "AES/GCM";
|
pub const TINY_ENC_AES_GCM: &str = "AES/GCM";
|
||||||
|
pub const TINY_ENC_CHACHA20_POLY1305: &str = "CHACHA20/POLY1305";
|
||||||
|
|
||||||
// Tiny enc magic tag
|
// Tiny enc magic tag
|
||||||
pub const TINY_ENC_MAGIC_TAG: u16 = 0x01;
|
pub const TINY_ENC_MAGIC_TAG: u16 = 0x01;
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
|
||||||
use rust_util::{opt_result, XResult};
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
pub fn try_aes_gcm_decrypt_with_salt(key: &[u8], nonce: &[u8], salt: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
|
||||||
let new_nonce = build_salted_nonce(nonce, salt);
|
|
||||||
if let Ok(decrypted) = aes_gcm_decrypt(key, &new_nonce, message) {
|
|
||||||
return Ok(decrypted);
|
|
||||||
}
|
|
||||||
aes_gcm_decrypt(key, nonce, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aes_gcm_decrypt(key: &[u8], nonce: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
|
||||||
let mut key: [u8; 32] = opt_result!(key.try_into(), "Invalid envelop: {}");
|
|
||||||
let mut aes256_gcm = Aes256GcmStreamDecryptor::new(key, nonce);
|
|
||||||
let mut b1 = aes256_gcm.update(message);
|
|
||||||
let b2 = opt_result!(aes256_gcm.finalize(), "Invalid envelop: {}");
|
|
||||||
b1.extend_from_slice(&b2);
|
|
||||||
key.zeroize();
|
|
||||||
Ok(b1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aes_gcm_encrypt_with_salt(key: &[u8], nonce: &[u8], salt: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
|
||||||
let new_nonce = build_salted_nonce(nonce, salt);
|
|
||||||
aes_gcm_encrypt(key, &new_nonce, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aes_gcm_encrypt(key: &[u8], nonce: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
|
||||||
let mut key: [u8; 32] = opt_result!(key.try_into(), "Invalid envelop: {}");
|
|
||||||
let mut aes256_gcm = Aes256GcmStreamEncryptor::new(key, nonce);
|
|
||||||
let mut b1 = aes256_gcm.update(message);
|
|
||||||
let (b2, tag) = aes256_gcm.finalize();
|
|
||||||
b1.extend_from_slice(&b2);
|
|
||||||
b1.extend_from_slice(&tag);
|
|
||||||
key.zeroize();
|
|
||||||
Ok(b1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_salted_nonce(nonce: &[u8], salt: &[u8]) -> Vec<u8> {
|
|
||||||
let mut nonce_with_salt = nonce.to_vec();
|
|
||||||
nonce_with_salt.extend_from_slice(salt);
|
|
||||||
let input = hex::decode(sha256::digest(nonce_with_salt)).unwrap();
|
|
||||||
input[0..12].to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_aes_gcm_01() {
|
|
||||||
let data_key = hex::decode("0001020304050607080910111213141516171819202122232425262728293031").unwrap();
|
|
||||||
let nonce = hex::decode("000102030405060708091011").unwrap();
|
|
||||||
|
|
||||||
let plain_text1 = "Hello world!".as_bytes();
|
|
||||||
let encrypted_text1 = "dce9511866417cff5123fa08c9e92cf156c5fc8bf6108ff28816fb58";
|
|
||||||
|
|
||||||
let plain_text2 = "This is a test message.".as_bytes();
|
|
||||||
let encrypted_text2 = "c0e45407290878b0426fea4c09597ce323b056f975c63cce6c8da516c2a78c7d71b590c869cf92";
|
|
||||||
|
|
||||||
let key256: [u8; 32] = data_key.as_slice().try_into().unwrap();
|
|
||||||
{
|
|
||||||
let mut encryptor = Aes256GcmStreamEncryptor::new(key256.clone(), &nonce);
|
|
||||||
let mut encrypted = encryptor.update(plain_text1);
|
|
||||||
let (last_block, tag) = encryptor.finalize();
|
|
||||||
encrypted.extend_from_slice(&last_block);
|
|
||||||
encrypted.extend_from_slice(&tag);
|
|
||||||
assert_eq!(encrypted_text1, hex::encode(&encrypted));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut encryptor = Aes256GcmStreamEncryptor::new(key256.clone(), &nonce);
|
|
||||||
let mut encrypted = encryptor.update(plain_text2);
|
|
||||||
let (last_block, tag) = encryptor.finalize();
|
|
||||||
encrypted.extend_from_slice(&last_block);
|
|
||||||
encrypted.extend_from_slice(&tag);
|
|
||||||
assert_eq!(encrypted_text2, hex::encode(&encrypted));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_aes_gcm_02() {
|
|
||||||
let data_key = hex::decode("aa01020304050607080910111213141516171819202122232425262728293031").unwrap();
|
|
||||||
let nonce = hex::decode("aa0102030405060708091011").unwrap();
|
|
||||||
|
|
||||||
let plain_text1 = hex::encode("Hello world!".as_bytes());
|
|
||||||
let encrypted_text1 = hex::decode("42b625d2bacb8a514076f14002f02770e9ccd98c90e556dc267aca30").unwrap();
|
|
||||||
|
|
||||||
let plain_text2 = hex::encode("This is a test message.".as_bytes());
|
|
||||||
let encrypted_text2 = hex::decode("5ebb20cdf5828e1e533ae1043ce6703cfa51574a83a069700aedefdbe2c735b01b74da214cba4a").unwrap();
|
|
||||||
|
|
||||||
let key256: [u8; 32] = data_key.as_slice().try_into().unwrap();
|
|
||||||
{
|
|
||||||
let mut decryptor = Aes256GcmStreamDecryptor::new(key256.clone(), &nonce);
|
|
||||||
let mut plain_text = decryptor.update(encrypted_text1.as_slice());
|
|
||||||
let last_block = decryptor.finalize().unwrap();
|
|
||||||
plain_text.extend_from_slice(&last_block);
|
|
||||||
assert_eq!(plain_text1, hex::encode(&plain_text));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut decryptor = Aes256GcmStreamDecryptor::new(key256.clone(), &nonce);
|
|
||||||
let mut plain_text = decryptor.update(encrypted_text2.as_slice());
|
|
||||||
let last_block = decryptor.finalize().unwrap();
|
|
||||||
plain_text.extend_from_slice(&last_block);
|
|
||||||
assert_eq!(plain_text2, hex::encode(&plain_text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
190
src/crypto_cryptor.rs
Normal file
190
src/crypto_cryptor.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
||||||
|
use chacha20_poly1305_stream::{ChaCha20Poly1305StreamDecryptor, ChaCha20Poly1305StreamEncryptor};
|
||||||
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::{consts, util_env};
|
||||||
|
|
||||||
|
pub struct KeyNonce<'a, 'b> {
|
||||||
|
pub k: &'a [u8],
|
||||||
|
pub n: &'b [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Cryptor {
|
||||||
|
Aes256Gcm,
|
||||||
|
ChaCha20Poly1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cryptor {
|
||||||
|
pub fn from(algorithm: &str) -> XResult<Self> {
|
||||||
|
match algorithm {
|
||||||
|
"aes256-gcm" | consts::TINY_ENC_AES_GCM => Ok(Cryptor::Aes256Gcm),
|
||||||
|
"chacha20-poly1305" | consts::TINY_ENC_CHACHA20_POLY1305 => Ok(Cryptor::ChaCha20Poly1305),
|
||||||
|
_ => simple_error!("Unknown algorithm: {}",algorithm),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> String {
|
||||||
|
let name = match self {
|
||||||
|
Cryptor::Aes256Gcm => consts::TINY_ENC_AES_GCM,
|
||||||
|
Cryptor::ChaCha20Poly1305 => consts::TINY_ENC_CHACHA20_POLY1305,
|
||||||
|
};
|
||||||
|
name.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encryptor(self, key_nonce: &KeyNonce) -> XResult<Box<dyn Encryptor>> {
|
||||||
|
get_encryptor(self, key_nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decryptor(self, key_nonce: &KeyNonce) -> XResult<Box<dyn Decryptor>> {
|
||||||
|
get_decryptor(self, key_nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Encryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8>;
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> (Vec<u8>, Vec<u8>);
|
||||||
|
|
||||||
|
fn encrypt(&mut self, message: &[u8]) -> Vec<u8> {
|
||||||
|
let mut cipher_text = self.update(message);
|
||||||
|
let (last_block, tag) = self.finalize();
|
||||||
|
cipher_text.extend_from_slice(&last_block);
|
||||||
|
cipher_text.extend_from_slice(&tag);
|
||||||
|
cipher_text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Decryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8>;
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> XResult<Vec<u8>>;
|
||||||
|
|
||||||
|
fn decrypt(&mut self, message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let mut plaintext = self.update(message);
|
||||||
|
let last_block = self.finalize()?;
|
||||||
|
plaintext.extend_from_slice(&last_block);
|
||||||
|
Ok(plaintext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_encryptor(crypto: Cryptor, key_nonce: &KeyNonce) -> XResult<Box<dyn Encryptor>> {
|
||||||
|
match crypto {
|
||||||
|
Cryptor::Aes256Gcm => {
|
||||||
|
let mut key: [u8; 32] = opt_result!(key_nonce.k.try_into(), "Bad AES 256 key: {}");
|
||||||
|
let aes256_gcm_stream_encryptor = Aes256GcmStreamEncryptor::new(
|
||||||
|
key, key_nonce.n);
|
||||||
|
key.zeroize();
|
||||||
|
Ok(Box::new(Aes256GcmEncryptor {
|
||||||
|
aes256_gcm_stream_encryptor,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Cryptor::ChaCha20Poly1305 => Ok(Box::new(ChaCha20Poly1305Encryptor {
|
||||||
|
chacha20_poly1305_stream_encryptor: ChaCha20Poly1305StreamEncryptor::new(
|
||||||
|
key_nonce.k, key_nonce.n)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_decryptor(crypto: Cryptor, key_nonce: &KeyNonce) -> XResult<Box<dyn Decryptor>> {
|
||||||
|
match crypto {
|
||||||
|
Cryptor::Aes256Gcm => {
|
||||||
|
let mut key: [u8; 32] = opt_result!(key_nonce.k.try_into(), "Bad AES 256 key: {}");
|
||||||
|
let aes256_gcm_stream_decryptor = Aes256GcmStreamDecryptor::new(
|
||||||
|
key, key_nonce.n);
|
||||||
|
key.zeroize();
|
||||||
|
Ok(Box::new(Aes256GcmDecryptor {
|
||||||
|
aes256_gcm_stream_decryptor,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Cryptor::ChaCha20Poly1305 => Ok(Box::new(ChaCha20Poly1305Decryptor {
|
||||||
|
chacha20_poly1305_stream_decryptor: ChaCha20Poly1305StreamDecryptor::new(
|
||||||
|
key_nonce.k, key_nonce.n)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Aes256GcmEncryptor {
|
||||||
|
aes256_gcm_stream_encryptor: Aes256GcmStreamEncryptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encryptor for Aes256GcmEncryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8> {
|
||||||
|
self.aes256_gcm_stream_encryptor.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
self.aes256_gcm_stream_encryptor.finalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChaCha20Poly1305Encryptor {
|
||||||
|
chacha20_poly1305_stream_encryptor: ChaCha20Poly1305StreamEncryptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encryptor for ChaCha20Poly1305Encryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8> {
|
||||||
|
self.chacha20_poly1305_stream_encryptor.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
self.chacha20_poly1305_stream_encryptor.finalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Aes256GcmDecryptor {
|
||||||
|
aes256_gcm_stream_decryptor: Aes256GcmStreamDecryptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decryptor for Aes256GcmDecryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8> {
|
||||||
|
self.aes256_gcm_stream_decryptor.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> XResult<Vec<u8>> {
|
||||||
|
Ok(self.aes256_gcm_stream_decryptor.finalize()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChaCha20Poly1305Decryptor {
|
||||||
|
chacha20_poly1305_stream_decryptor: ChaCha20Poly1305StreamDecryptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decryptor for ChaCha20Poly1305Decryptor {
|
||||||
|
fn update(&mut self, message: &[u8]) -> Vec<u8> {
|
||||||
|
self.chacha20_poly1305_stream_decryptor.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> XResult<Vec<u8>> {
|
||||||
|
Ok(self.chacha20_poly1305_stream_decryptor.finalize()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
pub fn get_cryptor_by_encryption_algorithm(encryption_algorithm: &Option<String>) -> XResult<Cryptor> {
|
||||||
|
let encryption_algorithm = encryption_algorithm.as_deref()
|
||||||
|
.or_else(|| util_env::get_default_encryption_algorithm())
|
||||||
|
.unwrap_or(consts::TINY_ENC_AES_GCM)
|
||||||
|
.to_lowercase();
|
||||||
|
let cryptor = match encryption_algorithm.as_str() {
|
||||||
|
"aes" | "aes/gcm" => Cryptor::Aes256Gcm,
|
||||||
|
"chacha20" | "chacha20/poly1305" => Cryptor::ChaCha20Poly1305,
|
||||||
|
_ => return simple_error!("Unknown encryption algorithm: {}, should be AES or CHACHA20", encryption_algorithm),
|
||||||
|
};
|
||||||
|
Ok(cryptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cryptor() {
|
||||||
|
let key = [0u8; 32];
|
||||||
|
let nonce = [0u8; 12];
|
||||||
|
let key_nonce = KeyNonce { k: &key, n: &nonce };
|
||||||
|
let ciphertext = Cryptor::Aes256Gcm.encryptor(&key_nonce).unwrap()
|
||||||
|
.encrypt(b"hello world");
|
||||||
|
let plaintext = Cryptor::Aes256Gcm.decryptor(&key_nonce).unwrap()
|
||||||
|
.decrypt(&ciphertext).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(b"hello world", plaintext.as_slice());
|
||||||
|
}
|
||||||
35
src/crypto_simple.rs
Normal file
35
src/crypto_simple.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use rust_util::XResult;
|
||||||
|
|
||||||
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
|
use crate::util_digest;
|
||||||
|
|
||||||
|
pub fn try_decrypt_with_salt(crypto: Cryptor, key_nonce: &KeyNonce, salt: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let new_nonce = build_salted_nonce(key_nonce.n, salt);
|
||||||
|
let new_key_nonce = KeyNonce { k: key_nonce.k, n: &new_nonce };
|
||||||
|
if let Ok(decrypted) = decrypt(crypto, &new_key_nonce, message) {
|
||||||
|
return Ok(decrypted);
|
||||||
|
}
|
||||||
|
decrypt(crypto, key_nonce, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(crypto: Cryptor, key_nonce: &KeyNonce, message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
crypto.decryptor(key_nonce)?.decrypt(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_with_salt(crypto: Cryptor, key_nonce: &KeyNonce, salt: &[u8], message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let new_nonce = build_salted_nonce(key_nonce.n, salt);
|
||||||
|
let new_key_nonce = KeyNonce { k: key_nonce.k, n: &new_nonce };
|
||||||
|
encrypt(crypto, &new_key_nonce, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(crypto: Cryptor, key_nonce: &KeyNonce, message: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
Ok(crypto.encryptor(key_nonce)?.encrypt(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_salted_nonce(nonce: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||||
|
let mut nonce_with_salt = nonce.to_vec();
|
||||||
|
nonce_with_salt.extend_from_slice(salt);
|
||||||
|
let input = util_digest::sha256_digest(&nonce_with_salt);
|
||||||
|
// let input = hex::decode(sha256::digest(nonce_with_salt)).unwrap();
|
||||||
|
input[0..12].to_vec()
|
||||||
|
}
|
||||||
60
src/file.rs
60
src/file.rs
@@ -1,60 +0,0 @@
|
|||||||
use std::io::{Read, Write};
|
|
||||||
|
|
||||||
use flate2::Compression;
|
|
||||||
use rust_util::{debugging, iff, opt_result, simple_error, XResult};
|
|
||||||
|
|
||||||
use crate::compress;
|
|
||||||
use crate::consts::{TINY_ENC_COMPRESSED_MAGIC_TAG, TINY_ENC_MAGIC_TAG};
|
|
||||||
use crate::spec::TinyEncryptMeta;
|
|
||||||
|
|
||||||
pub fn write_tiny_encrypt_meta<W: Write>(w: &mut W, meta: &TinyEncryptMeta, compress_meta: bool) -> XResult<usize> {
|
|
||||||
let tag = iff!(compress_meta, TINY_ENC_COMPRESSED_MAGIC_TAG, TINY_ENC_MAGIC_TAG);
|
|
||||||
opt_result!(w.write_all(&tag.to_be_bytes()), "Write tag failed: {}");
|
|
||||||
let mut encrypted_meta_bytes = opt_result!(serde_json::to_vec(&meta), "Generate meta json bytes failed: {}");
|
|
||||||
if compress_meta {
|
|
||||||
encrypted_meta_bytes = opt_result!(
|
|
||||||
compress::compress(Compression::default(), &encrypted_meta_bytes), "Compress encrypted meta failed: {}");
|
|
||||||
}
|
|
||||||
let encrypted_meta_bytes_len = encrypted_meta_bytes.len() as u32;
|
|
||||||
debugging!("Encrypted meta len: {}", encrypted_meta_bytes_len);
|
|
||||||
opt_result!(w.write_all(&encrypted_meta_bytes_len.to_be_bytes()), "Write meta len failed: {}");
|
|
||||||
opt_result!(w.write_all(&encrypted_meta_bytes), "Write meta failed: {}");
|
|
||||||
|
|
||||||
Ok(encrypted_meta_bytes.len() + 2 + 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_tiny_encrypt_meta_and_normalize<R: Read>(r: &mut R) -> XResult<TinyEncryptMeta> {
|
|
||||||
let mut meta = read_tiny_encrypt_meta(r);
|
|
||||||
let _ = meta.as_mut().map(|meta| meta.normalize());
|
|
||||||
meta
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_tiny_encrypt_meta<R: Read>(r: &mut R) -> XResult<TinyEncryptMeta> {
|
|
||||||
let mut tag_buff = [0_u8; 2];
|
|
||||||
opt_result!(r.read_exact(&mut tag_buff), "Read tag failed: {}");
|
|
||||||
let tag = u16::from_be_bytes(tag_buff);
|
|
||||||
let is_normal_tiny_enc = tag == TINY_ENC_MAGIC_TAG;
|
|
||||||
let is_compressed_tiny_enc = tag == TINY_ENC_COMPRESSED_MAGIC_TAG;
|
|
||||||
if !is_normal_tiny_enc && !is_compressed_tiny_enc {
|
|
||||||
return simple_error!("Tag is not 0x01 or 0x02, but is: 0x{:x}", tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut length_buff = [0_u8; 4];
|
|
||||||
opt_result!(r.read_exact(&mut length_buff), "Read length failed: {}");
|
|
||||||
let length = u32::from_be_bytes(length_buff);
|
|
||||||
if length > 1024 * 1024 {
|
|
||||||
return simple_error!("Meta too large: {}", length);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugging!("Encrypted meta len: {}", length);
|
|
||||||
let mut meta_buff = vec![0; length as usize];
|
|
||||||
opt_result!(r.read_exact(meta_buff.as_mut_slice()), "Read meta failed: {}");
|
|
||||||
|
|
||||||
debugging!("Tiny enc meta compressed: {}", is_compressed_tiny_enc);
|
|
||||||
if is_compressed_tiny_enc {
|
|
||||||
meta_buff = opt_result!(compress::decompress(&meta_buff), "Decompress meta failed: {}");
|
|
||||||
}
|
|
||||||
debugging!("Encrypted meta: {}", String::from_utf8_lossy(&meta_buff));
|
|
||||||
|
|
||||||
Ok(opt_result!(serde_json::from_slice(&meta_buff), "Parse meta failed: {}"))
|
|
||||||
}
|
|
||||||
82
src/lib.rs
Normal file
82
src/lib.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
pub use cmd_config::CmdConfig;
|
||||||
|
pub use cmd_config::config;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_decrypt::CmdDecrypt;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_decrypt::decrypt;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_decrypt::decrypt_single;
|
||||||
|
pub use cmd_directdecrypt::CmdDirectDecrypt;
|
||||||
|
pub use cmd_directdecrypt::direct_decrypt;
|
||||||
|
pub use cmd_encrypt::CmdEncrypt;
|
||||||
|
pub use cmd_simple_encrypt_decrypt::CmdSimpleEncrypt;
|
||||||
|
pub use cmd_simple_encrypt_decrypt::CmdSimpleDecrypt;
|
||||||
|
pub use cmd_encrypt::encrypt;
|
||||||
|
pub use cmd_encrypt::encrypt_single;
|
||||||
|
pub use cmd_encrypt::encrypt_single_file_out;
|
||||||
|
pub use cmd_simple_encrypt_decrypt::simple_encrypt;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_simple_encrypt_decrypt::simple_decrypt;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_execenv::CmdExecEnv;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
pub use cmd_execenv::exec_env;
|
||||||
|
pub use cmd_info::CmdInfo;
|
||||||
|
pub use cmd_info::info;
|
||||||
|
pub use cmd_info::info_single;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
pub use cmd_initkeychain::CmdInitKeychain;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
pub use cmd_initkeychain::init_keychain;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
pub use cmd_initpiv::CmdInitPiv;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
pub use cmd_initpiv::init_piv;
|
||||||
|
pub use cmd_version::CmdVersion;
|
||||||
|
pub use cmd_version::version;
|
||||||
|
pub use config::TinyEncryptConfig;
|
||||||
|
pub use util_log::init_tiny_encrypt_log;
|
||||||
|
|
||||||
|
mod consts;
|
||||||
|
mod util;
|
||||||
|
mod util_env;
|
||||||
|
mod util_digest;
|
||||||
|
mod util_progress;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
mod util_piv;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
mod util_pgp;
|
||||||
|
mod util_gpg;
|
||||||
|
mod util_ecdh;
|
||||||
|
mod compress;
|
||||||
|
mod config;
|
||||||
|
mod spec;
|
||||||
|
mod crypto_simple;
|
||||||
|
mod util_rsa;
|
||||||
|
mod crypto_cryptor;
|
||||||
|
mod wrap_key;
|
||||||
|
mod util_envelop;
|
||||||
|
mod util_file;
|
||||||
|
mod util_enc_file;
|
||||||
|
mod cmd_version;
|
||||||
|
mod cmd_config;
|
||||||
|
mod cmd_info;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
mod cmd_decrypt;
|
||||||
|
mod cmd_encrypt;
|
||||||
|
mod cmd_simple_encrypt_decrypt;
|
||||||
|
mod cmd_directdecrypt;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
mod cmd_initkeychain;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
mod cmd_initpiv;
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
mod util_keychainstatic;
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
mod cmd_execenv;
|
||||||
|
mod util_keychainkey;
|
||||||
|
mod util_simple_pbe;
|
||||||
|
mod util_log;
|
||||||
|
mod temporary_key;
|
||||||
|
mod util_mlkem;
|
||||||
|
|
||||||
87
src/main.rs
87
src/main.rs
@@ -3,31 +3,20 @@ extern crate core;
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
|
|
||||||
use crate::cmd_config::CmdConfig;
|
#[cfg(feature = "decrypt")]
|
||||||
use crate::cmd_decrypt::CmdDecrypt;
|
use tiny_encrypt::CmdDecrypt;
|
||||||
use crate::cmd_encrypt::CmdEncrypt;
|
#[cfg(feature = "decrypt")]
|
||||||
use crate::cmd_info::CmdInfo;
|
use tiny_encrypt::CmdExecEnv;
|
||||||
use crate::cmd_version::CmdVersion;
|
#[cfg(feature = "macos")]
|
||||||
|
use tiny_encrypt::CmdInitKeychain;
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
use tiny_encrypt::CmdInitPiv;
|
||||||
|
use tiny_encrypt::{init_tiny_encrypt_log, CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdSimpleDecrypt, CmdSimpleEncrypt, CmdVersion};
|
||||||
|
|
||||||
mod consts;
|
use zeroizing_alloc::ZeroAlloc;
|
||||||
mod util;
|
|
||||||
mod util_piv;
|
#[global_allocator]
|
||||||
mod util_pgp;
|
static ALLOC: ZeroAlloc<std::alloc::System> = ZeroAlloc(std::alloc::System);
|
||||||
mod util_ecdh;
|
|
||||||
mod util_p384;
|
|
||||||
mod util_x25519;
|
|
||||||
mod compress;
|
|
||||||
mod config;
|
|
||||||
mod spec;
|
|
||||||
mod crypto_aes;
|
|
||||||
mod crypto_rsa;
|
|
||||||
mod wrap_key;
|
|
||||||
mod file;
|
|
||||||
mod cmd_version;
|
|
||||||
mod cmd_config;
|
|
||||||
mod cmd_info;
|
|
||||||
mod cmd_decrypt;
|
|
||||||
mod cmd_encrypt;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "tiny-encrypt-rs")]
|
#[command(name = "tiny-encrypt-rs")]
|
||||||
@@ -42,27 +31,63 @@ enum Commands {
|
|||||||
/// Encrypt file(s)
|
/// Encrypt file(s)
|
||||||
#[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
|
||||||
|
#[command(arg_required_else_help = true, short_flag = 'E')]
|
||||||
|
SimpleEncrypt(CmdSimpleEncrypt),
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
/// Simple decrypt message
|
||||||
|
#[command(arg_required_else_help = true, short_flag = 'D')]
|
||||||
|
SimpleDecrypt(CmdSimpleDecrypt),
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
/// Decrypt file(s)
|
/// Decrypt file(s)
|
||||||
#[command(arg_required_else_help = true, short_flag = 'd')]
|
#[command(arg_required_else_help = true, short_flag = 'd')]
|
||||||
Decrypt(CmdDecrypt),
|
Decrypt(CmdDecrypt),
|
||||||
/// Show file info
|
/// Direct decrypt file(s)
|
||||||
|
#[command(arg_required_else_help = true)]
|
||||||
|
DirectDecrypt(CmdDirectDecrypt),
|
||||||
|
/// Show tiny encrypt file info
|
||||||
#[command(arg_required_else_help = true, short_flag = 'I')]
|
#[command(arg_required_else_help = true, short_flag = 'I')]
|
||||||
Info(CmdInfo),
|
Info(CmdInfo),
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
/// Init Keychain (Secure Enclave or Static)
|
||||||
|
#[command(arg_required_else_help = true, short_flag = 'K')]
|
||||||
|
InitKeychain(CmdInitKeychain),
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
/// Init PIV
|
||||||
|
#[command(arg_required_else_help = true, short_flag = 'P')]
|
||||||
|
InitPiv(CmdInitPiv),
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
/// Execute environment
|
||||||
|
#[command(arg_required_else_help = true, short_flag = 'X')]
|
||||||
|
ExecEnv(CmdExecEnv),
|
||||||
/// Show version
|
/// Show version
|
||||||
#[command(short_flag = 'v')]
|
#[command(short_flag = 'v')]
|
||||||
Version(CmdVersion),
|
Version(CmdVersion),
|
||||||
/// Show Config
|
/// Show configuration
|
||||||
#[command(short_flag = 'c')]
|
#[command(short_flag = 'c')]
|
||||||
Config(CmdConfig),
|
Config(CmdConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => cmd_encrypt::encrypt(cmd_encrypt),
|
Commands::Encrypt(cmd_encrypt) => tiny_encrypt::encrypt(cmd_encrypt),
|
||||||
Commands::Decrypt(cmd_decrypt) => cmd_decrypt::decrypt(cmd_decrypt),
|
Commands::SimpleEncrypt(cmd_simple_encrypt) => tiny_encrypt::simple_encrypt(cmd_simple_encrypt),
|
||||||
Commands::Info(cmd_info) => cmd_info::info(cmd_info),
|
#[cfg(feature = "decrypt")]
|
||||||
Commands::Version(cmd_version) => cmd_version::version(cmd_version),
|
Commands::SimpleDecrypt(cmd_simple_decrypt) => tiny_encrypt::simple_decrypt(cmd_simple_decrypt),
|
||||||
Commands::Config(cmd_config) => cmd_config::config(cmd_config),
|
#[cfg(feature = "decrypt")]
|
||||||
|
Commands::Decrypt(cmd_decrypt) => tiny_encrypt::decrypt(cmd_decrypt),
|
||||||
|
Commands::DirectDecrypt(cmd_direct_decrypt) => tiny_encrypt::direct_decrypt(cmd_direct_decrypt),
|
||||||
|
Commands::Info(cmd_info) => tiny_encrypt::info(cmd_info),
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
Commands::InitKeychain(cmd_keychain_key) => tiny_encrypt::init_keychain(cmd_keychain_key),
|
||||||
|
#[cfg(feature = "smartcard")]
|
||||||
|
Commands::InitPiv(cmd_init_piv) => tiny_encrypt::init_piv(cmd_init_piv),
|
||||||
|
#[cfg(feature = "decrypt")]
|
||||||
|
Commands::ExecEnv(cmd_exec_env) => tiny_encrypt::exec_env(cmd_exec_env),
|
||||||
|
Commands::Version(cmd_version) => tiny_encrypt::version(cmd_version),
|
||||||
|
Commands::Config(cmd_config) => tiny_encrypt::config(cmd_config),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
193
src/spec.rs
193
src/spec.rs
@@ -1,15 +1,16 @@
|
|||||||
use std::fs::Metadata;
|
use std::fs::Metadata;
|
||||||
|
|
||||||
use flate2::Compression;
|
|
||||||
use rust_util::{opt_result, util_time, XResult};
|
use rust_util::{opt_result, util_time, XResult};
|
||||||
use rust_util::util_time::get_millis;
|
use rust_util::util_time::get_millis;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{compress, crypto_aes};
|
use crate::{compress, crypto_simple};
|
||||||
use crate::consts::{SALT_META, TINY_ENC_AES_GCM};
|
use crate::consts::SALT_META;
|
||||||
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
use crate::util::{encode_base64, get_user_agent};
|
use crate::util::{encode_base64, get_user_agent};
|
||||||
|
|
||||||
pub const TINY_ENCRYPT_VERSION_10: &str = "1.0";
|
// Compatible with 1.0 is removed from v0.6.0
|
||||||
|
// pub const TINY_ENCRYPT_VERSION_10: &str = "1.0";
|
||||||
pub const TINY_ENCRYPT_VERSION_11: &str = "1.1";
|
pub const TINY_ENCRYPT_VERSION_11: &str = "1.1";
|
||||||
|
|
||||||
/// Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md)
|
/// Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md)
|
||||||
@@ -20,6 +21,8 @@ pub struct TinyEncryptMeta {
|
|||||||
pub created: u64,
|
pub created: u64,
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub latest_user_agent: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub encrypted_comment: Option<String>,
|
pub encrypted_comment: Option<String>,
|
||||||
@@ -47,6 +50,8 @@ pub struct TinyEncryptMeta {
|
|||||||
pub nonce: String,
|
pub nonce: String,
|
||||||
pub file_length: u64,
|
pub file_length: u64,
|
||||||
pub file_last_modified: u64,
|
pub file_last_modified: u64,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub file_edit_count: Option<u64>,
|
||||||
pub compress: bool,
|
pub compress: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,16 +68,55 @@ pub struct TinyEncryptEnvelop {
|
|||||||
/// NOTICE: Kms and Age is not being supported in the future
|
/// NOTICE: Kms and Age is not being supported in the future
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||||
pub enum TinyEncryptEnvelopType {
|
pub enum TinyEncryptEnvelopType {
|
||||||
#[serde(rename = "pgp")]
|
// OpenPGP Card RSA
|
||||||
Pgp,
|
#[serde(rename = "pgp-rsa", alias = "pgp")]
|
||||||
|
PgpRsa,
|
||||||
|
// OpenPGP Card X25519
|
||||||
#[serde(rename = "pgp-x25519")]
|
#[serde(rename = "pgp-x25519")]
|
||||||
PgpX25519,
|
PgpX25519,
|
||||||
|
// GPG Command Line
|
||||||
|
#[serde(rename = "gpg")]
|
||||||
|
Gpg,
|
||||||
|
// Keychain Static X25519 (less secure)
|
||||||
|
#[serde(rename = "static-x25519")]
|
||||||
|
StaticX25519,
|
||||||
|
// Keychain Static Kyber1024 (less secure)
|
||||||
|
#[serde(rename = "static-kyber1024")]
|
||||||
|
StaticKyber1024,
|
||||||
|
// Secure Enclave ECDH P256
|
||||||
|
#[serde(rename = "key-p256")]
|
||||||
|
KeyP256,
|
||||||
|
// Secure Enclave ML-KEM 768
|
||||||
|
#[serde(rename = "key-mlkem768")]
|
||||||
|
KeyMlKem768,
|
||||||
|
// Secure Enclave ML-KEM 1024
|
||||||
|
#[serde(rename = "key-mlkem1024")]
|
||||||
|
KeyMlKem1024,
|
||||||
|
// PIV ECDH P256
|
||||||
|
#[serde(rename = "piv-p256", alias = "ecdh")]
|
||||||
|
PivP256,
|
||||||
|
// PIV ECDH P384
|
||||||
|
#[serde(rename = "piv-p384", alias = "ecdh-p384")]
|
||||||
|
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
|
||||||
|
#[serde(rename = "piv-rsa")]
|
||||||
|
PivRsa,
|
||||||
|
// Age, tiny-encrypt-rs is not supported
|
||||||
#[serde(rename = "age")]
|
#[serde(rename = "age")]
|
||||||
Age,
|
Age,
|
||||||
#[serde(rename = "ecdh")]
|
// KMS, tiny-encrypt-rs is not supported
|
||||||
Ecdh,
|
|
||||||
#[serde(rename = "ecdh-p384")]
|
|
||||||
EcdhP384,
|
|
||||||
#[serde(rename = "kms")]
|
#[serde(rename = "kms")]
|
||||||
Kms,
|
Kms,
|
||||||
}
|
}
|
||||||
@@ -81,16 +125,96 @@ impl TinyEncryptEnvelopType {
|
|||||||
pub fn get_upper_name(&self) -> String {
|
pub fn get_upper_name(&self) -> String {
|
||||||
self.get_name().to_uppercase()
|
self.get_name().to_uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_name(&self) -> &'static str {
|
pub fn get_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
TinyEncryptEnvelopType::Pgp => "pgp",
|
TinyEncryptEnvelopType::PgpRsa => "pgp-rsa",
|
||||||
TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519",
|
TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519",
|
||||||
|
TinyEncryptEnvelopType::Gpg => "gpg",
|
||||||
|
TinyEncryptEnvelopType::StaticX25519 => "static-x25519",
|
||||||
|
TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024",
|
||||||
|
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::PivP384 => "piv-p384",
|
||||||
|
TinyEncryptEnvelopType::PivRsa => "piv-rsa",
|
||||||
TinyEncryptEnvelopType::Age => "age",
|
TinyEncryptEnvelopType::Age => "age",
|
||||||
TinyEncryptEnvelopType::Ecdh => "ecdh",
|
|
||||||
TinyEncryptEnvelopType::EcdhP384 => "ecdh-p384",
|
|
||||||
TinyEncryptEnvelopType::Kms => "kms",
|
TinyEncryptEnvelopType::Kms => "kms",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
match self {
|
||||||
|
TinyEncryptEnvelopType::StaticX25519
|
||||||
|
| TinyEncryptEnvelopType::StaticKyber1024
|
||||||
|
| TinyEncryptEnvelopType::KeyP256
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem768
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem1024
|
||||||
|
| TinyEncryptEnvelopType::Gpg
|
||||||
|
| TinyEncryptEnvelopType::Kms => true,
|
||||||
|
TinyEncryptEnvelopType::PgpRsa
|
||||||
|
| TinyEncryptEnvelopType::PgpX25519
|
||||||
|
| TinyEncryptEnvelopType::ExtP256
|
||||||
|
| TinyEncryptEnvelopType::ExtP384
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem768
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem1024
|
||||||
|
| TinyEncryptEnvelopType::PivP256
|
||||||
|
| TinyEncryptEnvelopType::PivP384
|
||||||
|
| TinyEncryptEnvelopType::PivRsa
|
||||||
|
| TinyEncryptEnvelopType::Age => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_hardware_security(&self) -> Option<bool> {
|
||||||
|
match self {
|
||||||
|
TinyEncryptEnvelopType::PgpRsa
|
||||||
|
| TinyEncryptEnvelopType::PgpX25519
|
||||||
|
| TinyEncryptEnvelopType::KeyP256
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem768
|
||||||
|
| TinyEncryptEnvelopType::KeyMlKem1024
|
||||||
|
| TinyEncryptEnvelopType::PivP256
|
||||||
|
| TinyEncryptEnvelopType::PivP384
|
||||||
|
| TinyEncryptEnvelopType::PivRsa
|
||||||
|
| TinyEncryptEnvelopType::Age => Some(true),
|
||||||
|
TinyEncryptEnvelopType::StaticX25519
|
||||||
|
| TinyEncryptEnvelopType::StaticKyber1024
|
||||||
|
| TinyEncryptEnvelopType::Kms => Some(false),
|
||||||
|
// GPG is unknown(hardware/software)
|
||||||
|
TinyEncryptEnvelopType::Gpg
|
||||||
|
| TinyEncryptEnvelopType::ExtP256
|
||||||
|
| TinyEncryptEnvelopType::ExtP384
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem768
|
||||||
|
| TinyEncryptEnvelopType::ExtMlKem1024 => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -104,21 +228,21 @@ pub struct EncEncryptedMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EncEncryptedMeta {
|
impl EncEncryptedMeta {
|
||||||
pub fn unseal(key: &[u8], nonce: &[u8], message: &[u8]) -> XResult<Self> {
|
pub fn unseal(crypto: Cryptor, key_nonce: &KeyNonce, message: &[u8]) -> XResult<Self> {
|
||||||
let mut decrypted = opt_result!(crypto_aes::try_aes_gcm_decrypt_with_salt(
|
let mut decrypted = opt_result!(crypto_simple::try_decrypt_with_salt(
|
||||||
key, nonce, SALT_META, message), "Decrypt failed: {}");
|
crypto, key_nonce, SALT_META, message), "Decrypt encrypted meta failed: {}");
|
||||||
decrypted = opt_result!(compress::decompress(&decrypted), "Decode faield: {}");
|
decrypted = opt_result!(compress::decompress(&decrypted), "Depress encrypted meta failed: {}");
|
||||||
let meta = opt_result!(
|
let meta = opt_result!(
|
||||||
serde_json::from_slice::<Self>(&decrypted), "Parse failed: {}");
|
serde_json::from_slice::<Self>(&decrypted), "Parse encrypted meta failed: {}");
|
||||||
Ok(meta)
|
Ok(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seal(&self, key: &[u8], nonce: &[u8]) -> XResult<Vec<u8>> {
|
pub fn seal(&self, crypto: Cryptor, key_nonce: &KeyNonce) -> XResult<Vec<u8>> {
|
||||||
let mut encrypted_meta_json = serde_json::to_vec(self).unwrap();
|
let mut encrypted_meta_json = serde_json::to_vec(self).unwrap();
|
||||||
encrypted_meta_json = opt_result!(
|
encrypted_meta_json = opt_result!(
|
||||||
compress::compress(Compression::default(), &encrypted_meta_json), "Compress failed: {}");
|
compress::compress_default(&encrypted_meta_json), "Compress encrypted meta failed: {}");
|
||||||
let encrypted = opt_result!(crypto_aes::aes_gcm_encrypt_with_salt(
|
let encrypted = opt_result!(crypto_simple::encrypt_with_salt(
|
||||||
key, nonce, SALT_META, encrypted_meta_json.as_slice()), "Encrypt failed: {}");
|
crypto, key_nonce, SALT_META, encrypted_meta_json.as_slice()), "Encrypt encrypted meta failed: {}");
|
||||||
Ok(encrypted)
|
Ok(encrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,11 +255,12 @@ pub struct EncMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TinyEncryptMeta {
|
impl TinyEncryptMeta {
|
||||||
pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, nonce: &[u8], envelops: Vec<TinyEncryptEnvelop>) -> Self {
|
pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, cryptor: Cryptor, nonce: &[u8], envelops: Vec<TinyEncryptEnvelop>) -> Self {
|
||||||
TinyEncryptMeta {
|
TinyEncryptMeta {
|
||||||
version: TINY_ENCRYPT_VERSION_11.to_string(),
|
version: TINY_ENCRYPT_VERSION_11.to_string(),
|
||||||
created: util_time::get_current_millis() as u64,
|
created: util_time::get_current_millis() as u64,
|
||||||
user_agent: get_user_agent(),
|
user_agent: get_user_agent(),
|
||||||
|
latest_user_agent: None,
|
||||||
comment: enc_metadata.comment.to_owned(),
|
comment: enc_metadata.comment.to_owned(),
|
||||||
encrypted_comment: enc_metadata.encrypted_comment.to_owned(),
|
encrypted_comment: enc_metadata.encrypted_comment.to_owned(),
|
||||||
encrypted_meta: enc_metadata.encrypted_meta.to_owned(),
|
encrypted_meta: enc_metadata.encrypted_meta.to_owned(),
|
||||||
@@ -147,28 +272,30 @@ impl TinyEncryptMeta {
|
|||||||
ecdh_point: None,
|
ecdh_point: None,
|
||||||
envelop: None,
|
envelop: None,
|
||||||
envelops: Some(envelops),
|
envelops: Some(envelops),
|
||||||
encryption_algorithm: Some(TINY_ENC_AES_GCM.to_string()),
|
encryption_algorithm: Some(cryptor.get_name()),
|
||||||
nonce: encode_base64(nonce),
|
nonce: encode_base64(nonce),
|
||||||
file_length: metadata.len(),
|
file_length: metadata.len(),
|
||||||
file_last_modified: match metadata.modified() {
|
file_last_modified: match metadata.modified() {
|
||||||
Ok(modified) => get_millis(&modified) as u64,
|
Ok(modified) => get_millis(&modified) as u64,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
},
|
},
|
||||||
|
file_edit_count: None,
|
||||||
compress: enc_metadata.compress,
|
compress: enc_metadata.compress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normalize(&mut self) {
|
// compatibility with legacy tiny encrypt format
|
||||||
|
pub fn normalize_envelops(&mut self) {
|
||||||
if self.envelops.is_none() {
|
if self.envelops.is_none() {
|
||||||
self.envelops = Some(vec![]);
|
self.envelops = Some(vec![]);
|
||||||
}
|
}
|
||||||
self.normalize_envelop();
|
self.normalize_kms_envelop();
|
||||||
self.normalize_pgp_envelop();
|
self.normalize_pgp_rsa_envelop();
|
||||||
self.normalize_age_envelop();
|
self.normalize_age_envelop();
|
||||||
self.normalize_ecdh_envelop();
|
self.normalize_piv_p256_envelop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_envelop(&mut self) {
|
fn normalize_kms_envelop(&mut self) {
|
||||||
if let (Some(envelop), Some(envelops)) = (&self.envelop, &mut self.envelops) {
|
if let (Some(envelop), Some(envelops)) = (&self.envelop, &mut self.envelops) {
|
||||||
envelops.push(TinyEncryptEnvelop {
|
envelops.push(TinyEncryptEnvelop {
|
||||||
r#type: TinyEncryptEnvelopType::Kms,
|
r#type: TinyEncryptEnvelopType::Kms,
|
||||||
@@ -180,11 +307,11 @@ impl TinyEncryptMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_pgp_envelop(&mut self) {
|
fn normalize_pgp_rsa_envelop(&mut self) {
|
||||||
if let (Some(pgp_envelop), Some(pgp_fingerprint), Some(envelops))
|
if let (Some(pgp_envelop), Some(pgp_fingerprint), Some(envelops))
|
||||||
= (&self.pgp_envelop, &self.pgp_fingerprint, &mut self.envelops) {
|
= (&self.pgp_envelop, &self.pgp_fingerprint, &mut self.envelops) {
|
||||||
envelops.push(TinyEncryptEnvelop {
|
envelops.push(TinyEncryptEnvelop {
|
||||||
r#type: TinyEncryptEnvelopType::Pgp,
|
r#type: TinyEncryptEnvelopType::PgpRsa,
|
||||||
kid: pgp_fingerprint.into(),
|
kid: pgp_fingerprint.into(),
|
||||||
desc: None,
|
desc: None,
|
||||||
encrypted_key: pgp_envelop.into(),
|
encrypted_key: pgp_envelop.into(),
|
||||||
@@ -208,11 +335,11 @@ impl TinyEncryptMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_ecdh_envelop(&mut self) {
|
fn normalize_piv_p256_envelop(&mut self) {
|
||||||
if let (Some(ecdh_envelop), Some(ecdh_point), Some(envelops))
|
if let (Some(ecdh_envelop), Some(ecdh_point), Some(envelops))
|
||||||
= (&self.ecdh_envelop, &self.ecdh_point, &mut self.envelops) {
|
= (&self.ecdh_envelop, &self.ecdh_point, &mut self.envelops) {
|
||||||
envelops.push(TinyEncryptEnvelop {
|
envelops.push(TinyEncryptEnvelop {
|
||||||
r#type: TinyEncryptEnvelopType::Ecdh,
|
r#type: TinyEncryptEnvelopType::PivP256,
|
||||||
kid: ecdh_point.into(),
|
kid: ecdh_point.into(),
|
||||||
desc: None,
|
desc: None,
|
||||||
encrypted_key: ecdh_envelop.into(),
|
encrypted_key: ecdh_envelop.into(),
|
||||||
|
|||||||
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())
|
||||||
|
}
|
||||||
245
src/util.rs
245
src/util.rs
@@ -1,24 +1,111 @@
|
|||||||
use std::{fs, io};
|
use std::io::{Read, Write};
|
||||||
use std::io::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
use base64::Engine;
|
|
||||||
use base64::engine::general_purpose;
|
use base64::engine::general_purpose;
|
||||||
|
use base64::Engine;
|
||||||
|
use dialoguer::console::Term;
|
||||||
|
use dialoguer::theme::ColorfulTheme;
|
||||||
|
use dialoguer::Confirm;
|
||||||
|
use pinentry::PassphraseInput;
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use rust_util::{information, simple_error, util_term, warning, XResult};
|
use rust_util::{information, opt_result, print_ex, simple_error, util_term, warning, XResult};
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::consts::TINY_ENC_FILE_EXT;
|
use crate::consts::{TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT};
|
||||||
|
use crate::util_digest::DigestWrite;
|
||||||
|
use crate::util_env;
|
||||||
|
|
||||||
pub fn read_pin(pin: &Option<String>) -> String {
|
pub struct SecVec(pub Vec<u8>);
|
||||||
match pin {
|
|
||||||
Some(pin) => pin.to_string(),
|
impl Drop for SecVec {
|
||||||
None => if util_term::read_yes_no("Use default PIN 123456, please confirm") {
|
fn drop(&mut self) {
|
||||||
"123456".into()
|
self.0.zeroize()
|
||||||
} else {
|
|
||||||
rpassword::prompt_password("Please input PIN: ").expect("Read PIN failed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for SecVec {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_stdin() -> XResult<Vec<u8>> {
|
||||||
|
let mut buffer = vec![];
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
opt_result!(stdin.read_to_end(&mut buffer), "Read stdin failed: {}");
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_pin(pin: &Option<String>) -> XResult<String> {
|
||||||
|
let mut ask_use_default_pin = true;
|
||||||
|
if let Some(pin) = pin {
|
||||||
|
if pin == "#INPUT#" {
|
||||||
|
ask_use_default_pin = false;
|
||||||
|
} else {
|
||||||
|
return Ok(pin.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ask_use_default_pin && is_use_default_pin() {
|
||||||
|
return Ok("123456".into());
|
||||||
|
}
|
||||||
|
let rpin = {
|
||||||
|
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
|
||||||
|
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
||||||
|
let secret = input
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if util_env::get_no_default_pin_hint() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let use_dialoguer = util_env::get_use_dialoguer();
|
||||||
|
if use_dialoguer {
|
||||||
|
register_ctrlc();
|
||||||
|
let confirm_result = Confirm::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Use default PIN?")
|
||||||
|
.interact();
|
||||||
|
if confirm_result.is_err() {
|
||||||
|
let _ = Term::stderr().show_cursor();
|
||||||
|
}
|
||||||
|
confirm_result.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
util_term::read_yes_no("Use default PIN 123456, please confirm")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_file_with_msg(path: &PathBuf) {
|
pub fn remove_file_with_msg(path: &PathBuf) {
|
||||||
@@ -68,25 +155,62 @@ pub fn require_file_not_exists(path: impl AsRef<Path>) -> XResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_key256_and_nonce() -> (Vec<u8>, Vec<u8>) {
|
pub fn make_nonce() -> SecVec {
|
||||||
|
let (_, nonce) = make_key256_and_nonce();
|
||||||
|
nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_key256_and_nonce() -> (SecVec, SecVec) {
|
||||||
let key: [u8; 32] = random();
|
let key: [u8; 32] = random();
|
||||||
let nonce: [u8; 12] = random();
|
let nonce: [u8; 12] = random();
|
||||||
let result = (key.into(), nonce.into());
|
let key_vec: Vec<u8> = key.into();
|
||||||
|
let nonce_vec: Vec<u8> = nonce.into();
|
||||||
let (mut key, mut nonce) = (key, nonce);
|
let (mut key, mut nonce) = (key, nonce);
|
||||||
key.zeroize();
|
key.zeroize();
|
||||||
nonce.zeroize();
|
nonce.zeroize();
|
||||||
result
|
(SecVec(key_vec), SecVec(nonce_vec))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_kdf(input: &[u8]) -> Vec<u8> {
|
pub fn simple_kdf(input: &[u8]) -> Vec<u8> {
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
let mut input = input.to_vec();
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
for _ in 0..8 {
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
let mut sha256 = DigestWrite::sha256();
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
sha256.write_all(&input).expect("SHOULD NOT HAPPEN");
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
input = sha256.digest();
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
}
|
||||||
let input = hex::decode(sha256::digest(input)).unwrap();
|
input
|
||||||
hex::decode(sha256::digest(input)).unwrap()
|
}
|
||||||
|
|
||||||
|
pub fn parse_pem(pem: &str) -> XResult<Vec<u8>> {
|
||||||
|
let mut pem = pem.trim().to_owned();
|
||||||
|
if pem.starts_with("-----BEGIN") {
|
||||||
|
let mut filter_lines = vec![];
|
||||||
|
let lines = pem.lines().skip(1);
|
||||||
|
for ln in lines {
|
||||||
|
if ln.starts_with("-----END") {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
filter_lines.push(ln.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pem = filter_lines.join("");
|
||||||
|
}
|
||||||
|
pem = pem.chars().filter(|c| *c != '\n' && *c != '\r').clone().collect::<String>();
|
||||||
|
|
||||||
|
Ok(opt_result!(decode_base64(&pem), "Decode PEM failed: {}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_pem(bs: &[u8], name: &str) -> String {
|
||||||
|
let bs_base64 = encode_base64(bs);
|
||||||
|
let mut pem = String::with_capacity(bs.len() + 64);
|
||||||
|
pem.push_str(&format!("-----BEGIN {}-----", name));
|
||||||
|
for (i, c) in bs_base64.chars().enumerate() {
|
||||||
|
if i % 64 == 0 { pem.push('\n'); }
|
||||||
|
pem.push(c);
|
||||||
|
}
|
||||||
|
if !pem.ends_with('\n') { pem.push('\n'); }
|
||||||
|
pem.push_str(&format!("-----END {}-----", name));
|
||||||
|
pem
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_base64(input: &str) -> XResult<Vec<u8>> {
|
pub fn decode_base64(input: &str) -> XResult<Vec<u8>> {
|
||||||
@@ -107,7 +231,7 @@ pub fn decode_base64_url_no_pad(input: &str) -> XResult<Vec<u8>> {
|
|||||||
|
|
||||||
pub fn read_number(hint: &str, from: usize, to: usize) -> usize {
|
pub fn read_number(hint: &str, from: usize, to: usize) -> usize {
|
||||||
loop {
|
loop {
|
||||||
print!("{} ({}-{}): ", hint, from, to);
|
print_ex!("{} ({}-{}): ", hint, from, to);
|
||||||
io::stdout().flush().ok();
|
io::stdout().flush().ok();
|
||||||
let mut buff = String::new();
|
let mut buff = String::new();
|
||||||
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
||||||
@@ -123,10 +247,17 @@ pub fn read_number(hint: &str, from: usize, to: usize) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_tiny_enc_file(filename: &str) -> bool {
|
||||||
|
filename.ends_with(TINY_ENC_FILE_EXT) || filename.ends_with(TINY_ENC_PEM_FILE_EXT)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_user_agent() -> String {
|
pub fn get_user_agent() -> String {
|
||||||
format!("TinyEncrypt-rs v{}@{}", env!("CARGO_PKG_VERSION"),
|
format!("TinyEncrypt-rs v{}@{}-{}", env!("CARGO_PKG_VERSION"), get_os(), get_arch())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_os() -> String {
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
"MacOS"
|
"macOS"
|
||||||
} else if cfg!(target_os = "ios") {
|
} else if cfg!(target_os = "ios") {
|
||||||
"iOS"
|
"iOS"
|
||||||
} else if cfg!(target_os = "android") {
|
} else if cfg!(target_os = "android") {
|
||||||
@@ -144,9 +275,34 @@ pub fn get_user_agent() -> String {
|
|||||||
} else if cfg!(target_os = "netbsd") {
|
} else if cfg!(target_os = "netbsd") {
|
||||||
"NetBSD"
|
"NetBSD"
|
||||||
} else {
|
} else {
|
||||||
panic!("Unsupported OS!");
|
"UnknownOS"
|
||||||
|
}.to_string()
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
pub fn get_arch() -> String {
|
||||||
|
if cfg!(target_arch = "x86_64") {
|
||||||
|
"x86-64"
|
||||||
|
} else if cfg!(target_arch = "x86") {
|
||||||
|
"x86"
|
||||||
|
} else if cfg!(target_arch = "aarch64") {
|
||||||
|
"aarch64"
|
||||||
|
} else if cfg!(target_arch = "arm") {
|
||||||
|
"arm"
|
||||||
|
} else if cfg!(target_arch = "riscv64") {
|
||||||
|
"riscv64"
|
||||||
|
} else if cfg!(target_arch = "riscv32") {
|
||||||
|
"riscv32"
|
||||||
|
} else if cfg!(target_arch = "mips64") {
|
||||||
|
"mips64"
|
||||||
|
} else if cfg!(target_arch = "mips") {
|
||||||
|
"mips"
|
||||||
|
} else if cfg!(target_arch = "powerpc64") {
|
||||||
|
"powerpc64"
|
||||||
|
} else if cfg!(target_arch = "powerpc") {
|
||||||
|
"powerpc"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
}.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zeroize(object: impl Zeroize) {
|
pub fn zeroize(object: impl Zeroize) {
|
||||||
@@ -155,9 +311,40 @@ pub fn zeroize(object: impl Zeroize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_line(ln: &str) {
|
pub fn read_line(ln: &str) {
|
||||||
print!("{}", ln);
|
print_ex!("{}", ln);
|
||||||
io::stdout().flush().ok();
|
io::stdout().flush().ok();
|
||||||
let mut buff = String::new();
|
let mut buff = String::new();
|
||||||
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ratio(numerator: u64, denominator: u64) -> String {
|
||||||
|
if denominator == 0 {
|
||||||
|
return "∞".to_string();
|
||||||
|
}
|
||||||
|
let r = (numerator * 10000) / denominator;
|
||||||
|
format!("{:.2}", r as f64 / 100f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
|
const CTRL_C_SET: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
pub fn register_ctrlc() {
|
||||||
|
#[allow(clippy::borrow_interior_mutable_const)]
|
||||||
|
if !CTRL_C_SET.load(Ordering::SeqCst) {
|
||||||
|
CTRL_C_SET.store(true, Ordering::SeqCst);
|
||||||
|
let _ = ctrlc::set_handler(move || {
|
||||||
|
// DO NOTHING
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_kdf() {
|
||||||
|
assert_eq!("30edbc354e8cf656adcddbeefbf3f5073372cdc42e4eca2e797bda8abebb6a05",
|
||||||
|
hex::encode(simple_kdf(b"")));
|
||||||
|
assert_eq!("0624d2a57bcb50f70aa19bab9fa75af1ca66cc701c341df865d430e2e6d9d936",
|
||||||
|
hex::encode(simple_kdf(b"hello")));
|
||||||
|
assert_eq!("43367d255eddedc3c84b692b68de6d3d21da28caad6abd20ed85a4f2c89706ad",
|
||||||
|
hex::encode(simple_kdf(b"hello world")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
83
src/util_digest.rs
Normal file
83
src/util_digest.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
use std::iter::repeat;
|
||||||
|
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use crypto::md5::Md5;
|
||||||
|
use crypto::ripemd160::Ripemd160;
|
||||||
|
use crypto::sha1::Sha1;
|
||||||
|
use crypto::sha2::{Sha224, Sha256, Sha384, Sha512, Sha512Trunc224, Sha512Trunc256};
|
||||||
|
use crypto::sha3::Sha3;
|
||||||
|
use crypto::whirlpool::Whirlpool;
|
||||||
|
use rust_util::{simple_error, XResult};
|
||||||
|
|
||||||
|
pub struct DigestWrite {
|
||||||
|
digest: Box<dyn Digest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for DigestWrite {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.digest.input(buf);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DigestWrite {
|
||||||
|
pub fn from_algo(algo: &str) -> XResult<Self> {
|
||||||
|
match get_digest_by_algorithm(algo) {
|
||||||
|
None => simple_error!("Unsupported algo: {}", algo),
|
||||||
|
Some(digest) => Ok(Self { digest })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sha256() -> Self {
|
||||||
|
Self { digest: Box::new(Sha256::new()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(self) -> Vec<u8> {
|
||||||
|
let mut digest = self.digest;
|
||||||
|
let mut buf: Vec<u8> = repeat(0).take((digest.output_bits() + 7) / 8).collect();
|
||||||
|
digest.result(&mut buf);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sha256_digest(input: &[u8]) -> Vec<u8> {
|
||||||
|
let mut digest = Sha256::new();
|
||||||
|
digest.input(input);
|
||||||
|
let mut buf: Vec<u8> = repeat(0).take((digest.output_bits() + 7) / 8).collect();
|
||||||
|
digest.result(&mut buf);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_digest_by_algorithm(algo: &str) -> Option<Box<dyn Digest>> {
|
||||||
|
let algo = algo.to_uppercase();
|
||||||
|
match algo.as_str() {
|
||||||
|
"RIPEMD160" => Some(Box::new(Ripemd160::new())),
|
||||||
|
"WHIRLPOOL" => Some(Box::new(Whirlpool::new())),
|
||||||
|
// "BLAKE2S" => Some(Box::new(Blake2s::new(iff!(options.blake_len == 0_usize, 32, options.blake_len)))),
|
||||||
|
// "BLAKE2B" => Some(Box::new(Blake2b::new(iff!(options.blake_len == 0_usize, 64, options.blake_len)))),
|
||||||
|
"MD5" => Some(Box::new(Md5::new())),
|
||||||
|
"SHA1" | "SHA-1" => Some(Box::new(Sha1::new())),
|
||||||
|
"SHA224" | "SHA-224" => Some(Box::new(Sha224::new())),
|
||||||
|
"SHA256" | "SHA-256" => Some(Box::new(Sha256::new())),
|
||||||
|
"SHA384" | "SHA-384" => Some(Box::new(Sha384::new())),
|
||||||
|
"SHA512" | "SHA-512" => Some(Box::new(Sha512::new())),
|
||||||
|
"SHA512-224" => Some(Box::new(Sha512Trunc224::new())),
|
||||||
|
"SHA512-256" => Some(Box::new(Sha512Trunc256::new())),
|
||||||
|
"SHA3-224" => Some(Box::new(Sha3::sha3_224())),
|
||||||
|
"SHA3-256" => Some(Box::new(Sha3::sha3_256())),
|
||||||
|
"SHA3-384" => Some(Box::new(Sha3::sha3_384())),
|
||||||
|
"SHA3-512" => Some(Box::new(Sha3::sha3_512())),
|
||||||
|
"SHAKE-128" => Some(Box::new(Sha3::shake_128())),
|
||||||
|
"SHAKE-256" => Some(Box::new(Sha3::shake_256())),
|
||||||
|
"KECCAK-224" => Some(Box::new(Sha3::keccak224())),
|
||||||
|
"KECCAK-256" => Some(Box::new(Sha3::keccak256())),
|
||||||
|
"KECCAK-384" => Some(Box::new(Sha3::keccak384())),
|
||||||
|
"KECCAK-512" => Some(Box::new(Sha3::keccak512())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,13 @@
|
|||||||
|
pub mod ecdh_p256 {
|
||||||
|
use std::ops::Deref;
|
||||||
|
use p256::{EncodedPoint, PublicKey};
|
||||||
use p256::ecdh::EphemeralSecret;
|
use p256::ecdh::EphemeralSecret;
|
||||||
|
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
||||||
|
use p256::pkcs8::EncodePublicKey;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rust_util::{opt_result, XResult};
|
use rust_util::{opt_result, XResult};
|
||||||
|
|
||||||
use p256::pkcs8::EncodePublicKey;
|
pub fn compute_p256_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
use p256::{EncodedPoint, PublicKey};
|
|
||||||
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
|
||||||
// use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
|
||||||
|
|
||||||
// #[derive(Debug)]
|
|
||||||
// pub struct EphemeralKeyBytes(EncodedPoint);
|
|
||||||
//
|
|
||||||
// impl EphemeralKeyBytes {
|
|
||||||
// pub fn from_public_key(epk: &PublicKey) -> Self {
|
|
||||||
// EphemeralKeyBytes(epk.to_encoded_point(true))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn decompress(&self) -> EncodedPoint {
|
|
||||||
// // EphemeralKeyBytes is a valid compressed encoding by construction.
|
|
||||||
// let p = PublicKey::from_encoded_point(&self.0).unwrap();
|
|
||||||
// p.to_encoded_point(false)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn compute_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
|
||||||
let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}");
|
let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}");
|
||||||
let encoded_point = opt_result!(EncodedPoint::from_bytes(public_key_point_bytes), "Parse public key point failed: {}");
|
let encoded_point = opt_result!(EncodedPoint::from_bytes(public_key_point_bytes), "Parse public key point failed: {}");
|
||||||
let public_key = PublicKey::from_encoded_point(&encoded_point).unwrap();
|
let public_key = PublicKey::from_encoded_point(&encoded_point).unwrap();
|
||||||
@@ -31,5 +16,65 @@ pub fn compute_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Ve
|
|||||||
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 {
|
||||||
|
use std::ops::Deref;
|
||||||
|
use p384::{EncodedPoint, PublicKey};
|
||||||
|
use p384::ecdh::EphemeralSecret;
|
||||||
|
use p384::elliptic_curve::sec1::FromEncodedPoint;
|
||||||
|
use p384::pkcs8::EncodePublicKey;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rust_util::{opt_result, XResult};
|
||||||
|
|
||||||
|
pub fn compute_p384_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}");
|
||||||
|
let encoded_point = opt_result!(EncodedPoint::from_bytes(public_key_point_bytes), "Parse public key point failed: {}");
|
||||||
|
let public_key = PublicKey::from_encoded_point(&encoded_point).unwrap();
|
||||||
|
|
||||||
|
let esk = EphemeralSecret::random(&mut OsRng);
|
||||||
|
let epk = esk.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: {}");
|
||||||
|
Ok((shared_secret.raw_secret_bytes().deref().to_vec(), epk_public_key_der.to_vec()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod ecdh_x25519 {
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
|
pub fn compute_x25519_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let public_key_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse X25519 public key hex failed: {}");
|
||||||
|
if public_key_bytes.len() != 32 {
|
||||||
|
return simple_error!("Parse X25519 key failed: not 32 bytes");
|
||||||
|
}
|
||||||
|
let public_key_bytes: [u8; 32] = public_key_bytes.try_into().unwrap();
|
||||||
|
let public_key_card = PublicKey::from(public_key_bytes);
|
||||||
|
|
||||||
|
let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
|
||||||
|
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
||||||
|
|
||||||
|
let shared_secret = ephemeral_secret.diffie_hellman(&public_key_card);
|
||||||
|
|
||||||
|
Ok((shared_secret.as_bytes().to_vec(), ephemeral_public.as_bytes().to_vec()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod ecdh_kyber1024 {
|
||||||
|
use pqcrypto_kyber::kyber1024;
|
||||||
|
use pqcrypto_kyber::kyber1024::PublicKey as Kyber1024PublicKey;
|
||||||
|
use pqcrypto_traits::kem::{Ciphertext, PublicKey, SharedSecret};
|
||||||
|
use rust_util::{opt_result, XResult};
|
||||||
|
|
||||||
|
pub fn compute_kyber1024_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let public_key_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse Kyber1024 public key hex failed: {}");
|
||||||
|
let public_key = opt_result!(Kyber1024PublicKey::from_bytes(&public_key_bytes), "Parse Kyber1024 public key failed: {}");
|
||||||
|
let (shared_secret, ciphertext) = kyber1024::encapsulate(&public_key);
|
||||||
|
|
||||||
|
Ok((shared_secret.as_bytes().to_vec(), ciphertext.as_bytes().to_vec()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
61
src/util_enc_file.rs
Normal file
61
src/util_enc_file.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use rust_util::{debugging, iff, opt_result, simple_error, XResult};
|
||||||
|
|
||||||
|
use crate::compress;
|
||||||
|
use crate::consts::{TINY_ENC_COMPRESSED_MAGIC_TAG, TINY_ENC_MAGIC_TAG};
|
||||||
|
use crate::spec::TinyEncryptMeta;
|
||||||
|
|
||||||
|
pub fn write_tiny_encrypt_meta(write: &mut impl Write, meta: &TinyEncryptMeta, compress_meta: bool) -> XResult<usize> {
|
||||||
|
let meta_tag = iff!(compress_meta, TINY_ENC_COMPRESSED_MAGIC_TAG, TINY_ENC_MAGIC_TAG);
|
||||||
|
opt_result!(write.write_all(&meta_tag.to_be_bytes()), "Write tag failed: {}");
|
||||||
|
let mut encrypted_meta_bytes = opt_result!(serde_json::to_vec(&meta), "Generate meta json bytes failed: {}");
|
||||||
|
if compress_meta {
|
||||||
|
encrypted_meta_bytes = opt_result!(
|
||||||
|
compress::compress_default(&encrypted_meta_bytes), "Compress encrypted meta failed: {}");
|
||||||
|
}
|
||||||
|
let encrypted_meta_bytes_len = encrypted_meta_bytes.len() as u32;
|
||||||
|
debugging!("Encrypted meta len: {}", encrypted_meta_bytes_len);
|
||||||
|
opt_result!(write.write_all(&encrypted_meta_bytes_len.to_be_bytes()), "Write meta len failed: {}");
|
||||||
|
opt_result!(write.write_all(&encrypted_meta_bytes), "Write meta failed: {}");
|
||||||
|
|
||||||
|
Ok(encrypted_meta_bytes.len() + 2 + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_tiny_encrypt_meta_and_normalize(r: &mut impl Read) -> XResult<(u32, bool, TinyEncryptMeta)> {
|
||||||
|
let mut meta_len_and_meta = read_tiny_encrypt_meta(r);
|
||||||
|
let _ = meta_len_and_meta.as_mut().map(|ml| ml.2.normalize_envelops());
|
||||||
|
meta_len_and_meta
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_tiny_encrypt_meta(r: &mut impl Read) -> XResult<(u32, bool, TinyEncryptMeta)> {
|
||||||
|
let mut meta_tag_buff = [0_u8; 2];
|
||||||
|
opt_result!(r.read_exact(&mut meta_tag_buff), "Read tag failed: {}");
|
||||||
|
let meta_tag = u16::from_be_bytes(meta_tag_buff);
|
||||||
|
debugging!("Found tag: {}", meta_tag);
|
||||||
|
let is_meta_normal = meta_tag == TINY_ENC_MAGIC_TAG;
|
||||||
|
let is_meta_compressed = meta_tag == TINY_ENC_COMPRESSED_MAGIC_TAG;
|
||||||
|
if !is_meta_normal && !is_meta_compressed {
|
||||||
|
return simple_error!("Tag is not 0x01 or 0x02, but is: 0x{:x}", meta_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut meta_length_buff = [0_u8; 4];
|
||||||
|
opt_result!(r.read_exact(&mut meta_length_buff), "Read length failed: {}");
|
||||||
|
let meta_length = u32::from_be_bytes(meta_length_buff);
|
||||||
|
if meta_length > 10 * 1024 * 1024 {
|
||||||
|
return simple_error!("Meta too large: {}", meta_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
debugging!("Encrypted meta length: {}", meta_length);
|
||||||
|
let mut meta_buff = vec![0; meta_length as usize];
|
||||||
|
opt_result!(r.read_exact(meta_buff.as_mut_slice()), "Read meta failed: {}");
|
||||||
|
|
||||||
|
debugging!("Tiny enc meta compressed: {}", is_meta_compressed);
|
||||||
|
if is_meta_compressed {
|
||||||
|
meta_buff = opt_result!(compress::decompress(&meta_buff), "Decompress meta failed: {}");
|
||||||
|
debugging!("Encrypted meta decompressed: {} byte(s) -> {} byte(s)", meta_length, meta_buff.len());
|
||||||
|
}
|
||||||
|
debugging!("Encrypted meta: {}", String::from_utf8_lossy(&meta_buff));
|
||||||
|
|
||||||
|
Ok((meta_length, is_meta_compressed, opt_result!(serde_json::from_slice(&meta_buff), "Parse meta failed: {}")))
|
||||||
|
}
|
||||||
91
src/util_env.rs
Normal file
91
src/util_env.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
use rust_util::util_env as rust_util_env;
|
||||||
|
use rust_util::{debugging, util_env, warning};
|
||||||
|
|
||||||
|
use crate::consts;
|
||||||
|
|
||||||
|
pub const TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM: &str = "TINY_ENCRYPT_DEFAULT_ALGORITHM";
|
||||||
|
pub const TINY_ENCRYPT_ENV_DEFAULT_COMPRESS: &str = "TINY_ENCRYPT_DEFAULT_COMPRESS";
|
||||||
|
pub const TINY_ENCRYPT_ENV_NO_PROGRESS: &str = "TINY_ENCRYPT_NO_PROGRESS";
|
||||||
|
pub const TINY_ENCRYPT_ENV_USE_DIALOGUER: &str = "TINY_ENCRYPT_USE_DIALOGUER";
|
||||||
|
pub const TINY_ENCRYPT_ENV_PIN: &str = "TINY_ENCRYPT_PIN";
|
||||||
|
pub const TINY_ENCRYPT_ENV_KEY_ID: &str = "TINY_ENCRYPT_KEY_ID";
|
||||||
|
pub const TINY_ENCRYPT_ENV_AUTO_SELECT_KEY_IDS: &str = "TINY_ENCRYPT_AUTO_SELECT_KEY_IDS";
|
||||||
|
pub const TINY_ENCRYPT_EVN_AUTO_COMPRESS_EXTS: &str = "TINY_ENCRYPT_AUTO_COMPRESS_EXTS";
|
||||||
|
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_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> {
|
||||||
|
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);
|
||||||
|
if let Some(env_algorithm) = env_default_algorithm {
|
||||||
|
let lower_default_algorithm = env_algorithm.to_lowercase();
|
||||||
|
match lower_default_algorithm.as_str() {
|
||||||
|
"aes" | "aes/gcm" => return Some(consts::TINY_ENC_AES_GCM),
|
||||||
|
"chacha20" | "chacha20/poly1305" => return Some(consts::TINY_ENC_CHACHA20_POLY1305),
|
||||||
|
_ => { warning!("Not matched any algorithm: {}", env_algorithm); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pin() -> Option<String> {
|
||||||
|
env::var(TINY_ENCRYPT_ENV_PIN).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_key_id() -> Option<String> {
|
||||||
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_KEY_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_gpg_cmd() -> Option<String> {
|
||||||
|
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> {
|
||||||
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_PIN_ENTRY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_auto_select_key_ids() -> Option<Vec<String>> {
|
||||||
|
get_env_with_split(TINY_ENCRYPT_ENV_AUTO_SELECT_KEY_IDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_auto_compress_file_exts() -> Option<Vec<String>> {
|
||||||
|
get_env_with_split(TINY_ENCRYPT_EVN_AUTO_COMPRESS_EXTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_compress() -> Option<bool> {
|
||||||
|
rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS).map(|val| util_env::is_on(&val))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_no_progress() -> bool {
|
||||||
|
rust_util_env::is_env_on(TINY_ENCRYPT_ENV_NO_PROGRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_no_default_pin_hint() -> bool {
|
||||||
|
rust_util_env::is_env_on(TINY_ENCRYPT_ENV_NO_DEFAULT_PIN_HINT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_use_dialoguer() -> bool {
|
||||||
|
rust_util_env::is_env_on(TINY_ENCRYPT_ENV_USE_DIALOGUER)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_env_with_split(env_name: &str) -> Option<Vec<String>> {
|
||||||
|
let val = rust_util_env::env_var(env_name);
|
||||||
|
debugging!("Environment variable {} = {:?}", env_name, &val);
|
||||||
|
val.map(|env_values| {
|
||||||
|
env_values.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
34
src/util_envelop.rs
Normal file
34
src/util_envelop.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use rust_util::iff;
|
||||||
|
|
||||||
|
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
||||||
|
use crate::spec::TinyEncryptEnvelop;
|
||||||
|
|
||||||
|
pub fn format_envelop(envelop: &TinyEncryptEnvelop, config: &Option<TinyEncryptConfig>) -> String {
|
||||||
|
let config_envelop = config.as_ref().and_then(|c| c.find_by_kid(&envelop.kid));
|
||||||
|
let envelop_kid = config_envelop.and_then(|e| e.sid.as_ref())
|
||||||
|
.map(|sid| format!(", Sid: {}", sid))
|
||||||
|
.unwrap_or_else(|| format!(", Kid: {}", envelop.kid));
|
||||||
|
let envelop_desc = get_envelop_desc(envelop, &config_envelop);
|
||||||
|
let desc = envelop_desc.as_ref()
|
||||||
|
.map(|desc| format!(", Desc: {}", desc))
|
||||||
|
.unwrap_or_default();
|
||||||
|
format!("{}{}{}", with_width_type(&envelop.r#type.get_upper_name()), envelop_kid, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_envelop_desc(envelop: &TinyEncryptEnvelop, config_envelop: &Option<&TinyEncryptConfigEnvelop>) -> Option<String> {
|
||||||
|
if let Some(desc) = &envelop.desc {
|
||||||
|
return Some(desc.to_string());
|
||||||
|
}
|
||||||
|
if let Some(config_envelop) = config_envelop {
|
||||||
|
return config_envelop.desc.clone();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_width_type(s: &str) -> String {
|
||||||
|
with_width(s, 13)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_width(s: &str, width: usize) -> String {
|
||||||
|
iff!(s.len() < width, format!("{}{}", s, " ".repeat(width - s.len())), s.to_string())
|
||||||
|
}
|
||||||
25
src/util_file.rs
Normal file
25
src/util_file.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use fs_set_times::SystemTimeSpec;
|
||||||
|
use rust_util::{information, warning};
|
||||||
|
use rust_util::util_time::UnixEpochTime;
|
||||||
|
|
||||||
|
use crate::spec::EncEncryptedMeta;
|
||||||
|
|
||||||
|
pub fn update_file_time(enc_meta: Option<EncEncryptedMeta>, path: &str) {
|
||||||
|
if let Some(enc_meta) = &enc_meta {
|
||||||
|
let create_time = enc_meta.c_time.map(SystemTime::from_millis);
|
||||||
|
let modify_time = enc_meta.m_time.map(SystemTime::from_millis);
|
||||||
|
if create_time.is_some() || modify_time.is_some() {
|
||||||
|
let set_times_result = fs_set_times::set_times(
|
||||||
|
path,
|
||||||
|
create_time.map(SystemTimeSpec::Absolute),
|
||||||
|
modify_time.map(SystemTimeSpec::Absolute),
|
||||||
|
);
|
||||||
|
match set_times_result {
|
||||||
|
Ok(_) => information!("Set file time succeed."),
|
||||||
|
Err(e) => warning!("Set file time failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/util_gpg.rs
Normal file
107
src/util_gpg.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Command encrypt use GnuPG(GPG)
|
||||||
|
/*
|
||||||
|
echo message | gpg -r KEY_ID -e -a --no-comment --comment "tiny-encrypt-1.6.0 - KEY_ID"
|
||||||
|
|
||||||
|
Success message:
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
Comment: tiny-encrypt-1.6.0 - C0FAD5E563B80E819603B0D9FFC2A910806894FD
|
||||||
|
|
||||||
|
hF4DcCBclRkzzAMSAQdA6nLd40IPxZF62Q54t2bpwvFXsG0Wy6SYxGEp1/K6rWgw
|
||||||
|
jgSx2ZiCntadkFrH35MJAYOx/DVW6ngxIic8hO+liBZfqI1lv7vlvVfs4sAe1bqK
|
||||||
|
0kcBDqp5SGNx2ENiDA4IbqDAp7JppQpEZrWJd2FGdbKviRyprVYgGILGJcMZQVNJ
|
||||||
|
agJfGGj7HPf5IbffrWWWyfE7oNCkSDZ2bw==
|
||||||
|
=tANz
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
|
||||||
|
Failed message:
|
||||||
|
gpg: C0FAD5E563B80E819603B0D9FFC2A910806894FF: skipped: No public key
|
||||||
|
gpg: [stdin]: encryption failed: No public key
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Command decrypt use GnuPG(GPG)
|
||||||
|
/*
|
||||||
|
echo '-----BEGIN PGP MESSAGE-----
|
||||||
|
Comment: tiny-encrypt-1.6.0
|
||||||
|
|
||||||
|
hF4DcCBclRkzzAMSAQdAESdgetyKsgdAR6kps5ThpP2TcZB0hyGrmDqGj/1+lXIw
|
||||||
|
c9cam+BxFkDT7mZafuls0tV4MwHwKi2z1gQFNgTWuC45rpXyK7BFg74Rua+qLzvJ
|
||||||
|
0kcBpKSZIvQ/lX8JQ4hM41k6ymeYBQMC2nzmhwl/g9NBFyn5+dlEzDiZvL8YyQFT
|
||||||
|
IDGbLcEBW7a0B02ZKZ4ELyIDp94hdcbhrg==
|
||||||
|
=QEBj
|
||||||
|
-----END PGP MESSAGE-----' | gpg -d
|
||||||
|
|
||||||
|
Failed message:
|
||||||
|
gpg: encrypted with 256-bit ECDH key, ID 70205C951933CC03, created 2023-10-05
|
||||||
|
"Hatter Jiang (2023) <jht5945@gmail.com>"
|
||||||
|
gpg: public key decryption failed: Operation cancelled
|
||||||
|
gpg: decryption failed: No secret key
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use rust_util::{opt_result, opt_value_result, simple_error, XResult};
|
||||||
|
|
||||||
|
use crate::util_env;
|
||||||
|
|
||||||
|
pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult<String> {
|
||||||
|
let message_hex = hex::encode(message);
|
||||||
|
|
||||||
|
let mut cmd = Command::new(get_gpg_cmd());
|
||||||
|
let gpg_encrypt_result = cmd
|
||||||
|
.args([
|
||||||
|
"-e", "-a", "--no-comment",
|
||||||
|
"-r", key_id,
|
||||||
|
"--comment", &format!("tiny-encrypt-v{} - {}", env!("CARGO_PKG_VERSION"), key_id)
|
||||||
|
])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
let gpg_encrypt = opt_result!(gpg_encrypt_result, "Run GPG encrypt failed: {}");
|
||||||
|
let mut gpg_encrypt_stdin = opt_value_result!(gpg_encrypt.stdin.as_ref(), "Get GPG encrypt stdin failed.");
|
||||||
|
opt_result!(gpg_encrypt_stdin.write_all(message_hex.as_bytes()), "Write GPG encrypt stdin failed: {}");
|
||||||
|
let encrypt_result = gpg_encrypt.wait_with_output();
|
||||||
|
|
||||||
|
let encrypt_output = opt_result!(encrypt_result, "GPG encrypt failed: {}");
|
||||||
|
let stdout = String::from_utf8_lossy(&encrypt_output.stdout).to_string();
|
||||||
|
let stderr = String::from_utf8_lossy(&encrypt_output.stderr).to_string();
|
||||||
|
if !encrypt_output.status.success() {
|
||||||
|
return simple_error!(
|
||||||
|
"GPG encrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
|
||||||
|
encrypt_output.status.code(), stdout, stderr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpg_decrypt(message: &str) -> XResult<Vec<u8>> {
|
||||||
|
let mut cmd = Command::new(get_gpg_cmd());
|
||||||
|
let gpg_decrypt_result = cmd
|
||||||
|
.arg("-d")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
let gpg_encrypt = opt_result!(gpg_decrypt_result, "Run GPG decrypt failed: {}");
|
||||||
|
let mut gpg_encrypt_stdin = opt_value_result!(gpg_encrypt.stdin.as_ref(), "Get GPG decrypt stdin failed.");
|
||||||
|
opt_result!(gpg_encrypt_stdin.write_all(message.as_bytes()), "Write GPG decrypt stdin failed: {}");
|
||||||
|
let decrypt_result = gpg_encrypt.wait_with_output();
|
||||||
|
|
||||||
|
let decrypt_output = opt_result!(decrypt_result, "GPG decrypt failed: {}");
|
||||||
|
let stdout = String::from_utf8_lossy(&decrypt_output.stdout).to_string();
|
||||||
|
let stderr = String::from_utf8_lossy(&decrypt_output.stderr).to_string();
|
||||||
|
if !decrypt_output.status.success() {
|
||||||
|
return simple_error!(
|
||||||
|
"GPG decrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
|
||||||
|
decrypt_output.status.code(), stdout, stderr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let decrypted = opt_result!(hex::decode(stdout.trim()), "Decode decrypted message failed: {}");
|
||||||
|
Ok(decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_gpg_cmd() -> String {
|
||||||
|
util_env::get_gpg_cmd().unwrap_or("gpg".to_string())
|
||||||
|
}
|
||||||
47
src/util_keychainkey.rs
Normal file
47
src/util_keychainkey.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use base64::engine::general_purpose::STANDARD;
|
||||||
|
use base64::Engine;
|
||||||
|
use rust_util::{simple_error, XResult};
|
||||||
|
use swift_secure_enclave_tool_rs::{ControlFlag, KeyMlKem, KeyPurpose};
|
||||||
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
|
|
||||||
|
pub fn is_support_se() -> bool {
|
||||||
|
swift_secure_enclave_tool_rs::is_secure_enclave_supported().unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_data(
|
||||||
|
envelop_type: TinyEncryptEnvelopType,
|
||||||
|
private_key_base64: &str,
|
||||||
|
ephemeral_public_key_bytes: &[u8],
|
||||||
|
) -> XResult<Vec<u8>> {
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
Ok(shared_secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_se_p256_keypair(control_flag: ControlFlag) -> XResult<(String, String)> {
|
||||||
|
if !is_support_se() {
|
||||||
|
return simple_error!("Secure enclave is not supported.");
|
||||||
|
}
|
||||||
|
let key_material =
|
||||||
|
swift_secure_enclave_tool_rs::generate_keypair(KeyPurpose::KeyAgreement, control_flag)?;
|
||||||
|
Ok((
|
||||||
|
hex::encode(&key_material.public_key_point),
|
||||||
|
STANDARD.encode(&key_material.private_key_representation),
|
||||||
|
))
|
||||||
|
}
|
||||||
294
src/util_keychainstatic.rs
Normal file
294
src/util_keychainstatic.rs
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use pqcrypto_kyber::kyber1024;
|
||||||
|
use pqcrypto_kyber::kyber1024::Ciphertext as Kyber1024Ciphertext;
|
||||||
|
use pqcrypto_kyber::kyber1024::PublicKey as Kyber1024PublicKey;
|
||||||
|
use pqcrypto_kyber::kyber1024::SecretKey as Kyber1024SecretKey;
|
||||||
|
use rust_util::{debugging, opt_result, opt_value_result, simple_error, util_file, XResult};
|
||||||
|
use security_framework::os::macos::keychain::{CreateOptions, SecKeychain};
|
||||||
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
const X2559_PLAIN_PREFIX: &str = "x25519-plain:";
|
||||||
|
const KYBER1024_PLAIN_PREFIX: &str = "kyber1024-plain:";
|
||||||
|
const KEYCHAIN_KEY_PREFIX: &str = "keychain:";
|
||||||
|
|
||||||
|
pub struct KeychainKey {
|
||||||
|
pub keychain_name: String,
|
||||||
|
pub service_name: String,
|
||||||
|
pub key_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum KeychainStaticSecretAlgorithm {
|
||||||
|
X25519,
|
||||||
|
Kyber1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeychainStaticSecretAlgorithm {
|
||||||
|
pub fn prefix(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::X25519 => X2559_PLAIN_PREFIX,
|
||||||
|
Self::Kyber1024 => KYBER1024_PLAIN_PREFIX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_prefix(str: &str) -> Option<Self> {
|
||||||
|
if str.starts_with(X2559_PLAIN_PREFIX) {
|
||||||
|
Some(Self::X25519)
|
||||||
|
} else if str.starts_with(KYBER1024_PLAIN_PREFIX) {
|
||||||
|
Some(Self::Kyber1024)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KeychainStaticSecret {
|
||||||
|
pub algo: KeychainStaticSecretAlgorithm,
|
||||||
|
pub secret: Vec<u8>,
|
||||||
|
pub public: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zeroize for KeychainStaticSecret {
|
||||||
|
fn zeroize(&mut self) {
|
||||||
|
self.secret.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeychainStaticSecret {
|
||||||
|
pub fn parse_bytes(bs: &[u8]) -> XResult<Self> {
|
||||||
|
let key_str = opt_result!(String::from_utf8(bs.to_vec()), "Parse static secret failed: {}");
|
||||||
|
Self::parse(&key_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(key: &str) -> XResult<Self> {
|
||||||
|
let algo = opt_value_result!(
|
||||||
|
KeychainStaticSecretAlgorithm::from_prefix(key), "Unknown static secret: {}", key);
|
||||||
|
let extract_key_hex = &key[algo.prefix().len()..];
|
||||||
|
let extract_key = opt_result!(hex::decode(extract_key_hex), "Decode static secret plain key failed: {}");
|
||||||
|
let (secret, public) = match algo {
|
||||||
|
KeychainStaticSecretAlgorithm::X25519 => {
|
||||||
|
(extract_key, None)
|
||||||
|
}
|
||||||
|
KeychainStaticSecretAlgorithm::Kyber1024 => {
|
||||||
|
// pub const PQCLEAN_KYBER1024_CLEAN_CRYPTO_SECRETKEYBYTES: usize = 3168;
|
||||||
|
// pub const PQCLEAN_KYBER1024_CLEAN_CRYPTO_PUBLICKEYBYTES: usize = 1568;
|
||||||
|
let secret_key_bytes_len = 3168;
|
||||||
|
let public_key_bytes_len = 1568;
|
||||||
|
if extract_key.len() != secret_key_bytes_len + public_key_bytes_len {
|
||||||
|
return simple_error!("Bad kyber1024 secret and public keys.");
|
||||||
|
}
|
||||||
|
(extract_key[0..secret_key_bytes_len].to_vec(), Some(extract_key[secret_key_bytes_len..].to_vec()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
algo,
|
||||||
|
secret,
|
||||||
|
public,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_str(&self) -> String {
|
||||||
|
let mut v = String::new();
|
||||||
|
v.push_str(self.algo.prefix());
|
||||||
|
v.push_str(&hex::encode(&self.secret));
|
||||||
|
if let Some(public) = &self.public {
|
||||||
|
v.push_str(&hex::encode(public));
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_x25519_bytes(secret: &[u8]) -> Self {
|
||||||
|
Self::from_bytes(KeychainStaticSecretAlgorithm::X25519, secret, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_kyber1024_bytes(secret: &[u8], public: &[u8]) -> Self {
|
||||||
|
Self::from_bytes(KeychainStaticSecretAlgorithm::Kyber1024, secret, Some(public))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(algo: KeychainStaticSecretAlgorithm, secret: &[u8], public: Option<&[u8]>) -> Self {
|
||||||
|
Self {
|
||||||
|
algo,
|
||||||
|
secret: secret.to_vec(),
|
||||||
|
public: public.map(|p| p.to_vec()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_kyber1204_static_secret(&self) -> XResult<(Kyber1024SecretKey, Kyber1024PublicKey)> {
|
||||||
|
use pqcrypto_traits::kem::{PublicKey, SecretKey};
|
||||||
|
let secret_key = opt_result!(Kyber1024SecretKey::from_bytes(&self.secret),
|
||||||
|
"Parse kyber1204 private key failed: {}");
|
||||||
|
let public_key = opt_result!(match &self.public {
|
||||||
|
None => return simple_error!("Kyber1204 public key not found."),
|
||||||
|
Some(public) => Kyber1024PublicKey::from_bytes(public),
|
||||||
|
}, "Parse kyber1204 public key failed: {}");
|
||||||
|
Ok((secret_key, public_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_x25519_static_secret(&self) -> XResult<StaticSecret> {
|
||||||
|
let secret_slice = self.secret.as_slice();
|
||||||
|
let mut inner_secret: [u8; 32] = opt_result!(secret_slice.try_into(), "X25519 secret key error: {}");
|
||||||
|
let static_secret = StaticSecret::from(inner_secret);
|
||||||
|
inner_secret.zeroize();
|
||||||
|
Ok(static_secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_x25519_public_key(&self) -> XResult<PublicKey> {
|
||||||
|
let static_secret = self.to_x25519_static_secret()?;
|
||||||
|
let public_key: PublicKey = (&static_secret).into();
|
||||||
|
Ok(public_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeychainKey {
|
||||||
|
pub fn from(keychain_name: &str, service_name: &str, key_name: &str) -> Self {
|
||||||
|
debugging!("Keychain key: {} - {} - {}", keychain_name, service_name, key_name);
|
||||||
|
Self {
|
||||||
|
keychain_name: keychain_name.to_string(),
|
||||||
|
service_name: service_name.to_string(),
|
||||||
|
key_name: key_name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(keychain_key: &str) -> XResult<Self> {
|
||||||
|
if !keychain_key.starts_with(KEYCHAIN_KEY_PREFIX) {
|
||||||
|
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||||
|
}
|
||||||
|
//keychain:keychain_name:service_name:key_name
|
||||||
|
let keychain_key_parts = keychain_key.split(':').collect::<Vec<_>>();
|
||||||
|
if keychain_key_parts.len() != 4 {
|
||||||
|
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
keychain_name: keychain_key_parts[1].to_string(),
|
||||||
|
service_name: keychain_key_parts[2].to_string(),
|
||||||
|
key_name: keychain_key_parts[3].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_str(&self) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push_str(KEYCHAIN_KEY_PREFIX);
|
||||||
|
s.push_str(&self.keychain_name);
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&self.service_name);
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&self.key_name);
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_password(&self) -> XResult<Option<Vec<u8>>> {
|
||||||
|
let sec_keychain = self.get_keychain()?;
|
||||||
|
debugging!("Try find generic password: {}.{}", &self.service_name, &self.key_name);
|
||||||
|
match sec_keychain.find_generic_password(&self.service_name, &self.key_name) {
|
||||||
|
Ok((item_password, _keychain_item)) => {
|
||||||
|
Ok(Some(item_password.as_ref().to_vec()))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Get password: {} failed: {}", &self.to_str(), e);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_password(&self, password: &[u8]) -> XResult<()> {
|
||||||
|
let sec_keychain = self.get_keychain()?;
|
||||||
|
if sec_keychain.find_generic_password(&self.service_name, &self.key_name).is_ok() {
|
||||||
|
return simple_error!("Password {}.{} exists", &self.service_name, &self.key_name);
|
||||||
|
}
|
||||||
|
opt_result!(
|
||||||
|
sec_keychain.set_generic_password(&self.service_name, &self.key_name, password),
|
||||||
|
"Set password {}.{} error: {}", &self.service_name, &self.key_name
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_keychain(&self) -> XResult<SecKeychain> {
|
||||||
|
if !self.keychain_name.is_empty() {
|
||||||
|
let keychain_file_name = format!("{}.keychain", &self.keychain_name);
|
||||||
|
debugging!("Open or create keychain: {}", &keychain_file_name);
|
||||||
|
let keychain_exists = check_keychain_exists(&keychain_file_name);
|
||||||
|
if keychain_exists {
|
||||||
|
Ok(opt_result!(SecKeychain::open(&keychain_file_name), "Open keychain: {}, failed: {}", &keychain_file_name))
|
||||||
|
} else {
|
||||||
|
match CreateOptions::new().prompt_user(true).create(&keychain_file_name) {
|
||||||
|
Ok(sec_keychain) => Ok(sec_keychain),
|
||||||
|
Err(ce) => match SecKeychain::open(&keychain_file_name) {
|
||||||
|
Ok(sec_keychain) => Ok(sec_keychain),
|
||||||
|
Err(oe) => simple_error!("Create keychain: {}, failed: {}, open also failed: {}", &self.keychain_name, ce, oe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(opt_result!(SecKeychain::default(), "Get keychain failed: {}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_x25519_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let static_x25519 = opt_value_result!(keychain_key.get_password()?, "Static X25519 not found: {}", &keychain_key.to_str());
|
||||||
|
|
||||||
|
let x25519_static_secret = KeychainStaticSecret::parse_bytes(&static_x25519)?;
|
||||||
|
let static_secret = x25519_static_secret.to_x25519_static_secret()?;
|
||||||
|
let inner_ephemeral_public_key: [u8; 32] = opt_result!(
|
||||||
|
ephemeral_public_key_bytes.try_into(), "X25519 public key error: {}");
|
||||||
|
let ephemeral_public_key = PublicKey::from(inner_ephemeral_public_key);
|
||||||
|
let shared_secret = static_secret.diffie_hellman(&ephemeral_public_key);
|
||||||
|
|
||||||
|
Ok(shared_secret.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_kyber1204_data(keychain_key: &KeychainKey, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
use pqcrypto_traits::kem::{Ciphertext, SharedSecret};
|
||||||
|
let static_kyber1204 = opt_value_result!(keychain_key.get_password()?, "Static Kyber1204 not found: {}", &keychain_key.to_str());
|
||||||
|
|
||||||
|
let kyber1204_static_secret = KeychainStaticSecret::parse_bytes(&static_kyber1204)?;
|
||||||
|
let (static_secret, _) = kyber1204_static_secret.to_kyber1204_static_secret()?;
|
||||||
|
let ciphertext = opt_result!(
|
||||||
|
Kyber1024Ciphertext::from_bytes(ephemeral_public_key_bytes), "Parse kyber1204 ciphertext failed: {}");
|
||||||
|
let shared_secret = kyber1024::decapsulate(&ciphertext, &static_secret);
|
||||||
|
|
||||||
|
Ok(shared_secret.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_static_x25519_secret() -> (String, PublicKey) {
|
||||||
|
let static_secret = StaticSecret::random();
|
||||||
|
let public_key: PublicKey = (&static_secret).into();
|
||||||
|
let static_secret_bytes = static_secret.as_bytes();
|
||||||
|
let x25519_static_secret = KeychainStaticSecret::from_x25519_bytes(static_secret_bytes);
|
||||||
|
(x25519_static_secret.to_str(), public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_static_kyber1024_secret() -> (String, Kyber1024PublicKey) {
|
||||||
|
use pqcrypto_traits::kem::{PublicKey, SecretKey};
|
||||||
|
let (public_key, private_key) = kyber1024::keypair();
|
||||||
|
let static_secret_bytes = private_key.as_bytes();
|
||||||
|
let static_public_bytes = public_key.as_bytes();
|
||||||
|
let kyber1024_static_secret =
|
||||||
|
KeychainStaticSecret::from_kyber1024_bytes(static_secret_bytes, static_public_bytes);
|
||||||
|
(kyber1024_static_secret.to_str(), public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_keychain_exists(keychain_file_name: &str) -> bool {
|
||||||
|
let keychain_path = PathBuf::from(util_file::resolve_file_path("~/Library/Keychains/"));
|
||||||
|
match keychain_path.read_dir() {
|
||||||
|
Ok(read_dir) => {
|
||||||
|
for dir in read_dir {
|
||||||
|
match dir {
|
||||||
|
Ok(dir) => if let Some(file_name) = dir.file_name().to_str() {
|
||||||
|
if file_name.starts_with(keychain_file_name) {
|
||||||
|
debugging!("Found key chain file: {:?}", dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Read path sub dir: {:?} failed: {}", keychain_path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Read path: {:?} failed: {}", keychain_path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
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.")
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
use p384::ecdh::EphemeralSecret;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use rust_util::{opt_result, XResult};
|
|
||||||
|
|
||||||
use p384::pkcs8::EncodePublicKey;
|
|
||||||
use p384::{EncodedPoint, PublicKey};
|
|
||||||
use p384::elliptic_curve::sec1::FromEncodedPoint;
|
|
||||||
|
|
||||||
pub fn compute_p384_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
|
||||||
let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}");
|
|
||||||
let encoded_point = opt_result!(EncodedPoint::from_bytes(public_key_point_bytes), "Parse public key point failed: {}");
|
|
||||||
let public_key = PublicKey::from_encoded_point(&encoded_point).unwrap();
|
|
||||||
|
|
||||||
let esk = EphemeralSecret::random(&mut OsRng);
|
|
||||||
let epk = esk.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: {}");
|
|
||||||
Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec()))
|
|
||||||
}
|
|
||||||
@@ -5,19 +5,19 @@ use rust_util::{failure, opt_result, opt_value_result, simple_error, success, wa
|
|||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
pub fn read_and_verify_openpgp_pin(trans: &mut OpenPgpTransaction, pin: &Option<String>) -> XResult<()> {
|
pub fn read_and_verify_openpgp_pin(trans: &mut OpenPgpTransaction, pin: &Option<String>) -> XResult<()> {
|
||||||
let pin = util::read_pin(pin);
|
let pin = util::read_pin(pin)?;
|
||||||
if let Err(e) = trans.verify_pw1_user(pin.as_ref()) {
|
if let Err(e) = trans.verify_pw1_user(pin.as_ref()) {
|
||||||
failure!("Verify user pin failed: {}", e);
|
failure!("Verify user pin failed: {}", e);
|
||||||
return simple_error!("User pin verify failed: {}", e);
|
return simple_error!("User pin verify failed: {}", e);
|
||||||
}
|
}
|
||||||
success!("User pin verify success!");
|
success!("User pin verify success!");
|
||||||
|
util::zeroize(pin);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_openpgp() -> XResult<OpenPgp> {
|
pub fn get_openpgp() -> XResult<OpenPgp> {
|
||||||
let card = match get_card() {
|
let card = match get_card() {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failure!("Get PGP card failed: {}", e);
|
|
||||||
return simple_error!("Get card failed: {}", e);
|
return simple_error!("Get card failed: {}", e);
|
||||||
}
|
}
|
||||||
Ok(card) => card
|
Ok(card) => card
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use rust_util::{information, simple_error, XResult};
|
use rust_util::{debugging, information, print_ex, simple_error, XResult};
|
||||||
use yubikey::piv::{RetiredSlotId, SlotId};
|
use yubikey::piv::{RetiredSlotId, SlotId};
|
||||||
|
|
||||||
use crate::config::TinyEncryptConfig;
|
use crate::config::TinyEncryptConfig;
|
||||||
|
|
||||||
pub fn read_piv_slot(config: &Option<TinyEncryptConfig>, kid: &str, slot: &Option<String>) -> XResult<String> {
|
pub fn read_piv_slot(config: &Option<TinyEncryptConfig>, kid: &str, slot: &Option<String>, silent: bool) -> XResult<String> {
|
||||||
match slot {
|
match slot {
|
||||||
Some(slot) => Ok(slot.to_string()),
|
Some(slot) => Ok(slot.to_string()),
|
||||||
None => {
|
None => {
|
||||||
if let Some(config) = config {
|
if let Some(config) = config {
|
||||||
if let Some(first_arg) = config.find_first_arg_by_kid(kid) {
|
if let Some(first_arg) = config.find_first_arg_by_kid(kid) {
|
||||||
|
if silent {
|
||||||
|
debugging!("Found kid: {}'s slot: {}", kid, first_arg);
|
||||||
|
} else {
|
||||||
information!("Found kid: {}'s slot: {}", kid, first_arg);
|
information!("Found kid: {}'s slot: {}", kid, first_arg);
|
||||||
|
}
|
||||||
return Ok(first_arg.to_string());
|
return Ok(first_arg.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print!("Input slot(eg 82, 83 ...): ");
|
print_ex!("Input slot(eg 82, 83 ...): ");
|
||||||
io::stdout().flush().ok();
|
io::stdout().flush().ok();
|
||||||
let mut buff = String::new();
|
let mut buff = String::new();
|
||||||
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
||||||
|
|||||||
46
src/util_progress.rs
Normal file
46
src/util_progress.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use rust_util::{debugging, util_msg};
|
||||||
|
|
||||||
|
use crate::util_env;
|
||||||
|
|
||||||
|
const PB_PROGRESS: &str = "#-";
|
||||||
|
const PB_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta})";
|
||||||
|
|
||||||
|
|
||||||
|
pub enum Progress {
|
||||||
|
NoProgress,
|
||||||
|
Progress(ProgressBar),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Progress {
|
||||||
|
pub fn new(total: u64) -> Self {
|
||||||
|
let no_progress = util_env::get_no_progress();
|
||||||
|
let is_atty = util_msg::is_atty();
|
||||||
|
|
||||||
|
if no_progress || !is_atty {
|
||||||
|
debugging!("No progress: [{}, {}]", no_progress, is_atty);
|
||||||
|
Self::NoProgress
|
||||||
|
} else {
|
||||||
|
let progress_bar = ProgressBar::new(total);
|
||||||
|
progress_bar.set_style(ProgressStyle::default_bar()
|
||||||
|
.template(PB_TEMPLATE).expect("SHOULD NOT FAIL")
|
||||||
|
.progress_chars(PB_PROGRESS)
|
||||||
|
);
|
||||||
|
Self::Progress(progress_bar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self, position: u64) {
|
||||||
|
match self {
|
||||||
|
Progress::NoProgress => {}
|
||||||
|
Progress::Progress(progress_bar) => progress_bar.set_position(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(&self) {
|
||||||
|
match self {
|
||||||
|
Progress::NoProgress => {}
|
||||||
|
Progress::Progress(progress_bar) => progress_bar.finish_and_clear(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,40 +4,21 @@ use x509_parser::prelude::FromDer;
|
|||||||
use x509_parser::public_key::RSAPublicKey;
|
use x509_parser::public_key::RSAPublicKey;
|
||||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||||
|
|
||||||
use crate::util::decode_base64;
|
use crate::util;
|
||||||
|
|
||||||
/// Parse RSA Subject Public Key Info(SPKI) to Rsa Public Key
|
/// Parse RSA Subject Public Key Info(SPKI) to Rsa Public Key
|
||||||
pub fn parse_spki(pem: &str) -> XResult<RsaPublicKey> {
|
pub fn parse_spki(pem: &str) -> XResult<RsaPublicKey> {
|
||||||
let der = pem_to_der_bytes(pem)?;
|
let der = util::parse_pem(pem)?;
|
||||||
let spki = opt_result!(SubjectPublicKeyInfo::from_der(&der), "Parse SKPI failed: {}").1;
|
let spki = opt_result!(SubjectPublicKeyInfo::from_der(&der), "Parse SKPI failed: {}").1;
|
||||||
let public_key_der = spki.subject_public_key.data;
|
let public_key_der = spki.subject_public_key.data;
|
||||||
let public_key = opt_result!(RSAPublicKey::from_der(&public_key_der), "Parse RSA public key failed: {}").1;
|
let public_key = opt_result!(RSAPublicKey::from_der(&public_key_der), "Parse RSA public key failed: {}").1;
|
||||||
let rsa_public_key = opt_result!(RsaPublicKey::new(
|
let rsa_public_key = opt_result!(RsaPublicKey::new(
|
||||||
BigUint::from_bytes_be(public_key.modulus),
|
BigUint::from_bytes_be(public_key.modulus),
|
||||||
BigUint::from_bytes_be(public_key.exponent),
|
BigUint::from_bytes_be(public_key.exponent),
|
||||||
), "Parse Rsa public key failed: {}");
|
), "Parse RSA public key failed: {}");
|
||||||
Ok(rsa_public_key)
|
Ok(rsa_public_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pem_to_der_bytes(pem: &str) -> XResult<Vec<u8>> {
|
|
||||||
let mut pem = pem.trim().to_owned();
|
|
||||||
if pem.starts_with("-----BEGIN") {
|
|
||||||
let mut filter_lines = vec![];
|
|
||||||
let lines = pem.lines().skip(1);
|
|
||||||
for ln in lines {
|
|
||||||
if ln.starts_with("-----END") {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
filter_lines.push(ln.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pem = filter_lines.join("");
|
|
||||||
}
|
|
||||||
pem = pem.chars().filter(|c| *c != '\n' && *c != '\r').clone().collect::<String>();
|
|
||||||
|
|
||||||
Ok(opt_result!(decode_base64(&pem), "Decode pem or der failed: {}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_spki() {
|
fn test_parse_spki() {
|
||||||
use rsa::traits::PublicKeyParts;
|
use rsa::traits::PublicKeyParts;
|
||||||
@@ -110,7 +91,7 @@ qaCoQsuRtnowGKzrbVdinukd1wj0LkBuz2oNMB3qsXyq7QtOxiFTuKkMOoQNUiCE
|
|||||||
KQIDAQAB
|
KQIDAQAB
|
||||||
-----END PUBLIC KEY-----";
|
-----END PUBLIC KEY-----";
|
||||||
let public_key = parse_spki(public_key_pem).unwrap();
|
let public_key = parse_spki(public_key_pem).unwrap();
|
||||||
let private_key_der = pem_to_der_bytes(&private_key_pem).unwrap();
|
let private_key_der = util::parse_pem(&private_key_pem).unwrap();
|
||||||
let private_key_info = PrivateKeyInfo::from_der(&private_key_der).unwrap();
|
let private_key_info = PrivateKeyInfo::from_der(&private_key_der).unwrap();
|
||||||
let private_key = RsaPrivateKey::try_from(private_key_info).unwrap();
|
let private_key = RsaPrivateKey::try_from(private_key_info).unwrap();
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
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,19 +0,0 @@
|
|||||||
use rand::rngs::OsRng;
|
|
||||||
use rust_util::{opt_result, simple_error, XResult};
|
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
|
||||||
|
|
||||||
pub fn compute_x25519_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
|
||||||
let public_key_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse X25519 public key hex failed: {}");
|
|
||||||
if public_key_bytes.len() != 32 {
|
|
||||||
return simple_error!("Parse X25519 key failed: not 32 bytes");
|
|
||||||
}
|
|
||||||
let public_key_bytes: [u8; 32] = public_key_bytes.try_into().unwrap();
|
|
||||||
let public_key_card = PublicKey::from(public_key_bytes);
|
|
||||||
|
|
||||||
let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
|
|
||||||
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
|
||||||
|
|
||||||
let shared_secret = ephemeral_secret.diffie_hellman(&public_key_card);
|
|
||||||
|
|
||||||
Ok((shared_secret.as_bytes().to_vec(), ephemeral_public.as_bytes().to_vec()))
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use rust_util::{opt_result, simple_error, XResult};
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::util::{decode_base64_url_no_pad, encode_base64_url_no_pad};
|
use crate::util;
|
||||||
|
|
||||||
|
pub const WRAP_KEY_PREFIX: &str = "WK:";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -20,22 +22,38 @@ pub struct WrapKeyHeader {
|
|||||||
pub e_pub_key: String,
|
pub e_pub_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WrapKeyHeader {
|
||||||
|
pub fn from(enc_type: &str, ephemeral_spki: &[u8]) -> Self {
|
||||||
|
WrapKeyHeader {
|
||||||
|
kid: None,
|
||||||
|
enc: enc_type.to_string(),
|
||||||
|
e_pub_key: util::encode_base64_url_no_pad(ephemeral_spki),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_e_pub_key_bytes(self) -> XResult<Vec<u8>> {
|
||||||
|
Ok(opt_result!(util::decode_base64_url_no_pad(&self.e_pub_key), "Invalid envelop e_pub_key: {}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WrapKey {
|
impl WrapKey {
|
||||||
pub fn encode(&self) -> XResult<String> {
|
pub fn encode(&self) -> XResult<String> {
|
||||||
let mut buf = String::with_capacity(512);
|
let mut buf = String::with_capacity(512);
|
||||||
buf.push_str("WK:");
|
buf.push_str(WRAP_KEY_PREFIX);
|
||||||
let header = serde_json::to_string(&self.header)?;
|
let header = serde_json::to_string(&self.header)?;
|
||||||
let header_str = encode_base64_url_no_pad(header.as_bytes());
|
let header_str = util::encode_base64_url_no_pad(header.as_bytes());
|
||||||
|
let nonce_str = util::encode_base64_url_no_pad(&self.nonce);
|
||||||
|
let encrypted_data_str = util::encode_base64_url_no_pad(&self.encrypted_data);
|
||||||
buf.push_str(&header_str);
|
buf.push_str(&header_str);
|
||||||
buf.push('.');
|
buf.push('.');
|
||||||
buf.push_str(&encode_base64_url_no_pad(&self.nonce));
|
buf.push_str(&nonce_str);
|
||||||
buf.push('.');
|
buf.push('.');
|
||||||
buf.push_str(&encode_base64_url_no_pad(&self.encrypted_data));
|
buf.push_str(&encrypted_data_str);
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(wk: &str) -> XResult<WrapKey> {
|
pub fn parse(wk: &str) -> XResult<WrapKey> {
|
||||||
if !wk.starts_with("WK:") {
|
if !wk.starts_with(WRAP_KEY_PREFIX) {
|
||||||
return simple_error!("Wrap key string must starts with WK:");
|
return simple_error!("Wrap key string must starts with WK:");
|
||||||
}
|
}
|
||||||
let wks = wk.split('.').collect::<Vec<_>>();
|
let wks = wk.split('.').collect::<Vec<_>>();
|
||||||
@@ -43,13 +61,13 @@ impl WrapKey {
|
|||||||
return simple_error!("Invalid wrap key.");
|
return simple_error!("Invalid wrap key.");
|
||||||
}
|
}
|
||||||
let header = wks[0].chars().skip(3).collect::<String>();
|
let header = wks[0].chars().skip(3).collect::<String>();
|
||||||
let header_bytes = opt_result!(decode_base64_url_no_pad(&header), "Invalid wrap key header: {}");
|
let header_bytes = opt_result!(util::decode_base64_url_no_pad(&header), "Invalid wrap key header: {}");
|
||||||
let nonce = wks[1];
|
let nonce = wks[1];
|
||||||
let encrypted_data = wks[2];
|
let encrypted_data = wks[2];
|
||||||
let header_str = opt_result!(String::from_utf8(header_bytes), "Invalid wrap key header: {}");
|
let header_str = opt_result!(String::from_utf8(header_bytes), "Invalid wrap key header: {}");
|
||||||
let header: WrapKeyHeader = opt_result!(serde_json::from_str(&header_str), "Invalid wrap key header: {}");
|
let header: WrapKeyHeader = opt_result!(serde_json::from_str(&header_str), "Invalid wrap key header: {}");
|
||||||
let nonce = opt_result!(decode_base64_url_no_pad(nonce), "Invalid wrap key: {}");
|
let nonce = opt_result!(util::decode_base64_url_no_pad(nonce), "Invalid wrap key: {}");
|
||||||
let encrypted_data = opt_result!(decode_base64_url_no_pad(encrypted_data), "Invalid wrap key: {}");
|
let encrypted_data = opt_result!(util::decode_base64_url_no_pad(encrypted_data), "Invalid wrap key: {}");
|
||||||
Ok(WrapKey {
|
Ok(WrapKey {
|
||||||
header,
|
header,
|
||||||
nonce,
|
nonce,
|
||||||
|
|||||||
Reference in New Issue
Block a user