yubikey/
msroots.rs

1//! PKCS#7 formatted certificate store for enterprise trusted roots.
2
3// Adapted from yubico-piv-tool:
4// <https://github.com/Yubico/yubico-piv-tool/>
5//
6// Copyright (c) 2014-2016 Yubico AB
7// All rights reserved.
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions are
11// met:
12//
13//   * Redistributions of source code must retain the above copyright
14//     notice, this list of conditions and the following disclaimer.
15//
16//   * Redistributions in binary form must reproduce the above
17//     copyright notice, this list of conditions and the following
18//     disclaimer in the documentation and/or other materials provided
19//     with the distribution.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33use crate::{
34    consts::{CB_OBJ_MAX, CB_OBJ_TAG_MAX},
35    serialization::*,
36    Error, Result, YubiKey,
37};
38use log::error;
39
40const OBJ_MSROOTS1: u32 = 0x005f_ff11;
41#[allow(dead_code)]
42const OBJ_MSROOTS2: u32 = 0x005f_ff12;
43#[allow(dead_code)]
44const OBJ_MSROOTS3: u32 = 0x005f_ff13;
45#[allow(dead_code)]
46const OBJ_MSROOTS4: u32 = 0x005f_ff14;
47const OBJ_MSROOTS5: u32 = 0x005f_ff15;
48
49const TAG_MSROOTS_END: u8 = 0x82;
50const TAG_MSROOTS_MID: u8 = 0x83;
51
52/// PKCS#7-formatted certificate store for enterprise trust roots.
53///
54/// The `msroots` file contains a bag of certificates with empty content and
55/// an empty signature, allowing an enterprise root certificate truststore to
56/// be written to and read from a YubiKey.
57///
58/// For more information, see:
59/// <https://docs.microsoft.com/en-us/windows-hardware/drivers/smartcard/developer-guidelines#-interoperability-with-msroots>
60pub struct MsRoots(Vec<u8>);
61
62impl MsRoots {
63    /// Initialize a local certificate struct from the given bytebuffer
64    pub fn new(msroots: impl AsRef<[u8]>) -> Result<Self> {
65        Ok(MsRoots(msroots.as_ref().into()))
66    }
67
68    /// Read `msroots` file from YubiKey
69    pub fn read(yubikey: &mut YubiKey) -> Result<Option<Self>> {
70        let txn = yubikey.begin_transaction()?;
71
72        // allocate first page
73        let mut data = Vec::with_capacity(CB_OBJ_MAX);
74
75        for object_id in OBJ_MSROOTS1..OBJ_MSROOTS5 {
76            let buf = txn.fetch_object(object_id)?;
77
78            let (_, tlv) = match Tlv::parse(&buf) {
79                Ok(res) => res,
80                Err(_) => return Ok(None),
81            };
82
83            if (TAG_MSROOTS_MID != tlv.tag || OBJ_MSROOTS5 == object_id)
84                && (TAG_MSROOTS_END != tlv.tag)
85            {
86                // the current object doesn't contain a valid part of a msroots file
87
88                // treat condition as object isn't found
89                return Ok(None);
90            }
91
92            data.extend_from_slice(tlv.value);
93
94            if tlv.tag == TAG_MSROOTS_END {
95                break;
96            }
97        }
98
99        MsRoots::new(&data).map(Some).map_err(|e| {
100            error!("error parsing msroots: {:?}", e);
101            e
102        })
103    }
104
105    /// Write `msroots` file to YubiKey
106    pub fn write(&self, yubikey: &mut YubiKey) -> Result<()> {
107        let mut buf = [0u8; CB_OBJ_MAX];
108        let mut offset: usize;
109        let mut data_offset: usize = 0;
110        let mut data_chunk: usize;
111        let data = &self.0;
112        let data_len = data.len();
113
114        let txn = yubikey.begin_transaction()?;
115
116        if data_len == 0 {
117            return txn.save_object(OBJ_MSROOTS1, &[]);
118        }
119
120        // Calculate number of objects required to store blob
121        let n_objs: usize = (data_len / (CB_OBJ_MAX - CB_OBJ_TAG_MAX)) + 1;
122
123        if n_objs > 5 {
124            return Err(Error::SizeError);
125        }
126
127        for i in 0..n_objs {
128            offset = 0;
129
130            data_chunk = if CB_OBJ_MAX - CB_OBJ_TAG_MAX < data_len - data_offset {
131                CB_OBJ_MAX - CB_OBJ_TAG_MAX
132            } else {
133                data_len - data_offset
134            };
135
136            offset += Tlv::write(
137                &mut buf,
138                if i == n_objs - 1 {
139                    TAG_MSROOTS_END
140                } else {
141                    TAG_MSROOTS_MID
142                },
143                &data[data_offset..(data_offset + data_chunk)],
144            )?;
145
146            txn.save_object(OBJ_MSROOTS1 + i as u32, &buf[..offset])?;
147
148            data_offset += data_chunk;
149        }
150
151        Ok(())
152    }
153}
154
155impl AsRef<[u8]> for MsRoots {
156    fn as_ref(&self) -> &[u8] {
157        self.0.as_ref()
158    }
159}