blob: d9452440349ddc9b16578aab2afab5492efad0e2 [file] [log] [blame]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
//! COSE_Sign* functionality.
use crate::{
cbor,
cbor::value::Value,
iana,
util::{cbor_type_error, to_cbor_array, AsCborValue, ValueTryAs},
CoseError, Header, ProtectedHeader, Result,
};
use alloc::{borrow::ToOwned, vec, vec::Vec};
#[cfg(test)]
mod tests;
/// Structure representing a cryptographic signature.
///
/// ```cddl
/// COSE_Signature = [
/// Headers,
/// signature : bstr
/// ]
/// ```
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CoseSignature {
pub protected: ProtectedHeader,
pub unprotected: Header,
pub signature: Vec<u8>,
}
impl crate::CborSerializable for CoseSignature {}
impl AsCborValue for CoseSignature {
fn from_cbor_value(value: Value) -> Result<Self> {
let mut a = value.try_as_array()?;
if a.len() != 3 {
return Err(CoseError::UnexpectedItem("array", "array with 3 items"));
}
// Remove array elements in reverse order to avoid shifts.
Ok(Self {
signature: a.remove(2).try_as_bytes()?,
unprotected: Header::from_cbor_value(a.remove(1))?,
protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
})
}
fn to_cbor_value(self) -> Result<Value> {
Ok(Value::Array(vec![
self.protected.cbor_bstr()?,
self.unprotected.to_cbor_value()?,
Value::Bytes(self.signature),
]))
}
}
/// Builder for [`CoseSignature`] objects.
#[derive(Debug, Default)]
pub struct CoseSignatureBuilder(CoseSignature);
impl CoseSignatureBuilder {
builder! {CoseSignature}
builder_set_protected! {protected}
builder_set! {unprotected: Header}
builder_set! {signature: Vec<u8>}
}
/// Signed payload with signatures.
///
/// ```cdl
/// COSE_Sign = [
/// Headers,
/// payload : bstr / nil,
/// signatures : [+ COSE_Signature]
/// ]
/// ```
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CoseSign {
pub protected: ProtectedHeader,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
pub signatures: Vec<CoseSignature>,
}
impl crate::CborSerializable for CoseSign {}
impl crate::TaggedCborSerializable for CoseSign {
const TAG: u64 = iana::CborTag::CoseSign as u64;
}
impl AsCborValue for CoseSign {
fn from_cbor_value(value: Value) -> Result<Self> {
let mut a = value.try_as_array()?;
if a.len() != 4 {
return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
}
// Remove array elements in reverse order to avoid shifts.
let signatures = a.remove(3).try_as_array_then_convert(|v| {
CoseSignature::from_cbor_value(v)
.map_err(|_e| CoseError::UnexpectedItem("non-signature", "map for COSE_Signature"))
})?;
Ok(Self {
signatures,
payload: match a.remove(2) {
Value::Bytes(b) => Some(b),
Value::Null => None,
v => return cbor_type_error(&v, "bstr or nil"),
},
unprotected: Header::from_cbor_value(a.remove(1))?,
protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
})
}
fn to_cbor_value(self) -> Result<Value> {
Ok(Value::Array(vec![
self.protected.cbor_bstr()?,
self.unprotected.to_cbor_value()?,
match self.payload {
Some(b) => Value::Bytes(b),
None => Value::Null,
},
to_cbor_array(self.signatures)?,
]))
}
}
impl CoseSign {
/// Verify the indidated signature value, using `verifier` on the signature value and serialized
/// data (in that order).
///
/// # Panics
///
/// This method will panic if `which` is >= `self.signatures.len()`.
pub fn verify_signature<F, E>(&self, which: usize, aad: &[u8], verifier: F) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
{
let sig = &self.signatures[which];
let tbs_data = self.tbs_data(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(
SignatureContext::CoseSignature,
self.protected.clone(),
Some(sig.protected.clone()),
aad,
self.payload.as_ref().unwrap_or(&vec![]),
)
}
}
/// Builder for [`CoseSign`] objects.
#[derive(Debug, Default)]
pub struct CoseSignBuilder(CoseSign);
impl CoseSignBuilder {
builder! {CoseSign}
builder_set_protected! {protected}
builder_set! {unprotected: Header}
builder_set_optional! {payload: Vec<u8>}
/// Add a signature value.
#[must_use]
pub fn add_signature(mut self, sig: CoseSignature) -> Self {
self.0.signatures.push(sig);
self
}
/// 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.
#[must_use]
pub fn add_created_signature<F>(self, mut sig: CoseSignature, aad: &[u8], signer: F) -> Self
where
F: FnOnce(&[u8]) -> Vec<u8>,
{
let tbs_data = self.0.tbs_data(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.
pub fn try_add_created_signature<F, E>(
self,
mut sig: CoseSignature,
aad: &[u8],
signer: F,
) -> Result<Self, E>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
{
let tbs_data = self.0.tbs_data(aad, &sig);
sig.signature = signer(&tbs_data)?;
Ok(self.add_signature(sig))
}
}
/// Signed payload with a single signature.
///
/// ```cddl
/// COSE_Sign1 = [
/// Headers,
/// payload : bstr / nil,
/// signature : bstr
/// ]
/// ```
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CoseSign1 {
pub protected: ProtectedHeader,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
pub signature: Vec<u8>,
}
impl crate::CborSerializable for CoseSign1 {}
impl crate::TaggedCborSerializable for CoseSign1 {
const TAG: u64 = iana::CborTag::CoseSign1 as u64;
}
impl AsCborValue for CoseSign1 {
fn from_cbor_value(value: Value) -> Result<Self> {
let mut a = value.try_as_array()?;
if a.len() != 4 {
return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
}
// Remove array elements in reverse order to avoid shifts.
Ok(Self {
signature: a.remove(3).try_as_bytes()?,
payload: match a.remove(2) {
Value::Bytes(b) => Some(b),
Value::Null => None,
v => return cbor_type_error(&v, "bstr or nil"),
},
unprotected: Header::from_cbor_value(a.remove(1))?,
protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
})
}
fn to_cbor_value(self) -> Result<Value> {
Ok(Value::Array(vec![
self.protected.cbor_bstr()?,
self.unprotected.to_cbor_value()?,
match self.payload {
Some(b) => Value::Bytes(b),
None => Value::Null,
},
Value::Bytes(self.signature),
]))
}
}
impl CoseSign1 {
/// Verify the signature value, using `verifier` on the signature value and serialized data (in
/// that order).
pub fn verify_signature<F, E>(&self, aad: &[u8], verifier: F) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
{
let tbs_data = self.tbs_data(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(
SignatureContext::CoseSign1,
self.protected.clone(),
None,
aad,
self.payload.as_ref().unwrap_or(&vec![]),
)
}
}
/// Builder for [`CoseSign1`] objects.
#[derive(Debug, Default)]
pub struct CoseSign1Builder(CoseSign1);
impl CoseSign1Builder {
builder! {CoseSign1}
builder_set_protected! {protected}
builder_set! {unprotected: Header}
builder_set! {signature: Vec<u8>}
builder_set_optional! {payload: Vec<u8>}
/// Calculate the signature value, using `signer` to generate the signature bytes. Any
/// protected header values should be set before using this method.
#[must_use]
pub fn create_signature<F>(self, aad: &[u8], signer: F) -> Self
where
F: FnOnce(&[u8]) -> Vec<u8>,
{
let sig_data = signer(&self.0.tbs_data(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>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
{
let sig_data = signer(&self.0.tbs_data(aad))?;
Ok(self.signature(sig_data))
}
}
/// Possible signature contexts.
#[derive(Clone, Copy)]
pub enum SignatureContext {
CoseSignature,
CoseSign1,
CounterSignature,
}
impl SignatureContext {
/// Return the context string as per RFC 8152 section 4.4.
fn text(&self) -> &'static str {
match self {
SignatureContext::CoseSignature => "Signature",
SignatureContext::CoseSign1 => "Signature1",
SignatureContext::CounterSignature => "CounterSignature",
}
}
}
/// Create a binary blob that will be signed.
///
/// ```cddl
/// Sig_structure = [
/// context : "Signature" / "Signature1" / "CounterSignature",
/// body_protected : empty_or_serialized_map,
/// ? sign_protected : empty_or_serialized_map,
/// external_aad : bstr,
/// payload : bstr
/// ]
/// ```
pub fn sig_structure_data(
context: SignatureContext,
body: ProtectedHeader,
sign: Option<ProtectedHeader>,
aad: &[u8],
payload: &[u8],
) -> Vec<u8> {
let mut arr = vec![
Value::Text(context.text().to_owned()),
body.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
];
if let Some(sign) = sign {
arr.push(sign.cbor_bstr().expect("failed to serialize header")); // safe: always
// serializable
}
arr.push(Value::Bytes(aad.to_vec()));
arr.push(Value::Bytes(payload.to_vec()));
let mut data = Vec::new();
cbor::ser::into_writer(&Value::Array(arr), &mut data).unwrap(); // safe: always serializable
data
}