diff --git a/CHANGELOG b/CHANGELOG index a25edfb..6a8a2f8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Changelog +### Unreleased + +- Add `signed_post` function to `AuthnRequest` + ### 0.0.15 - Updates dependencies diff --git a/src/idp/response_builder.rs b/src/idp/response_builder.rs index c5ac56e..db657e7 100644 --- a/src/idp/response_builder.rs +++ b/src/idp/response_builder.rs @@ -124,7 +124,7 @@ fn build_response( destination: Some(destination.to_string()), consent: None, issuer: Some(issuer.clone()), - signature: Some(Signature::template(&response_id, x509_cert)), + signature: Some(Signature::default_template(&response_id, x509_cert)), status: Some(Status { status_code: StatusCode { value: Some("urn:oasis:names:tc:SAML:2.0:status:Success".to_string()), diff --git a/src/schema/authn_request.rs b/src/schema/authn_request.rs index f7e670b..19b39bb 100644 --- a/src/schema/authn_request.rs +++ b/src/schema/authn_request.rs @@ -121,6 +121,29 @@ impl AuthnRequest { self } + pub fn add_signature(&mut self, signature: Signature) -> &mut Self { + self.signature = Some(signature); + self + } + + pub fn default_signature(&mut self, certificate_der: &[u8]) -> &mut Self { + self.add_signature(Signature::default_template(&self.id, certificate_der)) + } + + pub fn make_signature( + &mut self, + certificate_der: &[u8], + sig_alg: &str, + digest_alg: &str, + ) -> &mut Self { + self.add_signature(Signature::template( + &self.id, + certificate_der, + sig_alg, + digest_alg, + )) + } + #[cfg(feature = "xmlsec")] pub fn to_signed_xml( &self, diff --git a/src/service_provider/mod.rs b/src/service_provider/mod.rs index a237052..a0a41a6 100644 --- a/src/service_provider/mod.rs +++ b/src/service_provider/mod.rs @@ -78,6 +78,8 @@ pub enum Error { ResponseExpired { time: String }, #[error("SAML Response StatusCode is not successful: {}", code)] ResponseBadStatusCode { code: String }, + #[error("Failed to sign AuthnRequest: Signature is missing")] + RequestMissingSignature, #[error("Encrypted SAML Assertions are not yet supported")] EncryptedAssertionsNotYetSupported, #[error("SAML Response and all assertions must be signed")] @@ -512,9 +514,26 @@ fn parse_certificates(key_descriptor: &KeyDescriptor) -> Result, impl AuthnRequest { pub fn post(&self, relay_state: &str) -> Result, Box> { - let encoded = general_purpose::STANDARD.encode(self.to_xml()?.as_bytes()); + Ok(self.make_post(&self.to_xml()?, relay_state)) + } + + pub fn signed_post( + &self, + relay_state: &str, + private_key_der: &[u8], + ) -> Result, Box> { + if self.signature.is_none() { + Err(Error::RequestMissingSignature)? + } + + let signed_request = crypto::sign_xml(&self.to_xml()?, &private_key_der)?; + Ok(self.make_post(&signed_request, relay_state)) + } + + fn make_post(&self, request: &str, relay_state: &str) -> Option { + let encoded = general_purpose::STANDARD.encode(request.as_bytes()); if let Some(dest) = &self.destination { - Ok(Some(format!( + Some(format!( r#"
@@ -527,9 +546,9 @@ impl AuthnRequest { "#, dest, encoded, relay_state - ))) + )) } else { - Ok(None) + None } } diff --git a/src/signature.rs b/src/signature.rs index 2878fa6..58bcabc 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -21,7 +21,16 @@ pub struct Signature { } impl Signature { - pub fn template(ref_id: &str, x509_cert_der: &[u8]) -> Self { + pub fn default_template(ref_id: &str, x509_cert_der: &[u8]) -> Self { + Signature::template( + ref_id, + x509_cert_der, + "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + "http://www.w3.org/2000/09/xmldsig#sha1", + ) + } + + pub fn template(ref_id: &str, x509_cert_der: &[u8], sig_alg: &str, digest_alg: &str) -> Self { Signature { id: None, signed_info: SignedInfo { @@ -30,7 +39,7 @@ impl Signature { algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#".to_string(), }, signature_method: SignatureMethod { - algorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256".to_string(), + algorithm: sig_alg.to_string(), hmac_output_length: None, }, reference: vec![Reference { @@ -48,7 +57,7 @@ impl Signature { ], }), digest_method: DigestMethod { - algorithm: "http://www.w3.org/2000/09/xmldsig#sha1".to_string(), + algorithm: digest_alg.to_string(), }, digest_value: Some(DigestValue { base64_content: Some("".to_string()),