Skip to content

Commit aa8c99b

Browse files
committed
[WIP] pkcs5: encryption
Implements PKCS#5 encryption support, presently targeting only support for PBES2 with PBKDF2-SHA-256 and AES-CBC (with 128 or 256-bit key size) Note that these are presently the best options supported by PKCS#5 v2.1. Support for legacy algorithms like DES, 3DES, MD2, and SHA-1 is deliberately ommitted. We can revisit potentially adding these upon request if there is demand, however since these algorithms are insecure we don't support them in this initial implementation.
1 parent 2a2cdb7 commit aa8c99b

File tree

13 files changed

+340
-18
lines changed

13 files changed

+340
-18
lines changed

.github/workflows/pkcs5.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
strategy:
2626
matrix:
2727
rust:
28-
- 1.47.0 # MSRV
28+
- 1.49.0 # MSRV
2929
- stable
3030
target:
3131
- thumbv7em-none-eabi
@@ -38,14 +38,15 @@ jobs:
3838
toolchain: ${{ matrix.rust }}
3939
target: ${{ matrix.target }}
4040
override: true
41-
- run: cargo build --release --target ${{ matrix.target }}
41+
- run: cargo build --target ${{ matrix.target }} --release
42+
- run: cargo build --target ${{ matrix.target }} --release --features pbes2
4243

4344
test:
4445
runs-on: ubuntu-latest
4546
strategy:
4647
matrix:
4748
rust:
48-
- 1.47.0 # MSRV
49+
- 1.49.0 # MSRV
4950
- stable
5051
steps:
5152
- uses: actions/checkout@v1
@@ -55,3 +56,4 @@ jobs:
5556
toolchain: ${{ matrix.rust }}
5657
override: true
5758
- run: cargo test --release
59+
- run: cargo test --release --all-features

.github/workflows/workspace.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/checkout@v1
1717
- uses: actions-rs/toolchain@v1
1818
with:
19-
toolchain: 1.46.0 # MSRV
19+
toolchain: 1.49.0 # Highest MSRV in repo
2020
components: clippy
2121
override: true
2222
profile: minimal

Cargo.lock

Lines changed: 131 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ members = [
1616
"pkcs8",
1717
"spki",
1818
]
19+
20+
[patch.crates-io]
21+
aes = { git = "https://github.com/RustCrypto/block-ciphers" }
22+
block-modes = { git = "https://github.com/RustCrypto/block-ciphers" }

