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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- API Stability Policy guide documenting semantic versioning and breaking change policy (#72)
- Comprehensive module grouping in Hex docs for all keyrings, CMMs, caching, and streaming modules (#72)

### Changed
- Consolidated CHANGELOG entries to improve readability and scannability (#81)
- Enhanced streaming module documentation with usage guidance, memory efficiency details, and verification handling (#72)

## [0.6.0] - 2026-01-31

Expand Down
212 changes: 212 additions & 0 deletions guides/STABILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# API Stability Policy

This document describes the API stability guarantees for the AWS Encryption SDK for Elixir.

## Semantic Versioning

The AWS Encryption SDK for Elixir follows [Semantic Versioning 2.0.0](https://semver.org/):

- **MAJOR version** (X.0.0) - Incompatible API changes
- **MINOR version** (0.X.0) - New functionality, backward compatible
- **PATCH version** (0.0.X) - Bug fixes, backward compatible

### Version Format

Given a version number `MAJOR.MINOR.PATCH`:

- **MAJOR**: Incremented for breaking changes
- **MINOR**: Incremented for new features (backward compatible)
- **PATCH**: Incremented for bug fixes (backward compatible)

## Stability Guarantees

### Public API (Stable)

The following modules and functions are considered **public API** and follow strict backward compatibility:

#### Core Encryption/Decryption
- `AwsEncryptionSdk` - Main module facade
- `AwsEncryptionSdk.Client` - Client configuration and operations
- `AwsEncryptionSdk.Encrypt` - Encryption operations
- `AwsEncryptionSdk.Decrypt` - Decryption operations

#### Streaming API
- `AwsEncryptionSdk.Stream` - High-level streaming API
- `AwsEncryptionSdk.Stream.Encryptor` - Streaming encryption state machine
- `AwsEncryptionSdk.Stream.Decryptor` - Streaming decryption state machine

#### Keyrings
- `AwsEncryptionSdk.Keyring.Behaviour` - Keyring interface
- `AwsEncryptionSdk.Keyring.RawAes` - Raw AES keyring
- `AwsEncryptionSdk.Keyring.RawRsa` - Raw RSA keyring
- `AwsEncryptionSdk.Keyring.AwsKms` - AWS KMS keyring
- `AwsEncryptionSdk.Keyring.AwsKmsDiscovery` - KMS discovery keyring
- `AwsEncryptionSdk.Keyring.AwsKmsMrk` - KMS multi-region key keyring
- `AwsEncryptionSdk.Keyring.AwsKmsMrkDiscovery` - KMS MRK discovery keyring
- `AwsEncryptionSdk.Keyring.Multi` - Multi-keyring composition
- `AwsEncryptionSdk.Keyring.KmsClient` - KMS client interface

#### Cryptographic Materials Managers
- `AwsEncryptionSdk.Cmm.Behaviour` - CMM interface
- `AwsEncryptionSdk.Cmm.Default` - Default CMM
- `AwsEncryptionSdk.Cmm.Caching` - Caching CMM
- `AwsEncryptionSdk.Cmm.RequiredEncryptionContext` - Required context CMM

#### Caching
- `AwsEncryptionSdk.Cache.CryptographicMaterialsCache` - Cache interface
- `AwsEncryptionSdk.Cache.LocalCache` - Local in-memory cache

#### Data Structures
- `AwsEncryptionSdk.Materials.EncryptionMaterials` - Encryption materials
- `AwsEncryptionSdk.Materials.DecryptionMaterials` - Decryption materials
- `AwsEncryptionSdk.Materials.EncryptedDataKey` - Encrypted data key
- `AwsEncryptionSdk.AlgorithmSuite` - Algorithm suite definitions

**Guarantee**: Public API functions will not change signatures or behavior in backward-incompatible ways within the same MAJOR version.

### Internal API (Unstable)

The following modules are considered **internal implementation details** and may change without notice:

- `AwsEncryptionSdk.Format.*` - Message format serialization (internal)
- `AwsEncryptionSdk.Crypto.*` - Low-level cryptographic operations (internal)
- `AwsEncryptionSdk.Keyring.KmsKeyArn` - KMS ARN parsing (internal utility)
- `AwsEncryptionSdk.Cache.CacheEntry` - Cache entry implementation (internal)
- `AwsEncryptionSdk.Stream.SignatureAccumulator` - Internal to streaming (advanced use only)

**Warning**: Internal modules may change in MINOR or PATCH versions. Use at your own risk.

### Message Format Stability

The binary message format produced by encryption is **stable** and follows the [AWS Encryption SDK Specification](https://github.com/awslabs/aws-encryption-sdk-specification):

- Messages encrypted with this SDK can be decrypted by other AWS Encryption SDK implementations
- Messages encrypted by other SDKs can be decrypted by this SDK
- Message format compatibility is maintained across all MAJOR, MINOR, and PATCH versions
- Algorithm suites and message versions follow the official specification

## Breaking Change Policy

### What Constitutes a Breaking Change

A **breaking change** requires a MAJOR version bump and includes:

1. **Function signature changes**:
- Removing required function parameters
- Changing parameter types
- Changing return type structure
- Removing public functions

2. **Behavior changes**:
- Changing error conditions
- Modifying security guarantees
- Altering algorithm suite defaults
- Changing commitment policy behavior

3. **Data structure changes**:
- Removing struct fields used in public API
- Changing struct field types in breaking ways
- Modifying validation rules for inputs

### What Is NOT a Breaking Change

The following changes are **backward compatible** and only require MINOR or PATCH bumps:

1. **Additions**:
- Adding new optional parameters
- Adding new functions
- Adding new modules
- Adding new struct fields (with defaults)

2. **Fixes**:
- Bug fixes that correct incorrect behavior
- Security fixes
- Performance improvements
- Documentation improvements

3. **Internal changes**:
- Refactoring internal modules
- Optimizing implementation details
- Updating dependencies (within semver constraints)

## Deprecation Process

When we need to make breaking changes, we follow this deprecation timeline:

### Step 1: Deprecation Warning (MINOR release)

- Add `@deprecated` attribute to affected functions
- Update documentation with migration guide
- Add compile-time warnings
- Maintain full backward compatibility

Example:

```elixir
@deprecated "Use Client.encrypt/3 instead. This will be removed in v2.0.0"
def old_encrypt(data) do
# ... implementation
end
```

### Step 2: Deprecation Period (at least 6 months)

- Deprecated functions remain available for at least 6 months
- Multiple MINOR releases may occur during this period
- Migration guides and examples are provided
- Community feedback is collected

### Step 3: Removal (MAJOR release)

- Deprecated functions are removed
- MAJOR version is bumped (e.g., v1.5.0 → v2.0.0)
- CHANGELOG documents all breaking changes
- Migration guide is updated with complete upgrade path

## Dependency Policy

### Elixir and OTP

- **Minimum Elixir version**: We support Elixir versions for 2 years from their release
- **Minimum OTP version**: We support OTP versions that are actively maintained by the Erlang team
- **Current minimums**: Elixir 1.16+, OTP 26+

Raising minimum Elixir or OTP versions is considered a **breaking change** requiring a MAJOR bump.

### External Dependencies

- Production dependencies follow semver constraints
- We may update dependencies in MINOR releases if:
- Changes are backward compatible
- No API changes are required
- Security updates are needed

## Security Policy

Security fixes are prioritized and may be released as:

- **PATCH releases** - If the fix doesn't break backward compatibility
- **MAJOR releases** - If the fix requires breaking changes (rare)

Critical security issues may warrant backporting fixes to older MAJOR versions.

## Specification Compliance

This SDK follows the [AWS Encryption SDK Specification](https://github.com/awslabs/aws-encryption-sdk-specification):

- **Specification updates** that maintain backward compatibility → MINOR release
- **Specification updates** that break compatibility → MAJOR release
- **Algorithm suite deprecations** follow the official AWS guidance and our deprecation process

## Feedback and Questions

If you have questions about API stability or need clarification on whether a change is breaking:

- Open an issue on [GitHub](https://github.com/riddler/aws-encryption-sdk-elixir/issues)
- Ask in discussions
- Check the [CHANGELOG](../CHANGELOG.md) for detailed change documentation

## Version History

- **v0.x.x** (Pre-1.0): Development releases, API may change
- **v1.0.0** (Planned): First stable release with API stability guarantees
109 changes: 103 additions & 6 deletions lib/aws_encryption_sdk/stream/decryptor.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,120 @@
defmodule AwsEncryptionSdk.Stream.Decryptor do
@moduledoc """
Streaming decryptor state machine.
Streaming decryptor state machine for incremental ciphertext processing.

Processes ciphertext incrementally and emits plaintext frames. Designed for
use with Elixir's Stream functions.
## When to Use Streaming

Use `Stream.Decryptor` instead of `Client.decrypt/2` when:

- Decrypting large files that don't fit in memory
- Processing encrypted data from network streams
- Working with ciphertext sources that produce chunks incrementally
- Memory constraints require bounded memory usage

For small messages (< 1MB), the simpler `Client.decrypt/2` API is recommended.

## Memory Efficiency

The streaming decryptor maintains constant memory usage:

- Buffers only data needed to parse the current frame
- Emits plaintext incrementally after frame authentication
- No need to load entire ciphertext into memory

Memory usage is bounded by the frame size plus header size, regardless of
total message size.

## Plaintext Verification Status

Decrypted plaintext is tagged with verification status:

- **`:verified`** - Plaintext is authenticated and safe to use
- For unsigned suites: immediately after frame authentication
- For signed suites: after signature verification completes

- **`:unverified`** - Plaintext not yet cryptographically verified
- Only for signed algorithm suites
- Signature verification happens at end of stream
- **Do not use unverified plaintext** until signature validates

### Handling Signed Suites

For signed algorithm suites (ECDSA P-384), you must handle verification:

**Option 1: Fail immediately** (safest):

{:ok, dec} = Decryptor.init(
get_materials: materials_fn,
fail_on_signed: true
)

**Option 2: Buffer unverified plaintext** (for streaming):

plaintexts = []
for {plaintext, status} <- decrypted_chunks do
case status do
:verified -> use_plaintext(plaintext)
:unverified -> plaintexts = [plaintext | plaintexts]
end
end
# At end of stream, all buffered plaintext is verified

**Option 3: Use high-level API** (recommended):

Use `AwsEncryptionSdk.Stream.decrypt/3` which handles verification automatically.

## Integration with Elixir Streams

Designed to work seamlessly with `Stream` module:

File.stream!("encrypted.bin", [], 4096)
|> AwsEncryptionSdk.Stream.decrypt(client)
|> Stream.map(fn {plaintext, _status} -> plaintext end)
|> Stream.into(File.stream!("decrypted.bin"))
|> Stream.run()

See `AwsEncryptionSdk.Stream` for high-level streaming API.

## State Machine

The decryptor progresses through these states:

1. `:init` - Not started, awaiting ciphertext
2. `:reading_header` - Accumulating header bytes
3. `:decrypting` - Processing frames
4. `:reading_footer` - Accumulating footer (signed suites)
4. `:reading_footer` - Accumulating footer (signed suites only)
5. `:done` - Decryption complete

## Low-Level Example

For custom streaming logic, use the state machine directly:

get_materials = fn header ->
# Obtain decryption materials from CMM
cmm.get_decryption_materials(...)
end

{:ok, dec} = Decryptor.init(get_materials: get_materials)

# Process ciphertext chunks
{:ok, dec, plaintexts1} = Decryptor.update(dec, chunk1)
{:ok, dec, plaintexts2} = Decryptor.update(dec, chunk2)
{:ok, dec, final_plaintexts} = Decryptor.finalize(dec)

# Each plaintexts is a list of {binary, :verified | :unverified} tuples

## Security

For unsigned suites, plaintext is released immediately after frame authentication.
For signed suites, see `:fail_on_signed` option.
- Never release unauthenticated plaintext to untrusted contexts
- For signed suites, verify signature before using plaintext
- The decryptor validates authentication tags before emitting plaintext
- Commitment verification happens during header processing

## See Also

- `AwsEncryptionSdk.Stream` - High-level streaming API
- `AwsEncryptionSdk.Stream.Encryptor` - Streaming encryption
- `AwsEncryptionSdk.Client` - Non-streaming decryption API
"""

alias AwsEncryptionSdk.AlgorithmSuite
Expand Down
Loading