Skip to content

Commit 2efaf5f

Browse files
committed
rust: add tink-mac crate
Adapted from go/mac/*.
1 parent 7de5b9d commit 2efaf5f

20 files changed

+2058
-4
lines changed

rust/Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
[workspace]
2-
# members = ["daead", "mac", "prf", "testutil", "tink"]
3-
members = ["daead", "prf", "testutil", "tink"]
2+
members = ["daead", "mac", "prf", "testutil", "tink"]
43

54
# Patch dependencies on tink crates so that they refer to the versions within this same repository.
65
[patch.crates-io]
76
tink = { path = "tink" }
87
tink-daead = { path = "daead" }
9-
# tink-mac = { path = "mac" }
8+
tink-mac = { path = "mac" }
109
tink-prf = { path = "prf" }
1110
tink-testutil = { path = "testutil" }

rust/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Go packages and the equivalent Rust crates and modules, when available.
5353
| | `aead` |
5454
| `tink-daead` | `daead` |
5555
| | `hybrid` |
56-
| | `mac` |
56+
| `tink-mac` | `mac` |
5757
| `tink-prf` | `prf` |
5858
| | `signature` |
5959
| | `streamingaead` |

rust/mac/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "tink-mac"
3+
version = "0.1.0"
4+
authors = ["David Drysdale <drysdale@google.com>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
prost = "*"
9+
tink = "*"
10+
tink-prf = "*"
11+
12+
[dev-dependencies]
13+
hex = "*"
14+
lazy_static = "*"
15+
maplit = "*"
16+
serde = { version = "*", features = ["derive"] }
17+
serde_json = "*"
18+
tink-prf = "*"
19+
tink-testutil = "*"

rust/mac/src/aes_cmac_key_manager.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Licensed under the Apache License, Version 2.0 (the "License");
2+
// you may not use this file except in compliance with the License.
3+
// You may obtain a copy of the License at
4+
//
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
//
13+
////////////////////////////////////////////////////////////////////////////////
14+
15+
use prost::Message;
16+
use tink::{utils::wrap_err, TinkError};
17+
18+
/// Maximal version of AES-CMAC keys.
19+
pub const CMAC_KEY_VERSION: u32 = 0;
20+
/// Type URL of AES-CMAC keys that Tink supports.
21+
pub const CMAC_TYPE_URL: &str = "type.googleapis.com/google.crypto.tink.AesCmacKey";
22+
23+
/// Generates new AES-CMAC keys and produces new instances of AES-CMAC.
24+
pub(crate) struct AesCmacKeyManager;
25+
26+
impl AesCmacKeyManager {
27+
pub fn new() -> Self {
28+
Self
29+
}
30+
}
31+
32+
impl Default for AesCmacKeyManager {
33+
fn default() -> Self {
34+
Self::new()
35+
}
36+
}
37+
38+
impl tink::registry::KeyManager for AesCmacKeyManager {
39+
/// Create an [`AesCmac`](crate::subtle::AesCmac) instance for the given serialized
40+
/// [`AesCmacKey`](tink:;proto::AesCmacKey) proto.
41+
fn primitive(&self, serialized_key: &[u8]) -> Result<tink::Primitive, TinkError> {
42+
if serialized_key.is_empty() {
43+
return Err("AesCmacKeyManager: invalid key".into());
44+
}
45+
46+
let key = tink::proto::AesCmacKey::decode(serialized_key)
47+
.map_err(|e| wrap_err("decode failed", e))?;
48+
validate_key(&key)?;
49+
match crate::subtle::AesCmac::new(&key.key_value, key.params.unwrap().tag_size as usize) {
50+
Ok(p) => Ok(tink::Primitive::Mac(std::sync::Arc::new(p))),
51+
Err(e) => Err(wrap_err(
52+
"AesCmacKeyManager: cannot create new primitive",
53+
e,
54+
)),
55+
}
56+
}
57+
58+
/// Generate a new serialized [`AesCmacKey`](tink::proto::AesCmacKey) according to
59+
/// specification in the given [`AesCmacKeyFormat`](tink::proto::AesCmacKeyFormat).
60+
fn new_key(&self, serialized_key_format: &[u8]) -> Result<Vec<u8>, TinkError> {
61+
if serialized_key_format.is_empty() {
62+
return Err("AesCmacKeyManager: invalid key format".into());
63+
}
64+
let key_format = tink::proto::AesCmacKeyFormat::decode(serialized_key_format)
65+
.map_err(|_| TinkError::new("AesCmacKeyManager: invalid key format"))?;
66+
validate_key_format(&key_format)
67+
.map_err(|e| wrap_err("AesCmacKeyManager: invalid key format", e))?;
68+
let key_value = tink::subtle::random::get_random_bytes(key_format.key_size as usize);
69+
let mut sk = Vec::new();
70+
tink::proto::AesCmacKey {
71+
version: CMAC_KEY_VERSION,
72+
params: key_format.params,
73+
key_value,
74+
}
75+
.encode(&mut sk)
76+
.map_err(|e| wrap_err("Failed to encode new key", e))?;
77+
Ok(sk)
78+
}
79+
80+
fn does_support(&self, type_url: &str) -> bool {
81+
type_url == CMAC_TYPE_URL
82+
}
83+
84+
fn type_url(&self) -> String {
85+
CMAC_TYPE_URL.to_string()
86+
}
87+
88+
/// Create a new [`KeyData`](tink::proto::KeyData) according to the specification in the given
89+
/// serialized [`AesCmacKeyFormat`](tink::proto::AesCmacKeyFormat). This should be used
90+
/// solely by the key management API.
91+
fn new_key_data(
92+
&self,
93+
serialized_key_format: &[u8],
94+
) -> Result<tink::proto::KeyData, TinkError> {
95+
let serialized_key = self.new_key(serialized_key_format)?;
96+
97+
Ok(tink::proto::KeyData {
98+
type_url: CMAC_TYPE_URL.to_string(),
99+
value: serialized_key,
100+
key_material_type: tink::proto::key_data::KeyMaterialType::Symmetric as i32,
101+
})
102+
}
103+
}
104+
105+
/// Validate the given [`AesCmacKey`](tink::proto::AesCmacKey). It only validates the version of the
106+
/// key because other parameters will be validated in primitive construction.
107+
fn validate_key(key: &tink::proto::AesCmacKey) -> Result<(), TinkError> {
108+
tink::keyset::validate_key_version(key.version, CMAC_KEY_VERSION)
109+
.map_err(|e| wrap_err("AesCmacKeyManager: invalid version", e))?;
110+
let key_size = key.key_value.len();
111+
match &key.params {
112+
None => Err("missing AES-CMAC params".into()),
113+
Some(params) => crate::subtle::validate_cmac_params(key_size, params.tag_size as usize),
114+
}
115+
}
116+
117+
/// Validate the given [`AesCmacKeyFormat`](tink::proto::AesCmacKeyFormat).
118+
fn validate_key_format(format: &tink::proto::AesCmacKeyFormat) -> Result<(), TinkError> {
119+
match &format.params {
120+
None => Err("missing AES-CMAC params".into()),
121+
Some(params) => {
122+
crate::subtle::validate_cmac_params(format.key_size as usize, params.tag_size as usize)
123+
}
124+
}
125+
}

rust/mac/src/factory.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Licensed under the Apache License, Version 2.0 (the "License");
2+
// you may not use this file except in compliance with the License.
3+
// You may obtain a copy of the License at
4+
//
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
//
13+
////////////////////////////////////////////////////////////////////////////////
14+
15+
use std::sync::Arc;
16+
use tink::{utils::wrap_err, TinkError};
17+
18+
/// Create a [`tink::Mac`] primitive from the given keyset handle.
19+
pub fn new(h: &tink::keyset::Handle) -> Result<Box<dyn tink::Mac>, TinkError> {
20+
new_with_key_manager(h, None)
21+
}
22+
23+
/// Create a [`tink::Mac`] primitive from the given keyset handle and a custom key manager.
24+
pub fn new_with_key_manager(
25+
h: &tink::keyset::Handle,
26+
km: Option<Arc<dyn tink::registry::KeyManager>>,
27+
) -> Result<Box<dyn tink::Mac>, TinkError> {
28+
let ps = h
29+
.primitives_with_key_manager(km)
30+
.map_err(|e| wrap_err("mac::factory: cannot obtain primitive set", e))?;
31+
32+
let ret = WrappedMac::new(ps)?;
33+
Ok(Box::new(ret))
34+
}
35+
36+
/// A [`tink::Mac`] implementation that uses the underlying primitive set to compute and
37+
/// verify MACs.
38+
struct WrappedMac {
39+
ps: tink::primitiveset::PrimitiveSet,
40+
}
41+
42+
impl WrappedMac {
43+
fn new(ps: tink::primitiveset::PrimitiveSet) -> Result<WrappedMac, TinkError> {
44+
let primary = &ps.primary;
45+
if primary.is_none() {
46+
return Err("mac::factory: no primary primitive".into());
47+
}
48+
match primary.as_ref().unwrap().primitive {
49+
tink::Primitive::Mac(_) => {}
50+
_ => return Err("mac::factory: not a Mac primitive".into()),
51+
};
52+
for (_, primitives) in ps.entries.iter() {
53+
for p in primitives {
54+
match p.primitive {
55+
tink::Primitive::Mac(_) => {}
56+
_ => return Err("mac::factory: not a Mac primitive".into()),
57+
};
58+
}
59+
}
60+
Ok(WrappedMac { ps })
61+
}
62+
}
63+
64+
impl tink::Mac for WrappedMac {
65+
fn compute_mac(&self, data: &[u8]) -> Result<Vec<u8>, TinkError> {
66+
let primary = match &self.ps.primary {
67+
Some(p) => p,
68+
None => return Err("mac::factory: no primary primitive".into()),
69+
};
70+
let primitive = match &primary.primitive {
71+
tink::Primitive::Mac(p) => p,
72+
_ => return Err("mac::factory: not a Mac primitive".into()),
73+
};
74+
let mac = primitive.compute_mac(data)?;
75+
76+
let mut ret = Vec::with_capacity(primary.prefix.len() + mac.len());
77+
ret.extend_from_slice(&primary.prefix);
78+
ret.extend_from_slice(&mac);
79+
Ok(ret)
80+
}
81+
82+
fn verify_mac(&self, mac: &[u8], data: &[u8]) -> Result<(), TinkError> {
83+
// This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are
84+
// clearly insecure, thus should be discouraged.
85+
let prefix_size = tink::cryptofmt::NON_RAW_PREFIX_SIZE;
86+
if mac.len() <= prefix_size {
87+
return Err("mac::factory: invalid mac".into());
88+
}
89+
90+
// try non raw keys
91+
let prefix = &mac[..prefix_size];
92+
let mac_no_prefix = &mac[prefix_size..];
93+
let entries = self.ps.entries_for_prefix(&prefix);
94+
for entry in &entries {
95+
if let tink::Primitive::Mac(p) = &entry.primitive {
96+
if p.verify_mac(mac_no_prefix, data).is_ok() {
97+
return Ok(());
98+
}
99+
} else {
100+
return Err("mac::factory: not a Mac primitive".into());
101+
}
102+
}
103+
104+
let entries = self.ps.raw_entries();
105+
for entry in &entries {
106+
if let tink::Primitive::Mac(p) = &entry.primitive {
107+
if p.verify_mac(mac, data).is_ok() {
108+
return Ok(());
109+
}
110+
} else {
111+
return Err("mac::factory: not a Mac primitive".into());
112+
}
113+
}
114+
115+
// nothing worked
116+
Err("mac::factory: decryption failed".into())
117+
}
118+
}

0 commit comments

Comments
 (0)