/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/pk11_utils.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | use crate::err::{Error, Res}; |
8 | | |
9 | | #[derive(Debug, Clone, PartialEq, Eq)] |
10 | | pub struct Pkcs11Uri { |
11 | | pub token: Option<String>, |
12 | | } |
13 | | |
14 | 0 | fn percent_decode(s: &str) -> String { |
15 | 0 | let mut result = Vec::with_capacity(s.len()); |
16 | 0 | let bytes = s.as_bytes(); |
17 | 0 | let mut i = 0; |
18 | 0 | while i < bytes.len() { |
19 | 0 | if bytes[i] == b'%' |
20 | 0 | && i + 2 < bytes.len() |
21 | 0 | && let Ok(byte) = |
22 | 0 | u8::from_str_radix(std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""), 16) |
23 | | { |
24 | 0 | result.push(byte); |
25 | 0 | i += 3; |
26 | 0 | continue; |
27 | 0 | } |
28 | 0 | result.push(bytes[i]); |
29 | 0 | i += 1; |
30 | | } |
31 | 0 | String::from_utf8_lossy(&result).into_owned() |
32 | 0 | } |
33 | | |
34 | 0 | fn percent_encode(s: &str) -> String { |
35 | 0 | let mut result = String::with_capacity(s.len()); |
36 | 0 | for b in s.bytes() { |
37 | 0 | match b { |
38 | 0 | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => { |
39 | 0 | result.push(b as char); |
40 | 0 | } |
41 | | _ => { |
42 | | use std::fmt::Write as _; |
43 | 0 | write!(result, "%{b:02X}").expect("write to String"); |
44 | | } |
45 | | } |
46 | | } |
47 | 0 | result |
48 | 0 | } |
49 | | |
50 | | /// Parse a PKCS#11 URI (RFC 7512). |
51 | | /// Expects the input to start with "pkcs11:". |
52 | 0 | pub fn parse(uri: &str) -> Res<Pkcs11Uri> { |
53 | 0 | let path = uri.strip_prefix("pkcs11:").ok_or(Error::InvalidInput)?; |
54 | | |
55 | 0 | let mut token = None; |
56 | 0 | for attr in path.split(';') { |
57 | 0 | if let Some((key, value)) = attr.split_once('=') |
58 | 0 | && key == "token" |
59 | 0 | { |
60 | 0 | token = Some(percent_decode(value)); |
61 | 0 | } |
62 | | } |
63 | | |
64 | 0 | Ok(Pkcs11Uri { token }) |
65 | 0 | } |
66 | | |
67 | | /// Build a PKCS#11 URI from a token name. |
68 | | #[must_use] |
69 | 0 | pub fn build(token_name: &str) -> String { |
70 | 0 | format!("pkcs11:token={}", percent_encode(token_name)) |
71 | 0 | } |
72 | | |
73 | | #[cfg(test)] |
74 | | mod tests { |
75 | | use test_fixture::fixture_init; |
76 | | |
77 | | use super::*; |
78 | | |
79 | | #[test] |
80 | | fn parse_simple() { |
81 | | fixture_init(); |
82 | | let uri = parse("pkcs11:token=NSS%20Certificate%20DB").unwrap(); |
83 | | assert_eq!(uri.token.as_deref(), Some("NSS Certificate DB")); |
84 | | } |
85 | | |
86 | | #[test] |
87 | | fn parse_no_token() { |
88 | | fixture_init(); |
89 | | let uri = parse("pkcs11:manufacturer=Mozilla").unwrap(); |
90 | | assert_eq!(uri.token, None); |
91 | | } |
92 | | |
93 | | #[test] |
94 | | fn parse_multiple_attrs() { |
95 | | fixture_init(); |
96 | | let uri = parse("pkcs11:token=MyToken;manufacturer=Test;serial=1234").unwrap(); |
97 | | assert_eq!(uri.token.as_deref(), Some("MyToken")); |
98 | | } |
99 | | |
100 | | #[test] |
101 | | fn parse_not_pkcs11() { |
102 | | fixture_init(); |
103 | | assert!(parse("http://example.com").is_err()); |
104 | | } |
105 | | |
106 | | #[test] |
107 | | fn build_uri() { |
108 | | fixture_init(); |
109 | | assert_eq!( |
110 | | build("NSS Certificate DB"), |
111 | | "pkcs11:token=NSS%20Certificate%20DB" |
112 | | ); |
113 | | } |
114 | | |
115 | | #[test] |
116 | | fn roundtrip() { |
117 | | fixture_init(); |
118 | | let name = "My Token (Test-v2)"; |
119 | | let uri_str = build(name); |
120 | | let parsed = parse(&uri_str).unwrap(); |
121 | | assert_eq!(parsed.token.as_deref(), Some(name)); |
122 | | } |
123 | | } |