1use std::fs::File;
29use std::io::Read;
30use std::path::Path;
31use thiserror::Error;
32
33pub use hessra_token::{
35 add_service_node_attestation,
37 decode_token,
38 encode_token,
39 verify_biscuit_local,
41 verify_service_chain_biscuit_local,
42 Biscuit,
44 KeyPair,
45 PublicKey,
46 ServiceNode,
48 TokenError,
50};
51
52pub use hessra_config::{ConfigError, HessraConfig, Protocol};
53
54pub use hessra_api::{
55 ApiError, HessraClient, HessraClientBuilder, PublicKeyResponse, SignTokenRequest,
56 SignTokenResponse, SignoffInfo, TokenRequest, TokenResponse, VerifyServiceChainTokenRequest,
57 VerifyTokenRequest, VerifyTokenResponse,
58};
59
60#[derive(Error, Debug)]
62pub enum SdkError {
63 #[error("Configuration error: {0}")]
65 Config(#[from] ConfigError),
66
67 #[error("API error: {0}")]
69 Api(#[from] ApiError),
70
71 #[error("Token error: {0}")]
73 Token(#[from] TokenError),
74
75 #[error("JSON error: {0}")]
77 Json(#[from] serde_json::Error),
78
79 #[error("I/O error: {0}")]
81 Io(#[from] std::io::Error),
82
83 #[error("{0}")]
85 Generic(String),
86}
87
88#[derive(Clone, Debug, Default)]
94pub struct ServiceChain {
95 nodes: Vec<ServiceNode>,
97}
98
99impl ServiceChain {
100 pub fn new() -> Self {
102 Self { nodes: Vec::new() }
103 }
104
105 pub fn with_nodes(nodes: Vec<ServiceNode>) -> Self {
107 Self { nodes }
108 }
109
110 pub fn builder() -> ServiceChainBuilder {
112 ServiceChainBuilder::new()
113 }
114
115 pub fn add_node(&mut self, node: ServiceNode) -> &mut Self {
117 self.nodes.push(node);
118 self
119 }
120
121 pub fn with_node(mut self, node: ServiceNode) -> Self {
123 self.nodes.push(node);
124 self
125 }
126
127 pub fn nodes(&self) -> &[ServiceNode] {
129 &self.nodes
130 }
131
132 fn to_internal(&self) -> Vec<hessra_token::ServiceNode> {
134 self.nodes.to_vec()
135 }
136
137 pub fn from_json(json: &str) -> Result<Self, SdkError> {
139 let nodes: Vec<ServiceNode> = serde_json::from_str(json)?;
140 Ok(Self::with_nodes(nodes))
141 }
142
143 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self, SdkError> {
145 let mut file = File::open(path)?;
146 let mut contents = String::new();
147 file.read_to_string(&mut contents)?;
148 Self::from_json(&contents)
149 }
150
151 #[cfg(feature = "toml")]
153 pub fn from_toml(toml_str: &str) -> Result<Self, SdkError> {
154 use serde::Deserialize;
155
156 #[derive(Deserialize)]
157 struct TomlServiceChain {
158 nodes: Vec<ServiceNode>,
159 }
160
161 let chain: TomlServiceChain = toml::from_str(toml_str)
162 .map_err(|e| SdkError::Generic(format!("TOML parse error: {e}")))?;
163
164 Ok(Self::with_nodes(chain.nodes))
165 }
166
167 #[cfg(feature = "toml")]
169 pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self, SdkError> {
170 let mut file = File::open(path)?;
171 let mut contents = String::new();
172 file.read_to_string(&mut contents)?;
173 Self::from_toml(&contents)
174 }
175}
176
177#[derive(Debug, Default)]
179pub struct ServiceChainBuilder {
180 nodes: Vec<ServiceNode>,
181}
182
183impl ServiceChainBuilder {
184 pub fn new() -> Self {
186 Self::default()
187 }
188
189 pub fn add_node(mut self, node: ServiceNode) -> Self {
191 self.nodes.push(node);
192 self
193 }
194
195 pub fn build(self) -> ServiceChain {
197 ServiceChain::with_nodes(self.nodes)
198 }
199}
200
201pub struct Hessra {
206 client: HessraClient,
207 config: HessraConfig,
208}
209
210impl Hessra {
211 pub fn new(config: HessraConfig) -> Result<Self, SdkError> {
213 let client = HessraClientBuilder::new()
214 .from_config(&config)
215 .build()
216 .map_err(|e| SdkError::Generic(e.to_string()))?;
217
218 Ok(Self { client, config })
219 }
220
221 pub fn builder() -> HessraBuilder {
223 HessraBuilder::new()
224 }
225
226 pub async fn setup(&mut self) -> Result<(), SdkError> {
232 match self.get_public_key().await {
233 Ok(public_key) => {
234 self.config.public_key = Some(public_key);
235 Ok(())
236 }
237 Err(e) => Err(SdkError::Generic(e.to_string())),
238 }
239 }
240
241 pub async fn with_setup(&self) -> Result<Self, SdkError> {
246 match self.get_public_key().await {
247 Ok(public_key) => {
248 let config = self.config.to_builder().public_key(public_key).build()?;
249 Ok(Self::new(config)?)
250 }
251 Err(e) => Err(SdkError::Generic(e.to_string())),
252 }
253 }
254
255 pub async fn request_token(
258 &self,
259 resource: impl Into<String>,
260 operation: impl Into<String>,
261 ) -> Result<TokenResponse, SdkError> {
262 self.client
263 .request_token(resource.into(), operation.into())
264 .await
265 .map_err(|e| SdkError::Generic(e.to_string()))
266 }
267
268 pub async fn request_token_simple(
271 &self,
272 resource: impl Into<String>,
273 operation: impl Into<String>,
274 ) -> Result<String, SdkError> {
275 let response = self.request_token(resource, operation).await?;
276 match response.token {
277 Some(token) => Ok(token),
278 None => Err(SdkError::Generic(format!(
279 "Failed to get token: {}",
280 response.response_msg
281 ))),
282 }
283 }
284
285 pub async fn sign_token(
287 &self,
288 token: &str,
289 resource: &str,
290 operation: &str,
291 ) -> Result<SignTokenResponse, SdkError> {
292 self.client
293 .sign_token(token, resource, operation)
294 .await
295 .map_err(|e| SdkError::Generic(e.to_string()))
296 }
297
298 fn parse_authorization_service_url(url: &str) -> Result<(String, Option<u16>), SdkError> {
302 let url_str = if url.starts_with("http://") || url.starts_with("https://") {
303 url.to_string()
304 } else {
305 format!("https://{url}")
307 };
308
309 let parsed_url = url::Url::parse(&url_str).map_err(|e| {
310 SdkError::Generic(format!(
311 "Failed to parse authorization service URL '{url}': {e}"
312 ))
313 })?;
314
315 let host = parsed_url
316 .host_str()
317 .ok_or_else(|| SdkError::Generic(format!("No host found in URL: {url}")))?;
318
319 let port = if parsed_url.port().is_some() {
322 parsed_url.port()
323 } else if url.contains(':') && !url.starts_with("http://") && !url.starts_with("https://") {
324 if let Some(host_port) = url.split('/').next() {
327 if let Some(port_str) = host_port.split(':').nth(1) {
328 port_str.parse::<u16>().ok()
329 } else {
330 None
331 }
332 } else {
333 None
334 }
335 } else {
336 parsed_url.port()
337 };
338
339 Ok((host.to_string(), port))
340 }
341
342 pub async fn collect_signoffs(
345 &self,
346 initial_token_response: TokenResponse,
347 resource: &str,
348 operation: &str,
349 ) -> Result<String, SdkError> {
350 let pending_signoffs = match &initial_token_response.pending_signoffs {
352 Some(signoffs) if !signoffs.is_empty() => signoffs,
353 _ => {
354 return initial_token_response
355 .token
356 .ok_or_else(|| SdkError::Generic("No token in response".to_string()))
357 }
358 };
359
360 let mut current_token = initial_token_response.token.ok_or_else(|| {
361 SdkError::Generic("No initial token to collect signoffs for".to_string())
362 })?;
363
364 for signoff_info in pending_signoffs {
366 let (base_url, port) =
368 Self::parse_authorization_service_url(&signoff_info.authorization_service)?;
369
370 let mut client_builder = HessraClientBuilder::new()
374 .base_url(base_url)
375 .protocol(self.config.protocol.clone())
376 .mtls_cert(self.config.mtls_cert.clone())
377 .mtls_key(self.config.mtls_key.clone())
378 .server_ca(self.config.server_ca.clone());
379
380 if let Some(port) = port {
381 client_builder = client_builder.port(port);
382 }
383
384 let signoff_client = client_builder
385 .build()
386 .map_err(|e| SdkError::Generic(format!("Failed to create signoff client: {e}")))?;
387
388 let sign_response = signoff_client
389 .sign_token(¤t_token, resource, operation)
390 .await
391 .map_err(|e| {
392 SdkError::Generic(format!(
393 "Signoff failed for {}: {e}",
394 signoff_info.component
395 ))
396 })?;
397
398 current_token = sign_response.signed_token.ok_or_else(|| {
399 SdkError::Generic(format!(
400 "No signed token returned from {}: {}",
401 signoff_info.component, sign_response.response_msg
402 ))
403 })?;
404 }
405
406 Ok(current_token)
407 }
408
409 pub async fn request_token_with_signoffs(
412 &self,
413 resource: &str,
414 operation: &str,
415 ) -> Result<String, SdkError> {
416 let initial_response = self.request_token(resource, operation).await?;
417 self.collect_signoffs(initial_response, resource, operation)
418 .await
419 }
420
421 pub async fn verify_token(
427 &self,
428 token: impl Into<String>,
429 subject: impl Into<String>,
430 resource: impl Into<String>,
431 operation: impl Into<String>,
432 ) -> Result<(), SdkError> {
433 if self.config.public_key.is_some() {
434 self.verify_token_local(
435 token.into(),
436 subject.into(),
437 resource.into(),
438 operation.into(),
439 )
440 } else {
441 self.verify_token_remote(
442 token.into(),
443 subject.into(),
444 resource.into(),
445 operation.into(),
446 )
447 .await
448 .map(|_| ())
449 .map_err(|e| SdkError::Generic(e.to_string()))
450 }
451 }
452
453 pub async fn verify_token_remote(
455 &self,
456 token: impl Into<String>,
457 subject: impl Into<String>,
458 resource: impl Into<String>,
459 operation: impl Into<String>,
460 ) -> Result<String, SdkError> {
461 self.client
462 .verify_token(
463 token.into(),
464 subject.into(),
465 resource.into(),
466 operation.into(),
467 )
468 .await
469 .map_err(|e| SdkError::Generic(e.to_string()))
470 }
471
472 pub fn verify_token_local(
474 &self,
475 token: impl Into<String>,
476 subject: impl AsRef<str>,
477 resource: impl AsRef<str>,
478 operation: impl AsRef<str>,
479 ) -> Result<(), SdkError> {
480 let public_key_str = match &self.config.public_key {
481 Some(key) => key,
482 None => return Err(SdkError::Generic("Public key not configured".to_string())),
483 };
484
485 let public_key = PublicKey::from_pem(public_key_str.as_str())
486 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
487
488 let token_vec = decode_token(&token.into())?;
490
491 verify_biscuit_local(
492 token_vec,
493 public_key,
494 subject.as_ref().to_string(),
495 resource.as_ref().to_string(),
496 operation.as_ref().to_string(),
497 )
498 .map_err(SdkError::Token)
499 }
500
501 pub async fn verify_service_chain_token(
507 &self,
508 token: impl Into<String>,
509 subject: impl Into<String>,
510 resource: impl Into<String>,
511 operation: impl Into<String>,
512 service_chain: Option<&ServiceChain>,
513 component: Option<String>,
514 ) -> Result<(), SdkError> {
515 match (&self.config.public_key, service_chain) {
516 (Some(_), Some(chain)) => self.verify_service_chain_token_local(
517 token.into(),
518 subject.into(),
519 resource.into(),
520 operation.into(),
521 chain,
522 component,
523 ),
524 _ => self
525 .verify_service_chain_token_remote(
526 token.into(),
527 subject.into(),
528 resource.into(),
529 component,
530 )
531 .await
532 .map(|_| ())
533 .map_err(|e| SdkError::Generic(e.to_string())),
534 }
535 }
536
537 pub async fn verify_service_chain_token_remote(
539 &self,
540 token: impl Into<String>,
541 subject: impl Into<String>,
542 resource: impl Into<String>,
543 component: Option<String>,
544 ) -> Result<String, SdkError> {
545 self.client
546 .verify_service_chain_token(token.into(), subject.into(), resource.into(), component)
547 .await
548 .map_err(|e| SdkError::Generic(e.to_string()))
549 }
550
551 pub fn verify_service_chain_token_local(
553 &self,
554 token: String,
555 subject: impl AsRef<str>,
556 resource: impl AsRef<str>,
557 operation: impl AsRef<str>,
558 service_chain: &ServiceChain,
559 component: Option<String>,
560 ) -> Result<(), SdkError> {
561 let public_key_str = match &self.config.public_key {
562 Some(key) => key,
563 None => return Err(SdkError::Generic("Public key not configured".to_string())),
564 };
565
566 let public_key = PublicKey::from_pem(public_key_str.as_str())
567 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
568
569 let token_vec = decode_token(&token)?;
571
572 verify_service_chain_biscuit_local(
573 token_vec,
574 public_key,
575 subject.as_ref().to_string(),
576 resource.as_ref().to_string(),
577 operation.as_ref().to_string(),
578 service_chain.to_internal(),
579 component,
580 )
581 .map_err(SdkError::Token)
582 }
583
584 pub fn attest_service_chain_token(
588 &self,
589 token: String,
590 service: impl Into<String>,
591 ) -> Result<String, SdkError> {
592 let keypair_str = match &self.config.personal_keypair {
593 Some(keypair) => keypair,
594 None => {
595 return Err(SdkError::Generic(
596 "Personal keypair not configured".to_string(),
597 ))
598 }
599 };
600
601 let public_key_str = match &self.config.public_key {
602 Some(key) => key,
603 None => return Err(SdkError::Generic("Public key not configured".to_string())),
604 };
605
606 let keypair = KeyPair::from_private_key_pem(keypair_str.as_str())
608 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
609
610 let public_key = PublicKey::from_pem(public_key_str.as_str())
612 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
613
614 let token_vec = decode_token(&token)?;
616
617 let service_str = service.into();
619
620 let token_vec = add_service_node_attestation(token_vec, public_key, &service_str, &keypair)
621 .map_err(SdkError::Token)?;
622
623 Ok(encode_token(&token_vec))
624 }
625
626 pub async fn get_public_key(&self) -> Result<String, SdkError> {
628 self.client
629 .get_public_key()
630 .await
631 .map_err(|e| SdkError::Generic(e.to_string()))
632 }
633
634 pub fn client(&self) -> &HessraClient {
636 &self.client
637 }
638
639 pub fn config(&self) -> &HessraConfig {
641 &self.config
642 }
643}
644
645#[derive(Default)]
647pub struct HessraBuilder {
648 config_builder: hessra_config::HessraConfigBuilder,
649}
650
651impl HessraBuilder {
652 pub fn new() -> Self {
654 Self {
655 config_builder: HessraConfig::builder(),
656 }
657 }
658
659 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
661 self.config_builder = self.config_builder.base_url(base_url);
662 self
663 }
664
665 pub fn mtls_key(mut self, mtls_key: impl Into<String>) -> Self {
667 self.config_builder = self.config_builder.mtls_key(mtls_key);
668 self
669 }
670
671 pub fn mtls_cert(mut self, mtls_cert: impl Into<String>) -> Self {
673 self.config_builder = self.config_builder.mtls_cert(mtls_cert);
674 self
675 }
676
677 pub fn server_ca(mut self, server_ca: impl Into<String>) -> Self {
679 self.config_builder = self.config_builder.server_ca(server_ca);
680 self
681 }
682
683 pub fn port(mut self, port: u16) -> Self {
685 self.config_builder = self.config_builder.port(port);
686 self
687 }
688
689 pub fn protocol(mut self, protocol: Protocol) -> Self {
691 self.config_builder = self.config_builder.protocol(protocol);
692 self
693 }
694
695 pub fn public_key(mut self, public_key: impl Into<String>) -> Self {
697 self.config_builder = self.config_builder.public_key(public_key);
698 self
699 }
700
701 pub fn personal_keypair(mut self, keypair: impl Into<String>) -> Self {
703 self.config_builder = self.config_builder.personal_keypair(keypair);
704 self
705 }
706
707 pub fn build(self) -> Result<Hessra, SdkError> {
709 let config = self.config_builder.build()?;
710 Hessra::new(config)
711 }
712}
713
714pub async fn fetch_public_key(
718 base_url: impl Into<String>,
719 port: Option<u16>,
720 server_ca: impl Into<String>,
721) -> Result<String, SdkError> {
722 HessraClient::fetch_public_key(base_url, port, server_ca)
723 .await
724 .map_err(|e| SdkError::Generic(e.to_string()))
725}
726
727#[cfg(feature = "http3")]
731pub async fn fetch_public_key_http3(
732 base_url: impl Into<String>,
733 port: Option<u16>,
734 server_ca: impl Into<String>,
735) -> Result<String, SdkError> {
736 HessraClient::fetch_public_key_http3(base_url, port, server_ca)
737 .await
738 .map_err(|e| SdkError::Generic(e.to_string()))
739}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744
745 #[test]
746 fn test_service_chain_creation() {
747 let json = r#"[
749 {
750 "component": "service1",
751 "public_key": "ed25519/abcdef1234567890"
752 },
753 {
754 "component": "service2",
755 "public_key": "ed25519/0987654321fedcba"
756 }
757 ]"#;
758
759 let service_chain = ServiceChain::from_json(json).unwrap();
760 assert_eq!(service_chain.nodes().len(), 2);
761 assert_eq!(service_chain.nodes()[0].component, "service1");
762 assert_eq!(
763 service_chain.nodes()[0].public_key,
764 "ed25519/abcdef1234567890"
765 );
766 assert_eq!(service_chain.nodes()[1].component, "service2");
767 assert_eq!(
768 service_chain.nodes()[1].public_key,
769 "ed25519/0987654321fedcba"
770 );
771
772 let mut chain = ServiceChain::new();
774 let node = ServiceNode {
775 component: "service3".to_string(),
776 public_key: "ed25519/1122334455667788".to_string(),
777 };
778 chain.add_node(node);
779 assert_eq!(chain.nodes().len(), 1);
780 assert_eq!(chain.nodes()[0].component, "service3");
781 }
782
783 #[test]
784 fn test_service_chain_builder() {
785 let builder = ServiceChainBuilder::new();
786 let node1 = ServiceNode {
787 component: "auth".to_string(),
788 public_key: "ed25519/auth123".to_string(),
789 };
790 let node2 = ServiceNode {
791 component: "payment".to_string(),
792 public_key: "ed25519/payment456".to_string(),
793 };
794
795 let chain = builder.add_node(node1).add_node(node2).build();
796
797 assert_eq!(chain.nodes().len(), 2);
798 assert_eq!(chain.nodes()[0].component, "auth");
799 assert_eq!(chain.nodes()[1].component, "payment");
800 }
801
802 #[test]
803 fn test_parse_authorization_service_url() {
804 let (base_url, port) =
806 Hessra::parse_authorization_service_url("https://127.0.0.1:4433/sign_token").unwrap();
807 assert_eq!(base_url, "127.0.0.1");
808 assert_eq!(port, Some(4433));
809
810 let (base_url, port) =
812 Hessra::parse_authorization_service_url("http://example.com:8080/api/sign").unwrap();
813 assert_eq!(base_url, "example.com");
814 assert_eq!(port, Some(8080));
815
816 let (base_url, port) =
818 Hessra::parse_authorization_service_url("test.hessra.net:443/sign_token").unwrap();
819 assert_eq!(base_url, "test.hessra.net");
820 assert_eq!(port, Some(443));
821
822 let (base_url, port) =
824 Hessra::parse_authorization_service_url("example.com/api/endpoint").unwrap();
825 assert_eq!(base_url, "example.com");
826 assert_eq!(port, None);
827
828 let (base_url, port) =
830 Hessra::parse_authorization_service_url("https://localhost:8443").unwrap();
831 assert_eq!(base_url, "localhost");
832 assert_eq!(port, Some(8443));
833
834 let (base_url, port) = Hessra::parse_authorization_service_url("api.example.org").unwrap();
836 assert_eq!(base_url, "api.example.org");
837 assert_eq!(port, None);
838 }
839}