yacme_protocol/
lib.rs

1//! # ACME JWT implementation and ACME request types.
2//!
3//! Most ACME requests are authenticated as a JWT, signed by the
4//! account key. This module provides the implementation of that
5//! protocol, and the deserialization of the corresponding responses,
6//! as well as providing a [`Client`] type which can be used to
7//! track the correct Nonce through a series of requests.
8#![deny(unsafe_code)]
9#![deny(missing_docs)]
10
11use std::ops::Deref;
12use std::str::FromStr;
13
14mod base64;
15pub mod client;
16pub mod errors;
17#[allow(unsafe_code)]
18pub mod fmt;
19pub mod jose;
20pub mod request;
21pub mod response;
22
23pub use base64::Base64Data;
24pub use base64::Base64JSON;
25pub use client::Client;
26pub use errors::AcmeError;
27
28#[doc(no_inline)]
29pub use request::Request;
30#[doc(no_inline)]
31pub use response::Response;
32use serde::{Deserialize, Serialize};
33
34/// A result type which uses [`AcmeError`] as the error type.
35pub type Result<T> = ::std::result::Result<T, AcmeError>;
36
37/// Universal Resource Locator which provides
38/// a [`std::fmt::Debug`] implementation which prints the
39/// full URL (rather than the parsed parts) for compactness.
40///
41/// This tries to be a drop-in replacement for [`url::Url`].
42#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
43pub struct Url(url::Url);
44
45impl Url {
46    /// Underlying string representation of the URL.
47    pub fn as_str(&self) -> &str {
48        self.0.as_str()
49    }
50
51    /// Just the path component of the URL.
52    pub fn path(&self) -> &str {
53        self.0.path()
54    }
55
56    /// Just the host component of the URL.
57    pub fn host(&self) -> Option<&str> {
58        self.0.host_str()
59    }
60}
61
62impl Deref for Url {
63    type Target = url::Url;
64    fn deref(&self) -> &Self::Target {
65        &self.0
66    }
67}
68
69impl From<url::Url> for Url {
70    fn from(value: url::Url) -> Self {
71        Url(value)
72    }
73}
74
75impl From<Url> for url::Url {
76    fn from(value: Url) -> Self {
77        value.0
78    }
79}
80
81impl AsRef<str> for Url {
82    fn as_ref(&self) -> &str {
83        self.0.as_str()
84    }
85}
86
87impl std::fmt::Debug for Url {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_tuple("Url").field(&self.0.as_str()).finish()
90    }
91}
92
93impl FromStr for Url {
94    type Err = url::ParseError;
95
96    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
97        s.parse().map(Url)
98    }
99}
100
101#[cfg(test)]
102pub(crate) mod test {
103    use std::sync::Arc;
104
105    #[macro_export]
106    macro_rules! example {
107        ($name:tt) => {
108            include_str!(concat!(
109                env!("CARGO_MANIFEST_DIR"),
110                "/test-examples/",
111                $name
112            ))
113        };
114    }
115
116    #[macro_export]
117    macro_rules! key {
118        ($name:tt) => {
119            $crate::test::key(include_str!(concat!(
120                env!("CARGO_MANIFEST_DIR"),
121                "/test-examples/",
122                $name,
123                ".pem"
124            )))
125        };
126    }
127
128    #[macro_export]
129    macro_rules! response {
130        ($name:tt) => {
131            $crate::test::parse($crate::example!($name))
132        };
133    }
134
135    #[allow(dead_code)]
136    pub(crate) fn key(private: &str) -> Arc<yacme_key::SigningKey> {
137        let key = yacme_key::SigningKey::from_pkcs8_pem(
138            private,
139            yacme_key::SignatureKind::Ecdsa(yacme_key::EcdsaAlgorithm::P256),
140        )
141        .unwrap();
142
143        Arc::new(key)
144    }
145
146    pub(crate) fn parse(data: &str) -> http::Response<String> {
147        let mut lines = data.lines();
148
149        let status = {
150            let status_line = lines.next().unwrap().trim();
151            let (version, status) = status_line.split_once(' ').unwrap();
152
153            if !matches!(version, "HTTP/1.1") {
154                panic!("Expected HTTP/1.1, got {version}");
155            }
156
157            let (code, _reason) = status.split_once(' ').unwrap();
158            http::StatusCode::from_u16(code.parse().unwrap()).unwrap()
159        };
160
161        let mut headers = http::HeaderMap::new();
162
163        for line in lines.by_ref() {
164            if line.is_empty() {
165                break;
166            } else {
167                let (name, value) = line.trim().split_once(": ").unwrap();
168                headers.append(
169                    http::header::HeaderName::from_bytes(name.as_bytes()).unwrap(),
170                    value.parse().unwrap(),
171                );
172            }
173        }
174
175        let body: String = lines.collect();
176        let mut response = http::Response::new(body);
177        *response.headers_mut() = headers;
178        *response.status_mut() = status;
179        *response.version_mut() = http::Version::HTTP_11;
180        response
181    }
182}