project-oak / tink-rust Goto Github PK
View Code? Open in Web Editor NEWRust port of Tink cryptography library
License: Apache License 2.0
Rust port of Tink cryptography library
License: Apache License 2.0
cf. Go code
Although it's not yet included in a released version, the upstream Tink Go code has recently added HPKE support to its hybrid implementation.
Also, RFC 9180 was just published covering HPKE (cf. Cloudflare blog article).
This issue covers adding HPKE support to Tink-Rust.
aead/
) to allow IV injection.@daviddrysdale David, hello, I couldn't find any other contact information for you, so I had to reach out through an issue. I was wondering if you are the author of <>? It's a truly well-written book. I am planning to translate it into Chinese and was hoping to get your permission. If it's convenient for you, could you please open-source the source repository for me? Thank you.
Current setup is affected by RUSTSEC-2021-0115, but upgrading to zeroize_derive
1.2.0 requires a bump of MSRV to 1.51.
Wait and see if iqlusioninc/crates#880 results in a 1.1.x version of the crate that could be used instead.
Via cargo-raze.
Also extend CI to exercise the alternative build system.
Add checks for:
cargo udeps
Investigate whether there's a safe way in Rust to have a global registry of KeyManager
instances that are
typed to a particular primitive (rather than the catchall enum Primitive
).
See README for more details.
E.g. by using embedmd
(and checking in CI)
Upstream Go code additionally supports NIST P384 and P521.
While trying to do a cargo build on tink-rust, I'm receiving the following error blurb,
"Compiling tink-testing-server v0.1.0 (/home/dnovick/gitrepos/rust/tink-rust/testing)
error: failed to run custom build command for tink-testing-server v0.1.0 (/home/dnovick/gitrepos/rust/tink-rust/testing)
Caused by:
process didn't exit successfully: /home/dnovick/gitrepos/rust/tink-rust/target/debug/build/tink-testing-server-861acf93ba28d1bf/build-script-build
(exit code: 1)
--- stdout
cargo:rerun-if-changed=proto/testing_api.proto
--- stderr
error: no override and no default toolchain set".
Unfortunately, I don't see enough info in the error to clue me in on exactly what is wrong. I'm building on an Ubuntu 20.04. I made sure I pulled the Wycheproof submodule. Are there other build tools or settings I'm unaware of? Note: the lib builds fine. This error only occurs when trying to build the binaries.
Thanks,
Dave
As per RUSTSEC-2022-0071
The maintainers of Rusoto advise that all its crates are deprecated. This includes the common crates
rusoto_core
,rusoto_signature
,rusoto_credential
, and service crates such asrusoto_s3
andrusoto_ec2
.Users should migrate to the AWS SDK for Rust, which is maintained by AWS.
impl io::Read for noncebased::Reader
follows a relatively standard pattern:
It looks like this was directly ported from the Go implementation of Tink. Unfortunately, Go and Rust have different semantics for their standard I/O libraries; the Go implementation is leveraging guarantees about reader behaviour that Rust does not provide.
The Go implementation calls io.ReadFull
to read a complete segment, and then returns early if it received an error that is not io.ErrUnexpectedEOF
:
After handling any initial offset due to the header, it then proceeds to offset all its reads by 1 byte, using that extra final byte as a "query" to see whether there is another following segment. If no such chunk exists, the io.ErrUnexpectedEOF
error (that was not previously handled) is used to trigger last-segment handling.
This relies on the following semantics of io.ReadFull
:
io.ErrUnexpectedEOF
along with the bytes that were read.noncebased::Reader
currently calls io::Read::read
on the inner reader:
tink-rust/streaming/src/subtle/noncebased.rs
Lines 354 to 360 in 172314f
io::Read::read
provides no guarantee that it will fill the provided buffer if the inner reader would have sufficient bytes to do so:
It is not an error if the returned value
n
is smaller than the buffer size, even when the reader is not at the end of the stream yet. This may happen for example because fewer bytes are actually available right now (e. g. being close to end-of-file) or because read() was interrupted by a signal.
This means that the subsequent logic to check for the presence of the last segment can be incorrectly triggered by partial reads of middle segments, in addition to the last-segment read:
tink-rust/streaming/src/subtle/noncebased.rs
Lines 364 to 367 in 172314f
The Rust API that more closely matches io.ReadFull
is io::Read::read_exact
, but that doesn't have the same semantics for partial reads:
If this function encounters an "end of file" before completely filling the buffer, it returns an error of the kind
ErrorKind::UnexpectedEof
. The contents ofbuf
are unspecified in this case.
The Rust implementation needs to loop on io::Read::read
until either a full segment has been read (plus the query byte), or the inner reader returns Ok(0)
. See e.g. my implementation of io::Read
for STREAM in age
. (I don't currently implement the query-byte strategy, because in age
all chunks are the same size except possibly the final chunk, so I just handle the last-chunk-is-full case by decrypting twice.)
The lack of Send
and Sync
make the library almost unusable. I've had to resort to weird thread local tricks like the one below:
mod hello {
tonic::include_proto!("hello");
}
use std::cell::Cell;
use hello::{HelloRequest,HelloResponse};
use hello::hello_service_server::{HelloService,HelloServiceServer};
use tonic::transport::Server;
use tonic::{Request, Response, Status};
// This would be passed through the environment to the container.
static KEY: &str = "my encryption key";
thread_local! {
static DECRYPTER: Cell<Option<Box<dyn tink_core::Aead>>> = Cell::new(None)
}
fn parse_encryption_key(key: &str) -> Option<Box<dyn tink_core::Aead>> {
if key != "" {
let mut key_reader = tink_core::keyset::BinaryReader::new(key.as_bytes());
match tink_core::keyset::insecure::read(&mut key_reader) {
Ok(h) => Some(tink_aead::new(&h).unwrap()),
Err(e) => None }
} else {
None
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051";
let service = MyServer { };
Server::builder()
.add_service(HelloServiceServer::new(service))
.serve(addr.parse().unwrap())
.await?;
Ok(())
}
struct MyServer {
}
#[tonic::async_trait]
impl HelloService for MyServer {
async fn hello(
&self,
req: Request<HelloRequest>,
) -> Result<Response<HelloResponse>, Status> {
DECRYPTER.with(|c| {
let decrypter = match c.take() {
Some(d) => d,
None => parse_encryption_key(KEY).unwrap()
};
c.set(Some(decrypter));
});
Ok(Response::new(HelloResponse::default()))
}
}
What I'd like to be able to do is just:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let decrypter = parse_encryption_key(KEY).unwrap();
let addr = "[::1]:50051";
let service = MyServer { decrypter };
Server::builder()
.add_service(HelloServiceServer::new(service))
.serve(addr.parse().unwrap())
.await?;
Ok(())
}
struct MyServer {
decrypter: ...
}
The types should be such that this is easily supported. Most DB client libraries etc. play well together with Tonic. Not having Send and Sync also makes it impossible to hold a reference across await
points.
The Cargo.toml
files are rather lackadaisical and depend on "*"
; fix this to be more precise/accurate/correct.
The Rust port currently uses the RustCrypto crates for underlying crypto functionality. It may be useful to support alternative underlying crypto libraries, such as:
TBD whether alternatives would be selected at build time (e.g. via features) or more dynamically (trait impls?).
FYI, there's an open PR to add a stream
module to the RustCrypto aead
crate which StreamingAead
could potentially benefit from:
In particular I think it'd be nice if rage
and tink-rust
could potentially share code, particularly around things like async and/or parallel stream readers/writers.
Anyway, heads up we're working on some common abstractions for this sort of thing and would love your input, in particular if you think it would be helpful for things like StreamingAead
, and if you have any concerns about the proposed design.
I think age
and Tink (in all forms) might also use a common "flavor" of STREAM, although I haven't confirmed that.
Sidebar: STREAM isn't actually OAE2, but rather "nonce-based OAE" (nOAE). CHAIN is required for OAE2. (Edit: I now see the noncebased
streaming module, never mind)
Hello,
I tried testing gcpkms_example() test from integration_test, and "test_gcpkms_basic_aead()" test from gcp_kms_aead_test with valid gcp key uri and credential data, and it returns decryption error with following message:
"thread 'gcpkms::integration_test::gcpkms_example' panicked at 'called Result::unwrap()
on an Err
value: TinkError { msg: "aead::decrypt: decryption failed", src: None }', tests/tests/gcpkms/integration_test.rs"
This appears to be from aead_factory.rs, line 112.
Is this a known issue? Any suggestion how to solve this?
The upstream Tink codebase includes a collection of cross-language tests which would be good to run in CI.
Probably involves:
'rust'
as being supported for various operationsscripts/run-tests.sh
).I suspect that the main stumbling block will be that builds of all of the different testing servers will take too long.
A non-blocking API would be great. The crate is not that ergonomic to use in a project that relies on the tokio runtime. This is due to the fact that you can not spawn a tokio::Runtime
from within a runtime.
#[tokio::test]
async fn test_construction() -> Result<(), Box<dyn std::error::Error>> {
tink_aead::init();
dotenv::dotenv().unwrap();
let key_uri = std::env::var("GOOGLE_KMS_KEY_URI")?;
let credential_path_var = std::env::var("GOOGLE_APPLICATION_CREDENTIALS").unwrap();
let credential_path = std::path::Path::new(&credential_path_var);
let client =
tink_gcpkms::GcpClient::new_with_credentials(&key_uri, credential_path).unwrap();
let backend = client.get_aead(&key_uri).unwrap();
// ...
Ok(())
}
thread 'key_manager::tests::test_construction' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.'
Also, dyn <Primitive>
are cumbersome without Send + Sync
.
/// Returns a [`tink_core::Aead`] primitive from the given keyset handle.
pub fn new(h: &tink_core::keyset::Handle) -> Result<Box<dyn tink_core::Aead>, TinkError> {
new_with_key_manager(h, None)
}
I can get around the first issue with an actor and spawning a thread, but lack of Send + Sync
on the primitives is a tough one.
I believe there are a few changes that would need to be made:
AeadEnvelope
trait:#[async_trait::async_trait]
pub trait AeadEnvelope {
async fn encrypt(
&self,
plaintext: &[u8],
additional_data: &[u8],
) -> Result<Vec<u8>, TinkError>;
async fn decrypt(
&self,
ciphertext: &[u8],
additional_data: &[u8],
) -> Result<Vec<u8>, TinkError>;
}
dyn <Primitive>
will likely need to be dyn 'static + <Primitive> + Send + Sync
Thank you for your work and effort porting this.
First, need a version of prost that includes danburkert/prost@fdf9fdf.
Then the obvious changes needed to make Tink no_std
compatible would include the following (but there are bound to be others):
core
+ alloc
instead of std
, and have a std
feature for those things that definitely need std
:
core
/ alloc
types:
Box
=> alloc::boxed::Box
String
=> alloc::string::String
Vec
=> alloc::vec::Vec
std::sync::Arc
=> alloc::sync::Arc
std::fmt::*
=> core::fmt::*
std::collections::HashMap
=> alloc::collections::BTreeMap
std::sync::RwLock
=> spin::RwLock
std::convert::From
=> core::convert::From
TinkError
to wrap something that just implements core::fmt::Debug
rather than std::error::Error
.The tink-gcpkms
crate currently uses the (auto-generated) google-cloudkms1 crate for GCP client functionality. However, the latest version of this crate (1.0.14, from July 2020) depends on old versions of other crates:
hyper ^0.10
: most recent matching version is 0.10.16 from April 2019; current version is 0.14.2hyper-rustls ^0.6
: most recent matching version is 0.6.2 from July 2018; current version is 0.22.1These old dependencies make it very hard to use this crate in anything that uses more up-to-date versions of the deps, particularly since a native library (of which There Can Be Only One) ends up in the deps (ring-asm
from ring).
Possible options:
google-cloudkms1
crate.google-cloudkms1
crate and update that.google-cloudkms1
dependency and find another mechanism/library for GCP client functionality.tink-aead
has a error when building for 32 bit target
cargo check --target i686-unknown-linux-gnu
error: any use of this value will cause an error
--> aead/src/subtle/aes_gcm.rs:27:43
|
27 | const MAX_AES_GCM_PLAINTEXT_SIZE: usize = (1 << 36) - 32;
| ------------------------------------------^^^^^^^^^------
| |
| attempt to shift left by `36_i32`, which would overflow
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
error: could not compile `tink-aead` due to previous error
While the Rust port is under development, it's helpful to have error message that indicate what's gone wrong.
However, this is unwise for a working system if the error details can leak out to attackers.
Upstream Go code: https://github.com/google/tink/tree/master/go/hybrid
https://project-oak.github.io/tink-rust returns a 404 error
https://github.com/google/tink/blob/master/docs/KNOWN-ISSUES.md
I found some of those known issues existed in tink-rust
. We need to port the fixings.
Such as:
Before 1.4.0, AES-CTR-HMAC-AEAD keys and the EncryptThenAuthenticate subtle implementation may be vulnerable to chosen-ciphertext attacks. An attacker can generate ciphertexts that bypass the HMAC verification if and only if all of the following conditions are true:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.