Crates.io | pk11-uri-parser |
lib.rs | pk11-uri-parser |
version | 0.1.5 |
source | src |
created_at | 2024-10-24 15:31:37.766847 |
updated_at | 2024-11-27 02:29:45.471083 |
description | A zero-copy library to parse and (optionally) validate PKCS#11 URIs in accordance to RFC7512 specifications. |
homepage | |
repository | https://github.com/andrewoswald/pk11-uri-parser |
max_upload_size | |
id | 1421468 |
size | 79,660 |
A zero-copy library to parse and (optionally) validate PKCS#11 URIs in accordance to the RFC7512 specification.
Users of the library do not need to be intimately familiar with specification rules regarding what attributes belong to the path-component or the query-component, or to be knowledgeable about the various vendor-specific attribute rules.
A successfully parsed PKCS#11 URI will result in a PK11URIMapping
: a simple struct with aptly named methods to directly access respective string slices (&str
) of the given input. Vendor-specific attribute values are made available through an intuitive vendor
method which respectively provides &Vec<&str>
also referring to &str
from the input. Parsing errors are reported by way of a PK11URIError
, which offers a very user-friendly display as well as a help suggestion to resolve the issue. Lastly, debug builds provide "warning" messages when attribute values do not comply with the specification's "SHOULD"/"SHOULD NOT" guidelines (note: --release
builds do not include the warning related code).
Pull in the lib:
cargo add pk11-uri-parser
And run an example using a sample URI from the RFC7512 specification:
pub fn main() {
let pk11_uri = "pkcs11:token=The%20Software%20PKCS%2311%20Softtoken;
manufacturer=Snake%20Oil,%20Inc.;
model=1.0;
object=my-certificate;
type=cert;
id=%69%95%3E%5C%F4%BD%EC%91;
serial=
?pin-source=file:/etc/token_pin";
let mapping = pk11_uri_parser::parse(pk11_uri).expect("valid mapping");
// access some standard attributes:
assert_eq!(mapping.model(), Some("1.0"));
assert_eq!(mapping.object(), Some("my-certificate"));
assert_eq!(mapping.pin_source(), Some("file:/etc/token_pin"));
// note: explicit empty string for serial:
assert_eq!(mapping.serial(), Some(""));
// note: "slot-id" not included so `None`:
assert_eq!(mapping.slot_id(), None);
// use the `vendor` method:
assert_eq!(mapping.vendor("foo"), None);
}
It's typical to do some exploration to become familiarized with how PKCS#11 URIs identify cryptographic assets. This exploration is often* done using tools such as p11tool
or pkcs11-tool
, with the tool output being rather large (providing a great number of attribute/value pairs for a particular resource). These tool-derived uris will certainly parse correctly, but a dramatically shorter uri will often end up being just as effective and will generally end up being more portable. Likewise, it's out of scope for these tools to provide query-component attributes such as pin-value
or pin-source
. As such, you may wish to experiment in discovering the shortest possible uri for your use-case. This is where the PK11URIError
will likely provide some assistance.
*(sometimes it's not, and you prefer some "YOLO" testing):
Let's say you're attempting to use a YubiKey in order to use it for purposes of utilizing an HSM-bound private key. According to the Key Alias per Slot and Object Type
documentation, it may be worthwhile to try pkcs11:slot=9e;object=Private key for Card Authentication;type=Private Key
:
pub fn main() -> Result<(), pk11_uri_parser::PK11URIError> {
let pk11_uri = "pkcs11:slot=9e;object=Private key for Card Authentication;type=Private Key";
let mapping = pk11_uri_parser::parse(pk11_uri)?;
println!("mapping: {:?}", mapping);
Ok(())
}
which results in
Error: PK11URIError { pk11_uri: "pkcs11:slot=9e;object=Private key for Card Authentication;type=Private Key", error_span: (15, 57), violation: "Invalid component value: Appendix A of [RFC3986] specifies component values may not contain empty spaces.", help: "Replace `Private key for Card Authentication` with `Private%20key%20for%20Card%20Authentication`." }
which is helpful, but it's kind of ugly. Let's modify our source to showcase the PK11URIError
's Display
capability:
pub fn main() {
let pk11_uri = "pkcs11:slot=9e;object=Private key for Card Authentication;type=Private Key";
match pk11_uri_parser::parse(pk11_uri) {
Ok(mapping) => println!("mapping: {:?}", mapping),
Err(err) => println!("{err}")
}
}
results in
pkcs11:slot=9e;object=Private key for Card Authentication;type=Private Key
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid component value: Appendix A of [RFC3986] specifies component values may not contain empty spaces.
help: Replace `Private key for Card Authentication` with `Private%20key%20for%20Card%20Authentication`.
Ah, much better! And in a style Rust developers have become accustomed to. Let's employ the suggestion provided by help:
and try again:
pub fn main() {
let pk11_uri = "pkcs11:slot=9e;object=Private%20key%20for%20Card%20Authentication;type=Private Key";
match pk11_uri_parser::parse(pk11_uri) {
Ok(mapping) => println!("mapping: {:?}", mapping),
Err(err) => println!("{err}")
}
}
results in
pkcs11:slot=9e;object=Private%20key%20for%20Card%20Authentication;type=Private Key
^^^^^^^^^^^^^^^^ Invalid `pk11-pattr`: `pk11-type` = `"type" "=" ( "public" / "private" / "cert" / "secret-key" / "data" )`.
help: Replace `Private Key` value with one of `public`, `private`, `cert`, `secret-key`, or `data`.
Another error.. which demonstrates that the pk11-uri-parser
library will fail-quickly (ie, short-circuit further parsing) upon encountering an RFC7512 violation.
As previously noted, the RFC7512 specfication makes (optional) best-practice suggestions for attribute values by using terminology such as "SHOULD" and "SHOULD NOT". The pk11-uri-parser
library embraces these suggestions and when running under a debug
build, will emit warning messages when such suggestion related criteria is met. The messages begin with pkcs11 warning:
. It's important to note that warning related code is explicitly excluded from --release
builds.
pub fn main() {
let pk11_uri = "pkcs11:x-muppet=cookie<^^>monster!";
let mapping = pk11_uri_parser::parse(pk11_uri).expect("valid mapping");
let x_muppet = mapping.vendor("x-muppet").expect("valid vendor attribute");
println!("`x-muppet` vendor value: {:?}", x_muppet);
}
prints
pkcs11 warning: per RFC7512, the previously used convention of starting vendor attributes with an "x-" prefix is now deprecated. Identified: `x-muppet`.
pkcs11 warning: the `<` identified at offset 6 in `cookie<^^>monster!` of component `x-muppet=cookie<^^>monster!` SHOULD be percent-encoded.
pkcs11 warning: the `^` identified at offset 7 in `cookie<^^>monster!` of component `x-muppet=cookie<^^>monster!` SHOULD be percent-encoded.
pkcs11 warning: the `^` identified at offset 8 in `cookie<^^>monster!` of component `x-muppet=cookie<^^>monster!` SHOULD be percent-encoded.
pkcs11 warning: the `>` identified at offset 9 in `cookie<^^>monster!` of component `x-muppet=cookie<^^>monster!` SHOULD be percent-encoded.
`x-muppet` vendor value: ["cookie<^^>monster!"]
As showcased above, PKCS#11 URIs may contain "vendor-specific" attributes and that these vendor-specific attributes are allowed to have multiple values (thus the &Vec<&str>
option return type for the vendor
method). It's worth pointing out that while vendor-specific attributes may have multiple values, the RFC7512 specfification does not allow duplicate path-component names, regardless of standard or vendor attribute. A uri which contains duplicate path-component names will result in a PK11URIError
. Nevertheless, here's an example of a vendor-specific attribute which contains multiple values:
pub fn main() {
let pk11_uri = "pkcs11:NATO=alpha?NATO=bravo&NATO=charlie";
let mapping = pk11_uri_parser::parse(pk11_uri).expect("valid mapping");
let nato = mapping.vendor("NATO").expect("valid vendor attribute");
println!("`NATO` vendor value: {:?}", nato);
}
prints
`NATO` vendor value: ["alpha", "bravo", "charlie"]
At your disposal: fine-grained control over validtion and debug warnings. The default feature set is to always perform validation
and to provide pkcs11 warning:
messages when debug build attribute values do not comply with RFC7512 "SHOULD/SHOULD NOT" guidelines. To
do away with the defaults, simply assign default-features=false
in your pk11-uri-parser dependency stanza:
[dependencies]
pk11-uri-parser = {version = "0.1.4", default-features = false}
Please be aware, however, that doing so will introduce expect("my expectation")
calls required in the parsing logic. See the Cargo.toml file for more details.
This project's source code and documentation are licensed under the MIT license. See the LICENSE file for details.