Skip to main content

cryptouri/
lib.rs

1//! CryptoURI: URN-like namespace for cryptographic objects (keys, signatures, etc)
2//! with Bech32 encoding/checksums
3
4#![doc(
5    html_logo_url = "https://avatars3.githubusercontent.com/u/40766087?u=0267cf8b7fe892bbf35b6114d9eb48adc057d6ff",
6    html_root_url = "https://docs.rs/cryptouri/0.4.0"
7)]
8#![forbid(unsafe_code)]
9#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
10
11#[macro_use]
12mod encoding;
13#[macro_use]
14pub mod error;
15
16pub mod algorithm;
17pub mod hash;
18mod parts;
19pub mod public_key;
20pub mod secret_key;
21pub mod signature;
22
23pub use crate::{
24    encoding::Encodable,
25    error::{Error, ErrorKind},
26    hash::Hash,
27    public_key::PublicKey,
28    secret_key::SecretKey,
29    signature::Signature,
30};
31
32use crate::{
33    encoding::{Encoding, DASHERIZED_ENCODING, URI_ENCODING},
34    parts::Parts,
35};
36use anomaly::fail;
37use secrecy::{ExposeSecret, SecretString};
38
39/// `CryptoUri`: URI-based format for encoding cryptographic objects
40pub struct CryptoUri {
41    /// Kind of `CryptoUri` (e.g. secret key, public key, hashes, signatures)
42    kind: CryptoUriKind,
43
44    /// URI fragment (i.e. everything after `#`)
45    fragment: Option<SecretString>,
46}
47
48/// Kinds of `CryptoUri`s
49pub enum CryptoUriKind {
50    /// Hashes (i.e. cryptographic digests)
51    Hash(Hash),
52
53    /// Public keys (always asymmetric)
54    PublicKey(PublicKey),
55
56    /// Secret keys (symmetric or asymmetric)
57    SecretKey(SecretKey),
58
59    /// Digital signatures (always asymmetric)
60    Signature(Signature),
61}
62
63impl CryptoUri {
64    /// Parse a `CryptoUri` from a Bech32 encoded string using the given encoding
65    // TODO: parser generator rather than handrolling this?
66    fn parse(uri: &str, encoding: &Encoding) -> Result<Self, Error> {
67        let parts = Parts::decode(uri, encoding)?;
68
69        let kind = if parts.prefix.starts_with(encoding.hash_scheme) {
70            CryptoUriKind::Hash(Hash::new(
71                &parts.prefix[encoding.hash_scheme.len()..],
72                parts.data.expose_secret(),
73            )?)
74        } else if parts.prefix.starts_with(encoding.public_key_scheme) {
75            CryptoUriKind::PublicKey(PublicKey::new(
76                &parts.prefix[encoding.public_key_scheme.len()..],
77                parts.data.expose_secret(),
78            )?)
79        } else if parts.prefix.starts_with(encoding.secret_key_scheme) {
80            let alg_id = &parts.prefix[encoding.secret_key_scheme.len()..];
81
82            if alg_id.contains(encoding.combine) {
83                // Multi-algorithm combination (e.g. KDF)
84                let alg_ids = alg_id.split(encoding.combine).collect::<Vec<_>>();
85                CryptoUriKind::SecretKey(SecretKey::new_combination(
86                    &alg_ids,
87                    parts.data.expose_secret(),
88                )?)
89            } else {
90                CryptoUriKind::SecretKey(SecretKey::new(alg_id, parts.data.expose_secret())?)
91            }
92        } else if parts.prefix.starts_with(encoding.signature_scheme) {
93            CryptoUriKind::Signature(Signature::new(
94                &parts.prefix[encoding.signature_scheme.len()..],
95                parts.data.expose_secret(),
96            )?)
97        } else {
98            fail!(
99                ErrorKind::SchemeInvalid,
100                "unknown CryptoURI prefix: {}",
101                parts.prefix
102            )
103        };
104
105        Ok(Self {
106            kind,
107            fragment: parts.fragment,
108        })
109    }
110
111    /// Parse a `CryptoUri`
112    pub fn parse_uri(uri: &str) -> Result<Self, Error> {
113        Self::parse(uri, URI_ENCODING)
114    }
115
116    /// Parse a `CryptoUri` in URI-embeddable (a.k.a. "dasherized") encoding
117    pub fn parse_dasherized(token: &str) -> Result<Self, Error> {
118        Self::parse(token, DASHERIZED_ENCODING)
119    }
120
121    /// Return the `CryptoUriKind` for this URI
122    pub fn kind(&self) -> &CryptoUriKind {
123        &self.kind
124    }
125
126    /// Return a `SecretKey` if the underlying URI is a `crypto:sec:key:`
127    pub fn secret_key(&self) -> Option<&SecretKey> {
128        match self.kind {
129            CryptoUriKind::SecretKey(ref key) => Some(key),
130            _ => None,
131        }
132    }
133
134    /// Is this `CryptoUri` a `crypto:sec:key:`?
135    pub fn is_secret_key(&self) -> bool {
136        self.secret_key().is_some()
137    }
138
139    /// Return a `PublicKey` if the underlying URI is a `crypto:pub:key:`
140    pub fn public_key(&self) -> Option<&PublicKey> {
141        match self.kind {
142            CryptoUriKind::PublicKey(ref key) => Some(key),
143            _ => None,
144        }
145    }
146
147    /// Is this CryptoUri a `crypto:pub:key:`?
148    pub fn is_public_key(&self) -> bool {
149        self.public_key().is_some()
150    }
151
152    /// Return a `Digest` if the underlying URI is a `crypto:hash:`
153    pub fn hash(&self) -> Option<&Hash> {
154        match self.kind {
155            CryptoUriKind::Hash(ref hash) => Some(hash),
156            _ => None,
157        }
158    }
159
160    /// Is this CryptoUri a `crypto:hash:`?
161    pub fn is_hash(&self) -> bool {
162        self.hash().is_some()
163    }
164
165    /// Return a `Signature` if the underlying URI is a `crypto:pub:sig:`
166    pub fn signature(&self) -> Option<&Signature> {
167        match self.kind {
168            CryptoUriKind::Signature(ref sig) => Some(sig),
169            _ => None,
170        }
171    }
172
173    /// Is this CryptoUri a `crypto:pub:sig:`?
174    pub fn is_signature(&self) -> bool {
175        self.signature().is_some()
176    }
177
178    /// Obtain the fragment for this URI (i.e. everything after `#`)
179    pub fn fragment(&self) -> Option<&str> {
180        self.fragment
181            .as_ref()
182            .map(|fragment| fragment.expose_secret().as_ref())
183    }
184}
185
186impl Encodable for CryptoUri {
187    /// Serialize this `CryptoUri` as a URI-encoded `String`
188    fn to_uri_string(&self) -> String {
189        match self.kind {
190            CryptoUriKind::Hash(ref hash) => hash.to_uri_string(),
191            CryptoUriKind::PublicKey(ref pk) => pk.to_uri_string(),
192            CryptoUriKind::SecretKey(ref sk) => sk.to_uri_string(),
193            CryptoUriKind::Signature(ref sig) => sig.to_uri_string(),
194        }
195    }
196
197    /// Serialize this `CryptoUri` as a "dasherized" `String`
198    fn to_dasherized_string(&self) -> String {
199        match self.kind {
200            CryptoUriKind::Hash(ref hash) => hash.to_dasherized_string(),
201            CryptoUriKind::PublicKey(ref pk) => pk.to_dasherized_string(),
202            CryptoUriKind::SecretKey(ref sk) => sk.to_dasherized_string(),
203            CryptoUriKind::Signature(ref sig) => sig.to_dasherized_string(),
204        }
205    }
206}