diff options
author | David Drysdale <drysdale@google.com> | 2023-09-29 07:41:39 +0100 |
---|---|---|
committer | David Drysdale <drysdale@google.com> | 2023-09-29 07:41:39 +0100 |
commit | 16f92bd6772396b4e48d07ee8c4f07f434f9f39e (patch) | |
tree | f3415fd34619226be7a6906f226e928fef2b287b | |
parent | 62f7fb714cb81a73a67f640e0530373c6ec76f0a (diff) | |
download | coset-16f92bd6772396b4e48d07ee8c4f07f434f9f39e.tar.gz |
Upgrade coset to 0.3.5
This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update rust/crates/coset
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
Test: TreeHugger
Change-Id: I2a7ebac9bf1076944cae21d038afccf90fb7f7a9
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 127 | ||||
-rw-r--r-- | Android.bp | 9 | ||||
-rw-r--r-- | CHANGELOG.md | 10 | ||||
-rw-r--r-- | Cargo.lock | 14 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | Cargo.toml.orig | 4 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | src/common/mod.rs | 62 | ||||
-rw-r--r-- | src/common/tests.rs | 14 | ||||
-rw-r--r-- | src/encrypt/tests.rs | 12 | ||||
-rw-r--r-- | src/header/tests.rs | 8 | ||||
-rw-r--r-- | src/key/tests.rs | 4 | ||||
-rw-r--r-- | src/mac/tests.rs | 6 | ||||
-rw-r--r-- | src/sign/mod.rs | 155 | ||||
-rw-r--r-- | src/sign/tests.rs | 334 |
17 files changed, 621 insertions, 162 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 79d70ec..1b3ac45 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "8a8552a8b57f004d08d081230659b47578c86b66" + "sha1": "90f5513741844bd5c5e0a0a751360c7edd0e8992" }, "path_in_vcs": "" }
\ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14bc1f5..d5ff212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,17 +18,15 @@ jobs: rust: - stable - beta - - nightly-2022-01-01 + - nightly-2023-04-01 steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: ${{ matrix.rust }} components: rustfmt - override: true - - run: cargo build --release --workspace - - run: cargo build --release --workspace --features=std + - run: cargo +${{ matrix.rust }} build --release --workspace + - run: cargo +${{ matrix.rust }} build --release --workspace --features=std test: runs-on: ubuntu-latest @@ -37,19 +35,17 @@ jobs: rust: - stable - beta - - nightly-2022-01-01 + - nightly-2023-04-01 steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: submodules: true - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: ${{ matrix.rust }} components: rustfmt - override: true - - run: cargo test --workspace -- --nocapture - - run: cargo test --workspace --features=std -- --nocapture + - run: cargo +${{ matrix.rust }} test --workspace -- --nocapture + - run: cargo +${{ matrix.rust }} test --workspace --features=std -- --nocapture examples: runs-on: ubuntu-latest @@ -58,19 +54,17 @@ jobs: rust: - stable - beta - - nightly-2022-01-01 + - nightly-2023-04-01 steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: submodules: true - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: ${{ matrix.rust }} components: rustfmt - override: true - - run: cargo test --examples - - run: cargo test --features=std --examples + - run: cargo +${{ matrix.rust }} test --examples + - run: cargo +${{ matrix.rust }} test --features=std --examples no_std: name: Build for a no_std target @@ -80,17 +74,15 @@ jobs: rust: - stable - beta - - nightly-2022-01-01 + - nightly-2023-04-01 steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: ${{ matrix.rust }} components: rustfmt - target: thumbv6m-none-eabi - override: true - - run: cargo build --release --workspace --target thumbv6m-none-eabi + targets: thumbv6m-none-eabi + - run: cargo +${{ matrix.rust }} build --release --workspace --target thumbv6m-none-eabi msrv: name: Rust ${{matrix.rust}} MSRV @@ -100,108 +92,93 @@ jobs: matrix: rust: [1.56.0, 1.57.0] steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: submodules: true - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: ${{ matrix.rust }} components: rustfmt - override: true - run: rustc --version - - run: cargo build --release --workspace - - run: cargo build --release --workspace --all-features + - run: cargo +${{ matrix.rust }} build --release --workspace + - run: cargo +${{ matrix.rust }} build --release --workspace --all-features formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 - run: go install github.com/campoy/embedmd@97c13d6 - - uses: actions/setup-ruby@b007fae6f1ffbe3a51c00a6df6f5ff01184d5340 # v1 + - uses: ruby/setup-ruby@d2b39ad0b52eca07d23f3aa14fdf2a3fcc1f411c # v1.148.0 + with: + ruby-version: '2.7' - run: gem install mdl - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal - toolchain: nightly-2022-01-01 - override: true + toolchain: nightly-2023-04-01 components: rustfmt - - run: cargo fmt --all -- --check + - run: cargo +nightly-2023-04-01 fmt --all -- --check - run: scripts/check-format.sh clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal - toolchain: nightly-2022-01-01 - override: true + toolchain: stable components: rustfmt, clippy - - run: cargo clippy --all-features --all-targets + - run: cargo +stable clippy --all-features --all-targets - run: git diff --exit-code doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal toolchain: stable - run: RUSTDOCFLAGS="-Dwarnings" cargo doc --no-deps --document-private-items udeps: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal - toolchain: nightly-2022-01-01 - override: true + toolchain: nightly-2023-04-01 components: rustfmt - - uses: actions-rs/install@69ec87709ffb5b19a7b5ddbf610cb221498bb1eb # v0.1.2 - with: - crate: cargo-udeps - use-tool-cache: true - version: 0.1.25 - - run: cargo udeps + - run: cargo +nightly-2023-04-01 install --locked --version 0.1.39 cargo-udeps + - run: cargo +nightly-2023-04-01 udeps deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal - toolchain: nightly-2022-01-01 - override: true + toolchain: nightly-2023-04-01 components: rustfmt - - run: cargo install --locked --version 0.8.5 cargo-deny + - run: cargo +nightly-2023-04-01 install --locked --version 0.13.9 cargo-deny - run: cargo deny check coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: submodules: true fetch-depth: 0 - - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1 + - uses: dtolnay/rust-toolchain@a3ac054b2e7d62f514aa1bd57e3508c522fe772d # 1.68.2 with: - profile: minimal - toolchain: nightly-2022-01-01 - override: true + toolchain: nightly-2023-04-01 components: rustfmt - uses: actions-rs/install@69ec87709ffb5b19a7b5ddbf610cb221498bb1eb # v0.1.2 with: crate: cargo-tarpaulin - version: 0.20.1 + version: 0.25.2 use-tool-cache: true - - run: cargo tarpaulin --verbose --ignore-tests --all-features --timeout=600 --out Xml + - run: cargo +nightly-2023-04-01 tarpaulin --verbose --ignore-tests --all-features --timeout=600 --out Xml - name: Upload to codecov.io run: | bash <(curl -s https://codecov.io/bash) @@ -20,11 +20,10 @@ license { rust_test { name: "coset_test_src_lib", - // has rustc warnings host_supported: true, crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.4", + cargo_pkg_version: "0.3.5", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -42,11 +41,10 @@ rust_test { rust_library { name: "libcoset", - // has rustc warnings host_supported: true, crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.4", + cargo_pkg_version: "0.3.5", srcs: ["src/lib.rs"], edition: "2018", features: ["default"], @@ -64,10 +62,9 @@ rust_library { rust_library_rlib { name: "libcoset_nostd", - // has rustc warnings crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.4", + cargo_pkg_version: "0.3.5", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ diff --git a/CHANGELOG.md b/CHANGELOG.md index df2b2cf..b01d470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 0.3.5 - 2023-09-29 + +- Add helper methods to create and verify detached signatures: + - Add `CoseSignBuilder` methods `add_detached_signature` and `try_add_detached_signature`. + - Add `CoseSign` method `verify_detached_signature`. + - Add `CoseSign1Builder` methods `create_detached_signature` and `try_create_detached_signature`. + - Add `CoseSign1` method `verify_detached_signature`. +- Implement CBOR conversion traits for `ciborium::value::Value`. +- Update `ciborium` dependency. + ## 0.3.4 - 2023-01-25 - Add non-default `std` feature that turns on `impl Error for CoseError`. @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -15,15 +15,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "coset" -version = "0.3.4" +version = "0.3.5" dependencies = [ "ciborium", "ciborium-io", @@ -12,7 +12,7 @@ [package] edition = "2018" name = "coset" -version = "0.3.4" +version = "0.3.5" authors = [ "David Drysdale <drysdale@google.com>", "Paul Crowley <paulcrowley@google.com>", @@ -28,7 +28,7 @@ license = "Apache-2.0" repository = "https://github.com/google/coset" [dependencies.ciborium] -version = "^0.2.0" +version = "^0.2.1" default-features = false [dependencies.ciborium-io] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index e011996..1e35011 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "coset" -version = "0.3.4" +version = "0.3.5" authors = ["David Drysdale <drysdale@google.com>", "Paul Crowley <paulcrowley@google.com>"] edition = "2018" license = "Apache-2.0" @@ -15,7 +15,7 @@ default = [] std = [] [dependencies] -ciborium = { version = "^0.2.0", default-features = false } +ciborium = { version = "^0.2.1", default-features = false } ciborium-io = { version = "^0.2.0", features = ["alloc"] } [dev-dependencies] @@ -1,6 +1,6 @@ # This project was upgraded with external_updater. # Usage: tools/external_updater/updater.sh update rust/crates/coset -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "coset" description: "Set of types for supporting COSE" @@ -11,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/coset/coset-0.3.4.crate" + value: "https://static.crates.io/crates/coset/coset-0.3.5.crate" } - version: "0.3.4" + version: "0.3.5" license_type: NOTICE last_upgrade_date { year: 2023 - month: 2 - day: 15 + month: 9 + day: 29 } } @@ -1,7 +1,7 @@ # COSET [![Docs](https://img.shields.io/badge/docs-rust-brightgreen?style=for-the-badge)](https://google.github.io/coset) -[![CI Status](https://img.shields.io/github/workflow/status/google/coset/CI?color=blue&style=for-the-badge)](https://github.com/google/coset/actions?query=workflow%3ACI) +[![CI Status](https://img.shields.io/github/actions/workflow/status/google/coset/ci.yml?branch=main&color=blue&style=for-the-badge)](https://github.com/google/coset/actions?query=workflow%3ACI) [![codecov](https://img.shields.io/codecov/c/github/google/coset?style=for-the-badge)](https://codecov.io/gh/google/coset) This crate holds a set of Rust types for working with CBOR Object Signing and Encryption (COSE) objects, as defined in @@ -13,9 +13,13 @@ example](examples/signature.rs) for documentation on how to use the code. **This repo is under construction** and so details of the API and the code may change without warning. +## Features + +The `std` feature of the crate enables an implementation of `std::error::Error` for `CoseError`. + ## `no_std` Support -This crate supports `no_std`, but uses the `alloc` crate. +This crate supports `no_std` (when the `std` feature is not set, which is the default), but uses the `alloc` crate. ## Minimum Supported Rust Version diff --git a/src/common/mod.rs b/src/common/mod.rs index 106a3a3..b94ec31 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -57,8 +57,16 @@ pub enum CoseError { /// Crate-specific Result type pub type Result<T, E = CoseError> = core::result::Result<T, E>; -impl core::convert::From<cbor::de::Error<EndOfFile>> for CoseError { - fn from(e: cbor::de::Error<EndOfFile>) -> Self { +impl<T> core::convert::From<cbor::de::Error<T>> for CoseError { + fn from(e: cbor::de::Error<T>) -> Self { + // Make sure we use our [`EndOfFile`] marker. + use cbor::de::Error::{Io, RecursionLimitExceeded, Semantic, Syntax}; + let e = match e { + Io(_) => Io(EndOfFile), + Syntax(x) => Syntax(x), + Semantic(a, b) => Semantic(a, b), + RecursionLimitExceeded => RecursionLimitExceeded, + }; CoseError::DecodeFailed(e) } } @@ -107,40 +115,11 @@ impl CoseError { } } -/// Newtype wrapper around a byte slice to allow left-over data to be detected. -struct MeasuringReader<'a>(&'a [u8]); - -impl<'a> MeasuringReader<'a> { - fn new(buf: &'a [u8]) -> MeasuringReader<'a> { - MeasuringReader(buf) - } - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl<'a> ciborium_io::Read for &mut MeasuringReader<'a> { - type Error = EndOfFile; - - fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { - if data.len() > self.0.len() { - return Err(EndOfFile); - } - - let (prefix, suffix) = self.0.split_at(data.len()); - data.copy_from_slice(prefix); - self.0 = suffix; - Ok(()) - } -} - /// Read a CBOR [`Value`] from a byte slice, failing if any extra data remains after the `Value` has /// been read. -fn read_to_value(slice: &[u8]) -> Result<Value> { - let mut mr = MeasuringReader::new(slice); - let value = cbor::de::from_reader(&mut mr)?; - if mr.is_empty() { +fn read_to_value(mut slice: &[u8]) -> Result<Value> { + let value = cbor::de::from_reader(&mut slice)?; + if slice.is_empty() { Ok(value) } else { Err(CoseError::ExtraneousData) @@ -157,7 +136,8 @@ pub trait AsCborValue: Sized { /// Extension trait that adds serialization/deserialization methods. pub trait CborSerializable: AsCborValue { - /// Create an object instance from serialized CBOR data in a slice. + /// Create an object instance from serialized CBOR data in a slice. This method will fail (with + /// `CoseError::ExtraneousData`) if there is additional CBOR data after the object. fn from_slice(slice: &[u8]) -> Result<Self> { Self::from_cbor_value(read_to_value(slice)?) } @@ -197,6 +177,18 @@ pub trait TaggedCborSerializable: AsCborValue { } } +/// Trivial implementation of [`AsCborValue`] for [`Value`]. +impl AsCborValue for Value { + fn from_cbor_value(value: Value) -> Result<Self> { + Ok(value) + } + fn to_cbor_value(self) -> Result<Value> { + Ok(self) + } +} + +impl CborSerializable for Value {} + /// Algorithm identifier. pub type Algorithm = crate::RegisteredLabelWithPrivate<iana::Algorithm>; diff --git a/src/common/tests.rs b/src/common/tests.rs index ed6f82b..e7b59aa 100644 --- a/src/common/tests.rs +++ b/src/common/tests.rs @@ -101,7 +101,7 @@ fn test_label_sort() { #[test] fn test_label_decode_fail() { - let tests = vec![ + let tests = [ ("43010203", "expected int/tstr"), ("", "decode CBOR failure: Io(EndOfFile"), ("1e", "decode CBOR failure: Syntax"), @@ -182,7 +182,7 @@ fn test_registered_label_sort() { #[test] fn test_registered_label_decode_fail() { - let tests = vec![ + let tests = [ ("43010203", "expected int/tstr"), ("", "decode CBOR failure: Io(EndOfFile"), ("09", "expected recognized IANA value"), @@ -292,7 +292,7 @@ fn test_registered_label_with_private_sort() { #[test] fn test_registered_label_with_private_decode_fail() { - let tests = vec![ + let tests = [ ("43010203", "expected int/tstr"), ("", "decode CBOR failure: Io(EndOfFile"), ("09", "expected value in IANA or private use range"), @@ -325,7 +325,7 @@ const CBOR_INT_OUT_OF_RANGE_HEX: &str = "1b8000000000000000"; #[test] fn test_large_label_decode() { - let tests = vec![(CBOR_NINT_MIN_HEX, i64::MIN), (CBOR_INT_MAX_HEX, i64::MAX)]; + let tests = [(CBOR_NINT_MIN_HEX, i64::MIN), (CBOR_INT_MAX_HEX, i64::MAX)]; for (label_data, want) in tests.iter() { let data = hex::decode(label_data).unwrap(); let got = Label::from_slice(&data).unwrap(); @@ -335,7 +335,7 @@ fn test_large_label_decode() { #[test] fn test_large_label_decode_fail() { - let tests = vec![ + let tests = [ (CBOR_NINT_OUT_OF_RANGE_HEX, "out of range integer value"), (CBOR_INT_OUT_OF_RANGE_HEX, "out of range integer value"), ]; @@ -348,7 +348,7 @@ fn test_large_label_decode_fail() { #[test] fn test_large_registered_label_decode_fail() { - let tests = vec![ + let tests = [ (CBOR_NINT_OUT_OF_RANGE_HEX, "out of range integer value"), (CBOR_INT_OUT_OF_RANGE_HEX, "out of range integer value"), ]; @@ -361,7 +361,7 @@ fn test_large_registered_label_decode_fail() { #[test] fn test_large_registered_label_with_private_decode_fail() { - let tests = vec![ + let tests = [ (CBOR_NINT_OUT_OF_RANGE_HEX, "out of range integer value"), (CBOR_INT_OUT_OF_RANGE_HEX, "out of range integer value"), ]; diff --git a/src/encrypt/tests.rs b/src/encrypt/tests.rs index 56e24c4..997014b 100644 --- a/src/encrypt/tests.rs +++ b/src/encrypt/tests.rs @@ -71,7 +71,7 @@ fn test_cose_recipient_decode() { let mut got = CoseRecipient::from_slice(&got).unwrap(); got.protected.original_data = None; - for mut recip in &mut got.recipients { + for recip in &mut got.recipients { recip.protected.original_data = None; } assert_eq!(*recipient, got); @@ -80,7 +80,7 @@ fn test_cose_recipient_decode() { #[test] fn test_cose_recipient_decode_fail() { - let tests = vec![ + let tests = [ ( concat!( "a2", // 2-map (should be tuple) @@ -184,7 +184,7 @@ fn test_cose_encrypt_decode() { #[test] fn test_cose_encrypt_decode_fail() { - let tests = vec![ + let tests = [ ( concat!( "a2", // 2-map (should be tuple) @@ -473,10 +473,10 @@ fn test_rfc8152_cose_encrypt_decode() { let mut got = CoseEncrypt::from_tagged_slice(&got).unwrap(); got.protected.original_data = None; - for mut recip in &mut got.recipients { + for recip in &mut got.recipients { recip.protected.original_data = None; } - for mut sig in &mut got.unprotected.counter_signatures { + for sig in &mut got.unprotected.counter_signatures { sig.protected.original_data = None; } assert_eq!(*encrypt, got); @@ -518,7 +518,7 @@ fn test_cose_encrypt0_decode() { #[test] fn test_cose_encrypt0_decode_fail() { - let tests = vec![ + let tests = [ ( concat!( "a2", // 2-map (should be tuple) diff --git a/src/header/tests.rs b/src/header/tests.rs index 6ff901a..9dff115 100644 --- a/src/header/tests.rs +++ b/src/header/tests.rs @@ -153,7 +153,7 @@ fn test_header_encode() { assert_eq!(*header_data, hex::encode(&got), "case {}", i); let mut got = Header::from_slice(&got).unwrap(); - for mut sig in &mut got.counter_signatures { + for sig in &mut got.counter_signatures { sig.protected.original_data = None; } assert_eq!(*header, got); @@ -168,7 +168,7 @@ fn test_header_encode() { assert_eq!(*header_data, hex::encode(&protected_data), "case {}", i); let mut got = ProtectedHeader::from_slice(&protected_data).unwrap(); - for mut sig in &mut got.header.counter_signatures { + for sig in &mut got.header.counter_signatures { sig.protected.original_data = None; } assert!(!got.is_empty()); @@ -177,7 +177,7 @@ fn test_header_encode() { // Also try parsing as a protected header inside a `bstr` let prot_bstr_val = protected.cbor_bstr().unwrap(); let mut got = ProtectedHeader::from_cbor_bstr(prot_bstr_val).unwrap(); - for mut sig in &mut got.header.counter_signatures { + for sig in &mut got.header.counter_signatures { sig.protected.original_data = None; } assert!(!got.is_empty()); @@ -361,7 +361,7 @@ fn test_header_decode_fail() { #[test] fn test_header_decode_dup_fail() { - let tests = vec![ + let tests = [ ( concat!( "a3", // 3-map diff --git a/src/key/tests.rs b/src/key/tests.rs index 45f8eef..ada3673 100644 --- a/src/key/tests.rs +++ b/src/key/tests.rs @@ -569,7 +569,7 @@ fn test_cose_key_decode_fail() { #[test] fn test_cose_keyset_decode_fail() { - let tests = vec![( + let tests = [( concat!( "a1", // 1-map "a1", // 1-map @@ -587,7 +587,7 @@ fn test_cose_keyset_decode_fail() { #[test] fn test_cose_key_decode_dup_fail() { - let tests = vec![ + let tests = [ ( concat!( "a3", // 3-map diff --git a/src/mac/tests.rs b/src/mac/tests.rs index 9a5fffc..066e155 100644 --- a/src/mac/tests.rs +++ b/src/mac/tests.rs @@ -351,10 +351,10 @@ fn test_rfc8152_cose_mac_decode() { let mut got = CoseMac::from_tagged_slice(&got).unwrap(); got.protected.original_data = None; - for mut recip in &mut got.recipients { + for recip in &mut got.recipients { recip.protected.original_data = None; } - for mut sig in &mut got.unprotected.counter_signatures { + for sig in &mut got.unprotected.counter_signatures { sig.protected.original_data = None; } assert_eq!(*mac, got); @@ -396,7 +396,7 @@ fn test_cose_mac0_decode() { } #[test] fn test_cose_mac0_decode_fail() { - let tests = vec![ + let tests = [ ( concat!( "a2", // 2-map (should be tuple) diff --git a/src/sign/mod.rs b/src/sign/mod.rs index a6bb344..9abc6a4 100644 --- a/src/sign/mod.rs +++ b/src/sign/mod.rs @@ -142,7 +142,7 @@ impl AsCborValue for CoseSign { } impl CoseSign { - /// Verify the indidated signature value, using `verifier` on the signature value and serialized + /// Verify the indicated signature value, using `verifier` on the signature value and serialized /// data (in that order). /// /// # Panics @@ -157,6 +157,29 @@ impl CoseSign { verifier(&sig.signature, &tbs_data) } + /// Verify the indicated signature value for a detached payload, using `verifier` on the + /// signature value and serialized data (in that order). + /// + /// # Panics + /// + /// This method will panic if `which` is >= `self.signatures.len()`. + /// + /// This method will panic if `self.payload.is_some()`. + pub fn verify_detached_signature<F, E>( + &self, + which: usize, + payload: &[u8], + aad: &[u8], + verifier: F, + ) -> Result<(), E> + where + F: FnOnce(&[u8], &[u8]) -> Result<(), E>, + { + let sig = &self.signatures[which]; + let tbs_data = self.tbs_detached_data(payload, aad, sig); + verifier(&sig.signature, &tbs_data) + } + /// Construct the to-be-signed data for this object. fn tbs_data(&self, aad: &[u8], sig: &CoseSignature) -> Vec<u8> { sig_structure_data( @@ -167,6 +190,22 @@ impl CoseSign { self.payload.as_ref().unwrap_or(&vec![]), ) } + + /// Construct the to-be-signed data for this object, using a detached payload. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + fn tbs_detached_data(&self, payload: &[u8], aad: &[u8], sig: &CoseSignature) -> Vec<u8> { + assert!(self.payload.is_none()); + sig_structure_data( + SignatureContext::CoseSignature, + self.protected.clone(), + Some(sig.protected.clone()), + aad, + payload, + ) + } } /// Builder for [`CoseSign`] objects. @@ -199,6 +238,29 @@ impl CoseSignBuilder { self.add_signature(sig) } + /// Calculate the signature value for a detached payload, using `signer` to generate the + /// signature bytes that will be used to complete `sig`. Any protected header values should + /// be set before using this method. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + #[must_use] + pub fn add_detached_signature<F>( + self, + mut sig: CoseSignature, + payload: &[u8], + aad: &[u8], + signer: F, + ) -> Self + where + F: FnOnce(&[u8]) -> Vec<u8>, + { + let tbs_data = self.0.tbs_detached_data(payload, aad, &sig); + sig.signature = signer(&tbs_data); + self.add_signature(sig) + } + /// Calculate the signature value, using `signer` to generate the signature bytes that will be /// used to complete `sig`. Any protected header values should be set before using this /// method. @@ -215,6 +277,28 @@ impl CoseSignBuilder { sig.signature = signer(&tbs_data)?; Ok(self.add_signature(sig)) } + + /// Calculate the signature value for a detached payload, using `signer` to generate the + /// signature bytes that will be used to complete `sig`. Any protected header values should + /// be set before using this method. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + pub fn try_add_detached_signature<F, E>( + self, + mut sig: CoseSignature, + payload: &[u8], + aad: &[u8], + signer: F, + ) -> Result<Self, E> + where + F: FnOnce(&[u8]) -> Result<Vec<u8>, E>, + { + let tbs_data = self.0.tbs_detached_data(payload, aad, &sig); + sig.signature = signer(&tbs_data)?; + Ok(self.add_signature(sig)) + } } /// Signed payload with a single signature. @@ -283,6 +367,25 @@ impl CoseSign1 { verifier(&self.signature, &tbs_data) } + /// Verify the indicated signature value for a detached payload, using `verifier` on the + /// signature value and serialized data (in that order). + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + pub fn verify_detached_signature<F, E>( + &self, + payload: &[u8], + aad: &[u8], + verifier: F, + ) -> Result<(), E> + where + F: FnOnce(&[u8], &[u8]) -> Result<(), E>, + { + let tbs_data = self.tbs_detached_data(payload, aad); + verifier(&self.signature, &tbs_data) + } + /// Construct the to-be-signed data for this object. fn tbs_data(&self, aad: &[u8]) -> Vec<u8> { sig_structure_data( @@ -293,6 +396,22 @@ impl CoseSign1 { self.payload.as_ref().unwrap_or(&vec![]), ) } + + /// Construct the to-be-signed data for this object, using a detached payload. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + fn tbs_detached_data(&self, payload: &[u8], aad: &[u8]) -> Vec<u8> { + assert!(self.payload.is_none()); + sig_structure_data( + SignatureContext::CoseSign1, + self.protected.clone(), + None, + aad, + payload, + ) + } } /// Builder for [`CoseSign1`] objects. @@ -317,6 +436,21 @@ impl CoseSign1Builder { self.signature(sig_data) } + /// Calculate the signature value for a detached payload, using `signer` to generate the + /// signature bytes. Any protected header values should be set before using this method. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + #[must_use] + pub fn create_detached_signature<F>(self, payload: &[u8], aad: &[u8], signer: F) -> Self + where + F: FnOnce(&[u8]) -> Vec<u8>, + { + let sig_data = signer(&self.0.tbs_detached_data(payload, aad)); + self.signature(sig_data) + } + /// Calculate the signature value, using `signer` to generate the signature bytes. Any /// protected header values should be set before using this method. pub fn try_create_signature<F, E>(self, aad: &[u8], signer: F) -> Result<Self, E> @@ -326,6 +460,25 @@ impl CoseSign1Builder { let sig_data = signer(&self.0.tbs_data(aad))?; Ok(self.signature(sig_data)) } + + /// Calculate the signature value for a detached payload, using `signer` to generate the + /// signature bytes. Any protected header values should be set before using this method. + /// + /// # Panics + /// + /// This method will panic if `self.payload.is_some()`. + pub fn try_create_detached_signature<F, E>( + self, + payload: &[u8], + aad: &[u8], + signer: F, + ) -> Result<Self, E> + where + F: FnOnce(&[u8]) -> Result<Vec<u8>, E>, + { + let sig_data = signer(&self.0.tbs_detached_data(payload, aad))?; + Ok(self.signature(sig_data)) + } } /// Possible signature contexts. diff --git a/src/sign/tests.rs b/src/sign/tests.rs index d802b90..a35c14c 100644 --- a/src/sign/tests.rs +++ b/src/sign/tests.rs @@ -381,7 +381,7 @@ fn test_cose_sign_encode() { let mut got = CoseSign::from_slice(&got).unwrap(); got.protected.original_data = None; - for mut sig in &mut got.signatures { + for sig in &mut got.signatures { sig.protected.original_data = None; } assert_eq!(*sign, got); @@ -393,7 +393,7 @@ fn test_cose_sign_encode() { let mut got = CoseSign::from_tagged_slice(&got).unwrap(); got.protected.original_data = None; - for mut sig in &mut got.signatures { + for sig in &mut got.signatures { sig.protected.original_data = None; } assert_eq!(*sign, got); @@ -751,10 +751,10 @@ fn test_rfc8152_cose_sign_decode() { let mut got = CoseSign::from_tagged_slice(&got).unwrap(); got.protected.original_data = None; - for mut sig in &mut got.signatures { + for sig in &mut got.signatures { sig.protected.original_data = None; } - for mut sig in &mut got.unprotected.counter_signatures { + for sig in &mut got.unprotected.counter_signatures { sig.protected.original_data = None; } assert_eq!(*sign, got); @@ -1178,6 +1178,65 @@ fn test_sign_roundtrip() { } #[test] +fn test_sign_detached_roundtrip() { + let signer = FakeSigner {}; + let verifier = signer; + + let pt = b"This is the content"; + let aad = b"this is additional data"; + + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .key_id(b"11".to_vec()) + .build(); + let sign = CoseSignBuilder::new() + .protected(protected.clone()) + .add_detached_signature( + CoseSignatureBuilder::new().protected(protected).build(), + pt, + aad, + |pt| signer.sign(pt), + ) + .build(); + + let sign_data = sign.to_vec().unwrap(); + let mut sign = CoseSign::from_slice(&sign_data).unwrap(); + + assert!(sign + .verify_detached_signature(0, pt, aad, |sig, data| verifier.verify(sig, data)) + .is_ok()); + + // Changing an unprotected header leaves the signature valid. + sign.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned())); + assert!(sign + .verify_detached_signature(0, pt, aad, |sig, data| verifier.verify(sig, data)) + .is_ok()); + + // Providing a different `payload` means the signature won't validate. + assert!(sign + .verify_detached_signature(0, b"not payload", aad, |sig, data| verifier + .verify(sig, data)) + .is_err()); + + // Providing a different `aad` means the signature won't validate. + assert!(sign + .verify_detached_signature(0, pt, b"not aad", |sig, data| verifier.verify(sig, data)) + .is_err()); + + // Changing a protected header invalidates the signature. + let mut sign2 = sign.clone(); + sign2.protected = ProtectedHeader::default(); + assert!(sign2 + .verify_detached_signature(0, pt, aad, |sig, data| verifier.verify(sig, data)) + .is_err()); + let mut sign3 = sign; + sign3.signatures[0].protected = ProtectedHeader::default(); + assert!(sign2 + .verify_detached_signature(0, pt, aad, |sig, data| verifier.verify(sig, data)) + .is_err()); +} + +#[test] fn test_sign_noncanonical() { let signer = FakeSigner {}; let verifier = signer; @@ -1289,6 +1348,41 @@ fn test_sign_create_result() { } #[test] +fn test_sign_detached_create_result() { + let signer = FakeSigner {}; + + let pt = b"This is the content"; + let aad = b"this is additional data"; + + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .key_id(b"11".to_vec()) + .build(); + let _sign = CoseSignBuilder::new() + .protected(protected.clone()) + .try_add_detached_signature( + CoseSignatureBuilder::new() + .protected(protected.clone()) + .build(), + pt, + aad, + |pt| signer.try_sign(pt), + ) + .unwrap() + .build(); + + let result = CoseSignBuilder::new() + .protected(protected.clone()) + .try_add_detached_signature( + CoseSignatureBuilder::new().protected(protected).build(), + pt, + aad, + |pt| signer.fail_sign(pt), + ); + expect_err(result, "failed"); +} + +#[test] #[should_panic] fn test_sign_sig_index_invalid() { let signer = FakeSigner {}; @@ -1316,6 +1410,167 @@ fn test_sign_sig_index_invalid() { } #[test] +#[should_panic] +fn test_sign_detached_sig_index_invalid() { + let signer = FakeSigner {}; + let verifier = signer; + + let pt = b"This is the content"; + let aad = b"this is additional data"; + + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .key_id(b"11".to_vec()) + .build(); + let sign = CoseSignBuilder::new() + .protected(protected) + .add_detached_signature(CoseSignatureBuilder::new().build(), pt, aad, |pt| { + signer.sign(pt) + }) + .build(); + + // Attempt to verify an out-of-range signature + let _ = sign.verify_detached_signature(sign.signatures.len(), pt, aad, |sig, data| { + verifier.verify(sig, data) + }); +} + +#[test] +#[should_panic] +fn test_sign1_create_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + // Attempt to create a detached signature for a message with an embedded payload + let _ = CoseSign1Builder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .payload(payload.to_vec()) + .create_detached_signature(payload, aad, |pt| signer.sign(pt)) + .build(); +} + +#[test] +#[should_panic] +fn test_sign1_try_create_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + // Attempt to create a detached signature for a message with an embedded payload + let _ = CoseSign1Builder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .payload(payload.to_vec()) + .try_create_detached_signature(payload, aad, |pt| Ok::<Vec<u8>, String>(signer.sign(pt))) + .unwrap() + .build(); +} + +#[test] +#[should_panic] +fn test_sign1_verify_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + let mut sign1 = CoseSign1Builder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .create_detached_signature(payload, aad, |pt| signer.sign(pt)) + .build(); + + // Attempt to verify a detached signature for a message with an embedded payload + sign1.payload = Some(payload.to_vec()); + sign1 + .verify_detached_signature(payload, aad, |sig, data| signer.verify(sig, data)) + .unwrap() +} + +#[test] +#[should_panic] +fn test_sign_add_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + // Attempt to add a detached signature to a message with an embedded payload + let _ = CoseSignBuilder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .payload(payload.to_vec()) + .add_detached_signature(CoseSignatureBuilder::new().build(), payload, aad, |pt| { + signer.sign(pt) + }) + .build(); +} + +#[test] +#[should_panic] +fn test_sign_try_add_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + // Attempt to create a detached signature for a message with an embedded payload + let _ = CoseSignBuilder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .payload(payload.to_vec()) + .try_add_detached_signature(CoseSignatureBuilder::new().build(), payload, aad, |pt| { + Ok::<Vec<u8>, String>(signer.sign(pt)) + }) + .unwrap() + .build(); +} + +#[test] +#[should_panic] +fn test_sign_verify_detached_signature_embeddedpayload() { + let signer = FakeSigner {}; + + let payload = b"this is the content"; + let aad = b"this is additional data"; + + let mut sign = CoseSignBuilder::new() + .protected( + HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .build(), + ) + .add_detached_signature(CoseSignatureBuilder::new().build(), payload, aad, |pt| { + signer.sign(pt) + }) + .build(); + + // Attempt to verify a detached signature for a message with an embedded payload + sign.payload = Some(payload.to_vec()); + sign.verify_detached_signature(0, payload, aad, |sig, data| signer.verify(sig, data)) + .unwrap() +} + +#[test] fn test_sign1_roundtrip() { let signer = FakeSigner {}; let verifier = signer; @@ -1360,6 +1615,54 @@ fn test_sign1_roundtrip() { } #[test] +fn test_sign1_detached_roundtrip() { + let signer = FakeSigner {}; + let verifier = signer; + + let pt = b"This is the content"; + let aad = b"this is additional data"; + + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .key_id(b"11".to_vec()) + .build(); + let sign1 = CoseSign1Builder::new() + .protected(protected) + .create_detached_signature(pt, aad, |pt| signer.sign(pt)) + .build(); + + let sign1_data = sign1.to_vec().unwrap(); + let mut sign1 = CoseSign1::from_slice(&sign1_data).unwrap(); + + assert!(sign1 + .verify_detached_signature(pt, aad, |sig, data| verifier.verify(sig, data)) + .is_ok()); + + // Changing an unprotected header leaves the signature valid. + sign1.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned())); + assert!(sign1 + .verify_detached_signature(pt, aad, |sig, data| verifier.verify(sig, data)) + .is_ok()); + + // Providing a different 'payload' means the signature won't validate. + assert!(sign1 + .verify_detached_signature(b"not payload", aad, |sig, data| verifier.verify(sig, data)) + .is_err()); + + // Providing a different `aad` means the signature won't validate. + assert!(sign1 + .verify_detached_signature(pt, b"not aad", |sig, data| verifier.verify(sig, data)) + .is_err()); + + // Changing a protected header invalidates the signature. + sign1.protected.original_data = None; + sign1.protected.header.content_type = Some(ContentType::Text("text/plain".to_owned())); + assert!(sign1 + .verify_detached_signature(pt, aad, |sig, data| verifier.verify(sig, data)) + .is_err()); +} + +#[test] fn test_sign1_create_result() { let signer = FakeSigner {}; @@ -1385,6 +1688,29 @@ fn test_sign1_create_result() { } #[test] +fn test_sign1_create_detached_result() { + let signer = FakeSigner {}; + + let pt = b"This is the content"; + let aad = b"this is additional data"; + + let protected = HeaderBuilder::new() + .algorithm(iana::Algorithm::ES256) + .key_id(b"11".to_vec()) + .build(); + let _sign = CoseSign1Builder::new() + .protected(protected.clone()) + .try_create_detached_signature(pt, aad, |pt| signer.try_sign(pt)) + .unwrap() + .build(); + + let result = CoseSign1Builder::new() + .protected(protected) + .try_create_detached_signature(pt, aad, |pt| signer.fail_sign(pt)); + expect_err(result, "failed"); +} + +#[test] fn test_sign1_noncanonical() { let signer = FakeSigner {}; let verifier = signer; |