diff --git a/.github/workflows/nix-ci.yml b/.github/workflows/nix-ci.yml index 30a5eca..6157193 100644 --- a/.github/workflows/nix-ci.yml +++ b/.github/workflows/nix-ci.yml @@ -25,6 +25,26 @@ jobs: run: | nix flake check . --print-build-logs --max-jobs auto + nix_reproducible_check: + name: Ensure reproducibility (Nix) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v27 + + - uses: cachix/cachix-action@v14 + with: + name: naxdy-foss + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Build testbin twice + run: | + nix build .#staticrypt-testbin --print-build-logs -j auto + + nix build .#staticrypt-testbin --print-build-logs -j auto --rebuild + reproducible_check: name: Ensure reproducibility runs-on: ubuntu-latest @@ -44,17 +64,17 @@ jobs: - name: Ensure reproducibility run: | - cargo build --package staticrypt_testbin + cargo build --package staticrypt-testbin - sum1=$(sha256sum target/debug/staticrypt_testbin) + sum1=$(sha256sum target/debug/staticrypt-testbin) echo "Initial hash is $sum1" rm -rf target - cargo build --package staticrypt_testbin + cargo build --package staticrypt-testbin - sum2=$(sha256sum target/debug/staticrypt_testbin) + sum2=$(sha256sum target/debug/staticrypt-testbin) echo "Second hash is $sum2" diff --git a/Cargo.lock b/Cargo.lock index 783fc62..a91e7eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,23 +37,17 @@ dependencies = [ "subtle", ] -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cipher" @@ -76,9 +70,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core", @@ -153,9 +147,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -174,9 +168,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "ident_case" @@ -186,9 +180,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -205,25 +199,24 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "opaque-debug" @@ -233,9 +226,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -243,15 +236,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -277,9 +270,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -308,18 +301,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -356,9 +349,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -369,23 +362,50 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "staticrypt" -version = "1.1.1" +version = "1.2.0" dependencies = [ "aes-gcm", "staticrypt_macros", ] +[[package]] +name = "staticrypt-testbin" +version = "1.2.0" +dependencies = [ + "staticrypt", +] + [[package]] name = "staticrypt_macros" -version = "1.1.1" +version = "1.2.0" dependencies = [ "aes-gcm", "darling", @@ -396,13 +416,7 @@ dependencies = [ "quote", "rand", "syn", -] - -[[package]] -name = "staticrypt_testbin" -version = "1.1.1" -dependencies = [ - "staticrypt", + "thiserror", ] [[package]] @@ -419,43 +433,76 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ "winnow", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "universal-hash" @@ -475,97 +522,39 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index b247f17..4f52182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,14 +7,21 @@ repository.workspace = true version.workspace = true authors.workspace = true +[lints] +workspace = true + [workspace] members = [".", "macros", "testbin"] +[workspace.lints.clippy] +pedantic = { level = "warn", priority = 0 } +nursery = { level = "warn", priority = 0 } + [workspace.package] edition = "2024" license = "MIT" repository = "https://github.com/Naxdy/staticrypt" -version = "1.1.1" +version = "1.2.0" authors = ["Naxdy "] [workspace.dependencies] @@ -22,4 +29,4 @@ aes-gcm = "0.10.3" [dependencies] aes-gcm.workspace = true -staticrypt_macros = { version = "1.1.1", path = "macros" } +staticrypt_macros = { version = "1.2.0", path = "macros" } diff --git a/README.md b/README.md index 2ba7986..d5b327f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ its doc page for more info on initial setup. ```rust use staticrypt::*; -// Needs to be present at the root of the crate. +// Needs to be present at the root of the crate (i.e. `main.rs` or `lib.rs`). use_staticrypt!(); fn main() { @@ -40,11 +40,11 @@ strings target/debug/my_app | grep 42 If the output is blank / does not contain the string you are looking for, then your app is safe from static analysis tools. -## DISCLAIMER - -Although using tools like staticrypt makes it very difficult for attackers to view or alter -your data, it does _not_ make it impossible. You should develop your programs with the -assumption that a sufficiently determined attacker will be able to reverse engineer your -encryption and gain access to any data present in your binary, so it is **highly discouraged** to -use this crate to embed sensitive information like API keys, passwords, private keys etc. in your -application. +> [!WARNING] +> +> Although using tools like staticrypt makes it very difficult for attackers to view or alter +> your data, it does _not_ make it impossible. You should develop your programs with the +> assumption that a sufficiently determined attacker will be able to reverse engineer your +> encryption and gain access to any data present in your binary, so it is **highly discouraged** to +> use this crate to embed sensitive information like API keys, passwords, private keys etc. in your +> application. diff --git a/flake.nix b/flake.nix index 2e7c3cb..9f0a25d 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,8 @@ "aarch64-darwin" ]; + cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); + forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems ( @@ -54,6 +56,8 @@ env = { RUST_BACKTRACE = "1"; STATICRYPT_SEED = "01234567890123456789012345678901"; + # used in doc test + MY_SECRET_VAR = "super secret env"; RUSTFLAGS = "-Dwarnings"; RUSTDOCFLAGS = "-Dwarnings"; }; @@ -61,8 +65,6 @@ cargoArtifacts = craneLib.buildDepsOnly craneArgs; - cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); - treefmtEval = treefmt-nix.lib.evalModule pkgs ( import ./treefmt.nix { inherit rustToolchain cargoToml; } ); @@ -104,6 +106,28 @@ } ); + packages = forEachSupportedSystem ( + { + craneLib, + cargoArtifacts, + craneArgs, + ... + }: + { + staticrypt-testbin = craneLib.buildPackage ( + craneArgs + // { + pname = "staticrypt-testbin"; + version = cargoToml.workspace.package.version; + + inherit cargoArtifacts; + + cargoExtraArgs = "--locked -p staticrypt-testbin"; + } + ); + } + ); + checks = forEachSupportedSystem ( { pkgs, @@ -128,6 +152,8 @@ ''; } ); + + cargoClippy = craneLib.cargoClippy (craneArgs // { inherit cargoArtifacts; }); } ); }; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 46b0b24..2980634 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -7,6 +7,9 @@ repository.workspace = true version.workspace = true authors.workspace = true +[lints] +workspace = true + [lib] proc-macro = true @@ -20,3 +23,4 @@ proc-macro2 = "1.0.94" quote = "1.0.40" rand = "0.8.0" syn = "2.0.100" +thiserror = "2.0.17" diff --git a/macros/src/env.rs b/macros/src/env.rs new file mode 100644 index 0000000..ebb178d --- /dev/null +++ b/macros/src/env.rs @@ -0,0 +1,35 @@ +use proc_macro_error2::abort; +use proc_macro2::{Span, TokenStream}; +use syn::{LitStr, parse::Parse, parse2}; + +use crate::util::gen_decrypt_quote; + +struct ScEnvInput { + var_name: String, +} + +impl Parse for ScEnvInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let litstr: LitStr = input.parse()?; + Ok(Self { + var_name: litstr.value(), + }) + } +} + +pub fn sc_env(input: TokenStream) -> TokenStream { + let input: ScEnvInput = match parse2(input) { + Ok(e) => e, + Err(e) => return e.into_compile_error(), + }; + + let env_contents = match std::env::var(input.var_name) { + Ok(e) => e, + Err(e) => abort! { + Span::call_site(), + "Failed to read contents of environment variable: {}", e + }, + }; + + gen_decrypt_quote(env_contents.as_bytes(), true) +} diff --git a/macros/src/file.rs b/macros/src/file.rs index 9a99629..30814a4 100644 --- a/macros/src/file.rs +++ b/macros/src/file.rs @@ -1,11 +1,12 @@ use std::path::PathBuf; use darling::FromMeta; -use proc_macro::TokenStream; -use quote::quote; -use syn::{LitStr, parse::Parse, parse_macro_input}; +use proc_macro_error2::abort; +use proc_macro2::Span; +use proc_macro2::TokenStream; +use syn::{LitStr, parse::Parse, parse2}; -use crate::util::{byte_array_literal, encrypt, get_key, staticrypt_crate_name}; +use crate::util::gen_decrypt_quote; struct ScBytesInput { file_path: String, @@ -21,31 +22,19 @@ impl Parse for ScBytesInput { } pub fn sc_bytes(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ScBytesInput); + let input: ScBytesInput = match parse2(input) { + Ok(e) => e, + Err(e) => return e.into_compile_error(), + }; let file_contents = - std::fs::read(PathBuf::from_string(&input.file_path).expect("Failed to get path")) - .unwrap_or_else(|e| { - panic!("Failed to read contents of file {}: {e:?}", input.file_path) - }); - - let key = get_key(); - - let (encrypted, nonce) = encrypt(&file_contents, &key); - - let encrypted_literal = byte_array_literal(&encrypted); - - let nonce_literal = byte_array_literal(&nonce); - - let crate_name = staticrypt_crate_name(); - - quote! { - { - const ENCRYPTED: &[u8] = &#encrypted_literal; - const NONCE: &[u8] = &#nonce_literal; - - #crate_name::decrypt(ENCRYPTED, NONCE, crate::STATICRYPT_ENCRYPT_KEY) - } - } - .into() + match std::fs::read(PathBuf::from_string(&input.file_path).expect("Failed to get path")) { + Ok(e) => e, + Err(e) => abort! { + Span::call_site(), + "Failed to read contents of file {}: {:?}", input.file_path, e + }, + }; + + gen_decrypt_quote(&file_contents, false) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8d4658e..41f2945 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,17 +1,27 @@ +mod env; mod file; mod literal; mod util; use proc_macro::TokenStream; +use proc_macro_error2::proc_macro_error; #[proc_macro] +#[proc_macro_error] +pub fn sc_env(input: TokenStream) -> TokenStream { + env::sc_env(input.into()).into() +} + +#[proc_macro] +#[proc_macro_error] pub fn sc_bytes(input: TokenStream) -> TokenStream { - file::sc_bytes(input) + file::sc_bytes(input.into()).into() } #[proc_macro] +#[proc_macro_error] pub fn sc(input: TokenStream) -> TokenStream { - literal::sc(input) + literal::sc(input.into()).into() } #[proc_macro] diff --git a/macros/src/literal.rs b/macros/src/literal.rs index f028f33..b7f2bfd 100644 --- a/macros/src/literal.rs +++ b/macros/src/literal.rs @@ -1,8 +1,7 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{LitStr, parse::Parse, parse_macro_input}; +use proc_macro2::TokenStream; +use syn::{LitStr, parse::Parse, parse2}; -use crate::util::{byte_array_literal, encrypt, get_key, staticrypt_crate_name}; +use crate::util::gen_decrypt_quote; struct ScInput { literal: Vec, @@ -19,24 +18,10 @@ impl Parse for ScInput { } pub fn sc(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ScInput); + let input: ScInput = match parse2(input) { + Ok(e) => e, + Err(e) => return e.into_compile_error(), + }; - let key = get_key(); - - let (encrypted, nonce) = encrypt(&input.literal, &key); - - let encrypted_literal = byte_array_literal(&encrypted); - - let nonce_literal = byte_array_literal(&nonce); - - let crate_name = staticrypt_crate_name(); - - quote! { - { - const ENCRYPTED: &[u8] = &#encrypted_literal; - const NONCE: &[u8] = &#nonce_literal; - - ::std::string::String::from_utf8(#crate_name::decrypt(ENCRYPTED, NONCE, crate::STATICRYPT_ENCRYPT_KEY)).unwrap() - } - }.into() + gen_decrypt_quote(&input.literal, true) } diff --git a/macros/src/util.rs b/macros/src/util.rs index 01d1f9f..e6053c9 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -6,19 +6,76 @@ use aes_gcm::{ }; use parking_lot::Mutex; use proc_macro_crate::crate_name; +use proc_macro_error2::abort; use proc_macro2::{Span, TokenStream}; use quote::quote; use rand::SeedableRng; use rand::prelude::StdRng; use syn::Ident; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SeedError { + #[error("STATICRYPT_SEED must be at most 32 characters long, but is {0} characters long")] + InvalidLength(usize), +} + +#[derive(Error, Debug)] +pub enum EncryptionError { + #[error("Key is {0} characters long, when it should be exactly 32")] + InvalidKeyLength(usize), +} static RNG: LazyLock> = LazyLock::new(|| { - let seed = get_seed(); + let seed = get_seed().unwrap_or_else(|e| { + panic!("Failed to define global RNG variable: {e}"); + }); + let mut arg = [0; 32]; arg.copy_from_slice(&seed); + Mutex::new(StdRng::from_seed(arg)) }); +pub fn gen_decrypt_quote(contents: &[u8], is_string: bool) -> TokenStream { + let key = get_key(); + + let (encrypted, nonce) = match encrypt(contents, &key) { + Ok(e) => e, + Err(e) => { + abort! { + Span::call_site(), + "Could not encrypt data: {}", e + } + } + }; + + let encrypted_literal = byte_array_literal(&encrypted); + + let nonce_literal = byte_array_literal(&nonce); + + let crate_name = staticrypt_crate_name(); + + let d_quote = if is_string { + quote! { + ::std::string::String::from_utf8(#crate_name::decrypt(ENCRYPTED, NONCE, crate::STATICRYPT_ENCRYPT_KEY)).unwrap() + } + } else { + quote! { + #crate_name::decrypt(ENCRYPTED, NONCE, crate::STATICRYPT_ENCRYPT_KEY) + } + }; + + quote! { + { + const ENCRYPTED: &[u8] = &#encrypted_literal; + const NONCE: &[u8] = &#nonce_literal; + + #d_quote + } + } +} + pub fn init() -> TokenStream { let key = get_key(); let key_literal = byte_array_literal(&key); @@ -38,15 +95,18 @@ pub fn staticrypt_crate_name() -> TokenStream { } }, Err(e) => { - panic!("Error occurred while trying to determine crate name: {e}") + abort! { + Span::call_site(), + "Error occurred while trying to determine crate name: {}", e + } } } } /// Encrypts a byte input, returns a tuple in the form of (encrypted, nonce). -pub fn encrypt(input: &[u8], key: &[u8]) -> (Vec, Vec) { +pub fn encrypt(input: &[u8], key: &[u8]) -> Result<(Vec, Vec), EncryptionError> { if key.len() != 32 { - panic!("Key is {} characters long, when it should be 32", key.len()); + return Err(EncryptionError::InvalidKeyLength(key.len())); } let key = Key::::from_slice(key); @@ -58,7 +118,7 @@ pub fn encrypt(input: &[u8], key: &[u8]) -> (Vec, Vec) { .encrypt(&nonce, input) .expect("Failed to encrypt input"); - (ciphertext, nonce.to_vec()) + Ok((ciphertext, nonce.to_vec())) } pub fn get_key() -> Vec { @@ -68,7 +128,7 @@ pub fn get_key() -> Vec { ENCRYPT_KEY.clone() } -fn get_seed() -> Vec { +fn get_seed() -> Result, SeedError> { static RANDOM_SEED: LazyLock> = LazyLock::new(|| { let mut out = vec![0; 32]; @@ -78,22 +138,18 @@ fn get_seed() -> Vec { out }); - let mut seed: Vec = std::env::var("STATICRYPT_SEED") - .map(|e| e.into()) - .unwrap_or(RANDOM_SEED.to_vec()); + let mut seed: Vec = + std::env::var("STATICRYPT_SEED").map_or_else(|_| RANDOM_SEED.to_vec(), Into::into); if seed.len() > 32 { - panic!( - "STATICRYPT_SEED must be at most 32 characters long (is {} characters long)", - seed.len() - ); + return Err(SeedError::InvalidLength(seed.len())); } for i in seed.len()..32 { seed.push(RANDOM_SEED[i]); } - seed + Ok(seed) } pub fn byte_array_literal(input: &[u8]) -> TokenStream { diff --git a/src/lib.rs b/src/lib.rs index e792bba..81fb108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! The name is an abbreviation of "Static Encryption" - a Rust proc macro library to encrypt text //! literals or binary data using [`Aes256Gcm`]. //! -//! The crate is intended to be a successor to the [`litcrypt`](https://docs.rs/litcrypt/latest/litcrypt/), +//! The crate is intended to be a successor to [`litcrypt`](https://docs.rs/litcrypt/latest/litcrypt/), //! and expand on the overall idea of the library. //! //! Like litcrypt, staticrypt works by encrypting the given data at compile time. In its place, it @@ -47,7 +47,7 @@ //! assumption that a sufficiently determined attacker will be able to reverse engineer your //! encryption and gain access to any data present in your binary, so it is **highly discouraged** to //! use this crate to embed sensitive information like API keys, passwords, private keys etc. in your -//! application. +//! application. You have been warned! #![allow(clippy::needless_doctest_main)] #![warn(missing_docs)] @@ -116,12 +116,39 @@ pub use staticrypt_macros::sc; /// ``` pub use staticrypt_macros::sc_bytes; +/// Reads and encrypts the contents of the specified environment variable using [`Aes256Gcm`] with +/// the key embedded using [`use_staticrypt`] and a randomly generated nonce (derived from the +/// `STATICRYPT_SEED` variable at compile time). +/// +/// The contents of the environment variable are read at compile time. +/// +/// Example: +/// +/// ```rust +/// use staticrypt::*; +/// +/// use_staticrypt!(); +/// +/// fn main() { +/// let encrypted = sc_env!("MY_SECRET_VAR"); +/// +/// assert_eq!(encrypted, "super secret env"); +/// } +/// ``` +pub use staticrypt_macros::sc_env; + use_staticrypt!(); /// Decrypt an input with a given nonce and key using [`Aes256Gcm`]. /// /// Note that manually calling this function should not be necessary, as the [`sc`] macro already /// does this behind the scenes. +/// +/// # Panics +/// +/// This function will panic if the decryption fails. This could happen e.g. due to file corruption +/// of the resulting binary. +#[must_use] pub fn decrypt(input: &[u8], nonce: &[u8], key: &[u8]) -> Vec { let key = Key::::from_slice(key); let cipher = Aes256Gcm::new(key); diff --git a/testbin/Cargo.toml b/testbin/Cargo.toml index 53ba559..322e8b1 100644 --- a/testbin/Cargo.toml +++ b/testbin/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "staticrypt_testbin" +name = "staticrypt-testbin" version.workspace = true edition.workspace = true repository.workspace = true