tss_esapi/
tcti_ldr.rs

1// Copyright 2021 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Wrapper around the TCTI Loader Library interface.
5//! See section 3.5 of the TCG TSS 2.0 TPM Command Transmission Interface(TCTI) API
6//! Specification.
7
8use crate::{Error, Result, WrapperErrorKind};
9use log::error;
10use regex::Regex;
11use std::convert::TryFrom;
12use std::env;
13use std::ffi::CStr;
14use std::ffi::CString;
15use std::net::IpAddr;
16use std::path::PathBuf;
17use std::ptr::null_mut;
18use std::str::FromStr;
19
20const DEVICE: &str = "device";
21const MSSIM: &str = "mssim";
22const SWTPM: &str = "swtpm";
23const TABRMD: &str = "tabrmd";
24
25/// TCTI Context created via a TCTI Loader Library.
26/// Wrapper around the TSS2_TCTI_CONTEXT structure.
27#[derive(Debug)]
28#[allow(missing_copy_implementations)]
29pub struct TctiContext {
30    tcti_context: *mut tss_esapi_sys::TSS2_TCTI_CONTEXT,
31}
32
33impl TctiContext {
34    /// Allocate and initialize a new TctiContext structure.
35    pub fn initialize(name_conf: TctiNameConf) -> Result<Self> {
36        let mut tcti_context = null_mut();
37
38        let tcti_name_conf = CString::try_from(name_conf)?;
39
40        unsafe {
41            let ret =
42                tss_esapi_sys::Tss2_TctiLdr_Initialize(tcti_name_conf.as_ptr(), &mut tcti_context);
43            let ret = Error::from_tss_rc(ret);
44            if !ret.is_success() {
45                error!("Error when creating a TCTI context: {}", ret);
46                return Err(ret);
47            }
48        }
49        Ok(TctiContext { tcti_context })
50    }
51
52    /// Get access to the inner C pointer
53    pub(crate) fn tcti_context_ptr(&mut self) -> *mut tss_esapi_sys::TSS2_TCTI_CONTEXT {
54        self.tcti_context
55    }
56}
57
58impl Drop for TctiContext {
59    fn drop(&mut self) {
60        unsafe {
61            tss_esapi_sys::Tss2_TctiLdr_Finalize(&mut self.tcti_context);
62        }
63    }
64}
65
66// `Send` and `Sync` are implemented to allow `TctiContext` to be thread-safe.
67// This is necessary because `*mut TSS2_TCTI_CONTEXT` is not thread-safe by
68// default. We can confirm the safety as the pointer can only be accessed
69// in a thread-safe way (i.e. in methods that require a `&mut self`).
70unsafe impl Send for TctiContext {}
71unsafe impl Sync for TctiContext {}
72
73/// Wrapper around the TSS2_TCTI_INFO structure.
74#[derive(Debug)]
75#[allow(missing_copy_implementations)]
76pub struct TctiInfo {
77    tcti_info: *mut tss_esapi_sys::TSS2_TCTI_INFO,
78}
79
80impl TctiInfo {
81    /// Query the TCTI loading mechanism
82    pub fn get_info(name_conf: TctiNameConf) -> Result<Self> {
83        let mut tcti_info = null_mut();
84
85        let tcti_name_conf = CString::try_from(name_conf)?;
86
87        unsafe {
88            let ret = tss_esapi_sys::Tss2_TctiLdr_GetInfo(tcti_name_conf.as_ptr(), &mut tcti_info);
89            let ret = Error::from_tss_rc(ret);
90            if !ret.is_success() {
91                error!("Error when getting the TCTI_INFO structure: {}", ret);
92                return Err(ret);
93            }
94        }
95        Ok(TctiInfo { tcti_info })
96    }
97
98    /// Get the version field
99    pub fn version(&self) -> u32 {
100        unsafe { (*(self.tcti_info)).version }
101    }
102
103    /// Get the name field
104    pub fn name(&self) -> &CStr {
105        unsafe { CStr::from_ptr((*(self.tcti_info)).name) }
106    }
107
108    /// Get the description field
109    pub fn description(&self) -> &CStr {
110        unsafe { CStr::from_ptr((*(self.tcti_info)).description) }
111    }
112
113    /// Get the config_help field
114    pub fn config_help(&self) -> &CStr {
115        unsafe { CStr::from_ptr((*(self.tcti_info)).config_help) }
116    }
117}
118
119impl Drop for TctiInfo {
120    fn drop(&mut self) {
121        unsafe {
122            tss_esapi_sys::Tss2_TctiLdr_FreeInfo(&mut self.tcti_info);
123        }
124    }
125}
126
127/// Placeholder TCTI types that can be used when initialising a `Context` to determine which
128/// interface will be used to communicate with the TPM.
129#[derive(Clone, Debug, PartialEq, Eq)]
130pub enum TctiNameConf {
131    /// Connect to a TPM available as a device node on the system
132    ///
133    /// For more information about configuration, see [this page](https://www.mankier.com/3/Tss2_Tcti_Device_Init)
134    Device(DeviceConfig),
135    /// Connect to a TPM (simulator) available as a network device via the MSSIM protocol
136    ///
137    /// For more information about configuration, see [this page](https://www.mankier.com/3/Tss2_Tcti_Mssim_Init)
138    Mssim(NetworkTPMConfig),
139    /// Connect to a TPM (simulator) available as a network device via the SWTPM protocol
140    ///
141    /// For more information about configuration, see [this page](https://www.mankier.com/3/Tss2_Tcti_Mssim_Init)
142    Swtpm(NetworkTPMConfig),
143    /// Connect to a TPM through an Access Broker/Resource Manager daemon
144    ///
145    /// For more information about configuration, see [this page](https://www.mankier.com/3/Tss2_Tcti_Tabrmd_Init)
146    Tabrmd(TabrmdConfig),
147}
148
149impl TctiNameConf {
150    /// Gets a TCTI from the following environment variables, in order:
151    /// - TPM2TOOLS_TCTI
152    /// - TCTI
153    /// - TEST_TCTI
154    ///
155    /// # Examples
156    /// ```
157    /// # use tss_esapi::tcti_ldr::TctiNameConf;
158    /// // Create context
159    /// let tcti_name_conf = TctiNameConf::from_environment_variable().expect("Failed to get TCTI");
160    pub fn from_environment_variable() -> Result<Self> {
161        env::var("TPM2TOOLS_TCTI")
162            .or_else(|_| env::var("TCTI"))
163            .or_else(|_| env::var("TEST_TCTI"))
164            .map_err(|_| Error::WrapperError(WrapperErrorKind::ParamsMissing))
165            .and_then(|val| TctiNameConf::from_str(&val))
166    }
167}
168
169impl TryFrom<TctiNameConf> for CString {
170    type Error = Error;
171
172    fn try_from(tcti: TctiNameConf) -> Result<Self> {
173        let tcti_name = match tcti {
174            TctiNameConf::Device(..) => DEVICE,
175            TctiNameConf::Mssim(..) => MSSIM,
176            TctiNameConf::Swtpm(..) => SWTPM,
177            TctiNameConf::Tabrmd(..) => TABRMD,
178        };
179
180        let tcti_conf = match tcti {
181            TctiNameConf::Mssim(config) => {
182                if let ServerAddress::Hostname(name) = &config.host {
183                    if !hostname_validator::is_valid(name) {
184                        return Err(Error::WrapperError(WrapperErrorKind::InvalidParam));
185                    }
186                }
187                format!("host={},port={}", config.host, config.port)
188            }
189            TctiNameConf::Swtpm(config) => {
190                if let ServerAddress::Hostname(name) = &config.host {
191                    if !hostname_validator::is_valid(name) {
192                        return Err(Error::WrapperError(WrapperErrorKind::InvalidParam));
193                    }
194                }
195                format!("host={},port={}", config.host, config.port)
196            }
197            TctiNameConf::Device(DeviceConfig { path }) => path
198                .to_str()
199                .ok_or(Error::WrapperError(WrapperErrorKind::InvalidParam))?
200                .to_owned(),
201            TctiNameConf::Tabrmd(config) => {
202                format!("bus_name={},bus_type={}", config.bus_name, config.bus_type)
203            }
204        };
205
206        if tcti_conf.is_empty() {
207            CString::new(tcti_name).or(Err(Error::WrapperError(WrapperErrorKind::InvalidParam)))
208        } else {
209            CString::new(format!("{}:{}", tcti_name, tcti_conf))
210                .or(Err(Error::WrapperError(WrapperErrorKind::InvalidParam)))
211        }
212    }
213}
214
215impl FromStr for TctiNameConf {
216    type Err = Error;
217
218    fn from_str(config_str: &str) -> Result<Self> {
219        let device_pattern = Regex::new(r"^device(:(.*))?$").unwrap(); //should not fail
220        if let Some(captures) = device_pattern.captures(config_str) {
221            return Ok(TctiNameConf::Device(DeviceConfig::from_str(
222                captures.get(2).map_or("", |m| m.as_str()),
223            )?));
224        }
225
226        let mssim_pattern = Regex::new(r"^mssim(:(.*))?$").unwrap(); //should not fail
227        if let Some(captures) = mssim_pattern.captures(config_str) {
228            return Ok(TctiNameConf::Mssim(NetworkTPMConfig::from_str(
229                captures.get(2).map_or("", |m| m.as_str()),
230            )?));
231        }
232
233        let swtpm_pattern = Regex::new(r"^swtpm(:(.*))?$").unwrap(); //should not fail
234        if let Some(captures) = swtpm_pattern.captures(config_str) {
235            return Ok(TctiNameConf::Swtpm(NetworkTPMConfig::from_str(
236                captures.get(2).map_or("", |m| m.as_str()),
237            )?));
238        }
239
240        let tabrmd_pattern = Regex::new(r"^tabrmd(:(.*))?$").unwrap(); //should not fail
241        if let Some(captures) = tabrmd_pattern.captures(config_str) {
242            return Ok(TctiNameConf::Tabrmd(TabrmdConfig::from_str(
243                captures.get(2).map_or("", |m| m.as_str()),
244            )?));
245        }
246
247        Err(Error::WrapperError(WrapperErrorKind::InvalidParam))
248    }
249}
250
251#[test]
252fn validate_from_str_tcti() {
253    let tcti = TctiNameConf::from_str("mssim:port=1234,host=168.0.0.1").unwrap();
254    assert_eq!(
255        tcti,
256        TctiNameConf::Mssim(NetworkTPMConfig {
257            port: 1234,
258            host: ServerAddress::Ip(IpAddr::V4(std::net::Ipv4Addr::new(168, 0, 0, 1)))
259        })
260    );
261
262    let tcti = TctiNameConf::from_str("mssim").unwrap();
263    assert_eq!(
264        tcti,
265        TctiNameConf::Mssim(NetworkTPMConfig {
266            port: DEFAULT_SERVER_PORT,
267            host: Default::default()
268        })
269    );
270
271    let tcti = TctiNameConf::from_str("swtpm:port=1234,host=168.0.0.1").unwrap();
272    assert_eq!(
273        tcti,
274        TctiNameConf::Swtpm(NetworkTPMConfig {
275            port: 1234,
276            host: ServerAddress::Ip(IpAddr::V4(std::net::Ipv4Addr::new(168, 0, 0, 1)))
277        })
278    );
279
280    let tcti = TctiNameConf::from_str("swtpm").unwrap();
281    assert_eq!(
282        tcti,
283        TctiNameConf::Swtpm(NetworkTPMConfig {
284            port: DEFAULT_SERVER_PORT,
285            host: Default::default()
286        })
287    );
288
289    let tcti = TctiNameConf::from_str("device:/try/this/path").unwrap();
290    assert_eq!(
291        tcti,
292        TctiNameConf::Device(DeviceConfig {
293            path: PathBuf::from("/try/this/path"),
294        })
295    );
296
297    let tcti = TctiNameConf::from_str("device").unwrap();
298    assert_eq!(tcti, TctiNameConf::Device(Default::default()));
299
300    let tcti = TctiNameConf::from_str("tabrmd:bus_name=some.bus.Name2,bus_type=session").unwrap();
301    assert_eq!(
302        tcti,
303        TctiNameConf::Tabrmd(TabrmdConfig {
304            bus_name: String::from("some.bus.Name2"),
305            bus_type: BusType::Session
306        })
307    );
308
309    let tcti = TctiNameConf::from_str("tabrmd").unwrap();
310    assert_eq!(tcti, TctiNameConf::Tabrmd(Default::default()));
311}
312
313/// Configuration for a Device TCTI context
314///
315/// The default configuration uses the library default of
316/// `/dev/tpm0`.
317#[derive(Clone, Debug, PartialEq, Eq)]
318pub struct DeviceConfig {
319    /// Path to the device node to connect to
320    ///
321    /// If set to `None`, the default location is used
322    path: PathBuf,
323}
324
325impl Default for DeviceConfig {
326    fn default() -> Self {
327        DeviceConfig {
328            path: PathBuf::from("/dev/tpm0"),
329        }
330    }
331}
332
333impl FromStr for DeviceConfig {
334    type Err = Error;
335
336    fn from_str(config_str: &str) -> Result<Self> {
337        if config_str.is_empty() {
338            return Ok(Default::default());
339        }
340
341        Ok(DeviceConfig {
342            path: PathBuf::from(config_str),
343        })
344    }
345}
346
347#[test]
348fn validate_from_str_device_config() {
349    let config = DeviceConfig::from_str("").unwrap();
350    assert_eq!(config, Default::default());
351
352    let config = DeviceConfig::from_str("/dev/tpm0").unwrap();
353    assert_eq!(config.path, PathBuf::from("/dev/tpm0"));
354}
355
356/// Configuration for an Mssim TCTI context
357///
358/// The default configuration will point to `localhost:2321`
359#[derive(Clone, Debug, PartialEq, Eq)]
360pub struct NetworkTPMConfig {
361    /// Address of the server to connect to
362    ///
363    /// Defaults to `localhost`
364    host: ServerAddress,
365    /// Port used by the server at the address given in `host`
366    ///
367    /// Defaults to `2321`
368    port: u16,
369}
370
371const DEFAULT_SERVER_PORT: u16 = 2321;
372
373impl Default for NetworkTPMConfig {
374    fn default() -> Self {
375        NetworkTPMConfig {
376            host: Default::default(),
377            port: DEFAULT_SERVER_PORT,
378        }
379    }
380}
381
382impl FromStr for NetworkTPMConfig {
383    type Err = Error;
384
385    fn from_str(config_str: &str) -> Result<Self> {
386        if config_str.is_empty() {
387            return Ok(Default::default());
388        }
389        let host_pattern = Regex::new(r"(,|^)host=(.*?)(,|$)").unwrap(); // should not fail
390        let host = host_pattern
391            .captures(config_str)
392            .map_or(Ok(Default::default()), |captures| {
393                ServerAddress::from_str(captures.get(2).map_or("", |m| m.as_str()))
394            })?;
395
396        let port_pattern = Regex::new(r"(,|^)port=(.*?)(,|$)").unwrap(); // should not fail
397        let port =
398            port_pattern
399                .captures(config_str)
400                .map_or(Ok(DEFAULT_SERVER_PORT), |captures| {
401                    u16::from_str(captures.get(2).map_or("", |m| m.as_str()))
402                        .or(Err(Error::WrapperError(WrapperErrorKind::InvalidParam)))
403                })?;
404        Ok(NetworkTPMConfig { host, port })
405    }
406}
407
408#[test]
409fn validate_from_str_networktpm_config() {
410    let config = NetworkTPMConfig::from_str("").unwrap();
411    assert_eq!(config, Default::default());
412
413    let config = NetworkTPMConfig::from_str("fjshd89943r=joishdf894u9r,sio0983=9u98jj").unwrap();
414    assert_eq!(config, Default::default());
415
416    let config = NetworkTPMConfig::from_str("host=127.0.0.1,random=value").unwrap();
417    assert_eq!(
418        config.host,
419        ServerAddress::Ip(IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)))
420    );
421    assert_eq!(config.port, DEFAULT_SERVER_PORT);
422
423    let config = NetworkTPMConfig::from_str("port=1234,random=value").unwrap();
424    assert_eq!(config.host, Default::default());
425    assert_eq!(config.port, 1234);
426
427    let config = NetworkTPMConfig::from_str("host=localhost,port=1234").unwrap();
428    assert_eq!(
429        config.host,
430        ServerAddress::Hostname(String::from("localhost"))
431    );
432    assert_eq!(config.port, 1234);
433
434    let config = NetworkTPMConfig::from_str("port=1234,host=localhost").unwrap();
435    assert_eq!(config.host, "localhost".parse::<ServerAddress>().unwrap());
436    assert_eq!(config.port, 1234);
437
438    let config = NetworkTPMConfig::from_str("port=1234,host=localhost,random=value").unwrap();
439    assert_eq!(config.host, "localhost".parse::<ServerAddress>().unwrap());
440    assert_eq!(config.port, 1234);
441
442    let config = NetworkTPMConfig::from_str("host=1234.1234.1234.1234.12445.111").unwrap();
443    assert_eq!(
444        config.host,
445        ServerAddress::Hostname(String::from("1234.1234.1234.1234.12445.111"))
446    );
447
448    let _ = NetworkTPMConfig::from_str("port=abdef").unwrap_err();
449    let _ = NetworkTPMConfig::from_str("host=-timey-wimey").unwrap_err();
450    let _ = NetworkTPMConfig::from_str("host=abc@def").unwrap_err();
451    let _ = NetworkTPMConfig::from_str("host=").unwrap_err();
452    let _ = NetworkTPMConfig::from_str("port=").unwrap_err();
453    let _ = NetworkTPMConfig::from_str("port=,host=,yas").unwrap_err();
454}
455
456/// Address of a TPM server
457///
458/// The default value is `localhost`
459#[derive(Clone, Debug, PartialEq, Eq)]
460pub enum ServerAddress {
461    /// IPv4 or IPv6 address
462    Ip(IpAddr),
463    /// Hostname
464    ///
465    /// The string is checked for compatibility with DNS hostnames
466    /// before the context is created
467    Hostname(String),
468}
469
470impl FromStr for ServerAddress {
471    type Err = Error;
472
473    fn from_str(config_str: &str) -> Result<Self> {
474        if let Ok(addr) = IpAddr::from_str(config_str) {
475            return Ok(ServerAddress::Ip(addr));
476        }
477
478        if !hostname_validator::is_valid(config_str) {
479            return Err(Error::WrapperError(WrapperErrorKind::InvalidParam));
480        }
481
482        Ok(ServerAddress::Hostname(config_str.to_owned()))
483    }
484}
485
486impl std::fmt::Display for ServerAddress {
487    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488        match self {
489            ServerAddress::Ip(addr) => addr.fmt(f),
490            ServerAddress::Hostname(name) => name.fmt(f),
491        }
492    }
493}
494
495impl Default for ServerAddress {
496    fn default() -> Self {
497        ServerAddress::Hostname(String::from("localhost"))
498    }
499}
500
501/// Configuration for a TABRMD TCTI context
502#[derive(Clone, Debug, PartialEq, Eq)]
503pub struct TabrmdConfig {
504    /// Bus name to be used by TABRMD
505    ///
506    /// Defaults tp `com.intel.tss2.Tabrmd`
507    // TODO: this could be changed to use the `dbus` crate, which has some
508    // more dependencies, though
509    bus_name: String,
510    /// Bus type to be used by TABRMD
511    ///
512    /// Defaults to `System`
513    bus_type: BusType,
514}
515
516const DEFAULT_BUS_NAME: &str = "com.intel.tss2.Tabrmd";
517
518impl Default for TabrmdConfig {
519    fn default() -> Self {
520        TabrmdConfig {
521            bus_name: String::from(DEFAULT_BUS_NAME),
522            bus_type: Default::default(),
523        }
524    }
525}
526
527impl FromStr for TabrmdConfig {
528    type Err = Error;
529
530    fn from_str(config_str: &str) -> Result<Self> {
531        if config_str.is_empty() {
532            return Ok(Default::default());
533        }
534        let bus_name_pattern = Regex::new(r"(,|^)bus_name=(.*?)(,|$)").unwrap(); // should not fail
535        let bus_name = bus_name_pattern.captures(config_str).map_or(
536            Ok(DEFAULT_BUS_NAME.to_owned()),
537            |captures| {
538                let valid_bus_name_pattern =
539                    Regex::new(r"^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)+$").unwrap(); //should not fail
540                if !valid_bus_name_pattern.is_match(captures.get(2).map_or("", |m| m.as_str())) {
541                    return Err(Error::WrapperError(WrapperErrorKind::InvalidParam));
542                }
543                Ok(captures.get(2).map_or("", |m| m.as_str()).to_owned())
544            },
545        )?;
546
547        let bus_type_pattern = Regex::new(r"(,|^)bus_type=(.*?)(,|$)").unwrap(); // should not fail
548        let bus_type = bus_type_pattern
549            .captures(config_str)
550            .map_or(Ok(Default::default()), |captures| {
551                BusType::from_str(captures.get(2).map_or("", |m| m.as_str()))
552            })?;
553
554        Ok(TabrmdConfig { bus_name, bus_type })
555    }
556}
557
558/// DBus type for usage with TABRMD
559#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
560pub enum BusType {
561    #[default]
562    System,
563    Session,
564}
565
566impl FromStr for BusType {
567    type Err = Error;
568
569    fn from_str(config_str: &str) -> Result<Self> {
570        match config_str {
571            "session" => Ok(BusType::Session),
572            "system" => Ok(BusType::System),
573            _ => Err(Error::WrapperError(WrapperErrorKind::InvalidParam)),
574        }
575    }
576}
577
578impl std::fmt::Display for BusType {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        match self {
581            BusType::Session => write!(f, "session"),
582            BusType::System => write!(f, "system"),
583        }
584    }
585}
586
587#[test]
588fn validate_from_str_tabrmd_config() {
589    let config = TabrmdConfig::from_str("").unwrap();
590    assert_eq!(config, Default::default());
591
592    let config = TabrmdConfig::from_str("fjshd89943r=joishdf894u9r,sio0983=9u98jj").unwrap();
593    assert_eq!(config, Default::default());
594
595    let config = TabrmdConfig::from_str("bus_name=one.true.bus.Name,random=value").unwrap();
596    assert_eq!(&config.bus_name, "one.true.bus.Name");
597    assert_eq!(config.bus_type, Default::default());
598
599    let config = TabrmdConfig::from_str("bus_type=session,random=value").unwrap();
600    assert_eq!(&config.bus_name, DEFAULT_BUS_NAME);
601    assert_eq!(config.bus_type, BusType::Session);
602
603    let config = TabrmdConfig::from_str("bus_name=one.true.bus.Name,bus_type=system").unwrap();
604    assert_eq!(&config.bus_name, "one.true.bus.Name");
605    assert_eq!(config.bus_type, BusType::System);
606
607    let config = TabrmdConfig::from_str("bus_type=system,bus_name=one.true.bus.Name").unwrap();
608    assert_eq!(&config.bus_name, "one.true.bus.Name");
609    assert_eq!(config.bus_type, BusType::System);
610
611    let config =
612        TabrmdConfig::from_str("bus_type=system,bus_name=one.true.bus.Name,random=value").unwrap();
613    assert_eq!(&config.bus_name, "one.true.bus.Name");
614    assert_eq!(config.bus_type, BusType::System);
615
616    let _ = TabrmdConfig::from_str("bus_name=abc&.bcd").unwrap_err();
617    let _ = TabrmdConfig::from_str("bus_name=adfsdgdfg4gf4").unwrap_err();
618    let _ = TabrmdConfig::from_str("bus_name=,bus_type=,bla?").unwrap_err();
619    let _ = TabrmdConfig::from_str("bus_type=randooom").unwrap_err();
620}