base64ct/src/encoding.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ impl<T: Variant> Encoding for T {
231231

232232
fn encoded_len(bytes: &[u8]) -> usize {
233233
// TODO: replace with `unwrap_or` on stabilization
234+
#[allow(clippy::manual_unwrap_or)]
234235
match encoded_len_inner(bytes.len(), T::PADDED) {
235236
Some(v) => v,
236237
None => 0,

pkcs5/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@ readme = "README.md"
1717
der = { version = "0.2.7", features = ["oid"], path = "../der" }
1818
spki = { version = "0.2", path = "../spki" }
1919

20+
aes = { version = "=0.7.0-pre", optional = true }
21+
block-modes = { version = "=0.8.0-pre", optional = true, default-features = false }
22+
hmac = { version = "0.10", optional = true, default-features = false }
23+
pbkdf2 = { version = "0.7", optional = true, default-features = false }
24+
sha2 = { version = "0.9", optional = true, default-features = false }
25+
2026
[dev-dependencies]
2127
hex-literal = "0.3"
2228

29+
[features]
30+
pbes2 = ["aes", "block-modes", "hmac", "pbkdf2", "sha2"]
31+
2332
[package.metadata.docs.rs]
2433
all-features = true
2534
rustdoc-args = ["--cfg", "docsrs"]

pkcs5/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ dual licensed as above, without any additional terms or conditions.
3434
[docs-image]: https://docs.rs/pkcs5/badge.svg
3535
[docs-link]: https://docs.rs/pkcs5/
3636
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
37-
[rustc-image]: https://img.shields.io/badge/rustc-1.47+-blue.svg
37+
[rustc-image]: https://img.shields.io/badge/rustc-1.49+-blue.svg
3838
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
3939
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils
4040
[build-image]: https://github.com/RustCrypto/utils/workflows/pkcs5/badge.svg?branch=master&event=push

pkcs5/src/lib.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//!
55
//! # Minimum Supported Rust Version
66
//!
7-
//! This crate requires **Rust 1.47** at a minimum.
7+
//! This crate requires **Rust 1.49** at a minimum.
88
//!
99
//! # Usage
1010
//!
@@ -25,7 +25,7 @@
2525
#![forbid(unsafe_code)]
2626
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
2727

28-
pub use der::{self, Error, ObjectIdentifier, Result};
28+
pub use der::{self, Error, ObjectIdentifier};
2929
pub use spki::AlgorithmIdentifier;
3030

3131
use core::convert::{TryFrom, TryInto};
@@ -34,6 +34,10 @@ use der::{sequence, Any, Encodable, Encoder, Length};
3434
pub mod pbes1;
3535
pub mod pbes2;
3636

37+
/// Cryptographic errors
38+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
39+
pub struct CryptoError;
40+
3741
/// Supported PKCS#5 password-based encryption schemes.
3842
#[derive(Clone, Debug, Eq, PartialEq)]
3943
#[non_exhaustive]
@@ -51,6 +55,25 @@ pub enum EncryptionScheme<'a> {
5155
}
5256

5357
impl<'a> EncryptionScheme<'a> {
58+
/// Attempt to decrypt the given ciphertext in-place using a key derived
59+
/// from the provided password and this scheme's parameters.
60+
///
61+
/// Returns an error if the algorithm specified in this scheme's parameters
62+
/// is unsupported, or if the ciphertext is malformed (e.g. not a multiple
63+
/// of a block mode's padding)
64+
#[cfg(feature = "pbes2")]
65+
#[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))]
66+
pub fn decrypt_in_place<'b>(
67+
&self,
68+
password: impl AsRef<[u8]>,
69+
buffer: &'b mut [u8],
70+
) -> Result<&'b [u8], CryptoError> {
71+
match self {
72+
Self::Pbes2(params) => pbes2::encryption::decrypt_in_place(params, password, buffer),
73+
_ => Err(CryptoError),
74+
}
75+
}
76+
5477
/// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm.
5578
pub fn oid(&self) -> ObjectIdentifier {
5679
match self {
@@ -79,23 +102,23 @@ impl<'a> EncryptionScheme<'a> {
79102
impl<'a> TryFrom<&'a [u8]> for EncryptionScheme<'a> {
80103
type Error = Error;
81104

82-
fn try_from(bytes: &'a [u8]) -> Result<EncryptionScheme<'a>> {
105+
fn try_from(bytes: &'a [u8]) -> der::Result<EncryptionScheme<'a>> {
83106
AlgorithmIdentifier::try_from(bytes).and_then(TryInto::try_into)
84107
}
85108
}
86109

87110
impl<'a> TryFrom<Any<'a>> for EncryptionScheme<'a> {
88111
type Error = Error;
89112

90-
fn try_from(any: Any<'a>) -> Result<EncryptionScheme<'a>> {
113+
fn try_from(any: Any<'a>) -> der::Result<EncryptionScheme<'a>> {
91114
AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into)
92115
}
93116
}
94117

95118
impl<'a> TryFrom<AlgorithmIdentifier<'a>> for EncryptionScheme<'a> {
96119
type Error = Error;
97120

98-
fn try_from(alg: AlgorithmIdentifier<'a>) -> Result<EncryptionScheme<'_>> {
121+
fn try_from(alg: AlgorithmIdentifier<'a>) -> der::Result<EncryptionScheme<'_>> {
99122
match alg.oid {
100123
pbes2::PBES2_OID => pbes2::Parameters::try_from(alg.parameters_any()?).map(Into::into),
101124
_ => pbes1::Parameters::try_from(alg).map(Into::into),
@@ -116,14 +139,14 @@ impl<'a> From<pbes2::Parameters<'a>> for EncryptionScheme<'a> {
116139
}
117140

118141
impl<'a> Encodable for EncryptionScheme<'a> {
119-
fn encoded_len(&self) -> Result<Length> {
142+
fn encoded_len(&self) -> der::Result<Length> {
120143
match self {
121144
Self::Pbes1(pbes1) => pbes1.encoded_len(),
122145
Self::Pbes2(pbes2) => sequence::encoded_len(&[&pbes2::PBES2_OID, pbes2]),
123146
}
124147
}
125148

126-
fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> {
149+
fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> {
127150
match self {
128151
Self::Pbes1(pbes1) => pbes1.encode(encoder),
129152
Self::Pbes2(pbes2) => encoder.sequence(&[&pbes2::PBES2_OID, pbes2]),

0 commit comments

Comments
 (0)