tink_mac/
factory.rs

1// Copyright 2020 The Tink-Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17//! Provides an implementation of MAC using a set of underlying implementations.
18
19use std::sync::Arc;
20use tink_core::{utils::wrap_err, TinkError};
21use tink_proto::OutputPrefixType;
22
23const MAX_INT: usize = usize::MAX >> 1;
24
25/// Create a [`tink_core::Mac`] primitive from the given keyset handle.
26pub fn new(h: &tink_core::keyset::Handle) -> Result<Box<dyn tink_core::Mac>, TinkError> {
27    new_with_key_manager(h, None)
28}
29
30/// Create a [`tink_core::Mac`] primitive from the given keyset handle and a custom key manager.
31fn new_with_key_manager(
32    h: &tink_core::keyset::Handle,
33    km: Option<Arc<dyn tink_core::registry::KeyManager>>,
34) -> Result<Box<dyn tink_core::Mac>, TinkError> {
35    let ps = h
36        .primitives_with_key_manager(km)
37        .map_err(|e| wrap_err("mac::factory: cannot obtain primitive set", e))?;
38
39    let ret = WrappedMac::new(ps)?;
40    Ok(Box::new(ret))
41}
42
43/// A [`tink_core::Mac`] implementation that uses the underlying primitive set to compute and
44/// verify MACs.
45#[derive(Clone)]
46struct WrappedMac {
47    ps: tink_core::primitiveset::TypedPrimitiveSet<Box<dyn tink_core::Mac>>,
48}
49
50impl WrappedMac {
51    fn new(ps: tink_core::primitiveset::PrimitiveSet) -> Result<WrappedMac, TinkError> {
52        let entry = match &ps.primary {
53            None => return Err("mac::factory: no primary primitive".into()),
54            Some(p) => p,
55        };
56        match entry.primitive {
57            tink_core::Primitive::Mac(_) => {}
58            _ => return Err("mac::factory: not a Mac primitive".into()),
59        };
60        for (_, primitives) in ps.entries.iter() {
61            for p in primitives {
62                match p.primitive {
63                    tink_core::Primitive::Mac(_) => {}
64                    _ => return Err("mac::factory: not a Mac primitive".into()),
65                };
66            }
67        }
68        // The `.into()` call is only safe because we've just checked that all entries have
69        // the right type of primitive
70        Ok(WrappedMac { ps: ps.into() })
71    }
72}
73
74impl tink_core::Mac for WrappedMac {
75    fn compute_mac(&self, data: &[u8]) -> Result<Vec<u8>, TinkError> {
76        let primary = match &self.ps.primary {
77            Some(p) => p,
78            None => return Err("mac::factory: no primary primitive".into()),
79        };
80        let mac = if primary.prefix_type == OutputPrefixType::Legacy {
81            if data.len() >= MAX_INT {
82                return Err("mac::factory: data too long".into());
83            }
84            let mut local_data = Vec::with_capacity(data.len() + 1);
85            local_data.extend_from_slice(data);
86            local_data.push(0u8);
87            primary.primitive.compute_mac(&local_data)?
88        } else {
89            primary.primitive.compute_mac(data)?
90        };
91
92        let mut ret = Vec::with_capacity(primary.prefix.len() + mac.len());
93        ret.extend_from_slice(&primary.prefix);
94        ret.extend_from_slice(&mac);
95        Ok(ret)
96    }
97
98    fn verify_mac(&self, mac: &[u8], data: &[u8]) -> Result<(), TinkError> {
99        // This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are
100        // clearly insecure, thus should be discouraged.
101        let prefix_size = tink_core::cryptofmt::NON_RAW_PREFIX_SIZE;
102        if mac.len() <= prefix_size {
103            return Err("mac::factory: invalid mac".into());
104        }
105
106        // try non raw keys
107        let prefix = &mac[..prefix_size];
108        let mac_no_prefix = &mac[prefix_size..];
109        if let Some(entries) = self.ps.entries_for_prefix(prefix) {
110            for entry in entries {
111                let result = if entry.prefix_type == OutputPrefixType::Legacy {
112                    if data.len() >= MAX_INT {
113                        return Err("mac::factory: data too long".into());
114                    }
115                    let mut local_data = Vec::with_capacity(data.len() + 1);
116                    local_data.extend_from_slice(data);
117                    local_data.push(0u8);
118                    entry.primitive.verify_mac(mac_no_prefix, &local_data)
119                } else {
120                    entry.primitive.verify_mac(mac_no_prefix, data)
121                };
122                if result.is_ok() {
123                    return Ok(());
124                }
125            }
126        }
127
128        if let Some(entries) = self.ps.raw_entries() {
129            for entry in entries {
130                let result = if entry.prefix_type == OutputPrefixType::Legacy {
131                    // This diverges from the upstream Go code (as of v1.5.0), but matches the
132                    // behaviour of the upstream C++/Java/Python code.
133                    let mut local_data = Vec::with_capacity(data.len() + 1);
134                    local_data.extend_from_slice(data);
135                    local_data.push(tink_core::cryptofmt::LEGACY_START_BYTE);
136                    entry.primitive.verify_mac(mac, &local_data)
137                } else {
138                    entry.primitive.verify_mac(mac, data)
139                };
140                if result.is_ok() {
141                    return Ok(());
142                }
143            }
144        }
145
146        // nothing worked
147        Err("mac::factory: decryption failed".into())
148    }
149}