Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions MIGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ For each way the survey found:
tables map to rind's own API, where `finish` takes `&mut self`.
- **aws-lc-rs's API.** Port each call to rind's, using MIGRATING.md's tables.

rind panics on a CPU missing a feature it requires. Depend on `rind` and call
`rind::cpu::check()` at the start of each binary's `main`, exiting with its
error. A library leaves this to the binaries that use it, and says so in its
docs.
rind panics on a CPU missing a feature it requires. At the start of each
binary's `main`, refuse one by exiting with the error from an adapter's
`default_provider()`, rind-ring's `cpu::check()`, or `rind::cpu::check()`.
A library leaves this to the binaries that use it, and says so in its docs.

Commit as you go, with messages in the project's style.

Expand Down
14 changes: 7 additions & 7 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,11 @@ ring has none.

Each `rind-*` crate stands in for a crate's aws-lc-rs or ring backend. rind
panics on a CPU missing a feature it requires, so every adapter function
that builds a provider or ticketer refuses one with the adapter's own
`MissingFeature` instead, which displays rind's message. A static that the
upstream crate requires, such as `DEFAULT_PROVIDER`, cannot; nor can
rind-ring, whose signatures are ring's, or rind-rcgen's `KeyPair`, whose
errors are rcgen's. Call `rind::cpu::check()`, or rind-ring's `cpu::check()`, before using them.
that returns a provider or ticketer refuses one with the adapter's own
`MissingFeature` instead, which displays rind's message. rind-ring, whose
signatures are ring's, and rind-rcgen's `KeyPair`, whose errors are rcgen's,
cannot. Call rind-ring's `cpu::check()`, or `rind::cpu::check()` for
rind-rcgen, before using them.

### rind-ring

Expand Down Expand Up @@ -407,8 +407,8 @@ those algorithms only with its `aws_lc_rs` feature.
|---|---|
| dimpl's `crypto::aws_lc_rs::default_provider()` | `rind_dimpl::default_provider()?` |
| object_store's `aws`, `azure`, and `gcp` features | the `*-base` features, with `with_crypto_provider(rind_object_store::default_provider()?)` |
| jsonwebtoken's `crypto::aws_lc::DEFAULT_PROVIDER` | `rind_jsonwebtoken::DEFAULT_PROVIDER`, after `rind::cpu::check()?` |
| instant-acme's built-in provider | `Account::builder(http, &rind_instant_acme::DEFAULT_PROVIDER)`, after `rind::cpu::check()?` |
| jsonwebtoken's `crypto::aws_lc::DEFAULT_PROVIDER` | `rind_jsonwebtoken::default_provider()?` |
| instant-acme's built-in provider | `Account::builder(http, rind_instant_acme::default_provider()?)` |

