Fix OIDC: ECDSA private key support for SEC1 format.

This commit is contained in:
Maurus Decimus
2026-06-28 18:35:12 +02:00
parent 000691a285
commit 0c8e567888
3 changed files with 111 additions and 4 deletions

View File

@@ -5,7 +5,7 @@
*/
use crate::{
config::{build_ecdsa_pem, build_rsa_keypair},
config::{EcKeyCurve, build_ecdsa_pem, build_rsa_keypair},
manager::application::Resource,
};
use biscuit::{
@@ -197,19 +197,22 @@ async fn parse_ecdsa_key(
auth: &OidcProvider,
oidc_signature_algorithm: SignatureAlgorithm,
) -> Result<(Secret, AlgorithmParameters), String> {
let (alg, curve) = match oidc_signature_algorithm {
let (alg, curve, ec_curve) = match oidc_signature_algorithm {
SignatureAlgorithm::ES256 => (
&signature::ECDSA_P256_SHA256_FIXED_SIGNING,
EllipticCurve::P256,
EcKeyCurve::P256,
),
SignatureAlgorithm::ES384 => (
&signature::ECDSA_P384_SHA384_FIXED_SIGNING,
EllipticCurve::P384,
EcKeyCurve::P384,
),
_ => unreachable!(),
};
let ecdsa_key_pair = build_ecdsa_pem(alg, auth.signature_key.secret().await?.as_ref())?;
let ecdsa_key_pair =
build_ecdsa_pem(alg, ec_curve, auth.signature_key.secret().await?.as_ref())?;
let ecdsa_public_key = ecdsa_key_pair.public_key().as_ref();
let (x, y) = match oidc_signature_algorithm {

View File

@@ -17,6 +17,7 @@ use crate::{
use arc_swap::ArcSwap;
use groupware::GroupwareConfig;
use hyper::HeaderMap;
use pkcs8::EncodePrivateKey;
use ring::signature::{EcdsaKeyPair, RsaKeyPair};
use store::registry::bootstrap::Bootstrap;
use telemetry::Metrics;
@@ -115,8 +116,15 @@ pub fn build_rsa_keypair(pem: &str) -> Result<RsaKeyPair, String> {
Err("No RSA key found in PEM".to_string())
}
#[derive(Clone, Copy)]
pub enum EcKeyCurve {
P256,
P384,
}
pub fn build_ecdsa_pem(
alg: &'static ring::signature::EcdsaSigningAlgorithm,
curve: EcKeyCurve,
pem: &str,
) -> Result<EcdsaKeyPair, String> {
for item in rustls_pemfile::read_all(&mut Cursor::new(pem)) {
@@ -129,9 +137,104 @@ pub fn build_ecdsa_pem(
)
.map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}"));
}
rustls_pemfile::Item::Sec1Key(key) => {
let pkcs8 = curve.sec1_to_pkcs8(key.secret_sec1_der())?;
return EcdsaKeyPair::from_pkcs8(
alg,
pkcs8.as_bytes(),
&ring::rand::SystemRandom::new(),
)
.map_err(|err| format!("Failed to parse SEC1 ECDSA key: {err}"));
}
_ => continue, // Skip certificates, DH params, etc.
}
}
Err("No ECDSA key found in PEM".to_string())
Err("No usable ECDSA private key found in PEM (expected PKCS8 or SEC1)".to_string())
}
impl EcKeyCurve {
fn sec1_to_pkcs8(self, der: &[u8]) -> Result<pkcs8::SecretDocument, String> {
match self {
EcKeyCurve::P256 => p256::SecretKey::from_sec1_der(der)
.map_err(|err| format!("Failed to parse SEC1 ECDSA key: {err}"))?
.to_pkcs8_der()
.map_err(|err| format!("Failed to convert SEC1 ECDSA key to PKCS8: {err}")),
EcKeyCurve::P384 => p384::SecretKey::from_sec1_der(der)
.map_err(|err| format!("Failed to parse SEC1 ECDSA key: {err}"))?
.to_pkcs8_der()
.map_err(|err| format!("Failed to convert SEC1 ECDSA key to PKCS8: {err}")),
}
}
}
#[cfg(test)]
mod tests {
use super::{EcKeyCurve, build_ecdsa_pem};
use ring::signature::{ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING};
const P256_SEC1: &str = "-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJ9a6n/cu7XaQez5ZX8z8jDFkkfsMB1P9Vbqzbaes2zOoAoGCCqGSM49
AwEHoUQDQgAEPCbID7bo+8Nk1vIsTFhVKwRWvb9GWTzzwS75Dd8iZuFl23Twn6Sp
V2ZO1FC0WyXxcVOMZN2sJFlCjtaQS+p5Zg==
-----END EC PRIVATE KEY-----";
const P256_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgn1rqf9y7tdpB7Pll
fzPyMMWSR+wwHU/1VurNtp6zbM6hRANCAAQ8JsgPtuj7w2TW8ixMWFUrBFa9v0ZZ
PPPBLvkN3yJm4WXbdPCfpKlXZk7UULRbJfFxU4xk3awkWUKO1pBL6nlm
-----END PRIVATE KEY-----";
const P384_SEC1: &str = "-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAeecJf8ju/70Nf5nbI4DeRo/+Z3VWXUvB+GwuUczew7fyMbyc6B3EE
BskOIqvqu6egBwYFK4EEACKhZANiAAQQjDW03Xn2h9ZmmCMRx+uRaLLfg4o2XITE
pwACH9EY4IjTe9LNNp5CTjERd+RlpWxkYopmDS5Trzycz9sDxxSzzXmq90vomJqt
fTnNHPFHuR2SAiwuzUf26rcPwa7DCWk=
-----END EC PRIVATE KEY-----";
const P384_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAeecJf8ju/70Nf5nbI
4DeRo/+Z3VWXUvB+GwuUczew7fyMbyc6B3EEBskOIqvqu6ehZANiAAQQjDW03Xn2
h9ZmmCMRx+uRaLLfg4o2XITEpwACH9EY4IjTe9LNNp5CTjERd+RlpWxkYopmDS5T
rzycz9sDxxSzzXmq90vomJqtfTnNHPFHuR2SAiwuzUf26rcPwa7DCWk=
-----END PRIVATE KEY-----";
#[test]
fn ecdsa_pem_accepts_sec1_and_pkcs8() {
build_ecdsa_pem(
&ECDSA_P256_SHA256_FIXED_SIGNING,
EcKeyCurve::P256,
P256_SEC1,
)
.expect("P-256 SEC1 key should parse");
build_ecdsa_pem(
&ECDSA_P256_SHA256_FIXED_SIGNING,
EcKeyCurve::P256,
P256_PKCS8,
)
.expect("P-256 PKCS8 key should parse");
build_ecdsa_pem(
&ECDSA_P384_SHA384_FIXED_SIGNING,
EcKeyCurve::P384,
P384_SEC1,
)
.expect("P-384 SEC1 key should parse");
build_ecdsa_pem(
&ECDSA_P384_SHA384_FIXED_SIGNING,
EcKeyCurve::P384,
P384_PKCS8,
)
.expect("P-384 PKCS8 key should parse");
}
#[test]
fn ecdsa_pem_rejects_keyless_pem() {
let err = build_ecdsa_pem(
&ECDSA_P256_SHA256_FIXED_SIGNING,
EcKeyCurve::P256,
"-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----",
)
.unwrap_err();
assert!(err.contains("No usable ECDSA private key"), "{err}");
}
}