Skip to content

Commit 8de43ec

Browse files
committed
Add support for signing SAML requests
1 parent 643836e commit 8de43ec

File tree

7 files changed

+55
-4
lines changed

7 files changed

+55
-4
lines changed

.env.example

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@ SAML_IDP_METADATA_URL=https://sso.some.provider/saml/metadata.xml
1212
SAML_SP_ENTITY_ID=great-client-abcd
1313
SAML_SP_ACS_URL=http://localhost:3000/saml/acs
1414
SAML_SP_SLO_URL=http://localhost:3000/saml/slo
15-
SAML_SP_VERIFY_SIGNATURES=true
15+
SAML_SP_VERIFY_SIGNATURES=true
16+
17+
# private keys should always be in PEM format, not DER
18+
SAML_SP_PRIVATE_KEY_LOCATION=./keys/private.der
19+
20+
# can either be rsa or ecdsa
21+
SAML_SP_PRIVATE_KEY_TYPE=rsa

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
/target
22
.idea/
33
.env
4+
5+
keys/**
6+
!keys/.gitkeep

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ tracing = "0.1.41"
2525
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
2626
url = "2.5.4"
2727
urlencoding = "2.1.3"
28+
openssl = "0.10.68"

keys/.gitkeep

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
keys

src/env.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::sso::saml::SamlPrivateKeyType;
12
use thiserror::Error;
23
use tracing::warn;
34
use url::Url;
@@ -27,6 +28,8 @@ pub struct SamlConfig {
2728
pub acs_url: BaseUrlParts,
2829
pub slo_url: BaseUrlParts,
2930
pub verify_signatures: bool,
31+
pub private_key: String,
32+
pub private_key_type: SamlPrivateKeyType,
3033
}
3134

3235
#[derive(Debug)]
@@ -105,6 +108,14 @@ fn init_saml_config() -> Result<SamlConfig, EnvError> {
105108
verify_signatures: get_env("SAML_SP_VERIFY_SIGNATURES")?
106109
.parse()
107110
.map_err(|_| EnvError::VarError("SAML_SP_VERIFY_SIGNATURES"))?,
111+
private_key: get_env("SAML_SP_PRIVATE_KEY_LOCATION")?,
112+
private_key_type: get_env("SAML_SP_PRIVATE_KEY_TYPE").map(|val| {
113+
match val.to_ascii_lowercase().as_str() {
114+
"rsa" => Ok(SamlPrivateKeyType::Rsa),
115+
"ecdsa" => Ok(SamlPrivateKeyType::Ecdsa),
116+
_ => Err(EnvError::VarError("SAML_SP_PRIVATE_KEY_TYPE")),
117+
}
118+
})??,
108119
})
109120
}
110121

src/sso/saml.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::models::UserAttribute;
33
use base64::prelude::BASE64_STANDARD;
44
use base64::Engine;
55
use multimap::MultiMap;
6+
use openssl::pkey::PKey;
67
use samael::attribute::Attribute;
78
use samael::metadata::{EntityDescriptor, HTTP_REDIRECT_BINDING};
89
use samael::schema::{Assertion, Subject, SubjectNameID};
@@ -37,10 +38,23 @@ pub enum SamlSetupError {
3738

3839
#[error("Failed to build SAML service provider: {0}")]
3940
ServiceProviderError(#[from] samael::service_provider::ServiceProviderBuilderError),
41+
42+
#[error("Failed to read private key: {0}")]
43+
IOError(#[from] std::io::Error),
44+
45+
#[error("Failed to parse private key: {0}")]
46+
OpensslError(#[from] openssl::error::ErrorStack),
47+
}
48+
49+
#[derive(Debug, Clone)]
50+
pub enum SamlPrivateKeyType {
51+
Rsa,
52+
Ecdsa,
4053
}
4154

4255
pub struct SamlServiceProvider {
4356
sp: ServiceProvider,
57+
private_key: PKey<openssl::pkey::Private>,
4458
}
4559

4660
#[derive(Error, Debug)]
@@ -84,6 +98,18 @@ impl SamlServiceProvider {
8498
}
8599
})?;
86100

101+
let private_key_bytes = std::fs::read(&config.private_key)?;
102+
103+
let private_key = match config.private_key_type {
104+
SamlPrivateKeyType::Rsa => {
105+
PKey::from_rsa(openssl::rsa::Rsa::private_key_from_pem(&private_key_bytes)?)?
106+
}
107+
// TODO: untested
108+
SamlPrivateKeyType::Ecdsa => PKey::from_ec_key(
109+
openssl::ec::EcKey::private_key_from_pem(&private_key_bytes)?,
110+
)?,
111+
};
112+
87113
let sp = samael::service_provider::ServiceProviderBuilder::default()
88114
.entity_id(config.entity_id.clone())
89115
.allow_idp_initiated(false)
@@ -92,7 +118,7 @@ impl SamlServiceProvider {
92118
.slo_url(config.slo_url.full_url.clone())
93119
.build()?;
94120

95-
Ok(Self { sp })
121+
Ok(Self { sp, private_key })
96122
}
97123

98124
fn remove_signing_descriptors(mut metadata: EntityDescriptor) -> EntityDescriptor {
@@ -125,7 +151,9 @@ impl SamlServiceProvider {
125151
})?,
126152
)?;
127153

128-
let url = authn_request.redirect("")?.unwrap();
154+
let url = authn_request
155+
.signed_redirect("", self.private_key.clone())?
156+
.unwrap();
129157

130158
Ok(SamlAuthenticationRequest {
131159
id: authn_request.id.clone(),
@@ -182,7 +210,7 @@ impl SamlServiceProvider {
182210
attribute_statement
183211
.attributes
184212
.into_iter()
185-
.filter_map(|attribute| Self::parse_attribute(attribute))
213+
.filter_map(Self::parse_attribute)
186214
})
187215
.collect();
188216

0 commit comments

Comments
 (0)