├── .gitignore ├── proto ├── mod.rs ├── metadata.proto └── metadata.rs ├── src ├── rand.rs ├── errors.rs ├── lib.rs ├── pbkdf2.rs ├── aead.rs ├── main.rs └── cryptors.rs ├── NOTICE.md ├── .rustfmt.toml ├── CONTRIBUTING.md ├── Cargo.toml ├── tests └── test_cli.rs ├── README.md ├── CHANGELOG.md ├── .github └── workflows │ └── CI.yaml ├── LICENSE ├── LEGAL.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | tags 4 | -------------------------------------------------------------------------------- /proto/mod.rs: -------------------------------------------------------------------------------- 1 | // Header added by Tindercrypt's build.rs script 2 | #![allow(missing_docs)] 3 | 4 | // @generated 5 | 6 | pub mod metadata; 7 | -------------------------------------------------------------------------------- /src/rand.rs: -------------------------------------------------------------------------------- 1 | //! # Utilities for random numbers 2 | 3 | use rand::{rng, Rng}; 4 | 5 | /// Fill a buffer with random data. 6 | /// 7 | /// ``` 8 | /// use tindercrypt::rand::fill_buf; 9 | /// 10 | /// let mut buf = [0u8; 32]; 11 | /// fill_buf(&mut buf); 12 | /// ``` 13 | pub fn fill_buf(buf: &mut [u8]) { 14 | rng().fill(buf); 15 | } 16 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | The following legal notice applies to all files in this project, unless 2 | explicitly stated otherwise: 3 | 4 | SPDX-License-Identifier: MPL-2.0 5 | 6 | Copyright 2019, the Tindercrypt contributors. 7 | 8 | This Source Code Form is subject to the terms of the Mozilla Public 9 | License, v. 2.0. If a copy of the MPL was not distributed with this file, 10 | You can obtain one at https://mozilla.org/MPL/2.0/. 11 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_strings = false 2 | edition = "2018" 3 | wrap_comments = true 4 | 5 | # The default character limit is 100 chars per line, but a lot of people don't 6 | # have screens that support 200+ chars, i.e., screens over 14". Note that "200+" 7 | # is not a typo, since it's common that a developer may want to split the screen 8 | # in two, e.g., two Vim panes, or one editor window and a browser window. So, 9 | # since 100 chars is too long, we follow the PEP 8 recommendation, i.e., 79 10 | # chars, which seems to work fine in practice. 11 | max_width = 79 12 | comment_width = 79 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | First, read the [`NOTICE.md`] file for the legal status of the project. 4 | 5 | You can contribute by sending a pull request as follows: 6 | 7 | 1. Fork the repository. 8 | 2. Make your changes and then run the tests with `cargo test`. For changes in 9 | our `.proto` files, do the following: 10 | 11 | * Check if the `.proto` files pass the lint checks of Uber's [`prototool`]. 12 | * Compile the `.proto` files with `cargo build --features proto-gen`. 13 | * Commit the generated Rust code. 14 | 15 | 3. If your changes close any issues, specify them in the respective commits 16 | (`Closes #...`), and update `CHANGELOG.md` if necessary. 17 | 4. Create a pull request that targets the `master` branch. 18 | 19 | Once you've sent a PR, wait for the CI steps to finish successfully. When the CI 20 | and review process complete successfully, please ensure that your branch 21 | presents a clear history of changes, i.e., squash fixup commits and update stale 22 | commit messages. Finally, the PR will be merged with the "Rebase and merge" 23 | strategy. 24 | 25 | [`NOTICE.md`]: NOTICE.md 26 | [`prototool`]: https://github.com/uber/prototool 27 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! # Tindercrypt errors 2 | 3 | /// The errors that can be returned by the library. 4 | #[derive(thiserror::Error, Copy, Clone, Debug, PartialEq)] 5 | pub enum Error { 6 | /// The provided data buffer is too small for the requested action. 7 | #[error("The provided buffer is shorter than expected")] 8 | BufferTooSmall, 9 | /// The provided passphrase is too small for the key derivation process. 10 | #[error("The provided passphrase is shorter than expected")] 11 | PassphraseTooSmall, 12 | /// The provided key size was not the expected one. 13 | #[error( 14 | "The provided key does not have the required length for the \ 15 | encryption algorithm" 16 | )] 17 | KeySizeMismatch, 18 | /// The provided parameters to a crypto function are weak. 19 | #[error("The provided parameters for the encryption are too weak")] 20 | CryptoParamsWeak, 21 | /// Could not decrypt the data, e.g., due to a bad key, wrong nonce, 22 | /// corrupted tag. 23 | #[error("Could not decrypt the ciphertext")] 24 | DecryptionError, 25 | /// The provided buffer does not start with the expected metadata header. 26 | #[error("The provided buffer does not include a metadata header")] 27 | MetadataMissing, 28 | /// The metadata header of the encrypted buffer contains invalid values. 29 | #[error("The provided buffer has an invalid metadata header")] 30 | MetadataInvalid, 31 | } 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Tindercrypt 2 | //! 3 | //! Tindercrypt is a library that supports data encryption with symmetric 4 | //! cryptographic keys or passwords/passphrases. It supports [AES256-GCM] and 5 | //! [ChaCha20-Poly1305] for encryption/decryption, and [PBKDF2] for key 6 | //! derivation. These cryptographic primitives are provided by the [Ring] 7 | //! crypto library. 8 | //! 9 | //! Tindercrypt's main goal is to provide a safe and easy API for data 10 | //! encryption. The user of this library simply chooses an encryption algorithm 11 | //! and provides a key/passphrase to encrypt their data. To decrypt their data, 12 | //! they provide the same key/passphrase. Behind the scenes, Tindercrypt 13 | //! generates the necessary encryption metadata (salts, nonces, etc.) and 14 | //! bundles them with the encrypted data, so that it can retrieve them when 15 | //! decrypting the data later on. 16 | //! 17 | //! You can learn how Tindercrypt handles encryption metadata in the 18 | //! [`metadata`] module. 19 | //! 20 | //! [AES256-GCM]: https://en.wikipedia.org/wiki/Galois/Counter_Mode 21 | //! [ChaCha20-Poly1305]: https://tools.ietf.org/html/rfc7539 22 | //! [PBKDF2]: https://en.wikipedia.org/wiki/PBKDF2 23 | //! [Ring]: https://github.com/briansmith/ring 24 | //! [`metadata`]: metadata/index.html 25 | 26 | #![deny( 27 | warnings, 28 | missing_copy_implementations, 29 | missing_debug_implementations, 30 | missing_docs, 31 | trivial_casts, 32 | trivial_numeric_casts, 33 | unsafe_code, 34 | unstable_features, 35 | unused_import_braces, 36 | unused_qualifications, 37 | unused_extern_crates, 38 | unused_must_use, 39 | unused_results, 40 | variant_size_differences 41 | )] 42 | 43 | pub mod aead; 44 | pub mod cryptors; 45 | pub mod errors; 46 | pub mod metadata; 47 | pub mod pbkdf2; 48 | #[path = "../proto/mod.rs"] 49 | #[allow(unused_qualifications)] 50 | pub mod proto; 51 | pub mod rand; 52 | -------------------------------------------------------------------------------- /proto/metadata.proto: -------------------------------------------------------------------------------- 1 | // Protobuf definitions for the encryption metadata. 2 | // 3 | // This .proto file declares the types of the encryption metadata that will be 4 | // serialized along with the data. The main message is `Metadata`, which points 5 | // to the `KeyDerivationMeta` and `EncryptionMeta` messages, which hold various 6 | // info for each operation. 7 | // 8 | // Each algorithm choice is defined as an enum and is currently required. This 9 | // is not supported by the `proto3` syntax, so we use the `*_INVALID` name 10 | // to mark undefined choices. Because the names of the enum variants should be 11 | // globally unique, they are prepended with the name of the enum. 12 | // 13 | // This proto file should pass the lint checks of Uber's `prototool`: 14 | // https://github.com/uber/prototool 15 | 16 | syntax = "proto3"; 17 | 18 | package metadata; 19 | 20 | option go_package = "metadatapb"; 21 | option java_package = "com.metadata"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "MetadataProto"; 24 | 25 | enum HashFunction { 26 | HASH_FUNCTION_INVALID = 0; 27 | HASH_FUNCTION_SHA256 = 1; 28 | HASH_FUNCTION_SHA384 = 2; 29 | HASH_FUNCTION_SHA512 = 3; 30 | } 31 | 32 | enum KeyDerivationAlgorithm { 33 | KEY_DERIVATION_ALGORITHM_INVALID = 0; 34 | KEY_DERIVATION_ALGORITHM_NONE = 1; 35 | KEY_DERIVATION_ALGORITHM_PBKDF2 = 2; 36 | } 37 | 38 | enum EncryptionAlgorithm { 39 | ENCRYPTION_ALGORITHM_INVALID = 0; 40 | ENCRYPTION_ALGORITHM_AES256GCM = 1; 41 | ENCRYPTION_ALGORITHM_CHACHA20_POLY1305 = 2; 42 | } 43 | 44 | message KeyDerivationMetadata { 45 | KeyDerivationAlgorithm algo = 1; 46 | HashFunction hash_fn = 2; 47 | uint64 iterations = 3; 48 | bytes salt = 4; 49 | } 50 | 51 | message EncryptionMetadata { 52 | EncryptionAlgorithm algo = 1; 53 | bytes nonce = 2; 54 | } 55 | 56 | message Metadata { 57 | KeyDerivationMetadata key_deriv_meta = 1; 58 | EncryptionMetadata enc_meta = 2; 59 | uint64 ciphertext_size = 3; 60 | } 61 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tindercrypt" 3 | version = "0.3.4" 4 | authors = ["Alex Pyrgiotis "] 5 | edition = "2018" 6 | license = "MPL-2.0" 7 | description = """ 8 | Data encryption with symmetric cryptographic keys or passwords/passphrases, 9 | and self-contained encryption metadata 10 | """ 11 | homepage = "https://github.com/apyrgio/tindercrypt" 12 | repository = "https://github.com/apyrgio/tindercrypt" 13 | documentation = "https://docs.rs/tindercrypt" 14 | readme = "README.md" 15 | keywords = ["protocol-buffers", "cryptography", "encryption"] 16 | categories = ["command-line-utilities", "cryptography"] 17 | 18 | [dependencies] 19 | protobuf = "3.7.2" 20 | rand = "0.9" 21 | # XXX: There are no stable versions of `ring` [1], meaning that we must always 22 | # compile the newest version to ensure that the latest security patches are 23 | # there. However, this means that our library may break whenever `ring` changes 24 | # its API. We'll pin the `ring` version for now, but we have to find a way to 25 | # deal with this at some point. 26 | # 27 | # [1]: https://github.com/briansmith/ring#versioning--stability 28 | ring = "0.17" 29 | thiserror = "2" 30 | zeroize = "1" 31 | 32 | # NOTE: The following dependencies are required only for the CLI version of the 33 | # crate, and are only included if the `cli` feature is enabled. See also 34 | # https://github.com/rust-lang/cargo/issues/1982, for the current state of 35 | # binary-only dependencies in `cargo`. 36 | clap = { version = "2", optional = true } 37 | dialoguer = { version = "0.11", optional = true } 38 | lazy_static = { version = "1", optional = true } 39 | 40 | [dev-dependencies] 41 | assert_cmd = "2" 42 | assert_fs = "1" 43 | predicates = "3" 44 | 45 | [build-dependencies] 46 | protobuf-codegen= { version = "3.7.2", optional = true } 47 | 48 | [features] 49 | default = ["cli"] 50 | 51 | # Dependencies needed specifically for the CLI. 52 | cli = ["clap", "dialoguer", "lazy_static"] 53 | # Generate Rust code from .proto files. 54 | proto-gen = ["protobuf-codegen"] 55 | -------------------------------------------------------------------------------- /tests/test_cli.rs: -------------------------------------------------------------------------------- 1 | use assert_fs::prelude::*; 2 | use predicates::prelude::*; 3 | 4 | use assert_cmd::Command; 5 | 6 | fn cli() -> Command { 7 | let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); 8 | cmd.env_clear(); 9 | cmd.env("TINDERCRYPT_PASSPHRASE", "password1234"); 10 | cmd 11 | } 12 | 13 | fn encrypt() -> Command { 14 | let mut cmd = cli(); 15 | cmd.args(&["encrypt", "--iterations", "1"]); 16 | cmd 17 | } 18 | 19 | fn decrypt() -> Command { 20 | let mut cmd = cli(); 21 | cmd.arg("decrypt"); 22 | cmd 23 | } 24 | 25 | #[test] 26 | fn test_encrypt_decrypt() { 27 | // Test that encryption/decryption works properly with files. 28 | let temp_dir = assert_fs::TempDir::new().unwrap(); 29 | temp_dir.child("plaintext").write_str("secret").unwrap(); 30 | encrypt() 31 | .args(&["-i", "plaintext", "-o", "ciphertext"]) 32 | .current_dir(temp_dir.path()) 33 | .assert() 34 | .success(); 35 | 36 | decrypt() 37 | .args(&["-i", "ciphertext", "-o", "plaintext2"]) 38 | .current_dir(temp_dir.path()) 39 | .assert() 40 | .success(); 41 | temp_dir.child("plaintext2").assert("secret"); 42 | 43 | // Test that encryption/decryption works properly with stdin/stdout. 44 | let output = encrypt() 45 | .args(&["-e", "CHACHA20-POLY1305"]) 46 | .write_stdin("secret") 47 | .assert() 48 | .success() 49 | .get_output() 50 | .stdout 51 | .clone(); 52 | 53 | decrypt() 54 | .write_stdin(output.clone()) 55 | .assert() 56 | .success() 57 | .stdout("secret"); 58 | 59 | // Test that invalid ciphertexts return the appropriate error. 60 | decrypt() 61 | .write_stdin("secret") 62 | .assert() 63 | .failure() 64 | .stderr(predicate::str::starts_with("Error during decryption")) 65 | .stderr(predicate::str::ends_with("invalid metadata header\n")); 66 | 67 | // Test that a wrong password results to an error. 68 | decrypt() 69 | .env("TINDERCRYPT_PASSPHRASE", "wrongpass") 70 | .write_stdin(output.clone()) 71 | .assert() 72 | .failure() 73 | .stderr(predicate::str::starts_with("Error during decryption")) 74 | .stderr(predicate::str::ends_with( 75 | "Could not decrypt the ciphertext\n", 76 | )); 77 | } 78 | 79 | #[test] 80 | fn test_invalid_args() { 81 | // Test that errors in file I/O are reported properly. 82 | let temp_dir = assert_fs::TempDir::new().unwrap(); 83 | encrypt() 84 | .args(&["-i", "plaintext"]) 85 | .current_dir(temp_dir.path()) 86 | .assert() 87 | .failure() 88 | .stderr(predicate::str::starts_with("Could not read file")); 89 | 90 | temp_dir.child("plaintext").write_str("secret").unwrap(); 91 | encrypt() 92 | .args(&["-i", "plaintext", "-o", "bad/dir"]) 93 | .current_dir(temp_dir.path()) 94 | .assert() 95 | .failure() 96 | .stderr(predicate::str::starts_with("Could not create file")); 97 | 98 | // Test that invalid arguments for iterations are detected. 99 | encrypt() 100 | .args(&["-i", "plaintext", "-o", "ciphertext", "-I", "wrong"]) 101 | .current_dir(temp_dir.path()) 102 | .assert() 103 | .failure() 104 | .stderr(predicate::str::contains("integer greater than 0")); 105 | 106 | encrypt() 107 | .args(&["-i", "plaintext", "-o", "ciphertext", "-I", "0"]) 108 | .current_dir(temp_dir.path()) 109 | .assert() 110 | .failure() 111 | .stderr(predicate::str::contains("integer greater than 0")); 112 | 113 | // Test that an invalid encryption algorithm is reported properly. 114 | encrypt() 115 | .args(&["-i", "plaintext", "-o", "ciphertext", "-e", "wrong"]) 116 | .current_dir(temp_dir.path()) 117 | .assert() 118 | .failure() 119 | .stderr(predicate::str::contains("isn't a valid value for")); 120 | } 121 | -------------------------------------------------------------------------------- /src/pbkdf2.rs: -------------------------------------------------------------------------------- 1 | //! # PBKDF2 helpers 2 | //! 3 | //! This module contains helpers for the PBKDF2 algorithm. 4 | 5 | use crate::errors; 6 | use core::num; 7 | use ring::pbkdf2; 8 | 9 | /// Cryptographically create a symmetric key from a secret value. 10 | /// 11 | /// Create a symmetric key from a secret value, based on various PBKDF2 12 | /// parameters; an HMAC function, a salt and a number of iterations. 13 | /// 14 | /// This method returns an error if the parameters are too weak, e.g., a SHA-1 15 | /// digest function, zero number of iterations or no salt. Also, it returns an 16 | /// error if the user has not provided a buffer for the key or a secret value. 17 | /// 18 | /// ## Examples 19 | /// 20 | /// A safe method to derive a key with PBKDF2 is the following: 21 | /// 22 | /// ``` 23 | /// use tindercrypt::pbkdf2::derive_key; 24 | /// use tindercrypt::rand::fill_buf; 25 | /// use ring::pbkdf2; 26 | /// 27 | /// let digest_algo = pbkdf2::PBKDF2_HMAC_SHA256; 28 | /// let iterations = 100000; 29 | /// let mut salt = [0u8; 32]; 30 | /// let secret = "My secret password".as_bytes(); 31 | /// let mut key = [0u8; 32]; 32 | /// 33 | /// fill_buf(&mut salt); 34 | /// derive_key(digest_algo, iterations, &salt, &secret, &mut key); 35 | /// ``` 36 | pub fn derive_key( 37 | digest_algo: pbkdf2::Algorithm, 38 | iterations: usize, 39 | salt: &[u8], 40 | secret: &[u8], 41 | key: &mut [u8], 42 | ) -> Result<(), errors::Error> { 43 | if digest_algo == pbkdf2::PBKDF2_HMAC_SHA1 44 | || iterations < 1 45 | || salt.len() == 0 46 | { 47 | return Err(errors::Error::CryptoParamsWeak); 48 | } 49 | 50 | if secret.len() == 0 || key.len() == 0 { 51 | return Err(errors::Error::PassphraseTooSmall); 52 | } 53 | 54 | let iterations = num::NonZeroU32::new(iterations as u32).unwrap(); 55 | pbkdf2::derive(digest_algo, iterations, salt, secret, key); 56 | Ok(()) 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | fn test_pbkd2_derive_key() { 65 | let mut salt = [9; 10]; 66 | let mut secret = [99; 10]; 67 | let mut key1 = [0u8; 32]; 68 | let mut key2 = [0u8; 32]; 69 | let mut key3 = [0u8; 32]; 70 | let mut key4 = [0u8; 32]; 71 | let mut res: Result<(), errors::Error>; 72 | let params_err = Err(errors::Error::CryptoParamsWeak); 73 | let size_err = Err(errors::Error::PassphraseTooSmall); 74 | 75 | // Check that weak parameters and empty buffers are reported as errors. 76 | res = 77 | derive_key(pbkdf2::PBKDF2_HMAC_SHA1, 1, &salt, &secret, &mut key1); 78 | assert_eq!(res, params_err); 79 | res = derive_key( 80 | pbkdf2::PBKDF2_HMAC_SHA256, 81 | 0, 82 | &salt, 83 | &secret, 84 | &mut key1, 85 | ); 86 | assert_eq!(res, params_err); 87 | res = 88 | derive_key(pbkdf2::PBKDF2_HMAC_SHA256, 1, &[], &secret, &mut key1); 89 | assert_eq!(res, params_err); 90 | res = derive_key(pbkdf2::PBKDF2_HMAC_SHA256, 1, &salt, &[], &mut key1); 91 | assert_eq!(res, size_err); 92 | res = 93 | derive_key(pbkdf2::PBKDF2_HMAC_SHA256, 1, &salt, &secret, &mut []); 94 | assert_eq!(res, size_err); 95 | 96 | // Check that key derivation works, and that changes in the salt and 97 | // secret produce different keys. 98 | res = derive_key( 99 | pbkdf2::PBKDF2_HMAC_SHA256, 100 | 1, 101 | &salt, 102 | &secret, 103 | &mut key1, 104 | ); 105 | assert!(res.is_ok()); 106 | salt[0] = 0; 107 | res = derive_key( 108 | pbkdf2::PBKDF2_HMAC_SHA256, 109 | 1, 110 | &salt, 111 | &secret, 112 | &mut key2, 113 | ); 114 | assert!(res.is_ok()); 115 | salt[0] = 9; 116 | secret[0] = 0; 117 | res = derive_key( 118 | pbkdf2::PBKDF2_HMAC_SHA256, 119 | 1, 120 | &salt, 121 | &secret, 122 | &mut key3, 123 | ); 124 | assert!(res.is_ok()); 125 | secret[0] = 99; 126 | res = derive_key( 127 | pbkdf2::PBKDF2_HMAC_SHA256, 128 | 1, 129 | &salt, 130 | &secret, 131 | &mut key4, 132 | ); 133 | assert!(res.is_ok()); 134 | 135 | assert_ne!(key1, key2); 136 | assert_ne!(key1, key3); 137 | assert_ne!(key2, key3); 138 | assert_eq!(key1, key4); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tindercrypt 2 | 3 | A library that supports data encryption with symmetric cryptographic keys or 4 | passwords/passphrases. Uses [Protocol Buffers] for the serialization of the 5 | encryption metadata (salts, nonces, etc.) and is based on the [ring] Rust crate 6 | for the cryptographic primitives. 7 | 8 | [![CI](https://github.com/apyrgio/tindercrypt/workflows/CI/badge.svg?branch=master&event=schedule)](https://github.com/apyrgio/tindercrypt/actions?query=event%3Aschedule+branch%3Amaster) 9 | [![Crates.io](https://img.shields.io/crates/v/tindercrypt.svg)](https://crates.io/crates/tindercrypt) 10 | [![Docs.rs](https://docs.rs/tindercrypt/badge.svg)](https://docs.rs/tindercrypt) 11 | 12 | ## Overview 13 | 14 | Tindercrypt's main goal is to provide a safe and easy API for data 15 | encryption. The user of this library simply chooses an encryption algorithm 16 | and provides a key/passphrase to encrypt their data. To decrypt their data, 17 | they provide the same key/passphrase. Behind the scenes, Tindercrypt 18 | generates the necessary encryption metadata (salts, nonces, etc.) and 19 | bundles them with the encrypted data, so that it can retrieve them when 20 | decrypting the data later on. 21 | 22 | Features: 23 | 24 | * Does not reinvent crypto. Uses the cryptographic primitives of the 25 | well-tested [ring] crate; [PBKDF2] for key derivation, 26 | [AES256-GCM]/[ChaCha20-Poly1305] for symmetric encryption. 27 | * Sane defaults for all cryptographic operations; random nonces and 28 | salts, high number of key derivation iterations. 29 | * Extensibility and compatibility with older versions through [Protocol 30 | buffers]. 31 | * No book-keeping necessary by the user; all required metadata for 32 | the decryption are bundled with the ciphertext. 33 | * Offers a simple CLI tool that encrypts files with a passphrase. 34 | 35 | For a design overview, see the docs section on [Tindercrypt metadata]. 36 | 37 | ## Examples 38 | 39 | You can encrypt (seal) a data buffer with a passphrase as follows: 40 | 41 | ```rust 42 | use tindercrypt::cryptors::RingCryptor; 43 | 44 | let plaintext = "The cake is a lie".as_bytes(); 45 | let pass = "My secret passphrase".as_bytes(); 46 | let cryptor = RingCryptor::new(); 47 | 48 | let ciphertext = cryptor.seal_with_passphrase(pass, plaintext)?; 49 | let plaintext2 = cryptor.open(pass, &ciphertext)?; 50 | assert_eq!(plaintext2, plaintext); 51 | ``` 52 | 53 | You can find more examples in the docs section on [Tindercrypt's `RingCryptor`]. 54 | 55 | The equivalent operation in the CLI tool is the following: 56 | 57 | ``` 58 | $ echo The cake is a lie > plaintext 59 | $ export TINDERCRYPT_PASSPHRASE="My secret passphrase" # Note the extra space. 60 | $ tindercrypt encrypt -i plaintext -o ciphertext 61 | $ tindercrypt decrypt -i ciphertext 62 | The cake is a lie 63 | ``` 64 | 65 | ## Documentation 66 | 67 | You can read the latest docs in https://docs.rs/tindercrypt. 68 | 69 | ## Usage 70 | 71 | ### As a library 72 | 73 | When adding this crate to your `Cargo.toml`, add it with `default-features = 74 | false`, to ensure that CLI specific dependencies are not added to your 75 | dependency tree: 76 | 77 | ```toml 78 | tindercrypt = { version = "x.y.z", default-features = false } 79 | ``` 80 | 81 | ### As a binary 82 | 83 | You can run Tindercrypt using one of the binaries of the [stable releases], or 84 | the [nightly builds]. Alternatively, you can install it with one of the 85 | following methods: 86 | 87 | * From cargo: 88 | 89 | ``` 90 | $ cargo install tindercrypt 91 | ``` 92 | 93 | * From source: 94 | 95 | ``` 96 | $ git clone https://github.com/apyrgio/tindercrypt 97 | $ cd tindercrypt 98 | $ cargo build --release 99 | $ ./target/release/tindercrypt --help 100 | Tindecrypt: File encryption tool ... 101 | ``` 102 | 103 | ## Contributing 104 | 105 | You can read the [`CONTRIBUTING.md`] guide for more info on how to contribute to 106 | this project. 107 | 108 | ## Legal 109 | 110 | Licensed under MPL-2.0. Please read the [`NOTICE.md`] and [`LICENSE`] files for 111 | the full copyright and license information. If you feel like putting your 112 | mental stability to a test, feel free to read the [`LEGAL.md`] file for a foray 113 | into the waters of copyright law, and a glimpse of how they can be both boring 114 | and dangerous at the same time. 115 | 116 | [ring]: https://github.com/briansmith/ring 117 | [Protocol Buffers]: https://developers.google.com/protocol-buffers/ 118 | [PBKDF2]: https://en.wikipedia.org/wiki/PBKDF2 119 | [AES256-GCM]: https://en.wikipedia.org/wiki/Galois/Counter_Mode 120 | [ChaCha20-Poly1305]: https://tools.ietf.org/html/rfc7539 121 | [Tindercrypt metadata]: https://docs.rs/tindercrypt/latest/tindercrypt/metadata/index.html 122 | [Tindercrypt's `RingCryptor`]: https://docs.rs/tindercrypt/latest/tindercrypt/cryptors/struct.RingCryptor.html 123 | [stable releases]: https://github.com/apyrgio/tindercrypt/releases 124 | [nightly builds]: https://github.com/apyrgio/tindercrypt/actions?query=event%3Aschedule+branch%3Amaster 125 | [`CONTRIBUTING.md`]: CONTRIBUTING.md 126 | [`NOTICE.md`]: NOTICE.md 127 | [`LICENSE`]: LICENSE 128 | [`LEGAL.md`]: LEGAL.md 129 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog], and this project adheres to [Semantic 6 | Versioning]. 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.4] - 2025-04-13 11 | 12 | ### Fixed 13 | 14 | - Pin the `protobuf` crate to 3.7.2, to avoid API incompatibilities in the 15 | future (see [#10](https://github.com/apyrgio/tindercrypt/issues/10)). 16 | 17 | ## [0.3.3] - 2024-11-01 18 | 19 | ### Fixed 20 | 21 | - Various fixes in order to make Tindercrypt work with the current stable Rust 22 | version. 23 | 24 | ### Changed 25 | 26 | - Update the `protobuf` dependency to 3.x, which brings some changes in the API 27 | of the generated Rust code. Users of this library should not be affect, since 28 | the Tindercrypt API has not changed. 29 | 30 | ## [0.3.2] - 2021-06-21 31 | 32 | ### Fixed 33 | 34 | - Fix a `rustdoc::bare_urls` warning for some links that we used in our 35 | footnotes and did not have any style indication, by formatting them as 36 | hyperliks. 37 | 38 | ## [0.3.1] - 2021-04-20 39 | 40 | ### Fixed 41 | 42 | - Temporarily fix a build error for nightly Rust. In a nutshell, the generated 43 | Rust code for our proto files triggers a compiler warning in nightly Rust, 44 | which we ultimately treat as an error. Until this is fixed upstream, we 45 | silence this warning. See also: 46 | 47 | * https://github.com/stepancheg/rust-protobuf/issues/551 48 | * https://github.com/rust-lang/rust/issues/64266 49 | 50 | - Remove the temporary workaround for the aforementioned Rust warning, since the 51 | newly generated Rust code semi-resolves it. See also: 52 | 53 | * https://github.com/stepancheg/rust-protobuf/issues/551#issuecomment-802221146 54 | 55 | ### Changed 56 | 57 | - Bump the dialoguer dependency to v0.8.0, to fix a compilation error. 58 | 59 | ## [0.3.0] - 2021-01-11 60 | 61 | ### Added 62 | 63 | - Allow users to derive a key from a secret value and the encryption metadata. 64 | 65 | ### Removed 66 | 67 | - Remove the key derivation process that was performed internally in the 68 | following `RingCryptor` methods: 69 | 70 | * `seal_in_place` 71 | * `seal_with_meta` 72 | * `seal_with_key` 73 | * `open_in_place` 74 | * `open_with_meta` 75 | 76 | The change should impact just the users that used key derivation (PBKDF2) and 77 | passed a passphrase to any of the above functions. If you are affected, you 78 | can manually derive the key and pass it to the above functions. For more info, 79 | see the examples in the `RingCryptor` documentation. 80 | 81 | Note that the following methods are still performing key derivation 82 | internally: 83 | 84 | * `seal_with_passphrase` 85 | * `open` 86 | 87 | Finally, the reason for the removal was not security-related, but to give more 88 | control to the users on this front ([#6]). 89 | 90 | ## [0.2.2] - 2020-04-13 91 | 92 | ### Changed 93 | 94 | - Use the `thiserror` crate to make the library errors implement the `Error` 95 | trait, and remove some boilerplate code. 96 | 97 | ## [0.2.1] - 2020-03-30 98 | 99 | ### Changed 100 | 101 | - Move the CLI dependencies under a `cli` feature flag, so that users of the 102 | library don't need to pull them. 103 | 104 | ## [0.2.0] - 2020-03-22 105 | 106 | ### Added 107 | 108 | - Add Windows support. 109 | - Add a CI pipeline based on Github Actions. This pipeline tests the project 110 | on Ubuntu, MacOS and Windows platforms, and creates build artifacts for them. 111 | 112 | ### Changed 113 | 114 | - Bump the dependencies to their newest versions. 115 | 116 | ### Fixed 117 | 118 | - Fix some build warnings, that were ultimately treated as errors, by 119 | updating `protoc-rust` and generating new Rust code from our `.proto` files. 120 | These build warnings started to appear due to new versions of `rustc`. 121 | 122 | ### Removed 123 | 124 | - Remove support for the `HMAC-SHA512/256` hash function, used in conjunction 125 | with PBKDF2 for key derivation. This hash function was removed by the `ring` 126 | library, so we're left with no choice but to remove it from Tindercrypt it as 127 | well. 128 | 129 | ## [0.1.1] - 2019-08-10 130 | 131 | Version bump so that the Github tag and crates.io tag can be aligned. 132 | 133 | ## [0.1.0] - 2019-08-10 134 | 135 | Initial release. 136 | 137 | [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ 138 | [Semantic Versioning]: https://semver.org/spec/v2.0.0.html 139 | [#6]: https://github.com/apyrgio/tindercrypt/issues/6 140 | 141 | [Unreleased]: https://github.com/apyrgio/tindercrypt/compare/v0.3.3...HEAD 142 | [0.3.4]: https://github.com/apyrgio/tindercrypt/compare/v0.3.3...v0.3.4 143 | [0.3.3]: https://github.com/apyrgio/tindercrypt/compare/v0.3.2...v0.3.3 144 | [0.3.2]: https://github.com/apyrgio/tindercrypt/compare/v0.3.1...v0.3.2 145 | [0.3.1]: https://github.com/apyrgio/tindercrypt/compare/v0.3.0...v0.3.1 146 | [0.3.0]: https://github.com/apyrgio/tindercrypt/compare/v0.2.2...v0.3.0 147 | [0.2.2]: https://github.com/apyrgio/tindercrypt/compare/v0.2.1...v0.2.2 148 | [0.2.1]: https://github.com/apyrgio/tindercrypt/compare/v0.2.0...v0.2.1 149 | [0.2.0]: https://github.com/apyrgio/tindercrypt/compare/v0.1.1...v0.2.0 150 | [0.1.1]: https://github.com/apyrgio/tindercrypt/compare/v0.1.0...v0.1.1 151 | [0.1.0]: https://github.com/apyrgio/tindercrypt/releases/tag/v0.1.0 152 | -------------------------------------------------------------------------------- /.github/workflows/CI.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | schedule: 6 | - cron: '0 0 * * *' # Run every day at 00:00 UTC. 7 | 8 | env: 9 | RUST_BACKTRACE: full # Shows more info when a test fails. 10 | BINARY_NAME: tindercrypt 11 | 12 | jobs: 13 | basic_checks: 14 | name: Basic checks (cargo ${{ matrix.cmd }}) 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | cmd: 19 | - fmt 20 | - doc 21 | include: 22 | - cmd: fmt 23 | args: --all -- --check 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Install Rust 30 | uses: dtolnay/rust-toolchain@master 31 | with: 32 | toolchain: stable 33 | components: rustfmt 34 | 35 | - name: cargo ${{ matrix.cmd }} 36 | run: cargo ${{ matrix.cmd }} ${{ matrix.args }} 37 | 38 | 39 | lint_proto: 40 | name: Lint .proto files 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Download Uber's prototool 47 | run: | 48 | wget -O prototool \ 49 | https://github.com/uber/prototool/releases/download/v1.9.0/prototool-Linux-x86_64 50 | chmod +x prototool 51 | 52 | - name: Lint 53 | run: ./prototool lint proto 54 | 55 | test: 56 | name: Test ${{ matrix.rust }} on ${{ matrix.os }} 57 | runs-on: ${{ matrix.os }} 58 | strategy: 59 | fail-fast: false 60 | matrix: 61 | rust: 62 | - stable 63 | - beta 64 | - nightly 65 | os: 66 | - ubuntu-latest 67 | - windows-latest 68 | - macOS-latest 69 | 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v4 73 | 74 | - name: Install Rust (${{ matrix.rust }}) 75 | uses: dtolnay/rust-toolchain@master 76 | with: 77 | toolchain: ${{ matrix.rust }} 78 | 79 | # Catch any breaking changes in the dependencies early, by always updating 80 | # them before running the tests. 81 | - name: Update dependencies 82 | run: cargo update 83 | 84 | - name: Test 85 | run: cargo test -- --nocapture # Allow printing the output of tests 86 | 87 | build: 88 | name: Build on ${{ matrix.os }} 89 | runs-on: ${{ matrix.os }} 90 | strategy: 91 | fail-fast: false 92 | matrix: 93 | os: 94 | - ubuntu-latest 95 | - windows-latest 96 | - macOS-latest 97 | include: 98 | - os: ubuntu-latest 99 | arch: linux_amd64 100 | - os: windows-latest 101 | arch: windows_amd64 102 | extension: .exe 103 | - os: macOS-latest 104 | arch: darwin_amd64 105 | 106 | steps: 107 | - name: Checkout 108 | uses: actions/checkout@v4 109 | 110 | - name: Build 111 | run: cargo build --release 112 | 113 | - name: Upload binaries 114 | uses: actions/upload-artifact@v4 115 | with: 116 | name: ${{ env.BINARY_NAME }}_${{ matrix.arch }}${{ matrix.extension}} 117 | path: "target/release/${{ env.BINARY_NAME }}${{ matrix.extension }}" 118 | 119 | create_release: 120 | name: Create a release 121 | runs-on: ubuntu-latest 122 | needs: ["basic_checks", "test", "build", "lint_proto"] 123 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 124 | steps: 125 | - name: Checkout 126 | uses: actions/checkout@v4 127 | 128 | - name: Set release tag 129 | run: echo GITHUB_RELEASE_TAG=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV 130 | 131 | - name: Create release 132 | env: 133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 134 | run: | 135 | gh release create ${{ env.GITHUB_RELEASE_TAG }} \ 136 | --title "Release ${{ env.GITHUB_RELEASE_TAG }}" \ 137 | --draft --generate-notes 138 | 139 | upload_assets: 140 | name: Upload release assets ( ${{ matrix.arch }} ) 141 | needs: create_release 142 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 143 | runs-on: ubuntu-latest 144 | strategy: 145 | matrix: 146 | arch: 147 | - linux_amd64 148 | - windows_amd64 149 | - darwin_amd64 150 | include: 151 | - arch: windows_amd64 152 | extension: .exe 153 | 154 | steps: 155 | - name: Checkout 156 | uses: actions/checkout@v4 157 | 158 | - name: Set release tag 159 | run: echo GITHUB_RELEASE_TAG=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV 160 | 161 | - name: Download binary 162 | uses: actions/download-artifact@v4 163 | with: 164 | name: ${{ env.BINARY_NAME }}_${{ matrix.arch }}${{ matrix.extension}} 165 | path: assets_${{ matrix.arch }} 166 | 167 | - name: Upload release asset 168 | env: 169 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 170 | run: | 171 | cd assets_${{ matrix.arch }} 172 | TAGGED_ASSET_NAME=${{ env.BINARY_NAME }}_${{ env.GITHUB_RELEASE_TAG }}_${{ matrix.arch }}${{ matrix.extension}} 173 | mv ${{ env.BINARY_NAME }}${{ matrix.extension }} ${TAGGED_ASSET_NAME} 174 | gh release upload ${{ env.GITHUB_RELEASE_TAG }} ${TAGGED_ASSET_NAME} 175 | -------------------------------------------------------------------------------- /src/aead.rs: -------------------------------------------------------------------------------- 1 | //! # AEAD helpers 2 | //! 3 | //! This module contains some wrappers over the AEAD functions in the `ring` 4 | //! library. You are advised to not use these low-level functions directly, and 5 | //! instead use the functions provided by the [`cryptors`] module 6 | //! 7 | //! ## Examples 8 | //! 9 | //! You can encrypt (seal) and decrypt (open) a secret value as follows: 10 | //! 11 | //! ``` 12 | //! use ring::aead; 13 | //! use tindercrypt::rand::fill_buf; 14 | //! use tindercrypt::aead::{seal_in_place, open_in_place, NONCE_SIZE}; 15 | //! 16 | //! let algo = &aead::AES_256_GCM; 17 | //! let mut nonce = [0u8; NONCE_SIZE]; 18 | //! let aad = "My encryption context".as_bytes(); 19 | //! let mut key = vec![0u8; algo.key_len()]; 20 | //! let data = "The cake is a lie".as_bytes(); 21 | //! 22 | //! // Create a unique nonce and key. 23 | //! fill_buf(&mut nonce); 24 | //! fill_buf(&mut key); 25 | //! 26 | //! // Create a buffer large enough to hold the ciphertext and its tag. 27 | //! let mut buf = vec![0; data.len() + algo.tag_len()]; 28 | //! buf[..data.len()].copy_from_slice(&data); 29 | //! 30 | //! // Encrypt (seal) the data buffer in place, thereby ovewriting the 31 | //! // plaintext data with the ciphertext, and appending a tag at the end. 32 | //! seal_in_place(algo, nonce.clone(), &aad, &key, &mut buf); 33 | //! 34 | //! // Decrypt (open) the data buffer in place, thereby ovewriting ciphertext 35 | //! // with the plaintext (the previous tag will not be removed). 36 | //! open_in_place(algo, nonce.clone(), &aad, &key, &mut buf); 37 | //! assert_eq!(data, &buf[..data.len()]); 38 | //! 39 | //! // Ensure that the nonce is never used again. 40 | //! drop(nonce); 41 | //! 42 | //! ``` 43 | //! 44 | //! [`cryptors`]: ../cryptors/index.html 45 | 46 | use crate::errors; 47 | use ring::aead; 48 | 49 | /// The size of the nonces that `ring` expects. 50 | pub const NONCE_SIZE: usize = 12; 51 | 52 | /// Check if the provided key has the expected size for the specified 53 | /// algorithm. 54 | fn _check_key( 55 | algo: &'static aead::Algorithm, 56 | key: &[u8], 57 | ) -> Result<(), errors::Error> { 58 | if key.len() != algo.key_len() { 59 | return Err(errors::Error::KeySizeMismatch); 60 | } 61 | Ok(()) 62 | } 63 | 64 | /// Check if the buffer where the output will be stored is large enough to 65 | /// contain the tag. 66 | fn _check_in_out( 67 | algo: &'static aead::Algorithm, 68 | in_out: &[u8], 69 | ) -> Result<(), errors::Error> { 70 | if in_out.len() < algo.tag_len() { 71 | return Err(errors::Error::BufferTooSmall); 72 | } 73 | Ok(()) 74 | } 75 | 76 | /// Seal the contents of a data buffer in place. 77 | /// 78 | /// This function is a wrapper around the `seal_in_place()` function of the 79 | /// `ring` library. Its purpose is to simplify what needs to be passed to the 80 | /// underlying function and perform some early checks. The produced ciphertext 81 | /// will be stored in the same buffer as the plaintext, effectively erasing it. 82 | /// 83 | /// This function accepts the following parameters: 84 | /// 85 | /// * A `ring` AEAD algorithm, e.g., AES-256-GCM, 86 | /// * A nonce buffer with a specific size. This nonce must **NEVER** be reused 87 | /// for the same key. 88 | /// * A reference to some data (additional authenticated data), which won't be 89 | /// stored with the ciphertext, but will be used for the encryption and will 90 | /// be required for the decryption as well. 91 | /// * A reference to a symmetric key, whose size must match the size required 92 | /// by the AEAD algorithm. 93 | /// * A data buffer that holds the plaintext. The ciphertext will be 94 | /// stored in this buffer, so it must be large enough to contain the 95 | /// encrypted data and the tag as well. In practice, the user must craft a 96 | /// buffer that starts with the plaintext and add an empty space at the end, 97 | /// as large as the tag size expected by the algorithm. 98 | /// 99 | /// This function returns an error if the key/buffer sizes are not the expected 100 | /// ones. If the encryption fails, which should never happen in practice, this 101 | /// function panics. If the encryption succeeds, it returns the length of the 102 | /// plaintext. 103 | pub fn seal_in_place( 104 | algo: &'static aead::Algorithm, 105 | nonce: [u8; NONCE_SIZE], 106 | aad: &[u8], 107 | key: &[u8], 108 | in_out: &mut [u8], 109 | ) -> Result { 110 | _check_key(algo, key)?; 111 | _check_in_out(algo, in_out)?; 112 | 113 | let tag_size = algo.tag_len(); 114 | let plaintext_size: usize = in_out.len() - tag_size; 115 | let plaintext = &mut in_out[..plaintext_size]; 116 | let unbound_key = aead::UnboundKey::new(algo, key).unwrap(); 117 | let key = aead::LessSafeKey::new(unbound_key); 118 | let nonce = aead::Nonce::assume_unique_for_key(nonce); 119 | let aad = aead::Aad::from(aad); 120 | let res = key.seal_in_place_separate_tag(nonce, aad, plaintext); 121 | 122 | match res { 123 | Ok(t) => { 124 | let tag = &mut in_out[plaintext_size..]; 125 | tag.copy_from_slice(t.as_ref()); 126 | Ok(plaintext_size) 127 | } 128 | Err(error) => panic!("Error during sealing: {:?}", error), 129 | } 130 | } 131 | 132 | /// Open the contents of a sealed data buffer in place. 133 | /// 134 | /// This function is a wrapper around the `open_in_place()` function of the 135 | /// `ring` library. Its purpose is to simplify what needs to be passed to the 136 | /// underlying function and perform some early checks. The produced plaintext 137 | /// will be stored in the same buffer as the ciphertext, effectively erasing it. 138 | /// 139 | /// This function accepts the following parameters: 140 | /// 141 | /// * A `ring` AEAD algorithm, e.g., AES-256-GCM, 142 | /// * A nonce buffer with a specific size. 143 | /// * A reference to some data (additional authenticated data), which must be 144 | /// the same as the ones provided during the sealing process. 145 | /// * A reference to a symmetric key, whose size must match the size required 146 | /// by the AEAD algorithm. 147 | /// * A data buffer that holds the ciphertext and its tag. 148 | /// 149 | /// This function returns an error if the key/buffer sizes are not the expected 150 | /// ones, or if the decryption process fails, e.g., due to a wrong key, nonce, 151 | /// etc. If the decryption succeeds, it returns the length of the plaintext. 152 | pub fn open_in_place( 153 | algo: &'static aead::Algorithm, 154 | nonce: [u8; NONCE_SIZE], 155 | aad: &[u8], 156 | key: &[u8], 157 | in_out: &mut [u8], 158 | ) -> Result { 159 | _check_key(algo, key)?; 160 | _check_in_out(algo, in_out)?; 161 | 162 | let unbound_key = aead::UnboundKey::new(algo, key).unwrap(); 163 | let key = aead::LessSafeKey::new(unbound_key); 164 | let nonce = aead::Nonce::assume_unique_for_key(nonce); 165 | let aad = aead::Aad::from(aad); 166 | let res = key.open_in_place(nonce, aad, in_out); 167 | 168 | match res { 169 | Ok(plaintext) => Ok(plaintext.len()), 170 | Err(_) => Err(errors::Error::DecryptionError), 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | use super::*; 177 | 178 | const BUF_SIZE: usize = 36; // 36 bytes can contain the tag and data. 179 | 180 | #[test] 181 | fn test_key() { 182 | // Check that a key with incorrect size produces an error. 183 | // FIXME: Why can't I iterate over array pointers with different size? 184 | for algo in &[&aead::AES_256_GCM, &aead::CHACHA20_POLY1305] { 185 | for key in &[ 186 | vec![], 187 | vec![0; 1], 188 | vec![0; algo.key_len() - 1], 189 | vec![0; algo.key_len() + 1], 190 | ] { 191 | assert_eq!( 192 | _check_key(algo, key), 193 | Err(errors::Error::KeySizeMismatch) 194 | ); 195 | } 196 | } 197 | } 198 | 199 | #[test] 200 | fn test_in_out() { 201 | // Check that a key with incorrect size produces an error. 202 | // FIXME: Why can't I iterate over array pointers with different size? 203 | for algo in &[&aead::AES_256_GCM, &aead::CHACHA20_POLY1305] { 204 | for in_out in &[vec![], vec![0; 1], vec![0; algo.tag_len() - 1]] { 205 | assert_eq!( 206 | _check_in_out(algo, in_out), 207 | Err(errors::Error::BufferTooSmall) 208 | ); 209 | } 210 | } 211 | } 212 | 213 | fn _test_seal_open(algo: &'static aead::Algorithm) { 214 | let nonce = [1; 12]; 215 | let aad = [2; 9]; 216 | let key = vec![3; algo.key_len()]; 217 | let mut in_out: [u8; BUF_SIZE]; 218 | let mut res: Result; 219 | let plaintext_size = BUF_SIZE - algo.tag_len(); 220 | let exp_res = Ok(plaintext_size); 221 | let dec_err = Err(errors::Error::DecryptionError); 222 | let buf_err = Err(errors::Error::BufferTooSmall); 223 | let key_err = Err(errors::Error::KeySizeMismatch); 224 | 225 | // NOTE: We create a closure to avoid repetitions. 226 | let seal = || { 227 | let r; 228 | let mut _in_out = [4; BUF_SIZE]; 229 | r = seal_in_place(algo, nonce.clone(), &aad, &key, &mut _in_out); 230 | assert_eq!(r, exp_res); 231 | _in_out 232 | }; 233 | 234 | // Check that any type of data corruption makes decryption fail. 235 | // 236 | // Corrupted nonce. 237 | in_out = seal(); 238 | let mut bad_nonce = nonce.clone(); 239 | bad_nonce[0] = 9; 240 | res = open_in_place(algo, bad_nonce.clone(), &aad, &key, &mut in_out); 241 | assert_eq!(res, dec_err); 242 | 243 | // Corrupted additional authenticated data. 244 | in_out = seal(); 245 | let mut bad_aad = aad.clone(); 246 | bad_aad[0] = 9; 247 | res = open_in_place(algo, nonce.clone(), &bad_aad, &key, &mut in_out); 248 | assert_eq!(res, dec_err); 249 | 250 | // Corrupted key. 251 | in_out = seal(); 252 | let mut bad_key = key.clone(); 253 | bad_key[0] = 9; 254 | res = open_in_place(algo, nonce.clone(), &aad, &bad_key, &mut in_out); 255 | assert_eq!(res, dec_err); 256 | 257 | // Corrupted data. 258 | in_out = seal(); 259 | let mut bad_in_out = in_out.clone(); 260 | bad_in_out[0] = 9; 261 | res = open_in_place(algo, nonce.clone(), &aad, &key, &mut bad_in_out); 262 | assert_eq!(res, dec_err); 263 | 264 | // Corrupted tag. 265 | in_out = seal(); 266 | let mut bad_in_out = in_out.clone(); 267 | bad_in_out[in_out.len() - 1] = 9; 268 | res = open_in_place(algo, nonce.clone(), &aad, &key, &mut bad_in_out); 269 | assert_eq!(res, dec_err); 270 | 271 | // Incomplete data buffer. 272 | res = seal_in_place(algo, nonce.clone(), &aad, &key, &mut []); 273 | assert_eq!(res, buf_err); 274 | res = open_in_place(algo, nonce.clone(), &aad, &key, &mut []); 275 | assert_eq!(res, buf_err); 276 | 277 | // Incomplete key. 278 | res = seal_in_place(algo, nonce.clone(), &aad, &[], &mut in_out); 279 | assert_eq!(res, key_err); 280 | res = open_in_place(algo, nonce.clone(), &aad, &[], &mut in_out); 281 | assert_eq!(res, key_err); 282 | 283 | // Incorrect encryption algorithm. 284 | let algo2: &'static aead::Algorithm; 285 | if algo == &aead::AES_256_GCM { 286 | algo2 = &aead::CHACHA20_POLY1305; 287 | } else { 288 | algo2 = &aead::AES_256_GCM; 289 | } 290 | 291 | in_out = seal(); 292 | res = open_in_place(algo2, nonce.clone(), &aad, &key, &mut in_out); 293 | assert_eq!(res, dec_err); 294 | 295 | // Correct decryption. 296 | in_out = seal(); 297 | res = open_in_place(algo, nonce.clone(), &aad, &key, &mut in_out); 298 | assert_eq!(res, exp_res); 299 | assert_eq!(in_out[..res.unwrap()], vec![4u8; res.unwrap()][..]); 300 | } 301 | 302 | #[test] 303 | fn test_seal_open_aes() { 304 | _test_seal_open(&aead::AES_256_GCM); 305 | } 306 | 307 | #[test] 308 | fn test_seal_open_chacha20() { 309 | _test_seal_open(&aead::CHACHA20_POLY1305); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! # Tindercrypt CLI 2 | //! 3 | //! The Tindercrypt CLI allows the user to encrypt/decrypt a file using a 4 | //! passphrase. The user can also tweak some encryption parameters, such as 5 | //! the encryption algorithm or the number of key derivation iterations. 6 | //! 7 | //! As is, the CLI offers just a subset of the Tindercrypt library's 8 | //! functionality. For symmetric key encryption or more control over the 9 | //! encryption process, you are encouraged to use the library directly. 10 | 11 | #![deny( 12 | warnings, 13 | missing_copy_implementations, 14 | missing_debug_implementations, 15 | missing_docs, 16 | trivial_casts, 17 | trivial_numeric_casts, 18 | unsafe_code, 19 | unstable_features, 20 | unused_import_braces, 21 | unused_qualifications, 22 | unused_extern_crates, 23 | unused_must_use, 24 | unused_results, 25 | variant_size_differences 26 | )] 27 | 28 | use std::io::{self, Read, Write}; 29 | use std::{env, fmt, fs}; 30 | 31 | use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; 32 | use dialoguer::Password; 33 | #[macro_use] 34 | extern crate lazy_static; 35 | #[macro_use] 36 | extern crate clap; 37 | 38 | use tindercrypt::{cryptors, errors, metadata}; 39 | 40 | #[cfg(target_family = "unix")] 41 | use std::os::unix::fs::OpenOptionsExt; 42 | 43 | const PASSPHRASE_ENVVAR: &'static str = "TINDERCRYPT_PASSPHRASE"; 44 | const AES_ALGO: &'static str = "AES256-GCM"; 45 | const CHACHA_ALGO: &'static str = "CHACHA20-POLY1305"; 46 | 47 | lazy_static! { 48 | static ref AFTER_HELP: String = { 49 | format!( 50 | "A passphrase is required and can be provided via the {} \ 51 | environment variable. Else, you will be prompted to type it.", 52 | PASSPHRASE_ENVVAR 53 | ) 54 | }; 55 | static ref PBKDF2_DEFAULT_ITERATIONS: String = 56 | metadata::PBKDF2_DEFAULT_ITERATIONS.to_string(); 57 | } 58 | 59 | #[derive(Debug)] 60 | enum CLIError { 61 | DialogError { 62 | msg: String, 63 | dialog_error: dialoguer::Error, 64 | }, 65 | IOError { 66 | msg: String, 67 | io_error: io::Error, 68 | }, 69 | TCError { 70 | msg: String, 71 | tc_error: errors::Error, 72 | }, 73 | Error { 74 | msg: String, 75 | }, 76 | } 77 | 78 | impl CLIError { 79 | fn from_dialog_error(msg: String, dialog_error: dialoguer::Error) -> Self { 80 | CLIError::DialogError { msg, dialog_error } 81 | } 82 | fn from_io_error(msg: String, io_error: io::Error) -> Self { 83 | CLIError::IOError { msg, io_error } 84 | } 85 | fn from_tc_error(msg: String, tc_error: errors::Error) -> Self { 86 | CLIError::TCError { msg, tc_error } 87 | } 88 | fn new(msg: String) -> Self { 89 | CLIError::Error { msg } 90 | } 91 | } 92 | 93 | impl fmt::Display for CLIError { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | match self { 96 | CLIError::DialogError { msg, dialog_error } => { 97 | write!(f, "{}.\nReason: {}", msg, dialog_error) 98 | } 99 | CLIError::IOError { msg, io_error } => { 100 | write!(f, "{}.\nReason: {}", msg, io_error) 101 | } 102 | CLIError::TCError { msg, tc_error } => { 103 | write!(f, "{}.\nReason: {}", msg, tc_error) 104 | } 105 | CLIError::Error { msg } => write!(f, "{}", msg), 106 | } 107 | } 108 | } 109 | 110 | /// Convert the iterations argument from a string to an integer. 111 | fn _parse_iterations(iter_arg: &str) -> Result { 112 | let err_msg = "The number of iterations must be an integer greater than 0"; 113 | match iter_arg.parse::() { 114 | Ok(num) => { 115 | if num == 0 { 116 | return Err(CLIError::new(err_msg.to_string())); 117 | } 118 | Ok(num) 119 | } 120 | Err(_) => Err(CLIError::new(err_msg.to_string())), 121 | } 122 | } 123 | 124 | /// Validate the number of iterations, by attempting to parse them. 125 | fn _validate_iterations(iter_arg: String) -> Result<(), String> { 126 | match _parse_iterations(iter_arg.as_str()) { 127 | Ok(_) => Ok(()), 128 | Err(cli_error) => Err(format!("{}", cli_error)), 129 | } 130 | } 131 | 132 | /// Read file contents into a buffer. 133 | fn _read_file(name: &str) -> Result, CLIError> { 134 | match fs::read(name) { 135 | Ok(buf) => Ok(buf), 136 | Err(io_error) => Err(CLIError::from_io_error( 137 | format!("Could not read file: {}", name), 138 | io_error, 139 | )), 140 | } 141 | } 142 | 143 | /// Read buffer from stdin. 144 | /// 145 | /// Read buffer from stdin, until EOF. 146 | fn _read_stdin() -> Result, CLIError> { 147 | let mut buf = Vec::new(); 148 | match io::stdin().read_to_end(&mut buf) { 149 | Ok(_) => Ok(buf), 150 | Err(io_error) => Err(CLIError::from_io_error( 151 | "Could not read from stdin".to_string(), 152 | io_error, 153 | )), 154 | } 155 | } 156 | 157 | /// Create a file and write a buffer to it. 158 | /// 159 | /// The file will be created with read-write rights by the owner only. 160 | fn _write_file(name: &str, buf: &[u8]) -> Result<(), CLIError> { 161 | // Construct the options necessary to create a file that is read-writable 162 | // by the owner only. Note that this concept does not apply to Windows [1], 163 | // so we protect it via a conditional compilation guard. 164 | // 165 | // [1] The official docs do not explain how another user can access a file 166 | // upon creation, so I suppose that they simply can't, unless the file 167 | // is created in a directory with special permissions. This is 168 | // reinforced by this Sentry commit: 169 | // 170 | // https://github.com/getsentry/sentry-cli/pull/296/commits/e0494ae47832501c66088dab761dc1c73c7de9bc 171 | let mut open_opts = fs::OpenOptions::new(); 172 | let _ = open_opts.write(true).create_new(true); 173 | #[cfg(target_family = "unix")] 174 | let _ = open_opts.mode(0o600); 175 | 176 | let mut file = match open_opts.open(name) { 177 | Ok(f) => f, 178 | Err(e) => { 179 | return Err(CLIError::from_io_error( 180 | format!("Could not create file: {}", name), 181 | e, 182 | )) 183 | } 184 | }; 185 | 186 | match file.write_all(buf) { 187 | Ok(_) => Ok(()), 188 | Err(e) => Err(CLIError::from_io_error( 189 | format!("Could not write to file: {}", name), 190 | e, 191 | )), 192 | } 193 | } 194 | 195 | /// Write buffer to stdout. 196 | fn _write_stdout(buf: &[u8]) -> Result<(), CLIError> { 197 | match io::stdout().write_all(buf) { 198 | Ok(_) => Ok(()), 199 | Err(e) => Err(CLIError::from_io_error( 200 | "Could not write to stdout".to_string(), 201 | e, 202 | )), 203 | } 204 | } 205 | 206 | /// Read a buffer from a file or stdin. 207 | fn read_file_contents(ifile: &Option<&str>) -> Result, CLIError> { 208 | match ifile { 209 | Some(name) => _read_file(&name), 210 | None => _read_stdin(), 211 | } 212 | } 213 | 214 | /// Write a buffer to a file or stdout. 215 | fn write_file_contents( 216 | ofile: &Option<&str>, 217 | buf: &[u8], 218 | ) -> Result<(), CLIError> { 219 | match ofile { 220 | Some(name) => _write_file(name, buf), 221 | None => _write_stdout(buf), 222 | } 223 | } 224 | 225 | /// Read passphrase from TTY or environment variable. 226 | fn get_passphrase() -> Result { 227 | // Get the passphrase first from the environment variable. 228 | match env::var(PASSPHRASE_ENVVAR) { 229 | Ok(pass) => return Ok(pass), 230 | Err(_) => (), 231 | } 232 | 233 | // If not provided, prompt the user to type it. 234 | let pass = Password::new() 235 | .with_prompt("Enter password") 236 | .with_confirmation("Confirm password", "Passwords mismatch") 237 | .interact(); 238 | 239 | match pass { 240 | Ok(pass) => return Ok(pass), 241 | Err(e) => Err(CLIError::from_dialog_error( 242 | "Could not read passphrase from TTY".to_string(), 243 | e, 244 | )), 245 | } 246 | } 247 | 248 | /// Encrypt plaintext with a passphrase, and return the ciphertext. 249 | fn _seal<'a>( 250 | buf: &[u8], 251 | passphrase: &[u8], 252 | iterations: usize, 253 | algo: &'a str, 254 | ) -> Result, CLIError> { 255 | let cryptor = cryptors::RingCryptor::new(); 256 | 257 | // Generate the metadata for the PBKDF2 key derivation algorithm and 258 | // explicitly set the number of iterations. 259 | let mut key_meta = metadata::KeyDerivationMetadata::generate(); 260 | key_meta.iterations = iterations; 261 | let key_algo = metadata::KeyDerivationAlgorithm::PBKDF2(key_meta); 262 | 263 | // Generate the metadata for the encryption algorithm of the user's choice. 264 | let enc_meta = metadata::EncryptionMetadata::generate(); 265 | let enc_algo = match algo { 266 | AES_ALGO => metadata::EncryptionAlgorithm::AES256GCM(enc_meta), 267 | CHACHA_ALGO => { 268 | metadata::EncryptionAlgorithm::ChaCha20Poly1305(enc_meta) 269 | } 270 | _ => unreachable!(), 271 | }; 272 | 273 | let meta = metadata::Metadata::new(key_algo, enc_algo, buf.len()); 274 | 275 | // Derive the encryption key. 276 | let key = match cryptors::RingCryptor::derive_key(&meta, &passphrase) { 277 | Ok(key) => key, 278 | Err(tc_error) => { 279 | return Err(CLIError::from_tc_error( 280 | "Unexpected error during key derivation".to_string(), 281 | tc_error, 282 | )) 283 | } 284 | }; 285 | 286 | // Encrypt the plaintext with the created metadata. 287 | match cryptor.seal_with_meta(&meta, &key, &buf) { 288 | Ok(buf) => Ok(buf), 289 | Err(tc_error) => Err(CLIError::from_tc_error( 290 | "Unexpected error during encryption".to_string(), 291 | tc_error, 292 | )), 293 | } 294 | } 295 | 296 | /// Decrypt ciphertext with a passphrase, and return the plaintext. 297 | fn _open(buf: &[u8], passphrase: &[u8]) -> Result, CLIError> { 298 | let cryptor = cryptors::RingCryptor::new(); 299 | match cryptor.open(passphrase, buf) { 300 | Ok(buf) => Ok(buf), 301 | Err(tc_error) => Err(CLIError::from_tc_error( 302 | "Error during decryption".to_string(), 303 | tc_error, 304 | )), 305 | } 306 | } 307 | 308 | fn encrypt<'a>(m: &ArgMatches<'a>) -> Result<(), CLIError> { 309 | // NOTE: We can always unwrap the `iterations` and `algo` arguments, since 310 | // they have default values. 311 | let iterations = _parse_iterations(m.value_of("iterations").unwrap())?; 312 | let algo = m.value_of("enc_algo").unwrap(); 313 | 314 | let ifile = m.value_of("in_file"); 315 | let ofile = m.value_of("out_file"); 316 | let contents = read_file_contents(&ifile)?; 317 | 318 | let passphrase = get_passphrase()?; 319 | let buf = _seal(&contents, passphrase.as_bytes(), iterations, algo)?; 320 | 321 | let _ = write_file_contents(&ofile, &buf)?; 322 | Ok(()) 323 | } 324 | 325 | fn decrypt<'a>(m: &ArgMatches<'a>) -> Result<(), CLIError> { 326 | let ifile = m.value_of("in_file"); 327 | let ofile = m.value_of("out_file"); 328 | let contents = read_file_contents(&ifile)?; 329 | 330 | let passphrase = get_passphrase()?; 331 | let buf = _open(&contents, passphrase.as_bytes())?; 332 | 333 | let _ = write_file_contents(&ofile, &buf)?; 334 | Ok(()) 335 | } 336 | 337 | fn create_encrypt_parser<'a, 'b>() -> App<'a, 'b> { 338 | SubCommand::with_name("encrypt") 339 | .about("Encrypt a file with a passphrase") 340 | .after_help(AFTER_HELP.as_str()) 341 | .arg( 342 | Arg::with_name("in_file") 343 | .short("i") 344 | .long("in-file") 345 | .takes_value(true) 346 | .help( 347 | "The name of the file to be encrypted. If left blank, \ 348 | the file will be read from stdin", 349 | ), 350 | ) 351 | .arg( 352 | Arg::with_name("out_file") 353 | .short("o") 354 | .long("out-file") 355 | .takes_value(true) 356 | .help( 357 | "The name of the file to store the encrypted contents. If \ 358 | left blank, the encrypted contents will be written to \ 359 | stdout", 360 | ), 361 | ) 362 | .arg( 363 | Arg::with_name("iterations") 364 | .short("I") 365 | .long("iterations") 366 | .validator(_validate_iterations) 367 | .takes_value(true) 368 | .default_value(PBKDF2_DEFAULT_ITERATIONS.as_str()) 369 | .help( 370 | "The number of iterations for the PBKDF2 key derivation \ 371 | algorithm", 372 | ), 373 | ) 374 | .arg( 375 | Arg::with_name("enc_algo") 376 | .short("e") 377 | .long("encryption-algorithm") 378 | .takes_value(true) 379 | .possible_values(&[AES_ALGO, CHACHA_ALGO]) 380 | .default_value(AES_ALGO) 381 | .help("The algorithm that will be used for the encryption"), 382 | ) 383 | } 384 | 385 | fn create_decrypt_parser<'a, 'b>() -> App<'a, 'b> { 386 | SubCommand::with_name("decrypt") 387 | .about("Decrypt a file with a passphrase") 388 | .after_help(AFTER_HELP.as_str()) 389 | .arg( 390 | Arg::with_name("in_file") 391 | .short("i") 392 | .long("in-file") 393 | .takes_value(true) 394 | .help( 395 | "The name of the file to be decrypted. If left blank, \ 396 | the file will be read from stdin", 397 | ), 398 | ) 399 | .arg( 400 | Arg::with_name("out_file") 401 | .short("o") 402 | .long("out-file") 403 | .takes_value(true) 404 | .help( 405 | "The name of the file to store the decrypted contents. If \ 406 | left blank, the decrypted contents will be written to \ 407 | stdout", 408 | ), 409 | ) 410 | } 411 | 412 | fn create_parser<'a, 'b>() -> App<'a, 'b> { 413 | App::new("Tindecrypt: File encryption tool") 414 | .version(crate_version!()) 415 | .after_help(AFTER_HELP.as_str()) 416 | .setting(AppSettings::SubcommandRequired) 417 | .subcommand(create_encrypt_parser()) 418 | .subcommand(create_decrypt_parser()) 419 | } 420 | 421 | fn main() { 422 | let parser = create_parser(); 423 | let matches = parser.get_matches(); 424 | 425 | let res = match matches.subcommand() { 426 | ("encrypt", Some(m)) => encrypt(&m), 427 | ("decrypt", Some(m)) => decrypt(&m), 428 | _ => unreachable!(), 429 | }; 430 | 431 | match res { 432 | Ok(_) => std::process::exit(0), 433 | Err(e) => { 434 | eprintln!("{}", e); 435 | std::process::exit(1) 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /LEGAL.md: -------------------------------------------------------------------------------- 1 | # Abandon all hope, ye who enter here 2 | 3 | ## Table of contents 4 | 5 | * [Why a third document?](#why-a-third-document) 6 | * [Why use an SPDX identifier?](#why-use-an-spdx-identifier) 7 | * [Why this copyright?](#why-this-copyright) 8 | * [Why use a copyright in the first place?](#why-use-a-copyright-in-the-first-place) 9 | * [Why use this copyright format?](#why-use-this-copyright-format) 10 | * [Why not use the copyright symbol?](#why-not-use-the-copyright-symbol) 11 | * [Why state only the year of creation?](#why-state-only-the-year-of-creation) 12 | * [Who are the Tindercrypt contributors?](#who-are-the-tindercrypt-contributors) 13 | * [Why this license?](#why-this-license) 14 | * [Why do we need a license in the first place?](#why-do-we-need-a-license-in-the-first-place) 15 | * [Which are the most common licenses?](#which-are-the-most-common-licenses) 16 | * [Why were they not chosen?](#why-were-they-not-chosen) 17 | * [Why pick MPL-2.0?](#why-pick-mpl-20) 18 | * [Why is the legal notice not attached to every file?](#why-is-the-legal-notice-not-attached-to-every-file) 19 | * [Footnotes](#footnotes) 20 | 21 | ## Why a third document? 22 | 23 | This repo has two legal documents; the [`NOTICE.md`] file, that contains the 24 | copyright and the license header, and the [`LICENSE`] file, that contains the 25 | license in full. 26 | 27 | For most people, these documents may seem as standard procedure; walls of 28 | legalese that you find somewhere and you can copy-paste in your code. 29 | Unfortunately, there's no authoritative place that explains how you should 30 | license and/or copyright your FOSS project. Instead, there's incomplete and 31 | oft-times contradicting advice scattered all over the Internet, that you have to 32 | double-check against other sources to see if it's outdated, or if it even makes 33 | sense for FOSS. Also, you can't simply copy what big FOSS projects do either, 34 | because for the same license and language, each project may do different things, 35 | potentially wrong (shocking, I know). 36 | 37 | This document, while not legal, serves to explain why we chose this copyright 38 | and license. It's practically a log of all the questions I had while reading on 39 | this subject, and the best answers that I could find, based on various legal or 40 | dev sites. Amusingly, in some questions, many answers were both correct and 41 | wrong at the same time, depending on how you interpreted things! Well, not 42 | amusingly. Unless your idea of fun is to read legal texts until ichor drips from 43 | your eye sockets. 44 | 45 | Before we begin, I begrudgingly have to state the following typical disclaimers; 46 | I am not a lawyer ([IANAL]), this is not a legal advice, and if the fact that I 47 | have to chant these incantations does not prove how trigger-happy everyone seems 48 | to be in the legal realm, I am not sure what else will. 49 | 50 | All aboard! 51 | 52 | ## Why use an SPDX identifier? 53 | 54 | [SPDX] identifiers are used so that others don't need to check if the license 55 | you cite is the official one, or one slightly modified. This is not the case 56 | with this project, but this identifier helps clarifying any doubts. 57 | 58 | Don't get too happy though and attempt to throw the legal stuff away and just 59 | keep this identifier. The existence of an SPDX identifier does not imply that 60 | the license header or copyright notice can be omitted [1]. Yay, more 61 | boilerplate... 62 | 63 | ## Why this copyright? 64 | 65 | There's a lot to unpack here. We'll break this question down into the following 66 | questions: 67 | 68 | ### Why use a copyright in the first place? 69 | 70 | Copyright notices are no longer legally required, but they help in the 71 | following cases [2] [3]: 72 | 73 | * To prevent infringers from claiming that they didn't know that the work was 74 | copyrighted. 75 | * To record which people should grant their permission in order to change the 76 | project's license. 77 | * To provide attribution to authors of third-party code that has been copied to 78 | this project. 79 | * To explicitly state the dates of publications. 80 | 81 | **Fun fact!** the [Servo] project, which is a very large Rust and MPL-2.0 82 | project, does not specify a copyright notice for the project or its Rust code. 83 | 84 | ### Why use this copyright format? 85 | 86 | You've probably seen variations of this copyright format in other projects too, 87 | but something is always a bit different. Sometimes more than one year is 88 | specified, sometimes only the company or the author is specified, sometimes 89 | a `(C)` creeps in. So, which variation is correct? 90 | 91 | GNU proposes the following format for its GPL v3.0 license in its [How to Apply 92 | These Terms to Your New Programs] section: 93 | 94 | ``` 95 | Copyright (C) 96 | ``` 97 | 98 | The Apache license proposes a similar format in its [How to apply the Apache 99 | License to your work] section: 100 | 101 | ``` 102 | Copyright [yyyy] [name of copyright owner] 103 | ``` 104 | 105 | GNU strikes again with [an alternative copyright format], that takes 106 | modifications into consideration: 107 | 108 | ``` 109 | Copyright (C) year1, year2, year3 copyright-holder 110 | ``` 111 | 112 | And an [oft-cited article] ups the ante with this behemoth: 113 | 114 | ``` 115 | Copyright [year project started] - [current year], [project founder] and the 116 | [project name] contributors. 117 | ``` 118 | 119 | So, which variation is correct? Depends! The first is technically wrong if more 120 | than one contributors exist. The third contains additional years of 121 | publication which, while common, are not explicitly covered by the current 122 | copyright law. The fourth specifies year ranges, which don't cover the case 123 | of gaps, i.e., years with none or trivial changes. 124 | 125 | In our case, we use the copyright format of the Apache license. Keep on reading 126 | for an equally exciting rationale behind each piece of the copyright format. 127 | 128 | ### Why not use the copyright symbol? 129 | 130 | Both the copyright symbol `(C)` and the `All rights reserved` phrase are relics 131 | of the past [4] [5]. They are confusing and we should stop using them. 132 | 133 | Note that many people confusingly equate the absence of the copyright symbol 134 | with the absence of a copyright notice. This is wrong. Even when copyright 135 | notices were legally required, they were valid as long as they used the word 136 | "Copyright" [6]. 137 | 138 | ### Why state only the year of creation? 139 | 140 | Some copyright formats keep a log with the years that a file was edited. The 141 | perceived benefit is that subsequent changes will not be back-dated, thus the 142 | copyright will last longer. 143 | 144 | This may work for slowly-revised works such as books, but it's different in 145 | software: 146 | 147 | First, the pre-1989 copyright law does not expressly permit multiple years [7] 148 | [8]. Granted, it's a common practice, but most probably redundant by now. 149 | 150 | Second, non-trivial changes such as whitespace fixes do not warrant a bump in 151 | the copyright year [9]. What constitutes as a trivial change is debatable, and 152 | communicating it to the contributors is even more difficult. You'll need to 153 | ensure that they don't needlessly update the copyright, forget to do so when 154 | they must, or erroneously use a different year. It's not nice risking the 155 | validity of your copyrights due to typos. 156 | 157 | Third, in a FOSS project that uses Git, it will always be more accurate to find 158 | the years that a file or the project was modified, with the following command: 159 | 160 | ```shell 161 | $ git log --pretty=format:"%cd" --date=format:"%Y" | sort -u 162 | 2012 163 | 2014 164 | 2019 165 | ``` 166 | 167 | Whether git commands hold as legal exhibits is a different story. 168 | 169 | Fourth, almost all countries protect copyrighted work for at least 50 years 170 | after the author's death [10]. Remember, you're not writing "Lord of the Rings", 171 | you're writing FOSS [11]. Your project's shelf life contests that of beer. 172 | 173 | ### Who are the Tindercrypt contributors? 174 | 175 | A common way to log the contributors of a project is to add themselves to an 176 | `AUTHORS` file or to the copyright header of the file they contribute to. 177 | However, this info can easily go out of sync or encourage anti-patterns [12] 178 | [13] so, again, git to the rescue: 179 | 180 | ```shell 181 | $ git log --pretty=format:"%an <%ae>" | sort -u 182 | Jane Doe 183 | John Bots 184 | ``` 185 | 186 | ## Why this license? 187 | 188 | In order to explain why we chose a niche license like [Mozilla Public License 189 | 2.0 (MPL-2.0)] for this project, first we need to answer why we need a license 190 | in the first place, which are the most common licenses and why none of them fit 191 | the bill. 192 | 193 | ### Why do we need a license in the first place? 194 | 195 | Many people assume that by default, software is free to use, and by putting a 196 | license to it we pose some form of restriction. Actually, it's backwards. By 197 | default, software cannot be used by anyone other than its copyright holder, and 198 | users must ask for their explicit permission [14]. So, if one writes code that 199 | wants to be used by other people, they should pick a license that allows them to 200 | do so, without asking for permission. 201 | 202 | ### Which are the most common licenses? 203 | 204 | We'll use stats from Github, since it's currently the biggest code hosting 205 | platform for FOSS projects. As of writing this, the most common software 206 | licenses in Github [15] are the following: 207 | 208 | * MIT (~4M repos, notably Bootstrap, Visual Studio Code, Bitcoin) 209 | * Apache (~1M repos, notably Tensorflow, Kubernetes, Swift) 210 | * GPL family (~1M repos, notably Linux Kernel, Ansible, Signal) 211 | * BSD family (~200k repos, notably Redis, Homebrew) 212 | * LGPL family (~100K repos, notably Ethereum, libvirt) 213 | 214 | In the context of Rust, the above numbers become [16]: 215 | 216 | * MIT: ~16K repos 217 | * Apache:~4K repos 218 | * GPL family: ~4K repos 219 | * BSD family: ~1K repos 220 | * LGPL family: ~300 repos 221 | 222 | **Fun fact!** MIT and GPLv2 licenses do not entirely protect users against legal 223 | action from copyright holders, and are discouraged from being used, at least on 224 | their own [17]. Have this data point in mind for the current state of licensing 225 | in FOSS. 226 | 227 | ### Why were they not chosen? 228 | 229 | Of these licenses, MIT and Apache are generally considered as the most easy to 230 | work with. The Rust language itself and its associated tools are typically 231 | distributed under the MIT/ASL2 license [18]. The majority of the Rust repos also 232 | fall under one of those two licenses, as one can see in the above stats. The 233 | reason these licenses are so easy to work with is because they are "permissive", 234 | i.e., they allow the end user to do virtually anything they want with the code. 235 | This includes distributing a modified version of the code, without disclosing 236 | what modifications were made. This is a subject of endless debates between the 237 | FS and OSS camp and, if this project were to take a position, it would be the 238 | following: 239 | 240 | 1. Security-sensitive code cannot be fully trusted unless it's open. 241 | 2. Security fixes and extensions from third parties would greatly benefit the 242 | health of this project. 243 | 244 | Permissive licenses do not prohibit the above, but they don't enforce them 245 | either, so they emit a weak signal and thus are not useful for this project. 246 | This leaves the MIT, Apache and BSD family of licenses out. 247 | 248 | GPL licenses go to great lengths to ensure that the code remains free. They 249 | follow a share-alike approach, that requires any third-party code ("derivative 250 | work") that uses a GPL-licensed code to be licensed as GPL as well. Again, this 251 | is a subject of endless debates between the FS and OSS camp. Ultimately, we 252 | don't mind if this code is used in proprietary contexts, so long as it's open, 253 | so the GPL family of licenses is out. 254 | 255 | LGPL licenses are more permissive than GPL licenses. Users of LGPL-licensed 256 | libraries (via dynamic linking) do not have to relicense their software to 257 | LGPL. However, derivative works and executables (via static linking) must 258 | relicense themselves as LGPL. This means that in the Rust ecosystem, which 259 | favors static linking, LGPL licenses are effectively similar to GPL licenses 260 | [19]. This is undesirable for the reasons explained above, so the LGPL family of 261 | licenses is out. 262 | 263 | ### Why pick MPL-2.0? 264 | 265 | Since none of the common licenses fit the bill, we need to turn to less common 266 | ones. A license that is a middle ground between MIT/Apache and (L)GPL is the 267 | Mozilla Public License 2.0 (MPL-2.0). It's a relatively new license (published 268 | in 2012), that is compatible both with the Apache and (L)GPL licenses. There are 269 | ~40K repos in Github that use this license, notably Mozilla [Servo] and 270 | Hashicorp [Terraform], and ~900 Rust repos, the latter number being comparable 271 | to the number of BSD and LGPL Rust repos. 272 | 273 | The advantage of MPL-2.0 over the aforementioned licenses is that it enforces 274 | that the code will remain always open, without requiring anything else from the 275 | user, besides sharing any modifications they've made to the code. Also, since 276 | it's more modern, it has explicit clauses regarding patent rights and license 277 | compatibilities, making it easier for users and legal departments to comply 278 | with. 279 | 280 | ## Why is the legal notice not attached to every file? 281 | 282 | Before explaining why, let's see what the absence of a copyright and license 283 | notice from a file means for this project from a legal perspective: 284 | 285 | * **Absence of copyright:** As mentioned above, copyright is automatic and 286 | granted to the author the moment they publish their work, so each file would 287 | belong to its authors. 288 | * **Absence of license:** The code is still licensed under MPL-2.0. The MPL-2.0 289 | license has a clause that makes the code licensed under it, if the author 290 | ships a copy of the full MPL-2.0 license with their code [20], which this 291 | project does. 292 | 293 | So, even if a court ruled that the legal notice in [`NOTICE.md`] does not apply 294 | to all files, what would the default legal protection be? The sole difference 295 | would be that the copyright for a file would belong to its authors and not the 296 | project contributors collectively, as stated in [`NOTICE.md`]. This is 297 | cumbersome, but not catastrophic. Other than that, they are effectively the 298 | same. 299 | 300 | It would seem that we don't actually need to add the legal notice of [`NOTICE.md`] 301 | to every file in this repo, which is much more sane from a maintenance poin- 302 | **NOT SO FAST, PARTNER!** 303 | 304 | The author is still advised to add the per-file boilerplate header, in case 305 | another dev copy-pastes a file of this project to their project, without 306 | checking the root directory for a legal notice [21] [22], and thus claiming that 307 | they didn't know what the copyright/license was. This is commonly called 308 | "innocent infringement". 309 | 310 | *Oh cooome on...* Is this the best we can do? Suppose that this was not about 311 | "code repositories" but "poem anthologies". Am I allowed to pick a poem from 312 | someone's anthology, put it in my own anthology and publish it? No. Would it 313 | be reasonable to expect a copyright notice to be prepended before each poem in 314 | the book, so I don't get confused? By Toutatis, no! 315 | 316 | Adding a copyright notice **into each part** of a collective work was never a 317 | requirement for books and songs, the very things that copyright law was designed 318 | to protect. Yet somehow, this practice is encouraged in code. Maybe this happens 319 | because lawyers don't read code, as they read books and listen to songs. Or 320 | because they believe that devs will absentmindedly copy-paste a file from a 321 | project without skimming its `README` or home page [23]. Or because this is an 322 | easy task to automate. 323 | 324 | Personally, I believe that: 325 | 326 | 1. Explicitly stating in a single `NOTICE` file what is the legal status of a 327 | project. 328 | 2. Making everyone aware about this file by mentioning it in the `README`. 329 | 330 | should make the life of users and contributors much easier, and the legal status 331 | clearer, since there are no omitted or stale file headers. 332 | 333 | I also like to believe that there are tools nowadays, such as [FOSSA], that can 334 | help those devs, who in their copy-paste frenzy forget to retain the original 335 | license. Or use a package manager. 336 | 337 | Unfortunately, I don't (and hopefully will never) know what are the legal 338 | consequences of not having this legal notice splattered all over the files of 339 | this repo. But... 340 | 341 | **Fun fact!** Hashicorp, which happens to be a big MPL-2.0 user, does not have 342 | per-file boilerplate headers in its projects, e.g., [Terraform]. I don't know 343 | about you, but after this plunge into the sea of legal ambiguity, this is a 344 | comforting thought. 345 | 346 | ## Footnotes 347 | 348 | [1] From https://spdx.org/ids-how: 349 | 350 | SPDX IDs are intending to express information about licenses. Copyright 351 | notices ‐ statements about who owns the copyright in a file or project ‐ are 352 | outside the scope of SPDX short-form IDs. 353 | 354 | Therefore, you should not remove or modify existing copyright notices in 355 | files when adding an SPDX ID. 356 | 357 | --- 358 | 359 | When a license defines a recommended notice to attach to files under that 360 | license (sometimes called a "standard header"), the SPDX project recommends 361 | that the standard header be included in the files, in addition to an SPDX 362 | ID. 363 | 364 | [2] From https://softwarefreedom.org/resources/2012/ManagingCopyrightInformation.html: 365 | 366 | Contrary to popular belief, copyright notices aren't required to secure 367 | copyright. Each developer holds copyright in his or her code the moment it 368 | is written, and because all the world's major copyright systems—including 369 | the US after 1976—do not require notices, publishing code without a 370 | copyright notice doesn't change this. However, notices do have some legal 371 | effect. For example, someone who infringes the copyright of a program 372 | published without a notice may be able to claim that the infringement was 373 | "innocent" because he or she had no notice of the developers' copyright 374 | claim, and thus seek reduced damages. 375 | 376 | There are other good reasons to include copyright notices as well. They 377 | acknowledge the developers' contributions to the project. They also serve as 378 | a record of people who claim rights in the codebase, which may be needed if 379 | the project later wishes to seek the contributors' permission to change its 380 | license. Finally, when you incorporate third-party free software into your 381 | project, you must include the corresponding copyright notices—nearly every 382 | free software license requires it. 383 | 384 | [3] From https://ben.balter.com/2015/06/03/copyright-notices-for-websites-and-open-source-projects/: 385 | 386 | Historically, the primary point of putting copyright on anything is because 387 | in ye olden days (before 1979), it was a legal requirement that a publisher 388 | visually mark their work in order to secure their copyright under the 389 | United States Copyright Act. After the US became a signatory of the Berne 390 | convention (along with 167 other countries), that requirement was dropped, 391 | and copyright now automatically vests in the author at the time of 392 | publication in the vast majority of countries, notice or no notice. 393 | 394 | Today, explicit copyright notices in licenses, footers (or really in 395 | general), are not necessary for getting a copyright. They still have some 396 | uses, though. First, someone may want to use your work in ways not allowed 397 | by your license; notices help them determine who to ask for permission. 398 | Explicit notices can help you prove that you and your collaborators really 399 | are the copyright holders. They can serve to put a potential infringer on 400 | notice by providing an informal sniff test to counter the "Oh yeah, well I 401 | didn't know it was copyrighted" defense. For some users the copyright 402 | notice may suggest higher quality, as they expect that good software will 403 | include a notice. A notice may also help people determine when copyright 404 | might expire, but the date is ambiguous at best, and I'd suspect we'll have 405 | better ways to determine the date of publication 80+ years from now, if 406 | your code is still worth protecting. Git can track these things, but people 407 | may receive software outside of git or where the git history has not been 408 | retained. 409 | 410 | [4] https://www.plagiarismtoday.com/2015/08/18/the-bizarre-history-of-all-rights-reserved/ 411 | 412 | [5] https://www.plagiarismtoday.com/stopping-internet-plagiarism/your-copyrights-online/3-copyright-myths/ 413 | 414 | [6] From https://en.wikipedia.org/wiki/Copyright_symbol#Pre-1989_U.S._copyright_notice: 415 | 416 | In the United States, the copyright notice required prior to March 1, 1989, 417 | consists of ... the `(C)` symbol, or the word "Copyright" or abbreviation 418 | "Copr."; 419 | 420 | [7] https://danashultz.com/2013/10/09/copyright-notice-with-multiple-years-legitimate/ 421 | 422 | [8] https://techwhirl.com/updating-copyright-notices/ 423 | 424 | [9] From https://www.gnu.org/prep/maintain/html_node/Copyright-Notices.html: 425 | 426 | To update the list of year numbers, add each year in which you have made 427 | nontrivial changes to the package. 428 | 429 | --- 430 | 431 | You can use a range (‘2008-2010’) instead of listing individual years 432 | (‘2008, 2009, 2010’) if and only if: 1) every year in the range, inclusive, 433 | really is a “copyrightable” year that would be listed individually; 434 | 435 | [10] From https://www.wipo.int/treaties/en/ip/berne/summary_berne.html: 436 | 437 | As to the duration of protection, the general rule is that protection must 438 | be granted until the expiration of the 50th year after the author's death. 439 | 440 | [11] https://stackoverflow.com/a/20911485 441 | 442 | [12] From https://opensource.com/law/14/2/copyright-statements-source-files: 443 | 444 | * If I edit a file and it says at the top that the file is copyright BigCo, 445 | I am discouraged from editing that file, because of the implication that 446 | I'm treading on someone else's toes. Files should not have any indication 447 | that they are "owned" by any one person or company. (See this by Karl 448 | Fogel for more on "owning" code.) This actively discourages people 449 | jumping in and fixing stuff. 450 | * If N people contribute to a file, are we supposed to have N copyright 451 | statements in the file? This doesn't scale over time. Imagine what these 452 | files will look like 10 years from now, and fix the problem now. 453 | * Having author names in a file encourages people to contribute for the 454 | wrong reasons. 455 | * Git keeps track of who contributed what changes. It's not necessary to 456 | have explicit copyright statements. 457 | 458 | [13] https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/packages/python-xyz.scm 459 | 460 | [14] https://choosealicense.com/no-permission/ 461 | 462 | [15] Stats taken by Github with the following queries: 463 | 464 | * MIT: license:mit 465 | * Apache: license:apache-2.0 466 | * GPL: license:gpl 467 | * BSD: license:bsd-2-clause and license:bsd-3-clause 468 | * LGPL: license:lgpl 469 | * MPL-2.0: license:mpl-2.0 470 | 471 | [16] We simply add the `language:rust` search filter to the `license:...` 472 | filters mentioned in [15]. 473 | 474 | [17] From http://worthhiding.com/2018/01/18/licenses-loopholes-and-litigation-a-comprehensive-survey-of-software-case-law-and-open-source-licensing-in-the-united-states/: 475 | 476 | Indeed, while GPLv3 and Apache 2.0 are effective at protecting against all 477 | four mechanisms, GPLv2 left patent grants unaddressed, and the MIT license 478 | left both patents and object code copyright completely unaddressed. 479 | 480 | Despite its weakness, the MIT license ranks as the most popular in the open 481 | source community, with 45% of licensed projects on GitHub using it. The 482 | also-weak GPLv2 is second in GitHub popularity, with 13% of projects. 483 | 484 | [18] https://doc.rust-lang.org/1.5.0/complement-project-faq.html#why-a-bsd-style-permissive-license-rather-than-mpl-or-tri-license 485 | 486 | [19] https://internals.rust-lang.org/t/cargo-build-certain-dependencies-as-dylibs/4586/5 487 | 488 | [20] From https://www.mozilla.org/en-US/MPL/2.0/FAQ/: 489 | 490 | Q22: Does MPL 2.0 require that the MPL 2.0 license notice header be 491 | included in every file? 492 | 493 | The license notice must be in some way "attached" to each file. 494 | (Sec. 1.4.) In cases where putting it in the file is impossible or 495 | impractical, that requirement can be fulfilled by putting the notice 496 | somewhere that a recipient "would be likely to look for such a 497 | notice," such as a LICENSE file in the same directory as the file. 498 | 499 | --- 500 | 501 | Q25: What happens if someone doesn't use the per-file boilerplate, 502 | and just ships a copy of the full MPL 2 with their code? 503 | 504 | The code is licensed under the plain MPL 2. It is not considered 505 | Incompatible with Secondary Licenses. 506 | 507 | [21] From https://lu.is/blog/2012/03/17/on-the-importance-of-per-file-license-information/: 508 | 509 | It is true that in the best case scenario in many modern 510 | languages/frameworks, library-level is a great place to put licenses – in 511 | normal use, they'll get seen and understood. But lots of coding in the 512 | wild is not ""normal use."" I review a lot of different codebases these 513 | days, and files get separated from their parent projects and directories 514 | all the time. 515 | 516 | [22] From https://www.mozilla.org/en-US/MPL/2.0/FAQ/: 517 | 518 | While the license permits putting the header somewhere other than 519 | the file itself... putting the license notice in the file is the 520 | surest way to ensure that recipients are always notified. 521 | 522 | [23] In modern languages with first-class support for package managers, this 523 | should almost never happen. Suppose that it happens though. A lot of licenses 524 | propose adding a similar file header: 525 | 526 | Part of Foo project ... read the LICENSE file at the top of this directory 527 | 528 | If a dev simply copy-pastes this file to their project, without copying the 529 | LICENSE file, do they mean to use their own LICENSE file? Do they refer to the 530 | license of the Foo project? Which fork of the Foo project? 531 | 532 | 533 | [`NOTICE.md`]: /NOTICE.md 534 | [`LICENSE`]: /LICENSE 535 | [IANAL]: https://en.wikipedia.org/wiki/IANAL 536 | [SPDX]: https://spdx.org/ 537 | [Servo]: https://github.com/servo/servo 538 | [Terraform]: https://github.com/hashicorp/terraform 539 | [FOSSA]: https://fossa.com/ 540 | [How to Apply These Terms to Your New Programs]: https://www.gnu.org/licenses/gpl-3.0.html#howto 541 | [How to apply the Apache License to your work]: https://www.apache.org/licenses/LICENSE-2.0.html#apply 542 | [an alternative copyright format]: https://www.gnu.org/prep/maintain/html_node/Copyright-Notices.html 543 | [oft-cited article]: https://ben.balter.com/2015/06/03/copyright-notices-for-websites-and-open-source-projects/ 544 | [Mozilla Public License 2.0 (MPL-2.0)]: https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2) 545 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anstyle" 25 | version = "1.0.10" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.97" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 34 | 35 | [[package]] 36 | name = "assert_cmd" 37 | version = "2.0.16" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" 40 | dependencies = [ 41 | "anstyle", 42 | "bstr", 43 | "doc-comment", 44 | "libc", 45 | "predicates", 46 | "predicates-core", 47 | "predicates-tree", 48 | "wait-timeout", 49 | ] 50 | 51 | [[package]] 52 | name = "assert_fs" 53 | version = "1.1.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" 56 | dependencies = [ 57 | "anstyle", 58 | "doc-comment", 59 | "globwalk", 60 | "predicates", 61 | "predicates-core", 62 | "predicates-tree", 63 | "tempfile", 64 | ] 65 | 66 | [[package]] 67 | name = "atty" 68 | version = "0.2.14" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 71 | dependencies = [ 72 | "hermit-abi", 73 | "libc", 74 | "winapi", 75 | ] 76 | 77 | [[package]] 78 | name = "autocfg" 79 | version = "1.4.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 82 | 83 | [[package]] 84 | name = "bitflags" 85 | version = "1.3.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 88 | 89 | [[package]] 90 | name = "bitflags" 91 | version = "2.9.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 94 | 95 | [[package]] 96 | name = "bstr" 97 | version = "1.12.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 100 | dependencies = [ 101 | "memchr", 102 | "regex-automata", 103 | "serde", 104 | ] 105 | 106 | [[package]] 107 | name = "cc" 108 | version = "1.2.19" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" 111 | dependencies = [ 112 | "shlex", 113 | ] 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "clap" 123 | version = "2.34.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 126 | dependencies = [ 127 | "ansi_term", 128 | "atty", 129 | "bitflags 1.3.2", 130 | "strsim", 131 | "textwrap", 132 | "unicode-width 0.1.14", 133 | "vec_map", 134 | ] 135 | 136 | [[package]] 137 | name = "console" 138 | version = "0.15.11" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 141 | dependencies = [ 142 | "encode_unicode", 143 | "libc", 144 | "once_cell", 145 | "unicode-width 0.2.0", 146 | "windows-sys 0.59.0", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-deque" 151 | version = "0.8.6" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 154 | dependencies = [ 155 | "crossbeam-epoch", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.9.18" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 164 | dependencies = [ 165 | "crossbeam-utils", 166 | ] 167 | 168 | [[package]] 169 | name = "crossbeam-utils" 170 | version = "0.8.21" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 173 | 174 | [[package]] 175 | name = "dialoguer" 176 | version = "0.11.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 179 | dependencies = [ 180 | "console", 181 | "shell-words", 182 | "tempfile", 183 | "thiserror 1.0.69", 184 | "zeroize", 185 | ] 186 | 187 | [[package]] 188 | name = "difflib" 189 | version = "0.4.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 192 | 193 | [[package]] 194 | name = "doc-comment" 195 | version = "0.3.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 198 | 199 | [[package]] 200 | name = "either" 201 | version = "1.15.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 204 | 205 | [[package]] 206 | name = "encode_unicode" 207 | version = "1.0.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 210 | 211 | [[package]] 212 | name = "equivalent" 213 | version = "1.0.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 216 | 217 | [[package]] 218 | name = "errno" 219 | version = "0.3.11" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 222 | dependencies = [ 223 | "libc", 224 | "windows-sys 0.59.0", 225 | ] 226 | 227 | [[package]] 228 | name = "fastrand" 229 | version = "2.3.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 232 | 233 | [[package]] 234 | name = "float-cmp" 235 | version = "0.10.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 238 | dependencies = [ 239 | "num-traits", 240 | ] 241 | 242 | [[package]] 243 | name = "getrandom" 244 | version = "0.2.15" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 247 | dependencies = [ 248 | "cfg-if", 249 | "libc", 250 | "wasi 0.11.0+wasi-snapshot-preview1", 251 | ] 252 | 253 | [[package]] 254 | name = "getrandom" 255 | version = "0.3.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 258 | dependencies = [ 259 | "cfg-if", 260 | "libc", 261 | "r-efi", 262 | "wasi 0.14.2+wasi-0.2.4", 263 | ] 264 | 265 | [[package]] 266 | name = "globset" 267 | version = "0.4.16" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" 270 | dependencies = [ 271 | "aho-corasick", 272 | "bstr", 273 | "log", 274 | "regex-automata", 275 | "regex-syntax", 276 | ] 277 | 278 | [[package]] 279 | name = "globwalk" 280 | version = "0.9.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 283 | dependencies = [ 284 | "bitflags 2.9.0", 285 | "ignore", 286 | "walkdir", 287 | ] 288 | 289 | [[package]] 290 | name = "hashbrown" 291 | version = "0.15.2" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 294 | 295 | [[package]] 296 | name = "hermit-abi" 297 | version = "0.1.19" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 300 | dependencies = [ 301 | "libc", 302 | ] 303 | 304 | [[package]] 305 | name = "home" 306 | version = "0.5.11" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 309 | dependencies = [ 310 | "windows-sys 0.59.0", 311 | ] 312 | 313 | [[package]] 314 | name = "ignore" 315 | version = "0.4.23" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 318 | dependencies = [ 319 | "crossbeam-deque", 320 | "globset", 321 | "log", 322 | "memchr", 323 | "regex-automata", 324 | "same-file", 325 | "walkdir", 326 | "winapi-util", 327 | ] 328 | 329 | [[package]] 330 | name = "indexmap" 331 | version = "2.9.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 334 | dependencies = [ 335 | "equivalent", 336 | "hashbrown", 337 | ] 338 | 339 | [[package]] 340 | name = "lazy_static" 341 | version = "1.5.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 344 | 345 | [[package]] 346 | name = "libc" 347 | version = "0.2.171" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 350 | 351 | [[package]] 352 | name = "linux-raw-sys" 353 | version = "0.4.15" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 356 | 357 | [[package]] 358 | name = "linux-raw-sys" 359 | version = "0.9.4" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 362 | 363 | [[package]] 364 | name = "log" 365 | version = "0.4.27" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 368 | 369 | [[package]] 370 | name = "memchr" 371 | version = "2.7.4" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 374 | 375 | [[package]] 376 | name = "normalize-line-endings" 377 | version = "0.3.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 380 | 381 | [[package]] 382 | name = "num-traits" 383 | version = "0.2.19" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 386 | dependencies = [ 387 | "autocfg", 388 | ] 389 | 390 | [[package]] 391 | name = "once_cell" 392 | version = "1.21.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 395 | 396 | [[package]] 397 | name = "ppv-lite86" 398 | version = "0.2.21" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 401 | dependencies = [ 402 | "zerocopy", 403 | ] 404 | 405 | [[package]] 406 | name = "predicates" 407 | version = "3.1.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 410 | dependencies = [ 411 | "anstyle", 412 | "difflib", 413 | "float-cmp", 414 | "normalize-line-endings", 415 | "predicates-core", 416 | "regex", 417 | ] 418 | 419 | [[package]] 420 | name = "predicates-core" 421 | version = "1.0.9" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 424 | 425 | [[package]] 426 | name = "predicates-tree" 427 | version = "1.0.12" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 430 | dependencies = [ 431 | "predicates-core", 432 | "termtree", 433 | ] 434 | 435 | [[package]] 436 | name = "proc-macro2" 437 | version = "1.0.94" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 440 | dependencies = [ 441 | "unicode-ident", 442 | ] 443 | 444 | [[package]] 445 | name = "protobuf" 446 | version = "3.7.2" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" 449 | dependencies = [ 450 | "once_cell", 451 | "protobuf-support", 452 | "thiserror 1.0.69", 453 | ] 454 | 455 | [[package]] 456 | name = "protobuf-codegen" 457 | version = "3.7.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "5d3976825c0014bbd2f3b34f0001876604fe87e0c86cd8fa54251530f1544ace" 460 | dependencies = [ 461 | "anyhow", 462 | "once_cell", 463 | "protobuf", 464 | "protobuf-parse", 465 | "regex", 466 | "tempfile", 467 | "thiserror 1.0.69", 468 | ] 469 | 470 | [[package]] 471 | name = "protobuf-parse" 472 | version = "3.7.2" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" 475 | dependencies = [ 476 | "anyhow", 477 | "indexmap", 478 | "log", 479 | "protobuf", 480 | "protobuf-support", 481 | "tempfile", 482 | "thiserror 1.0.69", 483 | "which", 484 | ] 485 | 486 | [[package]] 487 | name = "protobuf-support" 488 | version = "3.7.2" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" 491 | dependencies = [ 492 | "thiserror 1.0.69", 493 | ] 494 | 495 | [[package]] 496 | name = "quote" 497 | version = "1.0.40" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 500 | dependencies = [ 501 | "proc-macro2", 502 | ] 503 | 504 | [[package]] 505 | name = "r-efi" 506 | version = "5.2.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 509 | 510 | [[package]] 511 | name = "rand" 512 | version = "0.9.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 515 | dependencies = [ 516 | "rand_chacha", 517 | "rand_core", 518 | "zerocopy", 519 | ] 520 | 521 | [[package]] 522 | name = "rand_chacha" 523 | version = "0.9.0" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 526 | dependencies = [ 527 | "ppv-lite86", 528 | "rand_core", 529 | ] 530 | 531 | [[package]] 532 | name = "rand_core" 533 | version = "0.9.3" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 536 | dependencies = [ 537 | "getrandom 0.3.2", 538 | ] 539 | 540 | [[package]] 541 | name = "regex" 542 | version = "1.11.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 545 | dependencies = [ 546 | "aho-corasick", 547 | "memchr", 548 | "regex-automata", 549 | "regex-syntax", 550 | ] 551 | 552 | [[package]] 553 | name = "regex-automata" 554 | version = "0.4.9" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 557 | dependencies = [ 558 | "aho-corasick", 559 | "memchr", 560 | "regex-syntax", 561 | ] 562 | 563 | [[package]] 564 | name = "regex-syntax" 565 | version = "0.8.5" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 568 | 569 | [[package]] 570 | name = "ring" 571 | version = "0.17.14" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 574 | dependencies = [ 575 | "cc", 576 | "cfg-if", 577 | "getrandom 0.2.15", 578 | "libc", 579 | "untrusted", 580 | "windows-sys 0.52.0", 581 | ] 582 | 583 | [[package]] 584 | name = "rustix" 585 | version = "0.38.44" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 588 | dependencies = [ 589 | "bitflags 2.9.0", 590 | "errno", 591 | "libc", 592 | "linux-raw-sys 0.4.15", 593 | "windows-sys 0.59.0", 594 | ] 595 | 596 | [[package]] 597 | name = "rustix" 598 | version = "1.0.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 601 | dependencies = [ 602 | "bitflags 2.9.0", 603 | "errno", 604 | "libc", 605 | "linux-raw-sys 0.9.4", 606 | "windows-sys 0.59.0", 607 | ] 608 | 609 | [[package]] 610 | name = "same-file" 611 | version = "1.0.6" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 614 | dependencies = [ 615 | "winapi-util", 616 | ] 617 | 618 | [[package]] 619 | name = "serde" 620 | version = "1.0.219" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 623 | dependencies = [ 624 | "serde_derive", 625 | ] 626 | 627 | [[package]] 628 | name = "serde_derive" 629 | version = "1.0.219" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 632 | dependencies = [ 633 | "proc-macro2", 634 | "quote", 635 | "syn", 636 | ] 637 | 638 | [[package]] 639 | name = "shell-words" 640 | version = "1.1.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 643 | 644 | [[package]] 645 | name = "shlex" 646 | version = "1.3.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 649 | 650 | [[package]] 651 | name = "strsim" 652 | version = "0.8.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 655 | 656 | [[package]] 657 | name = "syn" 658 | version = "2.0.100" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "unicode-ident", 665 | ] 666 | 667 | [[package]] 668 | name = "tempfile" 669 | version = "3.19.1" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 672 | dependencies = [ 673 | "fastrand", 674 | "getrandom 0.3.2", 675 | "once_cell", 676 | "rustix 1.0.5", 677 | "windows-sys 0.59.0", 678 | ] 679 | 680 | [[package]] 681 | name = "termtree" 682 | version = "0.5.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 685 | 686 | [[package]] 687 | name = "textwrap" 688 | version = "0.11.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 691 | dependencies = [ 692 | "unicode-width 0.1.14", 693 | ] 694 | 695 | [[package]] 696 | name = "thiserror" 697 | version = "1.0.69" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 700 | dependencies = [ 701 | "thiserror-impl 1.0.69", 702 | ] 703 | 704 | [[package]] 705 | name = "thiserror" 706 | version = "2.0.12" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 709 | dependencies = [ 710 | "thiserror-impl 2.0.12", 711 | ] 712 | 713 | [[package]] 714 | name = "thiserror-impl" 715 | version = "1.0.69" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 718 | dependencies = [ 719 | "proc-macro2", 720 | "quote", 721 | "syn", 722 | ] 723 | 724 | [[package]] 725 | name = "thiserror-impl" 726 | version = "2.0.12" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 729 | dependencies = [ 730 | "proc-macro2", 731 | "quote", 732 | "syn", 733 | ] 734 | 735 | [[package]] 736 | name = "tindercrypt" 737 | version = "0.3.4" 738 | dependencies = [ 739 | "assert_cmd", 740 | "assert_fs", 741 | "clap", 742 | "dialoguer", 743 | "lazy_static", 744 | "predicates", 745 | "protobuf", 746 | "protobuf-codegen", 747 | "rand", 748 | "ring", 749 | "thiserror 2.0.12", 750 | "zeroize", 751 | ] 752 | 753 | [[package]] 754 | name = "unicode-ident" 755 | version = "1.0.18" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 758 | 759 | [[package]] 760 | name = "unicode-width" 761 | version = "0.1.14" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 764 | 765 | [[package]] 766 | name = "unicode-width" 767 | version = "0.2.0" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 770 | 771 | [[package]] 772 | name = "untrusted" 773 | version = "0.9.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 776 | 777 | [[package]] 778 | name = "vec_map" 779 | version = "0.8.2" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 782 | 783 | [[package]] 784 | name = "wait-timeout" 785 | version = "0.2.1" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 788 | dependencies = [ 789 | "libc", 790 | ] 791 | 792 | [[package]] 793 | name = "walkdir" 794 | version = "2.5.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 797 | dependencies = [ 798 | "same-file", 799 | "winapi-util", 800 | ] 801 | 802 | [[package]] 803 | name = "wasi" 804 | version = "0.11.0+wasi-snapshot-preview1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 807 | 808 | [[package]] 809 | name = "wasi" 810 | version = "0.14.2+wasi-0.2.4" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 813 | dependencies = [ 814 | "wit-bindgen-rt", 815 | ] 816 | 817 | [[package]] 818 | name = "which" 819 | version = "4.4.2" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 822 | dependencies = [ 823 | "either", 824 | "home", 825 | "once_cell", 826 | "rustix 0.38.44", 827 | ] 828 | 829 | [[package]] 830 | name = "winapi" 831 | version = "0.3.9" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 834 | dependencies = [ 835 | "winapi-i686-pc-windows-gnu", 836 | "winapi-x86_64-pc-windows-gnu", 837 | ] 838 | 839 | [[package]] 840 | name = "winapi-i686-pc-windows-gnu" 841 | version = "0.4.0" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 844 | 845 | [[package]] 846 | name = "winapi-util" 847 | version = "0.1.9" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 850 | dependencies = [ 851 | "windows-sys 0.59.0", 852 | ] 853 | 854 | [[package]] 855 | name = "winapi-x86_64-pc-windows-gnu" 856 | version = "0.4.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 859 | 860 | [[package]] 861 | name = "windows-sys" 862 | version = "0.52.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 865 | dependencies = [ 866 | "windows-targets", 867 | ] 868 | 869 | [[package]] 870 | name = "windows-sys" 871 | version = "0.59.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 874 | dependencies = [ 875 | "windows-targets", 876 | ] 877 | 878 | [[package]] 879 | name = "windows-targets" 880 | version = "0.52.6" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 883 | dependencies = [ 884 | "windows_aarch64_gnullvm", 885 | "windows_aarch64_msvc", 886 | "windows_i686_gnu", 887 | "windows_i686_gnullvm", 888 | "windows_i686_msvc", 889 | "windows_x86_64_gnu", 890 | "windows_x86_64_gnullvm", 891 | "windows_x86_64_msvc", 892 | ] 893 | 894 | [[package]] 895 | name = "windows_aarch64_gnullvm" 896 | version = "0.52.6" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 899 | 900 | [[package]] 901 | name = "windows_aarch64_msvc" 902 | version = "0.52.6" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 905 | 906 | [[package]] 907 | name = "windows_i686_gnu" 908 | version = "0.52.6" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 911 | 912 | [[package]] 913 | name = "windows_i686_gnullvm" 914 | version = "0.52.6" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 917 | 918 | [[package]] 919 | name = "windows_i686_msvc" 920 | version = "0.52.6" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 923 | 924 | [[package]] 925 | name = "windows_x86_64_gnu" 926 | version = "0.52.6" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 929 | 930 | [[package]] 931 | name = "windows_x86_64_gnullvm" 932 | version = "0.52.6" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 935 | 936 | [[package]] 937 | name = "windows_x86_64_msvc" 938 | version = "0.52.6" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 941 | 942 | [[package]] 943 | name = "wit-bindgen-rt" 944 | version = "0.39.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 947 | dependencies = [ 948 | "bitflags 2.9.0", 949 | ] 950 | 951 | [[package]] 952 | name = "zerocopy" 953 | version = "0.8.24" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 956 | dependencies = [ 957 | "zerocopy-derive", 958 | ] 959 | 960 | [[package]] 961 | name = "zerocopy-derive" 962 | version = "0.8.24" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 965 | dependencies = [ 966 | "proc-macro2", 967 | "quote", 968 | "syn", 969 | ] 970 | 971 | [[package]] 972 | name = "zeroize" 973 | version = "1.8.1" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 976 | -------------------------------------------------------------------------------- /src/cryptors.rs: -------------------------------------------------------------------------------- 1 | //! # Cryptor structs for encryption/decryption 2 | //! 3 | //! A cryptor is a struct with methods that can encrypt (seal) a plaintext or 4 | //! decrypt (open) a ciphertext. This module provides [`RingCryptor`], a 5 | //! cryptor struct that provides the above functionality using various `ring` 6 | //! cryptographic primitives. 7 | //! 8 | //! [`RingCryptor`]: struct.RingCryptor.html 9 | 10 | use crate::aead; 11 | use crate::errors; 12 | use crate::metadata; 13 | use crate::pbkdf2; 14 | 15 | /// A cryptor that uses cryptographic primitives from the `ring` crate. 16 | /// 17 | /// If a user wants to encrypt a plaintext, they can use one of the `.seal_*` 18 | /// methods of the cryptor. In a nutshell, the encryption logic is the 19 | /// following: 20 | /// 21 | /// * Choose a key derivation and encryption algorithm. 22 | /// * Generate the [metadata] for these algorithms. 23 | /// * Serialize the [metadata] into a buffer large enough to hold the 24 | /// ciphertext and its tag. 25 | /// * Copy the plaintext in a specific position within the buffer. 26 | /// * Derive a symmetric key from a passphrase, if the PBKDF2 key derivation 27 | /// algorithm is used. 28 | /// * Encrypt the data in place. The original plaintext is not affected since 29 | /// it's a copy. 30 | /// * Return the buffer with the serialized metadata, ciphertext and tag. 31 | /// 32 | /// If a user wants to decrypt a plaintext, they can use one of the `.open_*` 33 | /// methods of the cryptor. In a nutshell, the decryption logic is the 34 | /// following: 35 | /// 36 | /// * Deserialize the [metadata] from the buffer header. Return an error if 37 | /// they are corrupted or don't exist. 38 | /// * Copy the ciphertext into a new buffer. 39 | /// * Derive a symmetric key from a passphrase, if the PBKDF2 key derivation 40 | /// algorithm is used. 41 | /// * Decrypt the data in place, or return a decryption error. The original 42 | /// ciphertext is not affected, since it's a copy. 43 | /// * Return the plaintext. 44 | /// 45 | /// The user can skip some of the copies and allocations, depending on which 46 | /// cryptor methods they choose to use. 47 | /// 48 | /// ## Examples 49 | /// 50 | /// The simplest way to encrypt a plaintext with a passprhase is the following: 51 | /// 52 | /// ``` 53 | /// use tindercrypt::cryptors::RingCryptor; 54 | /// 55 | /// let plaintext = "The cake is a lie".as_bytes(); 56 | /// let pass = "My secret passphrase".as_bytes(); 57 | /// let cryptor = RingCryptor::new(); 58 | /// 59 | /// let ciphertext = cryptor.seal_with_passphrase(pass, plaintext)?; 60 | /// let plaintext2 = cryptor.open(pass, &ciphertext)?; 61 | /// assert_eq!(plaintext2, plaintext); 62 | /// 63 | /// # use tindercrypt::errors; 64 | /// # Ok::<(), errors::Error>(()) 65 | /// ``` 66 | /// 67 | /// If the user can create their own buffers beforehand, the no-allocation path 68 | /// is the following: 69 | /// 70 | /// ``` 71 | /// use tindercrypt::metadata::Metadata; 72 | /// use tindercrypt::cryptors::RingCryptor; 73 | /// 74 | /// let plaintext = "The cake is a lie".as_bytes(); 75 | /// let pass = "My secret passphrase".as_bytes(); 76 | /// let cryptor = RingCryptor::new(); 77 | /// 78 | /// // The user can create this buffer beforehand. 79 | /// let meta = Metadata::generate_for_passphrase(plaintext.len()); 80 | /// let (mut _buf, meta_size) = meta.to_buf(); 81 | /// let mut buf = &mut _buf[meta_size..]; 82 | /// buf[..plaintext.len()].copy_from_slice(plaintext); 83 | /// 84 | /// // The user should derive the key from the passphrase. 85 | /// let key = RingCryptor::derive_key(&meta, pass)?; 86 | /// 87 | /// // These methods will not perform any allocation, and will encrypt/decrypt 88 | /// // the data in place. 89 | /// cryptor.seal_in_place(&meta, &key, buf)?; 90 | /// cryptor.open_in_place(&meta, &key, buf)?; 91 | /// assert_eq!(&buf[..plaintext.len()], plaintext); 92 | /// 93 | /// # use tindercrypt::errors; 94 | /// # Ok::<(), errors::Error>(()) 95 | /// ``` 96 | /// 97 | /// The user is also free to specify their own metadata for the encryption 98 | /// process, or use additional associated data (AAD). In the following example, 99 | /// the user instructs the cryptor to derive a symmetric key with 10 million 100 | /// PBKDF2 iterations, use the ChaCha20-Poly1305 encryption algorithm and bind 101 | /// the ciphertext with some AAD: 102 | /// 103 | /// ``` 104 | /// use tindercrypt::metadata; 105 | /// use tindercrypt::cryptors::RingCryptor; 106 | /// 107 | /// let plaintext = "The cake is a lie".as_bytes(); 108 | /// let pass = "My secret passphrase".as_bytes(); 109 | /// let aad = "My encryption context".as_bytes(); 110 | /// let cryptor = RingCryptor::new().with_aad(aad); // Set the AAD. 111 | /// 112 | /// // Create the Metadata struct. 113 | /// let mut key_deriv_meta = metadata::KeyDerivationMetadata::generate(); 114 | /// key_deriv_meta.iterations = 10000000; // 10 million PBKDF2 iterations 115 | /// # // ... but for the tests, lower the number of iterations. 116 | /// # key_deriv_meta.iterations = 1; 117 | /// let mut key_deriv_algo = metadata::KeyDerivationAlgorithm::PBKDF2(key_deriv_meta); 118 | /// let enc_meta = metadata::EncryptionMetadata::generate(); 119 | /// let enc_algo = metadata::EncryptionAlgorithm::ChaCha20Poly1305(enc_meta); 120 | /// let meta = metadata::Metadata::new(key_deriv_algo, enc_algo, plaintext.len()); 121 | /// 122 | /// // Serialize the Metadata struct into a buffer and copy the plaintext in it. 123 | /// let (mut buf, meta_size) = meta.to_buf(); 124 | /// buf[meta_size..meta_size + plaintext.len()].copy_from_slice(&plaintext); 125 | /// 126 | /// // Derive the key from the passphrase. 127 | /// let key = RingCryptor::derive_key(&meta, pass)?; 128 | /// 129 | /// // Encrypt and decrypt the data. 130 | /// let ciphertext = cryptor.seal_with_meta(&meta, &key, &mut buf[meta_size..])?; 131 | /// assert_eq!(cryptor.open(pass, &ciphertext)?, plaintext); 132 | /// 133 | /// # use tindercrypt::errors; 134 | /// # Ok::<(), errors::Error>(()) 135 | /// ``` 136 | /// 137 | /// [metadata]: ../metadata/index.html 138 | #[derive(Copy, Clone, Debug, PartialEq)] 139 | pub struct RingCryptor<'a> { 140 | aad: &'a [u8], 141 | } 142 | 143 | impl<'a> RingCryptor<'a> { 144 | /// Create a new cryptor instance. 145 | pub fn new() -> Self { 146 | Self { aad: &[] } 147 | } 148 | 149 | /// Specify the additional associated data (AAD) to be used. 150 | /// 151 | /// Normally, when encrypting/decrypting a data buffer, the user needs to 152 | /// provide just a key. In some cases though, especially when the same key 153 | /// is used multiple times, we may want to ensure that the data 154 | /// we're decrypting are the expected ones. 155 | /// 156 | /// By specifying associated data during the encryption, we bind them to 157 | /// the ciphertext. The associated data are not stored with the ciphertext, 158 | /// but are necessary to decrypt the data. This way, attempts to 159 | /// "cut-and-paste" a valid ciphertext into a different context are 160 | /// detected and rejected. 161 | pub fn with_aad(self, aad: &'a [u8]) -> Self { 162 | Self { aad } 163 | } 164 | 165 | /// Get the proper key size for the encryption algorithm. 166 | /// 167 | /// # Examples 168 | /// 169 | /// ``` 170 | /// use tindercrypt::metadata; 171 | /// use tindercrypt::cryptors::RingCryptor; 172 | /// 173 | /// let enc_meta = metadata::EncryptionMetadata::generate(); 174 | /// let enc_algo = metadata::EncryptionAlgorithm::ChaCha20Poly1305(enc_meta); 175 | /// 176 | /// // The ChaCha20 cipher requires a 256-bit key. 177 | /// assert_eq!(RingCryptor::get_key_size(&enc_algo), 32); 178 | /// ``` 179 | pub fn get_key_size(enc_algo: &metadata::EncryptionAlgorithm) -> usize { 180 | match enc_algo { 181 | metadata::EncryptionAlgorithm::AES256GCM(_) => { 182 | ring::aead::AES_256_GCM.key_len() 183 | } 184 | metadata::EncryptionAlgorithm::ChaCha20Poly1305(_) => { 185 | ring::aead::CHACHA20_POLY1305.key_len() 186 | } 187 | } 188 | } 189 | 190 | /// Verify that the key size matches the encryption algorithm. 191 | /// 192 | /// Keys of different size will fail verification with 193 | /// [`errors::Error::KeySizeMismatch`]. 194 | /// 195 | /// # Examples 196 | /// 197 | /// ``` 198 | /// use tindercrypt::metadata; 199 | /// use tindercrypt::errors::Error; 200 | /// use tindercrypt::cryptors::RingCryptor; 201 | /// 202 | /// let enc_meta = metadata::EncryptionMetadata::generate(); 203 | /// let enc_algo = metadata::EncryptionAlgorithm::ChaCha20Poly1305(enc_meta); 204 | /// 205 | /// // The ChaCha20 cipher requires a 256-bit key. 206 | /// let key = vec![9u8; 32]; 207 | /// assert_eq!(RingCryptor::verify_key_size(&enc_algo, &[]), Err(Error::KeySizeMismatch)); 208 | /// assert_eq!(RingCryptor::verify_key_size(&enc_algo, &key), Ok(())); 209 | /// ``` 210 | pub fn verify_key_size( 211 | enc_algo: &metadata::EncryptionAlgorithm, 212 | key: &[u8], 213 | ) -> Result<(), errors::Error> { 214 | if key.len() != Self::get_key_size(enc_algo) { 215 | return Err(errors::Error::KeySizeMismatch); 216 | } 217 | Ok(()) 218 | } 219 | 220 | fn _get_enc_info( 221 | &self, 222 | enc_algo: &metadata::EncryptionAlgorithm, 223 | ) -> (&'static ring::aead::Algorithm, [u8; aead::NONCE_SIZE]) { 224 | match enc_algo { 225 | metadata::EncryptionAlgorithm::AES256GCM(meta) => { 226 | (&ring::aead::AES_256_GCM, meta.nonce) 227 | } 228 | metadata::EncryptionAlgorithm::ChaCha20Poly1305(meta) => { 229 | (&ring::aead::CHACHA20_POLY1305, meta.nonce) 230 | } 231 | } 232 | } 233 | 234 | /// Derive a key from the provided secret and metadata, with no allocation. 235 | /// 236 | /// This function is useful for those that want to manage the key buffer 237 | /// themselves, either for performance or security reasons. 238 | /// 239 | /// Depending on the key derivation algorithm in the metadata, this 240 | /// function will derive a key from `secret` and store it into `key`. If 241 | /// the key derivation algorithm is 242 | /// [`metadata::KeyDerivationAlgorithm::None`], then the `secret` will just 243 | /// be copied to `key`. 244 | /// 245 | /// If the key buffer does not match the required key size, this function 246 | /// will return an error (see [`RingCryptor::verify_key_size`]). 247 | /// 248 | /// # Examples 249 | /// 250 | /// ``` 251 | /// use tindercrypt::metadata; 252 | /// use tindercrypt::errors::Error; 253 | /// use tindercrypt::cryptors::RingCryptor; 254 | /// 255 | /// let meta = metadata::Metadata::generate_for_passphrase(0); 256 | /// 257 | /// let pass = "My secret passphrase".as_bytes(); 258 | /// let mut key = [0u8; 32]; 259 | /// 260 | /// assert_eq!(RingCryptor::derive_key_no_alloc(&meta, &pass, &mut key), Ok(())); 261 | /// ``` 262 | pub fn derive_key_no_alloc( 263 | meta: &metadata::Metadata, 264 | secret: &[u8], 265 | key: &mut [u8], 266 | ) -> Result<(), errors::Error> { 267 | Self::verify_key_size(&meta.enc_algo, key)?; 268 | match meta.key_deriv_algo { 269 | metadata::KeyDerivationAlgorithm::None => { 270 | Self::verify_key_size(&meta.enc_algo, secret)?; 271 | key.copy_from_slice(secret); 272 | } 273 | metadata::KeyDerivationAlgorithm::PBKDF2(meta) => { 274 | let algo = match meta.hash_fn { 275 | metadata::HashFunction::SHA256 => { 276 | ring::pbkdf2::PBKDF2_HMAC_SHA256 277 | } 278 | metadata::HashFunction::SHA384 => { 279 | ring::pbkdf2::PBKDF2_HMAC_SHA384 280 | } 281 | metadata::HashFunction::SHA512 => { 282 | ring::pbkdf2::PBKDF2_HMAC_SHA512 283 | } 284 | }; 285 | pbkdf2::derive_key( 286 | algo, 287 | meta.iterations, 288 | &meta.salt, 289 | secret, 290 | key, 291 | )?; 292 | } 293 | }; 294 | Ok(()) 295 | } 296 | 297 | /// Derive a key from the provided secret and metadata. 298 | /// 299 | /// This function is useful for those that want an easy and safe way to 300 | /// derive a key. Compared to [`RingCryptor::derive_key_no_alloc`], it: 301 | /// 302 | /// * Allocates and returns a key buffer with the proper size. 303 | /// * Ensures that the key will be safely erased from memory after use, 304 | /// with the help of the [`zeroize`] crate. 305 | /// 306 | /// # Examples 307 | /// 308 | /// ``` 309 | /// use tindercrypt::metadata; 310 | /// use tindercrypt::errors::Error; 311 | /// use tindercrypt::cryptors::RingCryptor; 312 | /// 313 | /// let meta = metadata::Metadata::generate_for_passphrase(0); 314 | /// 315 | /// let pass = "My secret passphrase".as_bytes(); 316 | /// let key = RingCryptor::derive_key(&meta, &pass)?; 317 | /// 318 | /// # use tindercrypt::errors; 319 | /// # Ok::<(), errors::Error>(()) 320 | /// ``` 321 | /// 322 | /// [`zeroize`]: https://docs.rs/zeroize 323 | pub fn derive_key( 324 | meta: &metadata::Metadata, 325 | secret: &[u8], 326 | ) -> Result>, errors::Error> { 327 | let key_size = Self::get_key_size(&meta.enc_algo); 328 | let mut key = zeroize::Zeroizing::new(vec![0u8; key_size]); 329 | Self::derive_key_no_alloc(meta, secret, &mut key)?; 330 | Ok(key) 331 | } 332 | 333 | /// Encrypt (seal) the data buffer in place. 334 | /// 335 | /// This method gets the metadata necessary from the `EncryptionAlgorithm` 336 | /// enum and calls the respective AEAD wrapper. 337 | fn _seal_in_place( 338 | &self, 339 | enc_algo: &metadata::EncryptionAlgorithm, 340 | key: &[u8], 341 | buf: &mut [u8], 342 | ) -> Result { 343 | Self::verify_key_size(enc_algo, key)?; 344 | let (algo, nonce) = self._get_enc_info(enc_algo); 345 | aead::seal_in_place(algo, nonce, self.aad, key, buf) 346 | } 347 | 348 | /// Decrypt (open) the data buffer in place. 349 | /// 350 | /// This method gets the metadata necessary from the `EncryptionAlgorithm` 351 | /// enum and calls the respective AEAD wrapper. 352 | fn _open_in_place( 353 | &self, 354 | enc_algo: &metadata::EncryptionAlgorithm, 355 | key: &[u8], 356 | buf: &mut [u8], 357 | ) -> Result { 358 | Self::verify_key_size(enc_algo, key)?; 359 | let (algo, nonce) = self._get_enc_info(enc_algo); 360 | aead::open_in_place(algo, nonce, self.aad, key, buf) 361 | } 362 | 363 | /// Encrypt (seal) the data buffer in place. 364 | /// 365 | /// This method accepts a metadata instance, a key and a data buffer, that 366 | /// contains the plaintext and enough space for the tag. Key derivation, if 367 | /// required, must be performed by the user beforehand (see 368 | /// [`RingCryptor::derive_key`]). 369 | /// 370 | /// This method seals the data in place, using the encryption algorithm 371 | /// specified in the metadata. It is much faster than the `seal_with_*` 372 | /// methods that this cryptor provides, since it doesn't perform any 373 | /// allocations. The drawback is that the plaintext is not preserved and 374 | /// that the user must create the proper buffer layout beforehand. 375 | pub fn seal_in_place( 376 | &self, 377 | meta: &metadata::Metadata, 378 | key: &[u8], 379 | buf: &mut [u8], 380 | ) -> Result { 381 | self._seal_in_place(&meta.enc_algo, &key, buf) 382 | } 383 | 384 | /// Encrypt (seal) the data buffer using the provided metadata. 385 | /// 386 | /// This method accepts a metadata instance, a key and the plaintext. Key 387 | /// derivation, if required, must be performed by the user beforehand (see 388 | /// [`RingCryptor::derive_key`]). 389 | /// 390 | /// This method serializes the metadata instance to a buffer, copies the 391 | /// plaintext in it and then seals it in place. This way, the plaintext is 392 | /// preserved, at the cost of an extra copy. 393 | pub fn seal_with_meta( 394 | &self, 395 | meta: &metadata::Metadata, 396 | key: &[u8], 397 | plaintext: &[u8], 398 | ) -> Result, errors::Error> { 399 | // FIXME: Do we need so many `mut` here? 400 | let (mut buf, meta_size) = meta.to_buf(); 401 | let mut ciphertext = &mut buf[meta_size..]; 402 | 403 | ciphertext[..plaintext.len()].copy_from_slice(plaintext); 404 | let _ = self.seal_in_place(meta, key, &mut ciphertext)?; 405 | Ok(buf) 406 | } 407 | 408 | /// Encrypt (seal) the data buffer using a symmetric key. 409 | /// 410 | /// This method accepts a metadata instance, a symmetric key and the 411 | /// plaintext. 412 | /// 413 | /// It generates a metadata instance and then uses the `.seal_with_meta()` 414 | /// method to seal the data. The plaintext will be preserved, at the cost 415 | /// of an extra copy. 416 | pub fn seal_with_key( 417 | &self, 418 | key: &[u8], 419 | plaintext: &[u8], 420 | ) -> Result, errors::Error> { 421 | let meta = metadata::Metadata::generate_for_key(plaintext.len()); 422 | self.seal_with_meta(&meta, key, plaintext) 423 | } 424 | 425 | /// Encrypt (seal) the data buffer using a passphrase. 426 | /// 427 | /// This method accepts a metadata instance, a passphrase and the 428 | /// plaintext. 429 | /// 430 | /// It generates a metadata instance with the proper key derivation 431 | /// algorithm, derives the key from the passphrase, and then uses the 432 | /// `.seal_with_meta()` method to seal the data. The plaintext will be 433 | /// preserved, at the cost of an extra copy. 434 | pub fn seal_with_passphrase( 435 | &self, 436 | pass: &[u8], 437 | plaintext: &[u8], 438 | ) -> Result, errors::Error> { 439 | let meta = 440 | metadata::Metadata::generate_for_passphrase(plaintext.len()); 441 | let key = Self::derive_key(&meta, pass)?; 442 | self.seal_with_meta(&meta, &key, plaintext) 443 | } 444 | 445 | /// Decrypt (open) the data buffer in place. 446 | /// 447 | /// This method accepts a metadata instance, a key and a data buffer, that 448 | /// contains the plaintext and enough space for the tag. Key derivation, if 449 | /// required, must be performed by the user beforehand (see 450 | /// [`RingCryptor::derive_key`]). 451 | /// 452 | /// This method opens the data in place, using the encryption algorithm 453 | /// specified in the metadata. It is much faster than the other `open*` 454 | /// methods that this cryptor provides, since it doesn't perform any 455 | /// allocations. The drawback is that the ciphertext is not preserved. 456 | pub fn open_in_place( 457 | &self, 458 | meta: &metadata::Metadata, 459 | key: &[u8], 460 | buf: &mut [u8], 461 | ) -> Result { 462 | self._open_in_place(&meta.enc_algo, &key, buf) 463 | } 464 | 465 | /// Decrypt (open) the data buffer using the provided metadata. 466 | /// 467 | /// This method accepts a metadata instance, a key and the ciphertext. Key 468 | /// derivation, if required, must be performed by the user beforehand (see 469 | /// [`RingCryptor::derive_key`]). 470 | /// 471 | /// This method copies the ciphertext to a new buffer and decrypts (opens) 472 | /// it in place. Then, it returns the buffer with the plaintext. This way, 473 | /// the ciphertext is preserved, at the cost of an extra copy. 474 | /// 475 | /// **Note:** By "ciphertext" we don't refer to the whole buffer that the 476 | /// `seal_*` methods produce. We refer to the part with the encrypted 477 | /// payload, which does not include the metadata. For more info on how to 478 | /// extract this part, see the examples in the [`RingCryptor`] 479 | /// documentation. 480 | pub fn open_with_meta( 481 | &self, 482 | meta: &metadata::Metadata, 483 | key: &[u8], 484 | ciphertext: &[u8], 485 | ) -> Result, errors::Error> { 486 | let mut buf = ciphertext.to_vec(); 487 | let size = self.open_in_place(meta, key, &mut buf)?; 488 | let _ = buf.drain(size..); 489 | Ok(buf) 490 | } 491 | 492 | /// Decrypt (open) the data buffer. 493 | /// 494 | /// This method accepts a secret value (either a key or a passphrase) and 495 | /// a data buffer that contains the serialized metadata and the ciphertext. 496 | /// 497 | /// It deserializes the metadata and extracts the ciphertext from the 498 | /// buffer. Depending on the key derivation algorithm, this method may 499 | /// derive a key from the provided passpphrase using the values in the 500 | /// deserialized metadata. Then, it uses `.open_with_meta()` to decrypt the 501 | /// ciphertext. The buffer will be preserved, at the cost of an extra 502 | /// copy. 503 | pub fn open( 504 | &self, 505 | secret: &[u8], 506 | buf: &[u8], 507 | ) -> Result, errors::Error> { 508 | let (meta, meta_size) = metadata::Metadata::from_buf(buf)?; 509 | let ciphertext = &buf[meta_size..]; 510 | let key = Self::derive_key(&meta, secret)?; 511 | self.open_with_meta(&meta, &key, ciphertext) 512 | } 513 | } 514 | 515 | #[cfg(test)] 516 | mod tests { 517 | use super::*; 518 | 519 | /// Simplified options for the key derivation algorithm, used only in the 520 | /// tests. 521 | enum KeyOpts { 522 | None, 523 | PBKDF2, 524 | } 525 | 526 | /// Simplified options for the encryption algorithm, used only in the 527 | /// tests. 528 | enum EncOpts { 529 | AES, 530 | ChaCha, 531 | } 532 | 533 | /// Generate a metadata struct for the tests, based on the selected key 534 | /// derivation and encryption algorithms. 535 | fn generate_meta( 536 | size: usize, 537 | key_opts: KeyOpts, 538 | enc_opts: EncOpts, 539 | ) -> metadata::Metadata { 540 | // Key derivation algorithm. 541 | let key_algo = match key_opts { 542 | KeyOpts::None => metadata::KeyDerivationAlgorithm::None, 543 | KeyOpts::PBKDF2 => { 544 | let mut key_deriv_meta = 545 | metadata::KeyDerivationMetadata::generate(); 546 | key_deriv_meta.iterations = 1; 547 | metadata::KeyDerivationAlgorithm::PBKDF2(key_deriv_meta) 548 | } 549 | }; 550 | 551 | // Encryption algorithm. 552 | let enc_meta = metadata::EncryptionMetadata::generate(); 553 | let enc_algo = match enc_opts { 554 | EncOpts::AES => metadata::EncryptionAlgorithm::AES256GCM(enc_meta), 555 | EncOpts::ChaCha => { 556 | metadata::EncryptionAlgorithm::ChaCha20Poly1305(enc_meta) 557 | } 558 | }; 559 | 560 | metadata::Metadata::new(key_algo, enc_algo, size) 561 | } 562 | 563 | #[test] 564 | fn test_derive_key() { 565 | let key_err = Err(errors::Error::KeySizeMismatch); 566 | let pass_err = Err(errors::Error::PassphraseTooSmall); 567 | 568 | // Test 1 - No key derivation. 569 | // 570 | // Ensure that wrong key sizes are rejected. Also, check that the 571 | // provided secret gets copied to the key as is. 572 | let meta = metadata::Metadata::generate_for_key(0); 573 | let key_size = RingCryptor::get_key_size(&meta.enc_algo); 574 | let secret = vec![9u8; key_size]; 575 | let mut key = vec![0u8; key_size]; 576 | 577 | let res = RingCryptor::derive_key_no_alloc(&meta, &[], &mut key); 578 | assert_eq!(res, key_err); 579 | let res = RingCryptor::derive_key_no_alloc(&meta, &secret, &mut []); 580 | assert_eq!(res, key_err); 581 | let res = RingCryptor::derive_key_no_alloc(&meta, &secret, &mut key); 582 | assert_eq!(res, Ok(())); 583 | assert_eq!(secret, key); 584 | 585 | // Test 2 - PBKDF2 key derivation. 586 | // 587 | // Ensure that wrong key sizes are rejected. Also, check that the 588 | // derived key does not match the secret and is not empty. 589 | let meta = metadata::Metadata::generate_for_passphrase(0); 590 | let key_size = RingCryptor::get_key_size(&meta.enc_algo); 591 | let secret = vec![9u8; key_size]; 592 | let mut key = vec![0u8; key_size]; 593 | 594 | let res = RingCryptor::derive_key_no_alloc(&meta, &secret, &mut []); 595 | assert_eq!(res, key_err); 596 | let res = RingCryptor::derive_key_no_alloc(&meta, &[], &mut key); 597 | assert_eq!(res, pass_err); 598 | let res = RingCryptor::derive_key_no_alloc(&meta, &secret, &mut key); 599 | assert_eq!(res, Ok(())); 600 | assert!(key != secret); 601 | assert!(key != vec![0u8; key_size]); 602 | } 603 | 604 | #[test] 605 | fn test_seal_open_in_place() { 606 | // Create some initial data. 607 | let plaintext = "The cake is a lie".as_bytes(); 608 | let aad = "My context".as_bytes(); 609 | 610 | let buf_err = Err(errors::Error::BufferTooSmall); 611 | let dec_err = Err(errors::Error::DecryptionError); 612 | let key_err = Err(errors::Error::KeySizeMismatch); 613 | 614 | // Create two cryptors, one with additional associated data and one 615 | // without. 616 | let cryptor = RingCryptor::new(); 617 | let cryptor_with_aad = RingCryptor::new().with_aad(aad); 618 | 619 | // Use the following types of metadata and keys for the test: 620 | // 621 | // PBKDF2 key derivation and AES-256-GCM encryption, with passphrase. 622 | let meta1 = 623 | generate_meta(plaintext.len(), KeyOpts::PBKDF2, EncOpts::AES); 624 | let secret1 = "My passphrase 1".as_bytes(); 625 | let key_size = RingCryptor::get_key_size(&meta1.enc_algo); 626 | let mut key1 = vec![0u8; key_size]; 627 | RingCryptor::derive_key_no_alloc(&meta1, &secret1, &mut key1).unwrap(); 628 | 629 | // PBKDF2 key derivation and ChaCha20-Poly1305 encryption, with 630 | // passphrase. 631 | let meta2 = 632 | generate_meta(plaintext.len(), KeyOpts::PBKDF2, EncOpts::ChaCha); 633 | let key2 = key1.clone(); 634 | 635 | // No key derivation and AES-256-GCM encryption, with symmetric key. 636 | let meta3 = 637 | generate_meta(plaintext.len(), KeyOpts::None, EncOpts::AES); 638 | let key3 = vec![9u8; ring::aead::AES_256_GCM.key_len()]; 639 | 640 | // No key derivation and ChaCha20-Poly1305 encryption, with symmetric 641 | // key. 642 | let meta4 = 643 | generate_meta(plaintext.len(), KeyOpts::None, EncOpts::ChaCha); 644 | let key4 = key3.clone(); 645 | 646 | // Test that the encryption operation returns the expected errors for 647 | // each type of user mistake and metadata configuration. 648 | // 649 | // No buffer. 650 | for (meta, key) in &[ 651 | (meta1, &key1), 652 | (meta2, &key2), 653 | (meta3, &key3), 654 | (meta4, &key4), 655 | ] { 656 | let err = cryptor.seal_in_place(&meta, key, &mut []); 657 | assert_eq!(buf_err, err); 658 | } 659 | 660 | // Wrong key size. 661 | for meta in &[meta1, meta2, meta3, meta4] { 662 | let err = cryptor.seal_in_place(&meta, &[], &mut []); 663 | assert_eq!(key_err, err); 664 | let err = cryptor.seal_in_place(&meta, &secret1, &mut []); 665 | assert_eq!(key_err, err); 666 | } 667 | 668 | // Test that the encryption operation succeeds. 669 | let mut ciphertexts = Vec::new(); 670 | for (meta, key) in &[ 671 | (meta1, &key1), 672 | (meta2, &key2), 673 | (meta3, &key3), 674 | (meta4, &key4), 675 | ] { 676 | let (mut buf, meta_size) = meta.to_buf(); 677 | let mut ciphertext = &mut buf[meta_size..]; 678 | ciphertext[..plaintext.len()].copy_from_slice(plaintext); 679 | let res = cryptor.seal_in_place(&meta, key, &mut ciphertext); 680 | assert_eq!(res, Ok(plaintext.len())); 681 | 682 | let _ = buf.drain(..meta_size); 683 | ciphertexts.push(buf); 684 | } 685 | 686 | // Test that the decryption operation returns the expected errors for 687 | // each type of user mistake and metadata configuration. 688 | // 689 | // No buffer. 690 | for (meta, key) in &[ 691 | (meta1, &key1), 692 | (meta2, &key2), 693 | (meta3, &key3), 694 | (meta4, &key4), 695 | ] { 696 | let err = cryptor.open_in_place(&meta, key, &mut []); 697 | assert_eq!(buf_err, err); 698 | } 699 | 700 | // No key. 701 | for meta in &[meta1, meta2, meta3, meta4] { 702 | let err = cryptor.open_in_place(&meta, &[], &mut []); 703 | assert_eq!(key_err, err); 704 | } 705 | 706 | // Test that the decryption operation returns a decryption error when 707 | // the keys/passphrases are wrong. 708 | let wrong_key1 = vec![1u8; ring::aead::AES_256_GCM.key_len()]; 709 | let wrong_key2 = vec![2u8; ring::aead::CHACHA20_POLY1305.key_len()]; 710 | for (meta, wrong_key, buf) in &[ 711 | (meta1, &wrong_key1, &ciphertexts[0]), 712 | (meta2, &wrong_key2, &ciphertexts[1]), 713 | (meta3, &wrong_key1, &ciphertexts[2]), 714 | (meta4, &wrong_key2, &ciphertexts[3]), 715 | ] { 716 | let mut buf = buf.to_vec(); 717 | let err = cryptor.open_in_place(&meta, wrong_key, &mut buf); 718 | assert_eq!(dec_err, err); 719 | } 720 | 721 | // Test that the decryption operation returns a decryption error when 722 | // the additional associated data mismatch. 723 | for (meta, key, buf) in &mut [ 724 | (meta1, &key1, &ciphertexts[0]), 725 | (meta2, &key2, &ciphertexts[1]), 726 | (meta3, &key3, &ciphertexts[2]), 727 | (meta4, &key4, &ciphertexts[3]), 728 | ] { 729 | let mut buf = buf.to_vec(); 730 | let err = cryptor_with_aad.open_in_place(&meta, key, &mut buf); 731 | assert_eq!(dec_err, err); 732 | } 733 | 734 | // Test that the decryption operation returns a decryption error when 735 | // the encryption algorithms are incorrect. 736 | for (meta, key, buf) in &[ 737 | (meta1, &key1, &ciphertexts[1]), 738 | (meta2, &key2, &ciphertexts[0]), 739 | (meta3, &key3, &ciphertexts[3]), 740 | (meta4, &key4, &ciphertexts[2]), 741 | ] { 742 | let mut buf = buf.to_vec(); 743 | let err = cryptor.open_in_place(&meta, key, &mut buf); 744 | assert_eq!(dec_err, err); 745 | } 746 | 747 | // Test that the decryption operation returns a decryption error when 748 | // the key derivation / encryption algorithms match, but their metadata 749 | // mismatch. 750 | let wrong_meta1 = 751 | generate_meta(plaintext.len(), KeyOpts::PBKDF2, EncOpts::AES); 752 | let wrong_meta2 = 753 | generate_meta(plaintext.len(), KeyOpts::PBKDF2, EncOpts::ChaCha); 754 | let wrong_meta3 = 755 | generate_meta(plaintext.len(), KeyOpts::None, EncOpts::AES); 756 | let wrong_meta4 = 757 | generate_meta(plaintext.len(), KeyOpts::None, EncOpts::ChaCha); 758 | for (wrong_meta, key, buf) in &mut [ 759 | (wrong_meta1, &key1, &ciphertexts[0]), 760 | (wrong_meta2, &key2, &ciphertexts[1]), 761 | (wrong_meta3, &key3, &ciphertexts[2]), 762 | (wrong_meta4, &key4, &ciphertexts[3]), 763 | ] { 764 | let mut buf = buf.to_vec(); 765 | let err = cryptor.open_in_place(&wrong_meta, key, &mut buf); 766 | assert_eq!(dec_err, err); 767 | } 768 | 769 | // Test a successful decryption operation. 770 | for (meta, key, buf) in &[ 771 | (meta1, &key1, &ciphertexts[0]), 772 | (meta2, &key2, &ciphertexts[1]), 773 | (meta3, &key3, &ciphertexts[2]), 774 | (meta4, &key4, &ciphertexts[3]), 775 | ] { 776 | let mut buf = buf.to_vec(); 777 | let res = cryptor.open_in_place(&meta, key, &mut buf); 778 | assert_eq!(res, Ok(plaintext.len())); 779 | assert_eq!(&buf[..plaintext.len()], plaintext); 780 | } 781 | } 782 | 783 | #[test] 784 | fn test_seal_open() { 785 | let data = vec![9u8; 9]; 786 | 787 | let cryptor = RingCryptor::new().with_aad("death".as_bytes()); 788 | let res = cryptor.seal_with_passphrase("pass".as_bytes(), &data); 789 | assert!(res.is_ok()); 790 | 791 | let res = cryptor.open("pass".as_bytes(), &res.unwrap()); 792 | assert!(res.is_ok()); 793 | assert_eq!(data, res.unwrap()); 794 | } 795 | } 796 | -------------------------------------------------------------------------------- /proto/metadata.rs: -------------------------------------------------------------------------------- 1 | // This file is generated by rust-protobuf 3.7.2. Do not edit 2 | // .proto file is parsed by pure 3 | // @generated 4 | 5 | // https://github.com/rust-lang/rust-clippy/issues/702 6 | #![allow(unknown_lints)] 7 | #![allow(clippy::all)] 8 | 9 | #![allow(unused_attributes)] 10 | #![cfg_attr(rustfmt, rustfmt::skip)] 11 | 12 | #![allow(dead_code)] 13 | #![allow(missing_docs)] 14 | #![allow(non_camel_case_types)] 15 | #![allow(non_snake_case)] 16 | #![allow(non_upper_case_globals)] 17 | #![allow(trivial_casts)] 18 | #![allow(unused_results)] 19 | #![allow(unused_mut)] 20 | 21 | //! Generated file from `metadata.proto` 22 | 23 | /// Generated files are compatible only with the same version 24 | /// of protobuf runtime. 25 | const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_7_2; 26 | 27 | // @@protoc_insertion_point(message:metadata.KeyDerivationMetadata) 28 | #[derive(PartialEq,Clone,Default,Debug)] 29 | pub struct KeyDerivationMetadata { 30 | // message fields 31 | // @@protoc_insertion_point(field:metadata.KeyDerivationMetadata.algo) 32 | pub algo: ::protobuf::EnumOrUnknown, 33 | // @@protoc_insertion_point(field:metadata.KeyDerivationMetadata.hash_fn) 34 | pub hash_fn: ::protobuf::EnumOrUnknown, 35 | // @@protoc_insertion_point(field:metadata.KeyDerivationMetadata.iterations) 36 | pub iterations: u64, 37 | // @@protoc_insertion_point(field:metadata.KeyDerivationMetadata.salt) 38 | pub salt: ::std::vec::Vec, 39 | // special fields 40 | // @@protoc_insertion_point(special_field:metadata.KeyDerivationMetadata.special_fields) 41 | pub special_fields: ::protobuf::SpecialFields, 42 | } 43 | 44 | impl<'a> ::std::default::Default for &'a KeyDerivationMetadata { 45 | fn default() -> &'a KeyDerivationMetadata { 46 | ::default_instance() 47 | } 48 | } 49 | 50 | impl KeyDerivationMetadata { 51 | pub fn new() -> KeyDerivationMetadata { 52 | ::std::default::Default::default() 53 | } 54 | 55 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 56 | let mut fields = ::std::vec::Vec::with_capacity(4); 57 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 58 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 59 | "algo", 60 | |m: &KeyDerivationMetadata| { &m.algo }, 61 | |m: &mut KeyDerivationMetadata| { &mut m.algo }, 62 | )); 63 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 64 | "hash_fn", 65 | |m: &KeyDerivationMetadata| { &m.hash_fn }, 66 | |m: &mut KeyDerivationMetadata| { &mut m.hash_fn }, 67 | )); 68 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 69 | "iterations", 70 | |m: &KeyDerivationMetadata| { &m.iterations }, 71 | |m: &mut KeyDerivationMetadata| { &mut m.iterations }, 72 | )); 73 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 74 | "salt", 75 | |m: &KeyDerivationMetadata| { &m.salt }, 76 | |m: &mut KeyDerivationMetadata| { &mut m.salt }, 77 | )); 78 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 79 | "KeyDerivationMetadata", 80 | fields, 81 | oneofs, 82 | ) 83 | } 84 | } 85 | 86 | impl ::protobuf::Message for KeyDerivationMetadata { 87 | const NAME: &'static str = "KeyDerivationMetadata"; 88 | 89 | fn is_initialized(&self) -> bool { 90 | true 91 | } 92 | 93 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 94 | while let Some(tag) = is.read_raw_tag_or_eof()? { 95 | match tag { 96 | 8 => { 97 | self.algo = is.read_enum_or_unknown()?; 98 | }, 99 | 16 => { 100 | self.hash_fn = is.read_enum_or_unknown()?; 101 | }, 102 | 24 => { 103 | self.iterations = is.read_uint64()?; 104 | }, 105 | 34 => { 106 | self.salt = is.read_bytes()?; 107 | }, 108 | tag => { 109 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 110 | }, 111 | }; 112 | } 113 | ::std::result::Result::Ok(()) 114 | } 115 | 116 | // Compute sizes of nested messages 117 | #[allow(unused_variables)] 118 | fn compute_size(&self) -> u64 { 119 | let mut my_size = 0; 120 | if self.algo != ::protobuf::EnumOrUnknown::new(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID) { 121 | my_size += ::protobuf::rt::int32_size(1, self.algo.value()); 122 | } 123 | if self.hash_fn != ::protobuf::EnumOrUnknown::new(HashFunction::HASH_FUNCTION_INVALID) { 124 | my_size += ::protobuf::rt::int32_size(2, self.hash_fn.value()); 125 | } 126 | if self.iterations != 0 { 127 | my_size += ::protobuf::rt::uint64_size(3, self.iterations); 128 | } 129 | if !self.salt.is_empty() { 130 | my_size += ::protobuf::rt::bytes_size(4, &self.salt); 131 | } 132 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 133 | self.special_fields.cached_size().set(my_size as u32); 134 | my_size 135 | } 136 | 137 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 138 | if self.algo != ::protobuf::EnumOrUnknown::new(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID) { 139 | os.write_enum(1, ::protobuf::EnumOrUnknown::value(&self.algo))?; 140 | } 141 | if self.hash_fn != ::protobuf::EnumOrUnknown::new(HashFunction::HASH_FUNCTION_INVALID) { 142 | os.write_enum(2, ::protobuf::EnumOrUnknown::value(&self.hash_fn))?; 143 | } 144 | if self.iterations != 0 { 145 | os.write_uint64(3, self.iterations)?; 146 | } 147 | if !self.salt.is_empty() { 148 | os.write_bytes(4, &self.salt)?; 149 | } 150 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 151 | ::std::result::Result::Ok(()) 152 | } 153 | 154 | fn special_fields(&self) -> &::protobuf::SpecialFields { 155 | &self.special_fields 156 | } 157 | 158 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 159 | &mut self.special_fields 160 | } 161 | 162 | fn new() -> KeyDerivationMetadata { 163 | KeyDerivationMetadata::new() 164 | } 165 | 166 | fn clear(&mut self) { 167 | self.algo = ::protobuf::EnumOrUnknown::new(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID); 168 | self.hash_fn = ::protobuf::EnumOrUnknown::new(HashFunction::HASH_FUNCTION_INVALID); 169 | self.iterations = 0; 170 | self.salt.clear(); 171 | self.special_fields.clear(); 172 | } 173 | 174 | fn default_instance() -> &'static KeyDerivationMetadata { 175 | static instance: KeyDerivationMetadata = KeyDerivationMetadata { 176 | algo: ::protobuf::EnumOrUnknown::from_i32(0), 177 | hash_fn: ::protobuf::EnumOrUnknown::from_i32(0), 178 | iterations: 0, 179 | salt: ::std::vec::Vec::new(), 180 | special_fields: ::protobuf::SpecialFields::new(), 181 | }; 182 | &instance 183 | } 184 | } 185 | 186 | impl ::protobuf::MessageFull for KeyDerivationMetadata { 187 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 188 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 189 | descriptor.get(|| file_descriptor().message_by_package_relative_name("KeyDerivationMetadata").unwrap()).clone() 190 | } 191 | } 192 | 193 | impl ::std::fmt::Display for KeyDerivationMetadata { 194 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 195 | ::protobuf::text_format::fmt(self, f) 196 | } 197 | } 198 | 199 | impl ::protobuf::reflect::ProtobufValue for KeyDerivationMetadata { 200 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 201 | } 202 | 203 | // @@protoc_insertion_point(message:metadata.EncryptionMetadata) 204 | #[derive(PartialEq,Clone,Default,Debug)] 205 | pub struct EncryptionMetadata { 206 | // message fields 207 | // @@protoc_insertion_point(field:metadata.EncryptionMetadata.algo) 208 | pub algo: ::protobuf::EnumOrUnknown, 209 | // @@protoc_insertion_point(field:metadata.EncryptionMetadata.nonce) 210 | pub nonce: ::std::vec::Vec, 211 | // special fields 212 | // @@protoc_insertion_point(special_field:metadata.EncryptionMetadata.special_fields) 213 | pub special_fields: ::protobuf::SpecialFields, 214 | } 215 | 216 | impl<'a> ::std::default::Default for &'a EncryptionMetadata { 217 | fn default() -> &'a EncryptionMetadata { 218 | ::default_instance() 219 | } 220 | } 221 | 222 | impl EncryptionMetadata { 223 | pub fn new() -> EncryptionMetadata { 224 | ::std::default::Default::default() 225 | } 226 | 227 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 228 | let mut fields = ::std::vec::Vec::with_capacity(2); 229 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 230 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 231 | "algo", 232 | |m: &EncryptionMetadata| { &m.algo }, 233 | |m: &mut EncryptionMetadata| { &mut m.algo }, 234 | )); 235 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 236 | "nonce", 237 | |m: &EncryptionMetadata| { &m.nonce }, 238 | |m: &mut EncryptionMetadata| { &mut m.nonce }, 239 | )); 240 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 241 | "EncryptionMetadata", 242 | fields, 243 | oneofs, 244 | ) 245 | } 246 | } 247 | 248 | impl ::protobuf::Message for EncryptionMetadata { 249 | const NAME: &'static str = "EncryptionMetadata"; 250 | 251 | fn is_initialized(&self) -> bool { 252 | true 253 | } 254 | 255 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 256 | while let Some(tag) = is.read_raw_tag_or_eof()? { 257 | match tag { 258 | 8 => { 259 | self.algo = is.read_enum_or_unknown()?; 260 | }, 261 | 18 => { 262 | self.nonce = is.read_bytes()?; 263 | }, 264 | tag => { 265 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 266 | }, 267 | }; 268 | } 269 | ::std::result::Result::Ok(()) 270 | } 271 | 272 | // Compute sizes of nested messages 273 | #[allow(unused_variables)] 274 | fn compute_size(&self) -> u64 { 275 | let mut my_size = 0; 276 | if self.algo != ::protobuf::EnumOrUnknown::new(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID) { 277 | my_size += ::protobuf::rt::int32_size(1, self.algo.value()); 278 | } 279 | if !self.nonce.is_empty() { 280 | my_size += ::protobuf::rt::bytes_size(2, &self.nonce); 281 | } 282 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 283 | self.special_fields.cached_size().set(my_size as u32); 284 | my_size 285 | } 286 | 287 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 288 | if self.algo != ::protobuf::EnumOrUnknown::new(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID) { 289 | os.write_enum(1, ::protobuf::EnumOrUnknown::value(&self.algo))?; 290 | } 291 | if !self.nonce.is_empty() { 292 | os.write_bytes(2, &self.nonce)?; 293 | } 294 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 295 | ::std::result::Result::Ok(()) 296 | } 297 | 298 | fn special_fields(&self) -> &::protobuf::SpecialFields { 299 | &self.special_fields 300 | } 301 | 302 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 303 | &mut self.special_fields 304 | } 305 | 306 | fn new() -> EncryptionMetadata { 307 | EncryptionMetadata::new() 308 | } 309 | 310 | fn clear(&mut self) { 311 | self.algo = ::protobuf::EnumOrUnknown::new(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID); 312 | self.nonce.clear(); 313 | self.special_fields.clear(); 314 | } 315 | 316 | fn default_instance() -> &'static EncryptionMetadata { 317 | static instance: EncryptionMetadata = EncryptionMetadata { 318 | algo: ::protobuf::EnumOrUnknown::from_i32(0), 319 | nonce: ::std::vec::Vec::new(), 320 | special_fields: ::protobuf::SpecialFields::new(), 321 | }; 322 | &instance 323 | } 324 | } 325 | 326 | impl ::protobuf::MessageFull for EncryptionMetadata { 327 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 328 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 329 | descriptor.get(|| file_descriptor().message_by_package_relative_name("EncryptionMetadata").unwrap()).clone() 330 | } 331 | } 332 | 333 | impl ::std::fmt::Display for EncryptionMetadata { 334 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 335 | ::protobuf::text_format::fmt(self, f) 336 | } 337 | } 338 | 339 | impl ::protobuf::reflect::ProtobufValue for EncryptionMetadata { 340 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 341 | } 342 | 343 | // @@protoc_insertion_point(message:metadata.Metadata) 344 | #[derive(PartialEq,Clone,Default,Debug)] 345 | pub struct Metadata { 346 | // message fields 347 | // @@protoc_insertion_point(field:metadata.Metadata.key_deriv_meta) 348 | pub key_deriv_meta: ::protobuf::MessageField, 349 | // @@protoc_insertion_point(field:metadata.Metadata.enc_meta) 350 | pub enc_meta: ::protobuf::MessageField, 351 | // @@protoc_insertion_point(field:metadata.Metadata.ciphertext_size) 352 | pub ciphertext_size: u64, 353 | // special fields 354 | // @@protoc_insertion_point(special_field:metadata.Metadata.special_fields) 355 | pub special_fields: ::protobuf::SpecialFields, 356 | } 357 | 358 | impl<'a> ::std::default::Default for &'a Metadata { 359 | fn default() -> &'a Metadata { 360 | ::default_instance() 361 | } 362 | } 363 | 364 | impl Metadata { 365 | pub fn new() -> Metadata { 366 | ::std::default::Default::default() 367 | } 368 | 369 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 370 | let mut fields = ::std::vec::Vec::with_capacity(3); 371 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 372 | fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, KeyDerivationMetadata>( 373 | "key_deriv_meta", 374 | |m: &Metadata| { &m.key_deriv_meta }, 375 | |m: &mut Metadata| { &mut m.key_deriv_meta }, 376 | )); 377 | fields.push(::protobuf::reflect::rt::v2::make_message_field_accessor::<_, EncryptionMetadata>( 378 | "enc_meta", 379 | |m: &Metadata| { &m.enc_meta }, 380 | |m: &mut Metadata| { &mut m.enc_meta }, 381 | )); 382 | fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( 383 | "ciphertext_size", 384 | |m: &Metadata| { &m.ciphertext_size }, 385 | |m: &mut Metadata| { &mut m.ciphertext_size }, 386 | )); 387 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 388 | "Metadata", 389 | fields, 390 | oneofs, 391 | ) 392 | } 393 | } 394 | 395 | impl ::protobuf::Message for Metadata { 396 | const NAME: &'static str = "Metadata"; 397 | 398 | fn is_initialized(&self) -> bool { 399 | true 400 | } 401 | 402 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 403 | while let Some(tag) = is.read_raw_tag_or_eof()? { 404 | match tag { 405 | 10 => { 406 | ::protobuf::rt::read_singular_message_into_field(is, &mut self.key_deriv_meta)?; 407 | }, 408 | 18 => { 409 | ::protobuf::rt::read_singular_message_into_field(is, &mut self.enc_meta)?; 410 | }, 411 | 24 => { 412 | self.ciphertext_size = is.read_uint64()?; 413 | }, 414 | tag => { 415 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 416 | }, 417 | }; 418 | } 419 | ::std::result::Result::Ok(()) 420 | } 421 | 422 | // Compute sizes of nested messages 423 | #[allow(unused_variables)] 424 | fn compute_size(&self) -> u64 { 425 | let mut my_size = 0; 426 | if let Some(v) = self.key_deriv_meta.as_ref() { 427 | let len = v.compute_size(); 428 | my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; 429 | } 430 | if let Some(v) = self.enc_meta.as_ref() { 431 | let len = v.compute_size(); 432 | my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; 433 | } 434 | if self.ciphertext_size != 0 { 435 | my_size += ::protobuf::rt::uint64_size(3, self.ciphertext_size); 436 | } 437 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 438 | self.special_fields.cached_size().set(my_size as u32); 439 | my_size 440 | } 441 | 442 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 443 | if let Some(v) = self.key_deriv_meta.as_ref() { 444 | ::protobuf::rt::write_message_field_with_cached_size(1, v, os)?; 445 | } 446 | if let Some(v) = self.enc_meta.as_ref() { 447 | ::protobuf::rt::write_message_field_with_cached_size(2, v, os)?; 448 | } 449 | if self.ciphertext_size != 0 { 450 | os.write_uint64(3, self.ciphertext_size)?; 451 | } 452 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 453 | ::std::result::Result::Ok(()) 454 | } 455 | 456 | fn special_fields(&self) -> &::protobuf::SpecialFields { 457 | &self.special_fields 458 | } 459 | 460 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 461 | &mut self.special_fields 462 | } 463 | 464 | fn new() -> Metadata { 465 | Metadata::new() 466 | } 467 | 468 | fn clear(&mut self) { 469 | self.key_deriv_meta.clear(); 470 | self.enc_meta.clear(); 471 | self.ciphertext_size = 0; 472 | self.special_fields.clear(); 473 | } 474 | 475 | fn default_instance() -> &'static Metadata { 476 | static instance: Metadata = Metadata { 477 | key_deriv_meta: ::protobuf::MessageField::none(), 478 | enc_meta: ::protobuf::MessageField::none(), 479 | ciphertext_size: 0, 480 | special_fields: ::protobuf::SpecialFields::new(), 481 | }; 482 | &instance 483 | } 484 | } 485 | 486 | impl ::protobuf::MessageFull for Metadata { 487 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 488 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 489 | descriptor.get(|| file_descriptor().message_by_package_relative_name("Metadata").unwrap()).clone() 490 | } 491 | } 492 | 493 | impl ::std::fmt::Display for Metadata { 494 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 495 | ::protobuf::text_format::fmt(self, f) 496 | } 497 | } 498 | 499 | impl ::protobuf::reflect::ProtobufValue for Metadata { 500 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 501 | } 502 | 503 | #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] 504 | // @@protoc_insertion_point(enum:metadata.HashFunction) 505 | pub enum HashFunction { 506 | // @@protoc_insertion_point(enum_value:metadata.HashFunction.HASH_FUNCTION_INVALID) 507 | HASH_FUNCTION_INVALID = 0, 508 | // @@protoc_insertion_point(enum_value:metadata.HashFunction.HASH_FUNCTION_SHA256) 509 | HASH_FUNCTION_SHA256 = 1, 510 | // @@protoc_insertion_point(enum_value:metadata.HashFunction.HASH_FUNCTION_SHA384) 511 | HASH_FUNCTION_SHA384 = 2, 512 | // @@protoc_insertion_point(enum_value:metadata.HashFunction.HASH_FUNCTION_SHA512) 513 | HASH_FUNCTION_SHA512 = 3, 514 | } 515 | 516 | impl ::protobuf::Enum for HashFunction { 517 | const NAME: &'static str = "HashFunction"; 518 | 519 | fn value(&self) -> i32 { 520 | *self as i32 521 | } 522 | 523 | fn from_i32(value: i32) -> ::std::option::Option { 524 | match value { 525 | 0 => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_INVALID), 526 | 1 => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA256), 527 | 2 => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA384), 528 | 3 => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA512), 529 | _ => ::std::option::Option::None 530 | } 531 | } 532 | 533 | fn from_str(str: &str) -> ::std::option::Option { 534 | match str { 535 | "HASH_FUNCTION_INVALID" => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_INVALID), 536 | "HASH_FUNCTION_SHA256" => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA256), 537 | "HASH_FUNCTION_SHA384" => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA384), 538 | "HASH_FUNCTION_SHA512" => ::std::option::Option::Some(HashFunction::HASH_FUNCTION_SHA512), 539 | _ => ::std::option::Option::None 540 | } 541 | } 542 | 543 | const VALUES: &'static [HashFunction] = &[ 544 | HashFunction::HASH_FUNCTION_INVALID, 545 | HashFunction::HASH_FUNCTION_SHA256, 546 | HashFunction::HASH_FUNCTION_SHA384, 547 | HashFunction::HASH_FUNCTION_SHA512, 548 | ]; 549 | } 550 | 551 | impl ::protobuf::EnumFull for HashFunction { 552 | fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { 553 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); 554 | descriptor.get(|| file_descriptor().enum_by_package_relative_name("HashFunction").unwrap()).clone() 555 | } 556 | 557 | fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { 558 | let index = *self as usize; 559 | Self::enum_descriptor().value_by_index(index) 560 | } 561 | } 562 | 563 | impl ::std::default::Default for HashFunction { 564 | fn default() -> Self { 565 | HashFunction::HASH_FUNCTION_INVALID 566 | } 567 | } 568 | 569 | impl HashFunction { 570 | fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { 571 | ::protobuf::reflect::GeneratedEnumDescriptorData::new::("HashFunction") 572 | } 573 | } 574 | 575 | #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] 576 | // @@protoc_insertion_point(enum:metadata.KeyDerivationAlgorithm) 577 | pub enum KeyDerivationAlgorithm { 578 | // @@protoc_insertion_point(enum_value:metadata.KeyDerivationAlgorithm.KEY_DERIVATION_ALGORITHM_INVALID) 579 | KEY_DERIVATION_ALGORITHM_INVALID = 0, 580 | // @@protoc_insertion_point(enum_value:metadata.KeyDerivationAlgorithm.KEY_DERIVATION_ALGORITHM_NONE) 581 | KEY_DERIVATION_ALGORITHM_NONE = 1, 582 | // @@protoc_insertion_point(enum_value:metadata.KeyDerivationAlgorithm.KEY_DERIVATION_ALGORITHM_PBKDF2) 583 | KEY_DERIVATION_ALGORITHM_PBKDF2 = 2, 584 | } 585 | 586 | impl ::protobuf::Enum for KeyDerivationAlgorithm { 587 | const NAME: &'static str = "KeyDerivationAlgorithm"; 588 | 589 | fn value(&self) -> i32 { 590 | *self as i32 591 | } 592 | 593 | fn from_i32(value: i32) -> ::std::option::Option { 594 | match value { 595 | 0 => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID), 596 | 1 => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE), 597 | 2 => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2), 598 | _ => ::std::option::Option::None 599 | } 600 | } 601 | 602 | fn from_str(str: &str) -> ::std::option::Option { 603 | match str { 604 | "KEY_DERIVATION_ALGORITHM_INVALID" => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID), 605 | "KEY_DERIVATION_ALGORITHM_NONE" => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE), 606 | "KEY_DERIVATION_ALGORITHM_PBKDF2" => ::std::option::Option::Some(KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2), 607 | _ => ::std::option::Option::None 608 | } 609 | } 610 | 611 | const VALUES: &'static [KeyDerivationAlgorithm] = &[ 612 | KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID, 613 | KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE, 614 | KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2, 615 | ]; 616 | } 617 | 618 | impl ::protobuf::EnumFull for KeyDerivationAlgorithm { 619 | fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { 620 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); 621 | descriptor.get(|| file_descriptor().enum_by_package_relative_name("KeyDerivationAlgorithm").unwrap()).clone() 622 | } 623 | 624 | fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { 625 | let index = *self as usize; 626 | Self::enum_descriptor().value_by_index(index) 627 | } 628 | } 629 | 630 | impl ::std::default::Default for KeyDerivationAlgorithm { 631 | fn default() -> Self { 632 | KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID 633 | } 634 | } 635 | 636 | impl KeyDerivationAlgorithm { 637 | fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { 638 | ::protobuf::reflect::GeneratedEnumDescriptorData::new::("KeyDerivationAlgorithm") 639 | } 640 | } 641 | 642 | #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] 643 | // @@protoc_insertion_point(enum:metadata.EncryptionAlgorithm) 644 | pub enum EncryptionAlgorithm { 645 | // @@protoc_insertion_point(enum_value:metadata.EncryptionAlgorithm.ENCRYPTION_ALGORITHM_INVALID) 646 | ENCRYPTION_ALGORITHM_INVALID = 0, 647 | // @@protoc_insertion_point(enum_value:metadata.EncryptionAlgorithm.ENCRYPTION_ALGORITHM_AES256GCM) 648 | ENCRYPTION_ALGORITHM_AES256GCM = 1, 649 | // @@protoc_insertion_point(enum_value:metadata.EncryptionAlgorithm.ENCRYPTION_ALGORITHM_CHACHA20_POLY1305) 650 | ENCRYPTION_ALGORITHM_CHACHA20_POLY1305 = 2, 651 | } 652 | 653 | impl ::protobuf::Enum for EncryptionAlgorithm { 654 | const NAME: &'static str = "EncryptionAlgorithm"; 655 | 656 | fn value(&self) -> i32 { 657 | *self as i32 658 | } 659 | 660 | fn from_i32(value: i32) -> ::std::option::Option { 661 | match value { 662 | 0 => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID), 663 | 1 => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM), 664 | 2 => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305), 665 | _ => ::std::option::Option::None 666 | } 667 | } 668 | 669 | fn from_str(str: &str) -> ::std::option::Option { 670 | match str { 671 | "ENCRYPTION_ALGORITHM_INVALID" => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID), 672 | "ENCRYPTION_ALGORITHM_AES256GCM" => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM), 673 | "ENCRYPTION_ALGORITHM_CHACHA20_POLY1305" => ::std::option::Option::Some(EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305), 674 | _ => ::std::option::Option::None 675 | } 676 | } 677 | 678 | const VALUES: &'static [EncryptionAlgorithm] = &[ 679 | EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID, 680 | EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM, 681 | EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305, 682 | ]; 683 | } 684 | 685 | impl ::protobuf::EnumFull for EncryptionAlgorithm { 686 | fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { 687 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); 688 | descriptor.get(|| file_descriptor().enum_by_package_relative_name("EncryptionAlgorithm").unwrap()).clone() 689 | } 690 | 691 | fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { 692 | let index = *self as usize; 693 | Self::enum_descriptor().value_by_index(index) 694 | } 695 | } 696 | 697 | impl ::std::default::Default for EncryptionAlgorithm { 698 | fn default() -> Self { 699 | EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID 700 | } 701 | } 702 | 703 | impl EncryptionAlgorithm { 704 | fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { 705 | ::protobuf::reflect::GeneratedEnumDescriptorData::new::("EncryptionAlgorithm") 706 | } 707 | } 708 | 709 | static file_descriptor_proto_data: &'static [u8] = b"\ 710 | \n\x0emetadata.proto\x12\x08metadata\"\xb2\x01\n\x15KeyDerivationMetadat\ 711 | a\x124\n\x04algo\x18\x01\x20\x01(\x0e2\x20.metadata.KeyDerivationAlgorit\ 712 | hmR\x04algo\x12/\n\x07hash_fn\x18\x02\x20\x01(\x0e2\x16.metadata.HashFun\ 713 | ctionR\x06hashFn\x12\x1e\n\niterations\x18\x03\x20\x01(\x04R\niterations\ 714 | \x12\x12\n\x04salt\x18\x04\x20\x01(\x0cR\x04salt\"]\n\x12EncryptionMetad\ 715 | ata\x121\n\x04algo\x18\x01\x20\x01(\x0e2\x1d.metadata.EncryptionAlgorith\ 716 | mR\x04algo\x12\x14\n\x05nonce\x18\x02\x20\x01(\x0cR\x05nonce\"\xb3\x01\n\ 717 | \x08Metadata\x12E\n\x0ekey_deriv_meta\x18\x01\x20\x01(\x0b2\x1f.metadata\ 718 | .KeyDerivationMetadataR\x0ckeyDerivMeta\x127\n\x08enc_meta\x18\x02\x20\ 719 | \x01(\x0b2\x1c.metadata.EncryptionMetadataR\x07encMeta\x12'\n\x0fciphert\ 720 | ext_size\x18\x03\x20\x01(\x04R\x0eciphertextSize*w\n\x0cHashFunction\x12\ 721 | \x19\n\x15HASH_FUNCTION_INVALID\x10\0\x12\x18\n\x14HASH_FUNCTION_SHA256\ 722 | \x10\x01\x12\x18\n\x14HASH_FUNCTION_SHA384\x10\x02\x12\x18\n\x14HASH_FUN\ 723 | CTION_SHA512\x10\x03*\x86\x01\n\x16KeyDerivationAlgorithm\x12$\n\x20KEY_\ 724 | DERIVATION_ALGORITHM_INVALID\x10\0\x12!\n\x1dKEY_DERIVATION_ALGORITHM_NO\ 725 | NE\x10\x01\x12#\n\x1fKEY_DERIVATION_ALGORITHM_PBKDF2\x10\x02*\x87\x01\n\ 726 | \x13EncryptionAlgorithm\x12\x20\n\x1cENCRYPTION_ALGORITHM_INVALID\x10\0\ 727 | \x12\"\n\x1eENCRYPTION_ALGORITHM_AES256GCM\x10\x01\x12*\n&ENCRYPTION_ALG\ 728 | ORITHM_CHACHA20_POLY1305\x10\x02B+\n\x0ccom.metadataB\rMetadataProtoP\ 729 | \x01Z\nmetadatapbb\x06proto3\ 730 | "; 731 | 732 | /// `FileDescriptorProto` object which was a source for this generated file 733 | fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { 734 | static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); 735 | file_descriptor_proto_lazy.get(|| { 736 | ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() 737 | }) 738 | } 739 | 740 | /// `FileDescriptor` object which allows dynamic access to files 741 | pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { 742 | static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); 743 | static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); 744 | file_descriptor.get(|| { 745 | let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { 746 | let mut deps = ::std::vec::Vec::with_capacity(0); 747 | let mut messages = ::std::vec::Vec::with_capacity(3); 748 | messages.push(KeyDerivationMetadata::generated_message_descriptor_data()); 749 | messages.push(EncryptionMetadata::generated_message_descriptor_data()); 750 | messages.push(Metadata::generated_message_descriptor_data()); 751 | let mut enums = ::std::vec::Vec::with_capacity(3); 752 | enums.push(HashFunction::generated_enum_descriptor_data()); 753 | enums.push(KeyDerivationAlgorithm::generated_enum_descriptor_data()); 754 | enums.push(EncryptionAlgorithm::generated_enum_descriptor_data()); 755 | ::protobuf::reflect::GeneratedFileDescriptor::new_generated( 756 | file_descriptor_proto(), 757 | deps, 758 | messages, 759 | enums, 760 | ) 761 | }); 762 | ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) 763 | }) 764 | } 765 | --------------------------------------------------------------------------------