## Crates with their own crypto backend

Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ Bun's fastest asymmetric operations run on rind, ahead of Bun writing its own.
- [The launch post can say what building rind cost in AI tokens, time, and money](/quest/m1/ai-usage.md) - tokens, agent and maintainer hours, and API-price cost, before the post
- [rind's launch blog post is published](/quest/m1/launch-post.md) - the Bun PR waits on it
- [The bench report has a quiet Arrow Lake run and kixtop's build cost](/quest/m1/bench-apple-m4.md) - a fresh desktop column and M4 build costs before the launch post
- [rind-jsonwebtoken and rind-instant-acme check the CPU themselves](/quest/m1/adapter-cpu-check.md) - `default_provider()` like the other adapters, before 11.0.0 publishes
- [rind covers MoQ](/quest/m1/moq/README.md) - the primitives, CPU check, armv7 backend, and `rind-*` adapters MoQ needs, verified and released
13 changes: 0 additions & 13 deletions quest/m1/adapter-cpu-check.md

This file was deleted.

14 changes: 8 additions & 6 deletions rind-bench/benches/jsonwebtoken.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use jsonwebtoken::crypto::{CryptoProvider, aws_lc};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey};

const PROVIDERS: [(&str, &CryptoProvider); 2] = [
("aws-lc-rs", &aws_lc::DEFAULT_PROVIDER),
("rind", &rind_jsonwebtoken::DEFAULT_PROVIDER),
];
fn providers() -> [(&'static str, &'static CryptoProvider); 2] {
[
("aws-lc-rs", &aws_lc::DEFAULT_PROVIDER),
("rind", rind_jsonwebtoken::default_provider().unwrap()),
]
}

// The header and claims of a typical token, as jsonwebtoken signs them.
const MESSAGE: &[u8] = b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb290IjoiZGVtbyIsInB1dCI6WyIiXSwiZ2V0IjpbIiJdLCJleHAiOjE3OTI4MTYwMDAsImlhdCI6MTc2MTI4MDAwMH0";
Expand Down Expand Up @@ -81,7 +83,7 @@ fn sign(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("jwt-{algorithm:?}-sign").to_lowercase());
group.throughput(Throughput::Elements(1));

for (name, provider) in PROVIDERS {
for (name, provider) in providers() {
group.bench_function(format!("{name}/token"), |b| {
b.iter(|| {
let signer = (provider.signer_factory)(&algorithm, &encoding).unwrap();
Expand All @@ -105,7 +107,7 @@ fn verify(c: &mut Criterion) {
.try_sign(MESSAGE)
.unwrap();

for (name, provider) in PROVIDERS {
for (name, provider) in providers() {
group.bench_function(format!("{name}/token"), |b| {
b.iter(|| {
let verifier = (provider.verifier_factory)(&algorithm, &decoding).unwrap();
Expand Down
8 changes: 4 additions & 4 deletions rind-instant-acme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ use std::sync::Arc;

use instant_acme::{Account, DefaultClient};

// Refuses a CPU missing a feature rind requires, which DEFAULT_PROVIDER cannot.
let http = DefaultClient::new(Arc::new(rind_rustls::default_provider()?))?;
let builder = Account::builder(Box::new(http), &rind_instant_acme::DEFAULT_PROVIDER)?;
let builder = Account::builder(Box::new(http), rind_instant_acme::default_provider()?)?;
```

rind panics on a CPU missing a feature it requires, so call `rind::cpu::check()`
before using `DEFAULT_PROVIDER`, as `rind_rustls::default_provider()` does.
rind panics on a CPU missing a feature it requires, so `default_provider()`
checks first and returns an error naming it instead, as
`rind_rustls::default_provider()` does.

It signs account requests with ECDSA P-256 (ES256) keys, the same PKCS#8 keys instant-acme's aws-lc-rs and ring providers make, so stored credentials keep working.
It signs External Account Binding with HMAC-SHA256 and hashes JWK thumbprints and challenge digests with SHA-256.
Expand Down
31 changes: 24 additions & 7 deletions rind-instant-acme/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
//! use instant_acme::{Account, DefaultClient};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Refuses a CPU missing a feature rind requires, which DEFAULT_PROVIDER cannot.
//! let http = DefaultClient::new(Arc::new(rind_rustls::default_provider()?))?;
//! let builder = Account::builder(Box::new(http), &rind_instant_acme::DEFAULT_PROVIDER)?;
//! let builder = Account::builder(Box::new(http), rind_instant_acme::default_provider()?)?;
//! # Ok(())
//! # }
//! ```

use core::fmt;

use instant_acme::{
CryptoProvider, EcCurve, Error, HmacKey, HmacKeyProvider, Jwk, JwkThumbFields, Sha256,
SigningAlgorithm, SigningKey, SigningKeyProvider,
Expand All @@ -28,14 +29,30 @@ use rind::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, PublicKey, sign};
use rind::{digest, hmac};
use rustls_pki_types::PrivatePkcs8KeyDer;

/// An instant-acme [`CryptoProvider`] that signs with ECDSA P-256 account keys and HS256 EAB keys on rind.
/// A CPU feature rind requires that this CPU lacks.
#[derive(Debug)]
pub struct MissingFeature(rind::cpu::MissingFeature);

impl fmt::Display for MissingFeature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

impl std::error::Error for MissingFeature {}

/// Returns an instant-acme [`CryptoProvider`] that signs with ECDSA P-256
/// account keys and HS256 EAB keys on rind, or the CPU feature rind requires
/// that this CPU lacks.
///
/// It loads and generates the same PKCS#8 account keys as instant-acme's
/// aws-lc-rs and ring providers, so stored credentials move between them.
///
/// rind panics on a CPU missing a feature it requires, so call
/// [`rind::cpu::check`] first, as `rind_rustls::default_provider()` does.
pub static DEFAULT_PROVIDER: CryptoProvider = CryptoProvider {
pub fn default_provider() -> Result<&'static CryptoProvider, MissingFeature> {
rind::cpu::check().map_err(MissingFeature)?;
Ok(&PROVIDER)
}

static PROVIDER: CryptoProvider = CryptoProvider {
key_provider: &P256KeyProvider,
sha256: &RindSha256,
hmac: &HmacSha256Provider,
Expand Down
9 changes: 5 additions & 4 deletions rind-instant-acme/tests/ctgrind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crabgrind::memcheck::{self, MemState};
use crabgrind::valgrind::{self, RunningMode};
use instant_acme::{CryptoProvider, JwkThumbFields};
use rind::signature::{PublicKey, verify};
use rind_instant_acme::DEFAULT_PROVIDER;

/// Marks `value` secret: memcheck flags any branch or index that depends on it.
fn secret<T: ?Sized>(value: &T) {
Expand All @@ -30,8 +29,9 @@ fn mark<T: ?Sized>(value: &T, state: MemState) {

#[test]
fn account_key() {
let (generated, pkcs8) = DEFAULT_PROVIDER.key_provider.generate_key().unwrap();
let loaded = DEFAULT_PROVIDER.key_provider.load_key(pkcs8).unwrap();
let provider = rind_instant_acme::default_provider().unwrap();
let (generated, pkcs8) = provider.key_provider.generate_key().unwrap();
let loaded = provider.key_provider.load_key(pkcs8).unwrap();
let message = b"eyJhbGciOiJFUzI1NiJ9.eyJyaW5kIjp0cnVlfQ";

for key in [generated, loaded] {
Expand All @@ -53,6 +53,7 @@ fn account_key() {

#[test]
fn external_account_binding() {
let provider = rind_instant_acme::default_provider().unwrap();
let key = [0x42u8; 32];
let message = b"eyJhbGciOiJIUzI1NiJ9.eyJyaW5kIjp0cnVlfQ";
let expected = CryptoProvider::aws_lc_rs()
Expand All @@ -61,7 +62,7 @@ fn external_account_binding() {
.sign(message);

secret(&key);
let tag = DEFAULT_PROVIDER.hmac.load_key(&key).sign(message);
let tag = provider.hmac.load_key(&key).sign(message);
public(&tag[..]);
assert_eq!(tag, expected);
}
18 changes: 10 additions & 8 deletions rind-instant-acme/tests/differential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use instant_acme::{CryptoProvider, Error, JwkThumbFields, SigningKey};
use rind::signature::{EcdsaKeyPair, Ed25519KeyPair, sign, verify};
use rustls_pki_types::PrivatePkcs8KeyDer;

const RIND: &CryptoProvider = &rind_instant_acme::DEFAULT_PROVIDER;
fn rind() -> &'static CryptoProvider {
rind_instant_acme::default_provider().unwrap()
}

fn aws_lc() -> &'static CryptoProvider {
CryptoProvider::aws_lc_rs()
Expand Down Expand Up @@ -51,11 +53,11 @@ fn load(provider: &CryptoProvider, pkcs8: &[u8]) -> Result<Box<dyn SigningKey>,

#[test]
fn generated_keys_load_in_both_providers() {
for (generator, name) in [(RIND, "rind"), (aws_lc(), "aws-lc-rs")] {
for (generator, name) in [(rind(), "rind"), (aws_lc(), "aws-lc-rs")] {
let (key, pkcs8) = generator.key_provider.generate_key().unwrap();
let pkcs8 = pkcs8.secret_pkcs8_der();

let ours = load(RIND, pkcs8).unwrap_or_else(|e| panic!("{name} key in rind: {e}"));
let ours = load(rind(), pkcs8).unwrap_or_else(|e| panic!("{name} key in rind: {e}"));
let theirs = load(aws_lc(), pkcs8).unwrap_or_else(|e| panic!("{name} key in aws-lc: {e}"));

assert_eq!(jwk(&*key), jwk(&*ours), "{name}");
Expand All @@ -66,7 +68,7 @@ fn generated_keys_load_in_both_providers() {

#[test]
fn signatures_verify_across_providers() {
let (key, pkcs8) = RIND.key_provider.generate_key().unwrap();
let (key, pkcs8) = rind().key_provider.generate_key().unwrap();
let theirs = load(aws_lc(), pkcs8.secret_pkcs8_der()).unwrap();
let point = point(&*key);
let message = b"eyJhbGciOiJFUzI1NiJ9.eyJyaW5kIjp0cnVlfQ";
Expand All @@ -88,7 +90,7 @@ fn signatures_verify_across_providers() {

#[test]
fn malformed_keys_are_rejected_by_both_providers() {
let (_, p256) = RIND.key_provider.generate_key().unwrap();
let (_, p256) = rind().key_provider.generate_key().unwrap();
let p256 = p256.secret_pkcs8_der();
let p384 = EcdsaKeyPair::generate(&sign::ECDSA_P384_SHA384_FIXED)
.unwrap()
Expand Down Expand Up @@ -116,7 +118,7 @@ fn malformed_keys_are_rejected_by_both_providers() {
"aws-lc-rs accepted {what}"
);
assert!(
matches!(load(RIND, pkcs8), Err(Error::KeyRejected)),
matches!(load(rind(), pkcs8), Err(Error::KeyRejected)),
"rind accepted {what}"
);
}
Expand All @@ -129,7 +131,7 @@ fn sha256_matches() {
for len in 0..300 {
let data = rng.bytes(len);
assert_eq!(
RIND.sha256.hash(&data),
rind().sha256.hash(&data),
aws_lc().sha256.hash(&data),
"{len}"
);
Expand All @@ -142,7 +144,7 @@ fn hmac_matches() {
// Keys either side of the 64-byte block, past which HMAC hashes the key.
for key_len in [0, 1, 16, 32, 63, 64, 65, 128, 200] {
let key = rng.bytes(key_len);
let ours = RIND.hmac.load_key(&key);
let ours = rind().hmac.load_key(&key);
let theirs = aws_lc().hmac.load_key(&key);
assert_eq!(
serde_json::to_string(&ours.algorithm()).unwrap(),
Expand Down
9 changes: 5 additions & 4 deletions rind-instant-acme/tests/pebble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use instant_acme::{
OrderStatus, RetryPolicy,
};
use rcgen::{CertificateParams, PKCS_ECDSA_P256_SHA256, PublicKeyData, SerialNumber};
use rind_instant_acme::DEFAULT_PROVIDER;
use rustls_pki_types::CertificateDer;
use rustls_pki_types::pem::PemObject;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
Expand Down Expand Up @@ -48,21 +47,23 @@ async fn issue_certificate() {
Box::new(DefaultClient::with_pem_root(&root, provider).unwrap())
};

let acme = rind_instant_acme::default_provider().unwrap();

// Creating the account generates its key and signs the EAB with HS256.
let eab = ExternalAccountKey::new(EAB_KID.to_string(), DEFAULT_PROVIDER.hmac.load_key(EAB_KEY));
let eab = ExternalAccountKey::new(EAB_KID.to_string(), acme.hmac.load_key(EAB_KEY));
let new_account = NewAccount {
contact: &[],
terms_of_service_agreed: true,
only_return_existing: false,
};
let (_, credentials) = Account::builder(http(), &DEFAULT_PROVIDER)
let (_, credentials) = Account::builder(http(), acme)
.unwrap()
.create(&new_account, directory, Some(&eab))
.await
.unwrap();

// Ordering from stored credentials loads the PKCS#8 key back.
let account = Account::builder(http(), &DEFAULT_PROVIDER)
let account = Account::builder(http(), acme)
.unwrap()
.from_credentials(credentials)
.await
Expand Down
5 changes: 2 additions & 3 deletions rind-jsonwebtoken/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ This crate provides an integration between [jsonwebtoken](https://github.com/Kea
Use it like:

```rust
// rind panics on a CPU missing a feature it requires, so refuse it first.
rind::cpu::check()?;
rind_jsonwebtoken::DEFAULT_PROVIDER.install_default().unwrap();
rind_jsonwebtoken::default_provider()?.install_default().unwrap();
```

And then use jsonwebtoken as normal.
rind panics on a CPU missing a feature it requires, so `default_provider()` checks first and returns an error naming it instead.
It signs and verifies HS256/384/512, ES256/384, RS256/384/512, PS256/384/512, and EdDSA, and implements the key utilities jsonwebtoken needs for JWKs.

License: Apache-2.0 OR ISC OR MIT-0
33 changes: 24 additions & 9 deletions rind-jsonwebtoken/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
//! Use it like:
//!
//! ```rust
//! # fn main() -> Result<(), rind::cpu::MissingFeature> {
//! // rind panics on a CPU missing a feature it requires, so refuse it first.
//! rind::cpu::check()?;
//! rind_jsonwebtoken::DEFAULT_PROVIDER
//! # fn main() -> Result<(), rind_jsonwebtoken::MissingFeature> {
//! rind_jsonwebtoken::default_provider()?
//! .install_default()
//! .unwrap();
//! # Ok(())
Expand All @@ -18,6 +16,8 @@
//!
//! And then use jsonwebtoken as normal.

use core::fmt;

use jsonwebtoken::crypto::{CryptoProvider, JwtSigner, JwtVerifier, KeyUtils};
use jsonwebtoken::errors::{Error, ErrorKind, Result};
use jsonwebtoken::jwk::{EllipticCurve, ThumbprintHash};
Expand All @@ -31,11 +31,26 @@ use rind::signature::{
};
use rind::{digest, hmac};

/// A jsonwebtoken [`CryptoProvider`] that signs and verifies every algorithm with rind.
///
/// rind panics on a CPU missing a feature it requires, so call
/// [`rind::cpu::check`] first.
pub static DEFAULT_PROVIDER: CryptoProvider = CryptoProvider {
/// A CPU feature rind requires that this CPU lacks.
#[derive(Debug)]
pub struct MissingFeature(rind::cpu::MissingFeature);

impl fmt::Display for MissingFeature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

impl std::error::Error for MissingFeature {}

/// Returns a jsonwebtoken [`CryptoProvider`] that signs and verifies every
/// algorithm with rind, or the CPU feature rind requires that this CPU lacks.
pub fn default_provider() -> std::result::Result<&'static CryptoProvider, MissingFeature> {
rind::cpu::check().map_err(MissingFeature)?;
Ok(&PROVIDER)
}

static PROVIDER: CryptoProvider = CryptoProvider {
signer_factory: new_signer,
verifier_factory: new_verifier,
key_utils: KeyUtils {
Expand Down
Loading