GREASE Support
GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism defined in RFC 9420 Section 13.5 to ensure that implementations properly handle unknown values and maintain protocol extensibility.
What is GREASE?
GREASE values are special reserved values that follow a specific pattern (0x0A0A, 0x1A1A, 0x2A2A, ..., 0xEAEA) and are used to:
- Test extensibility: Ensure implementations don’t reject messages containing unknown values
- Prevent ossification: Help maintain forward compatibility by exercising unknown value handling paths
- Identify bugs: Catch implementations that incorrectly assume all possible values are known
RFC-defined vs. Custom GREASE Values
The 15 values defined in RFC 9420 are the “official” GREASE values. When OpenMLS generates GREASE values (e.g., via with_grease()), it uses these RFC-defined values. The is_grease() method only returns true for these specific values.
However, any unknown value can serve a similar purpose. The difference is that for non-RFC values, we cannot distinguish whether they are intentionally injected “GREASE-like” values or genuinely unknown identifiers from future protocol extensions.
The Purpose of GREASE
Important: The entire point of GREASE is that implementations should not check for these values. GREASE values exist to ensure that your unknown-value handling code paths are exercised. Applications can inject GREASE values into their capabilities to discourage other implementations from:
- Failing on unknown values
- Hard-coding assumptions about which values exist
- Breaking when the protocol is extended
See Important Notes for details on how OpenMLS handles GREASE during validation.
GREASE in OpenMLS
OpenMLS supports GREASE values for the following types:
- Ciphersuites (
VerifiableCiphersuite) - Extensions (
ExtensionType::Grease) - Proposals (
ProposalType::Grease) - Credentials (
CredentialType::Grease)
GREASE Handling
OpenMLS:
- Recognizes GREASE values during deserialization
- Filters GREASE values during validation to prevent false negatives (GREASE values are treated the same as unknown values)
- Preserves GREASE values when present in capabilities
- Provides convenience methods to inject random GREASE values into capabilities
Note: GREASE values are NOT automatically injected. Library users who wish to include GREASE values in their capabilities should use the with_grease() method described below.
Using GREASE Values
In Capabilities
You can include GREASE values in your KeyPackage capabilities to test interoperability:
#![allow(unused)]
fn main() {
use openmls::prelude::*;
let capabilities = Capabilities::builder()
.proposals(vec![
ProposalType::Add,
ProposalType::Update,
ProposalType::Remove,
ProposalType::Grease(0x0A0A), // Add a GREASE proposal type
])
.extensions(vec![
ExtensionType::ApplicationId,
ExtensionType::Grease(0x1A1A), // Add a GREASE extension type
])
.credentials(vec![
CredentialType::Basic,
CredentialType::Grease(0x2A2A), // Add a GREASE credential type
])
.build();
}
Injecting Random GREASE Values
The easiest way to add GREASE values is using the with_grease() method on Capabilities or CapabilitiesBuilder:
#![allow(unused)]
fn main() {
use openmls::prelude::*;
use openmls_rust_crypto::OpenMlsRustCrypto;
let provider = OpenMlsRustCrypto::default();
// Using CapabilitiesBuilder
let capabilities = Capabilities::builder()
.with_grease(provider.rand())
.build();
// Or on an existing Capabilities instance
let capabilities = Capabilities::default()
.with_grease(provider.rand());
}
This will add one random GREASE value to each capability list (ciphersuites, extensions, proposals, and credentials) if no GREASE value is already present.
Generating Individual Random GREASE Values
OpenMLS also provides a helper function to generate individual random GREASE values:
#![allow(unused)]
fn main() {
use openmls::grease::random_grease_value;
use openmls_rust_crypto::OpenMlsRustCrypto;
let crypto = OpenMlsRustCrypto::default();
let grease_value = random_grease_value(&crypto);
// Use in capabilities
let grease_proposal = ProposalType::Grease(grease_value);
}
Checking for GREASE Values
All GREASE-capable types provide an is_grease() method. Note that this method only identifies the RFC-defined GREASE values—it cannot detect custom unknown values that serve a similar purpose.
Caution: As explained in The Purpose of GREASE, applications should generally not use is_grease() for filtering or decision-making. The method exists primarily for OpenMLS’s internal validation logic and for testing/debugging purposes.
#![allow(unused)]
fn main() {
use openmls::prelude::*;
let proposal = ProposalType::Grease(0x0A0A);
assert!(proposal.is_grease());
let extension = ExtensionType::Grease(0x1A1A);
assert!(extension.is_grease());
let credential = CredentialType::Grease(0x2A2A);
assert!(credential.is_grease());
use openmls_traits::types::VerifiableCiphersuite;
let ciphersuite = VerifiableCiphersuite::new(0x3A3A);
assert!(ciphersuite.is_grease());
}
GREASE Values
The following 15 values are defined as GREASE values in RFC 9420:
0x0A0A0x1A1A0x2A2A0x3A3A0x4A4A0x5A5A0x6A6A0x7A7A0x8A8A0x9A9A0xAAAA0xBABA0xCACA0xDADA0xEAEA
Important Notes
GREASE Values Cannot Be Used for Operations
GREASE ciphersuites, in particular, cannot be used for actual cryptographic operations. They exist only to test capability negotiation and should never be selected as the active ciphersuite for a group.
Validation Automatically Filters GREASE
When OpenMLS validates capabilities, it automatically filters out GREASE values. This means:
- Two members with different GREASE values in their capabilities can still interoperate
- GREASE values don’t affect capability intersection or matching
- Required capabilities never include GREASE values
Interoperability Testing
Including GREASE values in your capabilities is recommended for testing interoperability with other MLS implementations. It helps ensure that:
- Other implementations correctly handle unknown values
- Your implementation correctly filters GREASE during validation
- Protocol extensibility is maintained
Example: Full Usage
Here’s a complete example showing GREASE usage with the recommended with_grease() method:
#![allow(unused)]
fn main() {
use openmls::prelude::*;
use openmls_basic_credential::SignatureKeyPair;
use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_traits::types::Ciphersuite;
let provider = OpenMlsRustCrypto::default();
// Create a credential
let credential = BasicCredential::new(b"Alice".to_vec());
let signature_keys = SignatureKeyPair::new(
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519.signature_algorithm()
).unwrap();
// Create capabilities with automatic random GREASE values
let capabilities = Capabilities::builder()
.with_grease(provider.rand())
.build();
// Create a KeyPackage with GREASE values
let key_package = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.build(
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
&provider,
&signature_keys,
CredentialWithKey {
credential: credential.into(),
signature_key: signature_keys.public().into(),
},
)
.unwrap();
// The KeyPackage can be used normally - GREASE values are handled during validation
}
Example: Manually Specifying GREASE Values
If you need to specify particular GREASE values (e.g., for testing or interoperability):
#![allow(unused)]
fn main() {
use openmls::prelude::*;
use openmls_basic_credential::SignatureKeyPair;
use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_traits::types::Ciphersuite;
let provider = OpenMlsRustCrypto::default();
// Create a credential
let credential = BasicCredential::new(b"Alice".to_vec());
let signature_keys = SignatureKeyPair::new(
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519.signature_algorithm()
).unwrap();
// Create capabilities with specific GREASE values
let capabilities = Capabilities::builder()
.proposals(vec![
ProposalType::Add,
ProposalType::Update,
ProposalType::Remove,
ProposalType::Grease(0x0A0A),
ProposalType::Grease(0x1A1A),
])
.extensions(vec![
ExtensionType::ApplicationId,
ExtensionType::Grease(0x2A2A),
])
.credentials(vec![
CredentialType::Basic,
CredentialType::Grease(0x3A3A),
])
.build();
// Create a KeyPackage with these capabilities
let key_package = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.build(
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
&provider,
&signature_keys,
CredentialWithKey {
credential: credential.into(),
signature_key: signature_keys.public().into(),
},
)
.unwrap();
}
Further Reading
- RFC 9420 Section 13.5: GREASE
- RFC 8701: Applying GREASE to TLS Extensibility - The original GREASE specification for TLS