RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.
This commit is contained in:
311
zeroidc/vendor/openidconnect/src/claims.rs
vendored
Normal file
311
zeroidc/vendor/openidconnect/src/claims.rs
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
use std::fmt::{Debug, Formatter, Result as FormatterResult};
|
||||
use std::marker::PhantomData;
|
||||
use std::str;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::de::{Deserialize, DeserializeOwned, Deserializer, MapAccess, Visitor};
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::helpers::FlattenFilter;
|
||||
use crate::types::helpers::{split_language_tag_key, timestamp_to_utc, utc_to_seconds};
|
||||
use crate::types::{LocalizedClaim, Timestamp};
|
||||
use crate::{
|
||||
AddressCountry, AddressLocality, AddressPostalCode, AddressRegion, EndUserBirthday,
|
||||
EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName,
|
||||
EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone,
|
||||
EndUserUsername, EndUserWebsiteUrl, FormattedAddress, LanguageTag, StreetAddress,
|
||||
SubjectIdentifier,
|
||||
};
|
||||
|
||||
///
|
||||
/// Additional claims beyond the set of Standard Claims defined by OpenID Connect Core.
|
||||
///
|
||||
pub trait AdditionalClaims: Debug + DeserializeOwned + Serialize + 'static {}
|
||||
|
||||
///
|
||||
/// No additional claims.
|
||||
///
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
// In order to support serde flatten, this must be an empty struct rather than an empty
|
||||
// tuple struct.
|
||||
pub struct EmptyAdditionalClaims {}
|
||||
impl AdditionalClaims for EmptyAdditionalClaims {}
|
||||
|
||||
///
|
||||
/// Address claims.
|
||||
///
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct AddressClaim {
|
||||
///
|
||||
/// Full mailing address, formatted for display or use on a mailing label.
|
||||
///
|
||||
/// This field MAY contain multiple lines, separated by newlines. Newlines can be represented
|
||||
/// either as a carriage return/line feed pair (`\r\n`) or as a single line feed character
|
||||
/// (`\n`).
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub formatted: Option<FormattedAddress>,
|
||||
///
|
||||
/// Full street address component, which MAY include house number, street name, Post Office Box,
|
||||
/// and multi-line extended street address information.
|
||||
///
|
||||
/// This field MAY contain multiple lines, separated by newlines. Newlines can be represented
|
||||
/// either as a carriage return/line feed pair (`\r\n`) or as a single line feed character
|
||||
/// (`\n`).
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub street_address: Option<StreetAddress>,
|
||||
///
|
||||
/// City or locality component.
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub locality: Option<AddressLocality>,
|
||||
///
|
||||
/// State, province, prefecture, or region component.
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub region: Option<AddressRegion>,
|
||||
///
|
||||
/// Zip code or postal code component.
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub postal_code: Option<AddressPostalCode>,
|
||||
///
|
||||
/// Country name component.
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub country: Option<AddressCountry>,
|
||||
}
|
||||
|
||||
///
|
||||
/// Gender claim.
|
||||
///
|
||||
pub trait GenderClaim: Clone + Debug + DeserializeOwned + Serialize + 'static {}
|
||||
|
||||
///
|
||||
/// Standard Claims defined by OpenID Connect Core.
|
||||
///
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct StandardClaims<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
pub(crate) sub: SubjectIdentifier,
|
||||
pub(crate) name: Option<LocalizedClaim<EndUserName>>,
|
||||
pub(crate) given_name: Option<LocalizedClaim<EndUserGivenName>>,
|
||||
pub(crate) family_name: Option<LocalizedClaim<EndUserFamilyName>>,
|
||||
pub(crate) middle_name: Option<LocalizedClaim<EndUserMiddleName>>,
|
||||
pub(crate) nickname: Option<LocalizedClaim<EndUserNickname>>,
|
||||
pub(crate) preferred_username: Option<EndUserUsername>,
|
||||
pub(crate) profile: Option<LocalizedClaim<EndUserProfileUrl>>,
|
||||
pub(crate) picture: Option<LocalizedClaim<EndUserPictureUrl>>,
|
||||
pub(crate) website: Option<LocalizedClaim<EndUserWebsiteUrl>>,
|
||||
pub(crate) email: Option<EndUserEmail>,
|
||||
pub(crate) email_verified: Option<bool>,
|
||||
pub(crate) gender: Option<GC>,
|
||||
pub(crate) birthday: Option<EndUserBirthday>,
|
||||
pub(crate) zoneinfo: Option<EndUserTimezone>,
|
||||
pub(crate) locale: Option<LanguageTag>,
|
||||
pub(crate) phone_number: Option<EndUserPhoneNumber>,
|
||||
pub(crate) phone_number_verified: Option<bool>,
|
||||
pub(crate) address: Option<AddressClaim>,
|
||||
pub(crate) updated_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
impl<GC> StandardClaims<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
///
|
||||
/// Initializes a set of Standard Claims.
|
||||
///
|
||||
/// The Subject (`sub`) claim is the only required Standard Claim.
|
||||
///
|
||||
pub fn new(subject: SubjectIdentifier) -> Self {
|
||||
Self {
|
||||
sub: subject,
|
||||
name: None,
|
||||
given_name: None,
|
||||
family_name: None,
|
||||
middle_name: None,
|
||||
nickname: None,
|
||||
preferred_username: None,
|
||||
profile: None,
|
||||
picture: None,
|
||||
website: None,
|
||||
email: None,
|
||||
email_verified: None,
|
||||
gender: None,
|
||||
birthday: None,
|
||||
zoneinfo: None,
|
||||
locale: None,
|
||||
phone_number: None,
|
||||
phone_number_verified: None,
|
||||
address: None,
|
||||
updated_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the Subject (`sub`) claim.
|
||||
///
|
||||
pub fn subject(&self) -> &SubjectIdentifier {
|
||||
&self.sub
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the Subject (`sub`) claim.
|
||||
///
|
||||
pub fn set_subject(mut self, subject: SubjectIdentifier) -> Self {
|
||||
self.sub = subject;
|
||||
self
|
||||
}
|
||||
|
||||
field_getters_setters![
|
||||
pub self [self] ["claim"] {
|
||||
set_name -> name[Option<LocalizedClaim<EndUserName>>],
|
||||
set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],
|
||||
set_family_name ->
|
||||
family_name[Option<LocalizedClaim<EndUserFamilyName>>],
|
||||
set_middle_name ->
|
||||
middle_name[Option<LocalizedClaim<EndUserMiddleName>>],
|
||||
set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],
|
||||
set_preferred_username -> preferred_username[Option<EndUserUsername>],
|
||||
set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],
|
||||
set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],
|
||||
set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],
|
||||
set_email -> email[Option<EndUserEmail>],
|
||||
set_email_verified -> email_verified[Option<bool>],
|
||||
set_gender -> gender[Option<GC>],
|
||||
set_birthday -> birthday[Option<EndUserBirthday>],
|
||||
set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],
|
||||
set_locale -> locale[Option<LanguageTag>],
|
||||
set_phone_number -> phone_number[Option<EndUserPhoneNumber>],
|
||||
set_phone_number_verified -> phone_number_verified[Option<bool>],
|
||||
set_address -> address[Option<AddressClaim>],
|
||||
set_updated_at -> updated_at[Option<DateTime<Utc>>],
|
||||
}
|
||||
];
|
||||
}
|
||||
impl<GC> FlattenFilter for StandardClaims<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
// When another struct (i.e., additional claims) is co-flattened with this one, only include
|
||||
// fields in that other struct which are not part of this struct.
|
||||
fn should_include(field_name: &str) -> bool {
|
||||
!matches!(
|
||||
split_language_tag_key(field_name),
|
||||
("sub", None)
|
||||
| ("name", _)
|
||||
| ("given_name", _)
|
||||
| ("family_name", _)
|
||||
| ("middle_name", _)
|
||||
| ("nickname", _)
|
||||
| ("preferred_username", None)
|
||||
| ("profile", _)
|
||||
| ("picture", _)
|
||||
| ("website", _)
|
||||
| ("email", None)
|
||||
| ("email_verified", None)
|
||||
| ("gender", None)
|
||||
| ("birthday", None)
|
||||
| ("zoneinfo", None)
|
||||
| ("locale", None)
|
||||
| ("phone_number", None)
|
||||
| ("phone_number_verified", None)
|
||||
| ("address", None)
|
||||
| ("updated_at", None)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<'de, GC> Deserialize<'de> for StandardClaims<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
///
|
||||
/// Special deserializer that supports [RFC 5646](https://tools.ietf.org/html/rfc5646) language
|
||||
/// tags associated with human-readable client metadata fields.
|
||||
///
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ClaimsVisitor<GC: GenderClaim>(PhantomData<GC>);
|
||||
impl<'de, GC> Visitor<'de> for ClaimsVisitor<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
type Value = StandardClaims<GC>;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
|
||||
formatter.write_str("struct StandardClaims")
|
||||
}
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
deserialize_fields! {
|
||||
map {
|
||||
[sub]
|
||||
[LanguageTag(name)]
|
||||
[LanguageTag(given_name)]
|
||||
[LanguageTag(family_name)]
|
||||
[LanguageTag(middle_name)]
|
||||
[LanguageTag(nickname)]
|
||||
[Option(preferred_username)]
|
||||
[LanguageTag(profile)]
|
||||
[LanguageTag(picture)]
|
||||
[LanguageTag(website)]
|
||||
[Option(email)]
|
||||
[Option(email_verified)]
|
||||
[Option(gender)]
|
||||
[Option(birthday)]
|
||||
[Option(zoneinfo)]
|
||||
[Option(locale)]
|
||||
[Option(phone_number)]
|
||||
[Option(phone_number_verified)]
|
||||
[Option(address)]
|
||||
[Option(DateTime(Seconds(updated_at)))]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(ClaimsVisitor(PhantomData))
|
||||
}
|
||||
}
|
||||
impl<GC> Serialize for StandardClaims<GC>
|
||||
where
|
||||
GC: GenderClaim,
|
||||
{
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
|
||||
where
|
||||
SE: Serializer,
|
||||
{
|
||||
serialize_fields! {
|
||||
self -> serializer {
|
||||
[sub]
|
||||
[LanguageTag(name)]
|
||||
[LanguageTag(given_name)]
|
||||
[LanguageTag(family_name)]
|
||||
[LanguageTag(middle_name)]
|
||||
[LanguageTag(nickname)]
|
||||
[Option(preferred_username)]
|
||||
[LanguageTag(profile)]
|
||||
[LanguageTag(picture)]
|
||||
[LanguageTag(website)]
|
||||
[Option(email)]
|
||||
[Option(email_verified)]
|
||||
[Option(gender)]
|
||||
[Option(birthday)]
|
||||
[Option(zoneinfo)]
|
||||
[Option(locale)]
|
||||
[Option(phone_number)]
|
||||
[Option(phone_number_verified)]
|
||||
[Option(address)]
|
||||
[Option(DateTime(Seconds(updated_at)))]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
197
zeroidc/vendor/openidconnect/src/core/crypto.rs
vendored
Normal file
197
zeroidc/vendor/openidconnect/src/core/crypto.rs
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use ring::hmac;
|
||||
use ring::rand::SecureRandom;
|
||||
use ring::signature as ring_signature;
|
||||
|
||||
use crate::types::Base64UrlEncodedBytes;
|
||||
use crate::{JsonWebKey, SignatureVerificationError, SigningError};
|
||||
|
||||
use super::{jwk::CoreJsonCurveType, CoreJsonWebKey, CoreJsonWebKeyType};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub fn sign_hmac(key: &[u8], hmac_alg: hmac::Algorithm, msg: &[u8]) -> hmac::Tag {
|
||||
let signing_key = hmac::Key::new(hmac_alg, key);
|
||||
hmac::sign(&signing_key, msg)
|
||||
}
|
||||
|
||||
pub fn verify_hmac(
|
||||
key: &CoreJsonWebKey,
|
||||
hmac_alg: hmac::Algorithm,
|
||||
msg: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Result<(), SignatureVerificationError> {
|
||||
let k = key.k.as_ref().ok_or_else(|| {
|
||||
SignatureVerificationError::InvalidKey("Symmetric key `k` is missing".to_string())
|
||||
})?;
|
||||
let verification_key = hmac::Key::new(hmac_alg, k);
|
||||
hmac::verify(&verification_key, msg, signature)
|
||||
.map_err(|_| SignatureVerificationError::CryptoError("bad HMAC".to_string()))
|
||||
}
|
||||
|
||||
pub fn sign_rsa(
|
||||
key: &ring_signature::RsaKeyPair,
|
||||
padding_alg: &'static dyn ring_signature::RsaEncoding,
|
||||
rng: &dyn SecureRandom,
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>, SigningError> {
|
||||
let sig_len = key.public_modulus_len();
|
||||
let mut sig = vec![0; sig_len];
|
||||
key.sign(padding_alg, rng, msg, &mut sig)
|
||||
.map_err(|_| SigningError::CryptoError)?;
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
fn rsa_public_key(
|
||||
key: &CoreJsonWebKey,
|
||||
) -> Result<(&Base64UrlEncodedBytes, &Base64UrlEncodedBytes), String> {
|
||||
if *key.key_type() != CoreJsonWebKeyType::RSA {
|
||||
Err("RSA key required".to_string())
|
||||
} else {
|
||||
let n = key
|
||||
.n
|
||||
.as_ref()
|
||||
.ok_or_else(|| "RSA modulus `n` is missing".to_string())?;
|
||||
let e = key
|
||||
.e
|
||||
.as_ref()
|
||||
.ok_or_else(|| "RSA exponent `e` is missing".to_string())?;
|
||||
Ok((n, e))
|
||||
}
|
||||
}
|
||||
|
||||
fn ec_public_key(
|
||||
key: &CoreJsonWebKey,
|
||||
) -> Result<
|
||||
(
|
||||
&Base64UrlEncodedBytes,
|
||||
&Base64UrlEncodedBytes,
|
||||
&CoreJsonCurveType,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
if *key.key_type() != CoreJsonWebKeyType::EllipticCurve {
|
||||
Err("EC key required".to_string())
|
||||
} else {
|
||||
let x = key
|
||||
.x
|
||||
.as_ref()
|
||||
.ok_or_else(|| "EC `x` part is missing".to_string())?;
|
||||
let y = key
|
||||
.y
|
||||
.as_ref()
|
||||
.ok_or_else(|| "EC `y` part is missing".to_string())?;
|
||||
let crv = key
|
||||
.crv
|
||||
.as_ref()
|
||||
.ok_or_else(|| "EC `crv` part is missing".to_string())?;
|
||||
Ok((x, y, crv))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_rsa_signature(
|
||||
key: &CoreJsonWebKey,
|
||||
params: &ring_signature::RsaParameters,
|
||||
msg: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Result<(), SignatureVerificationError> {
|
||||
let (n, e) = rsa_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;
|
||||
// let's n and e as a big integers to prevent issues with leading zeros
|
||||
// according to https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.1
|
||||
// `n` is alwasy unsigned (hence has sign plus)
|
||||
|
||||
let n_bigint = BigInt::from_bytes_be(Sign::Plus, n.deref());
|
||||
let e_bigint = BigInt::from_bytes_be(Sign::Plus, e.deref());
|
||||
let public_key = ring_signature::RsaPublicKeyComponents {
|
||||
n: &n_bigint.to_bytes_be().1,
|
||||
e: &e_bigint.to_bytes_be().1,
|
||||
};
|
||||
|
||||
public_key
|
||||
.verify(params, msg, signature)
|
||||
.map_err(|_| SignatureVerificationError::CryptoError("bad signature".to_string()))
|
||||
}
|
||||
/// According to RFC5480, Section-2.2 implementations of Elliptic Curve Cryptography MUST support the uncompressed form.
|
||||
/// The first octet of the octet string indicates whether the uncompressed or compressed form is used. For the uncompressed
|
||||
/// form, the first octet has to be 0x04.
|
||||
/// According to https://briansmith.org/rustdoc/ring/signature/index.html#ecdsa__fixed-details-fixed-length-pkcs11-style-ecdsa-signatures,
|
||||
/// to recover the X and Y coordinates from an octet string, the Octet-String-To-Elliptic-Curve-Point Conversion
|
||||
/// is used (Section 2.3.4 of https://www.secg.org/sec1-v2.pdf).
|
||||
|
||||
pub fn verify_ec_signature(
|
||||
key: &CoreJsonWebKey,
|
||||
params: &'static ring_signature::EcdsaVerificationAlgorithm,
|
||||
msg: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Result<(), SignatureVerificationError> {
|
||||
let (x, y, crv) = ec_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;
|
||||
if *crv == CoreJsonCurveType::P521 {
|
||||
return Err(SignatureVerificationError::UnsupportedAlg(
|
||||
"P521".to_string(),
|
||||
));
|
||||
}
|
||||
let mut pk = vec![0x04];
|
||||
pk.extend(x.deref());
|
||||
pk.extend(y.deref());
|
||||
let public_key = ring_signature::UnparsedPublicKey::new(params, pk);
|
||||
public_key
|
||||
.verify(msg, signature)
|
||||
.map_err(|_| SignatureVerificationError::CryptoError("EC Signature was wrong".to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::{
|
||||
core::{crypto::rsa_public_key, CoreJsonWebKey},
|
||||
SignatureVerificationError,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_leading_zeros_are_parsed_correctly() {
|
||||
// The message we signed
|
||||
let msg = "THIS IS A SIGNATURE TEST";
|
||||
let signature = base64::decode_config("bg0ohqKwYHAiODeG6qkJ-6IhodN7LGPxAh4hbWeIoBdSXrXMt8Ft8U0BV7vANPvF56h20XB9C0021x2kt7iAbMgPNcZ7LCuXMPPq04DrBpMHafH5BXBwnyDKJKrzDm5sfr6OgEkcxSLHaSJ6gTWQ3waPt6_SeH2-Fi74rg13MHyX-0iqz7bZveoBbGIs5yQCwvXgrDS9zW5LUwUHozHfE6FuSi_Z92ioXeu7FHHDg1KFfg3hs8ZLx4wAX15Vw2GCQOzvyNdbItxXRLnrN1NPqxFquVNo5RGlx6ihR1Jfe7y_n0NSR2q2TuU4cIwR0LRwEaANy5SDqtleQPrTEn8nGQ", base64::URL_SAFE_NO_PAD).unwrap();
|
||||
// RSA pub key with leading 0
|
||||
let key : CoreJsonWebKey = serde_json::from_value(serde_json::json!(
|
||||
{
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"use": "sig",
|
||||
"kid": "TEST_KEY_ID",
|
||||
"alg": "RS256",
|
||||
"n": "AN0M6Y760b9Ok2PxDOps1TgSmiOaR9mLIfUHtZ_o-6JypOckGcl1CxrteyokOb3WyDsfIAN9fFNrycv5YoLKO7sh0IcfzNEXFgzK84HTBcGuqhN8NV98Z6N9EryUrgJYsJeVoPYm0MzkDe4NyWHhnq-9OyNCQzVELH0NhhViQqRyM92OPrJcQlk8s3ZvcgRmkd-rEtRua8SbS3GEvfvgweVy5-qcJCGoziKfx-IteMOm6yKoHvqisKb91N-qw_kSS4YQUx-DZVDo2g24F7VIbcYzJGUOU674HUF1j-wJyXzG3VV8lAXD8hABs5Lh87gr8_hIZD5gbYBJRObJk9XZbfk"
|
||||
}
|
||||
)).unwrap();
|
||||
|
||||
// Old way of verifying the jwt, take the modulus directly form the JWK
|
||||
let (n, e) = rsa_public_key(&key)
|
||||
.map_err(SignatureVerificationError::InvalidKey)
|
||||
.unwrap();
|
||||
|
||||
let public_key = ring_signature::RsaPublicKeyComponents {
|
||||
n: n.deref(),
|
||||
e: e.deref(),
|
||||
};
|
||||
// This fails, since ring expects the keys to have no leading zeros
|
||||
assert! {
|
||||
public_key
|
||||
.verify(
|
||||
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
|
||||
msg.as_bytes(),
|
||||
&signature,
|
||||
).is_err()
|
||||
};
|
||||
// This should succeed as the function uses big-integers to actually harmonize parsing
|
||||
assert! {
|
||||
verify_rsa_signature(
|
||||
&key,
|
||||
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
|
||||
msg.as_bytes(),
|
||||
&signature,
|
||||
).is_ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
1249
zeroidc/vendor/openidconnect/src/core/jwk.rs
vendored
Normal file
1249
zeroidc/vendor/openidconnect/src/core/jwk.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1313
zeroidc/vendor/openidconnect/src/core/mod.rs
vendored
Normal file
1313
zeroidc/vendor/openidconnect/src/core/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
11
zeroidc/vendor/openidconnect/src/core/tests.rs
vendored
Normal file
11
zeroidc/vendor/openidconnect/src/core/tests.rs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
use super::CoreGrantType;
|
||||
|
||||
#[test]
|
||||
fn test_grant_type_serialize() {
|
||||
let serialized_implicit = serde_json::to_string(&CoreGrantType::Implicit).unwrap();
|
||||
assert_eq!("\"implicit\"", serialized_implicit);
|
||||
assert_eq!(
|
||||
CoreGrantType::Implicit,
|
||||
serde_json::from_str::<CoreGrantType>(&serialized_implicit).unwrap()
|
||||
);
|
||||
}
|
||||
1448
zeroidc/vendor/openidconnect/src/discovery.rs
vendored
Normal file
1448
zeroidc/vendor/openidconnect/src/discovery.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
184
zeroidc/vendor/openidconnect/src/helpers.rs
vendored
Normal file
184
zeroidc/vendor/openidconnect/src/helpers.rs
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
use serde::de::value::MapDeserializer;
|
||||
use serde::de::{DeserializeOwned, Deserializer, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_value::{Value, ValueDeserializer};
|
||||
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt::{Debug, Formatter, Result as FormatterResult};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub(crate) trait FlattenFilter {
|
||||
fn should_include(field_name: &str) -> bool;
|
||||
}
|
||||
|
||||
/// Helper container for filtering map keys out of serde(flatten). This is needed because
|
||||
/// [`crate::StandardClaims`] doesn't have a fixed set of field names due to its support for
|
||||
/// localized claims. Consequently, serde by default passes all of the claims to the deserializer
|
||||
/// for `AC` (additional claims), leading to duplicate claims. [`FilteredFlatten`] is used for
|
||||
/// eliminating the duplicate claims.
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
// We include another level of flattening here because the derived flatten
|
||||
// ([`serde::private::de::FlatMapDeserializer`]) seems to support a wider set of types
|
||||
// (e.g., various forms of enum tagging) than [`serde_value::ValueDeserializer`].
|
||||
#[serde(flatten)]
|
||||
inner: Flatten<T>,
|
||||
#[serde(skip)]
|
||||
_phantom: PhantomData<F>,
|
||||
}
|
||||
impl<F, T> From<T> for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
inner: Flatten { inner: value },
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T> AsRef<T> for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
}
|
||||
impl<F, T> AsMut<T> for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
self.inner.as_mut()
|
||||
}
|
||||
}
|
||||
impl<F, T> PartialEq for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + PartialEq + Serialize,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
impl<F, T> Clone for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: Clone + DeserializeOwned + Serialize,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Flatten {
|
||||
inner: self.inner.inner.clone(),
|
||||
},
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T> Debug for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: Debug + DeserializeOwned + Serialize,
|
||||
{
|
||||
// Transparent Debug since we don't care about this struct.
|
||||
fn fmt(&self, f: &mut Formatter) -> FormatterResult {
|
||||
Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, F, T> Deserialize<'de> for FilteredFlatten<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct MapVisitor<F: FlattenFilter, T: DeserializeOwned + Serialize>(PhantomData<(F, T)>);
|
||||
|
||||
impl<'de, F, T> Visitor<'de> for MapVisitor<F, T>
|
||||
where
|
||||
F: FlattenFilter,
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
type Value = Flatten<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
|
||||
formatter.write_str("map type T")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut entries = Vec::<(Value, Value)>::new();
|
||||
// JSON only supports String keys, and we really only need to support JSON input.
|
||||
while let Some(key) = map.next_key::<Value>()? {
|
||||
let key_str = String::deserialize(ValueDeserializer::new(key.clone()))?;
|
||||
if F::should_include(&key_str) {
|
||||
entries.push((key, map.next_value()?));
|
||||
}
|
||||
}
|
||||
|
||||
Deserialize::deserialize(MapDeserializer::new(entries.into_iter()))
|
||||
.map_err(serde_value::DeserializerError::into_error)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FilteredFlatten {
|
||||
inner: deserializer.deserialize_map(MapVisitor(PhantomData::<(F, T)>))?,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Flatten<T>
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
#[serde(flatten, bound = "T: DeserializeOwned + Serialize")]
|
||||
inner: T,
|
||||
}
|
||||
impl<T> AsRef<T> for Flatten<T>
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
impl<T> AsMut<T> for Flatten<T>
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for Flatten<T>
|
||||
where
|
||||
T: DeserializeOwned + PartialEq + Serialize,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
impl<T> Debug for Flatten<T>
|
||||
where
|
||||
T: Debug + DeserializeOwned + Serialize,
|
||||
{
|
||||
// Transparent Debug since we don't care about this struct.
|
||||
fn fmt(&self, f: &mut Formatter) -> FormatterResult {
|
||||
Debug::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
50
zeroidc/vendor/openidconnect/src/http_utils.rs
vendored
Normal file
50
zeroidc/vendor/openidconnect/src/http_utils.rs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
|
||||
use oauth2::AccessToken;
|
||||
|
||||
pub const MIME_TYPE_JSON: &str = "application/json";
|
||||
pub const MIME_TYPE_JWKS: &str = "application/jwk-set+json";
|
||||
pub const MIME_TYPE_JWT: &str = "application/jwt";
|
||||
|
||||
pub const BEARER: &str = "Bearer";
|
||||
|
||||
// The [essence](https://mimesniff.spec.whatwg.org/#mime-type-essence) is the <type>/<subtype>
|
||||
// representation.
|
||||
pub fn content_type_has_essence(content_type: &HeaderValue, expected_essence: &str) -> bool {
|
||||
#[allow(clippy::or_fun_call)]
|
||||
content_type
|
||||
.to_str()
|
||||
.ok()
|
||||
.filter(|ct| {
|
||||
ct[..ct.find(';').unwrap_or(ct.len())].to_lowercase() == expected_essence.to_lowercase()
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn check_content_type(headers: &HeaderMap, expected_content_type: &str) -> Result<(), String> {
|
||||
headers
|
||||
.get(CONTENT_TYPE)
|
||||
.map_or(Ok(()), |content_type|
|
||||
// Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and
|
||||
// may be followed by optional whitespace and/or a parameter (e.g., charset).
|
||||
// See https://tools.ietf.org/html/rfc7231#section-3.1.1.1.
|
||||
if !content_type_has_essence(content_type, expected_content_type) {
|
||||
Err(
|
||||
format!(
|
||||
"Unexpected response Content-Type: {:?}, should be `{}`",
|
||||
content_type,
|
||||
expected_content_type
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn auth_bearer(access_token: &AccessToken) -> (HeaderName, HeaderValue) {
|
||||
(
|
||||
AUTHORIZATION,
|
||||
HeaderValue::from_str(&format!("{} {}", BEARER, access_token.secret()))
|
||||
.expect("invalid access token"),
|
||||
)
|
||||
}
|
||||
1109
zeroidc/vendor/openidconnect/src/id_token.rs
vendored
Normal file
1109
zeroidc/vendor/openidconnect/src/id_token.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
837
zeroidc/vendor/openidconnect/src/jwt.rs
vendored
Normal file
837
zeroidc/vendor/openidconnect/src/jwt.rs
vendored
Normal file
@@ -0,0 +1,837 @@
|
||||
use std::fmt::{Debug, Formatter, Result as FormatterResult};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::str;
|
||||
|
||||
use serde::de::{DeserializeOwned, Error as _, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{
|
||||
JsonWebKey, JsonWebKeyId, JsonWebKeyType, JsonWebKeyUse, JweContentEncryptionAlgorithm,
|
||||
JwsSigningAlgorithm, PrivateSigningKey, SignatureVerificationError, SigningError,
|
||||
};
|
||||
|
||||
new_type![
|
||||
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
|
||||
JsonWebTokenContentType(String)
|
||||
];
|
||||
|
||||
new_type![
|
||||
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
|
||||
JsonWebTokenType(String)
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum JsonWebTokenAlgorithm<JE, JS, JT>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
{
|
||||
Encryption(JE),
|
||||
// This is ugly, but we don't expose this module via the public API, so it's fine.
|
||||
Signature(JS, PhantomData<JT>),
|
||||
///
|
||||
/// No digital signature or MAC performed.
|
||||
///
|
||||
/// # Security Warning
|
||||
///
|
||||
/// This algorithm provides no security over the integrity of the JSON Web Token. Clients
|
||||
/// should be careful not to rely on unsigned JWT's for security purposes. See
|
||||
/// [Critical vulnerabilities in JSON Web Token libraries](
|
||||
/// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) for
|
||||
/// further discussion.
|
||||
///
|
||||
None,
|
||||
}
|
||||
impl<'de, JE, JS, JT> Deserialize<'de> for JsonWebTokenAlgorithm<JE, JS, JT>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
|
||||
// TODO: get rid of this clone() (see below)
|
||||
let s: String = serde_json::from_value(value.clone()).map_err(D::Error::custom)?;
|
||||
|
||||
// NB: These comparisons are case sensitive. Section 4.1.1 of RFC 7515 states: "The "alg"
|
||||
// value is a case-sensitive ASCII string containing a StringOrURI value."
|
||||
if s == "none" {
|
||||
Ok(JsonWebTokenAlgorithm::None)
|
||||
// TODO: Figure out a way to deserialize the enums without giving up ownership
|
||||
} else if let Ok(val) = serde_json::from_value::<JE>(value.clone()) {
|
||||
Ok(JsonWebTokenAlgorithm::Encryption(val))
|
||||
} else if let Ok(val) = serde_json::from_value::<JS>(value) {
|
||||
Ok(JsonWebTokenAlgorithm::Signature(val, PhantomData))
|
||||
} else {
|
||||
Err(D::Error::custom(format!(
|
||||
"unrecognized JSON Web Algorithm `{}`",
|
||||
s
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<JE, JS, JT> Serialize for JsonWebTokenAlgorithm<JE, JS, JT>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
{
|
||||
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
|
||||
where
|
||||
SE: Serializer,
|
||||
{
|
||||
match self {
|
||||
JsonWebTokenAlgorithm::Encryption(ref enc) => enc.serialize(serializer),
|
||||
JsonWebTokenAlgorithm::Signature(ref sig, _) => sig.serialize(serializer),
|
||||
JsonWebTokenAlgorithm::None => serializer.serialize_str("none"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct JsonWebTokenHeader<JE, JS, JT>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
{
|
||||
#[serde(
|
||||
bound = "JE: JweContentEncryptionAlgorithm<JT>, JS: JwsSigningAlgorithm<JT>, JT: JsonWebKeyType"
|
||||
)]
|
||||
pub alg: JsonWebTokenAlgorithm<JE, JS, JT>,
|
||||
// Additional critical header parameters that must be understood by this implementation. Since
|
||||
// we don't understand any such extensions, we reject any JWT with this value present (the
|
||||
// spec specifically prohibits including public (standard) headers in this field).
|
||||
// See https://tools.ietf.org/html/rfc7515#section-4.1.11.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub crit: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cty: Option<JsonWebTokenContentType>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kid: Option<JsonWebKeyId>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub typ: Option<JsonWebTokenType>,
|
||||
// Other JOSE header fields are omitted since the OpenID Connect spec specifically says that
|
||||
// the "x5u", "x5c", "jku", "jwk" header parameter fields SHOULD NOT be used.
|
||||
// See http://openid.net/specs/openid-connect-core-1_0-final.html#IDToken.
|
||||
#[serde(skip)]
|
||||
_phantom_jt: PhantomData<JT>,
|
||||
}
|
||||
|
||||
pub trait JsonWebTokenPayloadSerde<P>: Debug
|
||||
where
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
{
|
||||
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE>;
|
||||
fn serialize(payload: &P) -> Result<String, serde_json::Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct JsonWebTokenJsonPayloadSerde;
|
||||
impl<P> JsonWebTokenPayloadSerde<P> for JsonWebTokenJsonPayloadSerde
|
||||
where
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
{
|
||||
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE> {
|
||||
serde_json::from_slice(payload)
|
||||
.map_err(|err| DE::custom(format!("Failed to parse payload JSON: {:?}", err)))
|
||||
}
|
||||
|
||||
fn serialize(payload: &P) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(payload).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper trait so that we can get borrowed payload when we have a reference to the JWT and owned
|
||||
// payload when we own the JWT.
|
||||
pub trait JsonWebTokenAccess<JE, JS, JT, P>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
{
|
||||
type ReturnType;
|
||||
|
||||
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT>;
|
||||
fn unverified_payload(self) -> Self::ReturnType;
|
||||
fn unverified_payload_ref(&self) -> &P;
|
||||
|
||||
fn payload<JU, JW>(
|
||||
self,
|
||||
signature_alg: &JS,
|
||||
key: &JW,
|
||||
) -> Result<Self::ReturnType, SignatureVerificationError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
JW: JsonWebKey<JS, JT, JU>;
|
||||
}
|
||||
|
||||
///
|
||||
/// Error creating a JSON Web Token.
|
||||
///
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum JsonWebTokenError {
|
||||
///
|
||||
/// Failed to serialize JWT.
|
||||
///
|
||||
#[error("Failed to serialize JWT")]
|
||||
SerializationError(#[source] serde_json::Error),
|
||||
///
|
||||
/// Failed to sign JWT.
|
||||
///
|
||||
#[error("Failed to sign JWT")]
|
||||
SigningError(#[source] SigningError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
header: JsonWebTokenHeader<JE, JS, JT>,
|
||||
payload: P,
|
||||
signature: Vec<u8>,
|
||||
signing_input: String,
|
||||
_phantom: PhantomData<S>,
|
||||
}
|
||||
impl<JE, JS, JT, P, S> JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
pub fn new<JU, K, SK>(payload: P, signing_key: &SK, alg: &JS) -> Result<Self, JsonWebTokenError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
K: JsonWebKey<JS, JT, JU>,
|
||||
SK: PrivateSigningKey<JS, JT, JU, K>,
|
||||
{
|
||||
let header = JsonWebTokenHeader::<JE, _, _> {
|
||||
alg: JsonWebTokenAlgorithm::Signature(alg.clone(), PhantomData),
|
||||
crit: None,
|
||||
cty: None,
|
||||
kid: signing_key.as_verification_key().key_id().cloned(),
|
||||
typ: None,
|
||||
_phantom_jt: PhantomData,
|
||||
};
|
||||
|
||||
let header_json =
|
||||
serde_json::to_string(&header).map_err(JsonWebTokenError::SerializationError)?;
|
||||
let header_base64 = base64::encode_config(&header_json, base64::URL_SAFE_NO_PAD);
|
||||
|
||||
let serialized_payload =
|
||||
S::serialize(&payload).map_err(JsonWebTokenError::SerializationError)?;
|
||||
let payload_base64 = base64::encode_config(&serialized_payload, base64::URL_SAFE_NO_PAD);
|
||||
|
||||
let signing_input = format!("{}.{}", header_base64, payload_base64);
|
||||
|
||||
let signature = signing_key
|
||||
.sign(alg, signing_input.as_bytes())
|
||||
.map_err(JsonWebTokenError::SigningError)?;
|
||||
|
||||
Ok(JsonWebToken {
|
||||
header,
|
||||
payload,
|
||||
signature,
|
||||
signing_input,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Owned JWT.
|
||||
impl<JE, JS, JT, P, S> JsonWebTokenAccess<JE, JS, JT, P> for JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
type ReturnType = P;
|
||||
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT> {
|
||||
&self.header
|
||||
}
|
||||
fn unverified_payload(self) -> Self::ReturnType {
|
||||
self.payload
|
||||
}
|
||||
fn unverified_payload_ref(&self) -> &P {
|
||||
&self.payload
|
||||
}
|
||||
fn payload<JU, JW>(
|
||||
self,
|
||||
signature_alg: &JS,
|
||||
key: &JW,
|
||||
) -> Result<Self::ReturnType, SignatureVerificationError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
JW: JsonWebKey<JS, JT, JU>,
|
||||
{
|
||||
key.verify_signature(
|
||||
signature_alg,
|
||||
self.signing_input.as_bytes(),
|
||||
&self.signature,
|
||||
)?;
|
||||
Ok(self.payload)
|
||||
}
|
||||
}
|
||||
// Borrowed JWT.
|
||||
impl<'a, JE, JS, JT, P, S> JsonWebTokenAccess<JE, JS, JT, P> for &'a JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
type ReturnType = &'a P;
|
||||
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT> {
|
||||
&self.header
|
||||
}
|
||||
fn unverified_payload(self) -> Self::ReturnType {
|
||||
&self.payload
|
||||
}
|
||||
fn unverified_payload_ref(&self) -> &P {
|
||||
&self.payload
|
||||
}
|
||||
fn payload<JU, JW>(
|
||||
self,
|
||||
signature_alg: &JS,
|
||||
key: &JW,
|
||||
) -> Result<Self::ReturnType, SignatureVerificationError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
JW: JsonWebKey<JS, JT, JU>,
|
||||
{
|
||||
key.verify_signature(
|
||||
signature_alg,
|
||||
self.signing_input.as_bytes(),
|
||||
&self.signature,
|
||||
)?;
|
||||
Ok(&self.payload)
|
||||
}
|
||||
}
|
||||
impl<'de, JE, JS, JT, P, S> Deserialize<'de> for JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct JsonWebTokenVisitor<
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
>(
|
||||
PhantomData<JE>,
|
||||
PhantomData<JS>,
|
||||
PhantomData<JT>,
|
||||
PhantomData<P>,
|
||||
PhantomData<S>,
|
||||
);
|
||||
impl<'de, JE, JS, JT, P, S> Visitor<'de> for JsonWebTokenVisitor<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
type Value = JsonWebToken<JE, JS, JT, P, S>;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
|
||||
formatter.write_str("JsonWebToken")
|
||||
}
|
||||
|
||||
fn visit_str<DE>(self, v: &str) -> Result<Self::Value, DE>
|
||||
where
|
||||
DE: serde::de::Error,
|
||||
{
|
||||
let raw_token = v.to_string();
|
||||
let header: JsonWebTokenHeader<JE, JS, JT>;
|
||||
let payload: P;
|
||||
let signature;
|
||||
let signing_input;
|
||||
|
||||
{
|
||||
let parts = raw_token.split('.').collect::<Vec<_>>();
|
||||
|
||||
// NB: We avoid including the full payload encoding in the error output to avoid
|
||||
// clients potentially logging sensitive values.
|
||||
if parts.len() != 3 {
|
||||
return Err(DE::custom(format!(
|
||||
"Invalid JSON web token: found {} parts (expected 3)",
|
||||
parts.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let header_json =
|
||||
base64::decode_config(parts[0], crate::core::base64_url_safe_no_pad())
|
||||
.map_err(|err| {
|
||||
DE::custom(format!("Invalid base64url header encoding: {:?}", err))
|
||||
})?;
|
||||
header = serde_json::from_slice(&header_json).map_err(|err| {
|
||||
DE::custom(format!("Failed to parse header JSON: {:?}", err))
|
||||
})?;
|
||||
|
||||
let raw_payload =
|
||||
base64::decode_config(parts[1], crate::core::base64_url_safe_no_pad())
|
||||
.map_err(|err| {
|
||||
DE::custom(format!("Invalid base64url payload encoding: {:?}", err))
|
||||
})?;
|
||||
payload = S::deserialize::<DE>(&raw_payload)?;
|
||||
|
||||
signature =
|
||||
base64::decode_config(parts[2], crate::core::base64_url_safe_no_pad())
|
||||
.map_err(|err| {
|
||||
DE::custom(format!(
|
||||
"Invalid base64url signature encoding: {:?}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
signing_input = format!("{}.{}", parts[0], parts[1]);
|
||||
}
|
||||
|
||||
Ok(JsonWebToken {
|
||||
header,
|
||||
payload,
|
||||
signature,
|
||||
signing_input,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(JsonWebTokenVisitor(
|
||||
PhantomData,
|
||||
PhantomData,
|
||||
PhantomData,
|
||||
PhantomData,
|
||||
PhantomData,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl<JE, JS, JT, P, S> Serialize for JsonWebToken<JE, JS, JT, P, S>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
P: Debug + DeserializeOwned + Serialize,
|
||||
S: JsonWebTokenPayloadSerde<P>,
|
||||
{
|
||||
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
|
||||
where
|
||||
SE: Serializer,
|
||||
{
|
||||
let signature_base64 = base64::encode_config(&self.signature, base64::URL_SAFE_NO_PAD);
|
||||
serializer.serialize_str(&format!("{}.{}", self.signing_input, signature_base64))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::marker::PhantomData;
|
||||
use std::string::ToString;
|
||||
|
||||
use crate::core::{
|
||||
CoreJsonWebKey, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm, CoreRsaPrivateSigningKey,
|
||||
};
|
||||
use crate::JsonWebKeyId;
|
||||
|
||||
use super::{
|
||||
JsonWebToken, JsonWebTokenAccess, JsonWebTokenAlgorithm, JsonWebTokenJsonPayloadSerde,
|
||||
JsonWebTokenPayloadSerde,
|
||||
};
|
||||
|
||||
type CoreAlgorithm = JsonWebTokenAlgorithm<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
>;
|
||||
|
||||
pub const TEST_JWT: &str =
|
||||
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZ\
|
||||
GFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGU\
|
||||
gcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlc\
|
||||
mUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e\
|
||||
5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3l\
|
||||
fWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV\
|
||||
0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41\
|
||||
Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg";
|
||||
|
||||
const TEST_JWT_PAYLOAD: &str = "It\u{2019}s a dangerous business, Frodo, going out your \
|
||||
door. You step onto the road, and if you don't keep your feet, \
|
||||
there\u{2019}s no knowing where you might be swept off \
|
||||
to.";
|
||||
|
||||
pub const TEST_RSA_PUB_KEY: &str = "{
|
||||
\"kty\": \"RSA\",
|
||||
\"kid\": \"bilbo.baggins@hobbiton.example\",
|
||||
\"use\": \"sig\",
|
||||
\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT\
|
||||
-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV\
|
||||
wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-\
|
||||
oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde\
|
||||
3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC\
|
||||
LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g\
|
||||
HdrNP5zw\",
|
||||
\"e\": \"AQAB\"
|
||||
}";
|
||||
|
||||
pub const TEST_EC_PUB_KEY_P256: &str = r#"{
|
||||
"kty": "EC",
|
||||
"kid": "bilbo.baggins@hobbiton.example",
|
||||
"use": "sig",
|
||||
"crv": "P-256",
|
||||
"x": "t6PHivOTggpaX9lkMkis2p8kMhy-CktJAFTz6atReZw",
|
||||
"y": "ODobXupKlD0DeM1yRd7bX4XFNBO1HOgCT1UCu0KY3lc"
|
||||
}"#;
|
||||
pub const TEST_EC_PUB_KEY_P384: &str = r#"{
|
||||
"kty": "EC",
|
||||
"kid": "bilbo.baggins@hobbiton.example",
|
||||
"use": "sig",
|
||||
"crv" : "P-384",
|
||||
"x": "9ywsUbxX59kJXFRiWHcx97wRKNiF8Hc9F5wI08n8h2ek_qAl0veEc36k1Qz6KLiL",
|
||||
"y": "6PWlqjRbaV7V8ohDscM243IneuLZmxDGLiGNA1w69fQhEDsvZtKLUQ5KiHLgR3op"
|
||||
}"#;
|
||||
|
||||
// This is the PEM form of the test private key from:
|
||||
// https://tools.ietf.org/html/rfc7520#section-3.4
|
||||
pub const TEST_RSA_PRIV_KEY: &str = "-----BEGIN RSA PRIVATE KEY-----\n\
|
||||
MIIEowIBAAKCAQEAn4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8/KuKPEHLd4\n\
|
||||
rHVTeT+O+XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz/AJmSCpMaJMRBSFKrKb2wqVwG\n\
|
||||
U/NsYOYL+QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj+oBHqFEHYpP\n\
|
||||
e7Tpe+OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzw\n\
|
||||
OHrtIQbS0FVbb9k3+tVTU4fg/3L/vniUFAKwuCLqKnS2BYwdq/mzSnbLY7h/qixo\n\
|
||||
R7jig3//kRhuaxwUkRz5iaiQkqgc5gHdrNP5zwIDAQABAoIBAG1lAvQfhBUSKPJK\n\
|
||||
Rn4dGbshj7zDSr2FjbQf4pIh/ZNtHk/jtavyO/HomZKV8V0NFExLNi7DUUvvLiW7\n\
|
||||
0PgNYq5MDEjJCtSd10xoHa4QpLvYEZXWO7DQPwCmRofkOutf+NqyDS0QnvFvp2d+\n\
|
||||
Lov6jn5C5yvUFgw6qWiLAPmzMFlkgxbtjFAWMJB0zBMy2BqjntOJ6KnqtYRMQUxw\n\
|
||||
TgXZDF4rhYVKtQVOpfg6hIlsaoPNrF7dofizJ099OOgDmCaEYqM++bUlEHxgrIVk\n\
|
||||
wZz+bg43dfJCocr9O5YX0iXaz3TOT5cpdtYbBX+C/5hwrqBWru4HbD3xz8cY1TnD\n\
|
||||
qQa0M8ECgYEA3Slxg/DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex/fp7AZ/9\n\
|
||||
nRaO7HX/+SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr/WCsmGpeNqQn\n\
|
||||
ev1T7IyEsnh8UMt+n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0kCgYEAuKE2\n\
|
||||
dh+cTf6ERF4k4e/jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR/cu0Dm1MZwW\n\
|
||||
mtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoB\n\
|
||||
vyY898EXvRD+hdqRxHlSqAZ192zB3pVFJ0s7pFcCgYAHw9W9eS8muPYv4ZhDu/fL\n\
|
||||
2vorDmD1JqFcHCxZTOnX1NWWAj5hXzmrU0hvWvFC0P4ixddHf5Nqd6+5E9G3k4E5\n\
|
||||
2IwZCnylu3bqCWNh8pT8T3Gf5FQsfPT5530T2BcsoPhUaeCnP499D+rb2mTnFYeg\n\
|
||||
mnTT1B/Ue8KGLFFfn16GKQKBgAiw5gxnbocpXPaO6/OKxFFZ+6c0OjxfN2PogWce\n\
|
||||
TU/k6ZzmShdaRKwDFXisxRJeNQ5Rx6qgS0jNFtbDhW8E8WFmQ5urCOqIOYk28EBi\n\
|
||||
At4JySm4v+5P7yYBh8B8YD2l9j57z/s8hJAxEbn/q8uHP2ddQqvQKgtsni+pHSk9\n\
|
||||
XGBfAoGBANz4qr10DdM8DHhPrAb2YItvPVz/VwkBd1Vqj8zCpyIEKe/07oKOvjWQ\n\
|
||||
SgkLDH9x2hBgY01SbP43CvPk0V72invu2TGkI/FXwXWJLLG7tDSgw4YyfhrYrHmg\n\
|
||||
1Vre3XB9HH8MYBVB6UIexaAq4xSeoemRKTBesZro7OKjKT8/GmiO\
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
|
||||
#[test]
|
||||
fn test_jwt_algorithm_deserialization() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<CoreAlgorithm>("\"A128CBC-HS256\"")
|
||||
.expect("failed to deserialize"),
|
||||
JsonWebTokenAlgorithm::Encryption(
|
||||
CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<CoreAlgorithm>("\"A128GCM\"").expect("failed to deserialize"),
|
||||
JsonWebTokenAlgorithm::Encryption(CoreJweContentEncryptionAlgorithm::Aes128Gcm),
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<CoreAlgorithm>("\"HS256\"").expect("failed to deserialize"),
|
||||
JsonWebTokenAlgorithm::Signature(CoreJwsSigningAlgorithm::HmacSha256, PhantomData),
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<CoreAlgorithm>("\"RS256\"").expect("failed to deserialize"),
|
||||
JsonWebTokenAlgorithm::Signature(
|
||||
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
|
||||
PhantomData,
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<CoreAlgorithm>("\"none\"").expect("failed to deserialize"),
|
||||
JsonWebTokenAlgorithm::None,
|
||||
);
|
||||
|
||||
serde_json::from_str::<CoreAlgorithm>("\"invalid\"")
|
||||
.expect_err("deserialization should have failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jwt_algorithm_serialization() {
|
||||
assert_eq!(
|
||||
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(
|
||||
CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256
|
||||
))
|
||||
.expect("failed to serialize"),
|
||||
"\"A128CBC-HS256\"",
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(
|
||||
CoreJweContentEncryptionAlgorithm::Aes128Gcm
|
||||
))
|
||||
.expect("failed to serialize"),
|
||||
"\"A128GCM\"",
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(
|
||||
CoreJwsSigningAlgorithm::HmacSha256,
|
||||
PhantomData,
|
||||
))
|
||||
.expect("failed to serialize"),
|
||||
"\"HS256\"",
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(
|
||||
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
|
||||
PhantomData,
|
||||
))
|
||||
.expect("failed to serialize"),
|
||||
"\"RS256\"",
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::None)
|
||||
.expect("failed to serialize"),
|
||||
"\"none\"",
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JsonWebTokenStringPayloadSerde;
|
||||
impl JsonWebTokenPayloadSerde<String> for JsonWebTokenStringPayloadSerde {
|
||||
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<String, DE> {
|
||||
Ok(String::from_utf8(payload.to_owned()).unwrap())
|
||||
}
|
||||
fn serialize(payload: &String) -> Result<String, serde_json::Error> {
|
||||
Ok(payload.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jwt_basic() {
|
||||
fn verify_jwt<A>(jwt_access: A, key: &CoreJsonWebKey, expected_payload: &str)
|
||||
where
|
||||
A: JsonWebTokenAccess<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
String,
|
||||
>,
|
||||
A::ReturnType: ToString,
|
||||
{
|
||||
{
|
||||
let header = jwt_access.unverified_header();
|
||||
assert_eq!(
|
||||
header.alg,
|
||||
JsonWebTokenAlgorithm::Signature(
|
||||
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
|
||||
PhantomData,
|
||||
)
|
||||
);
|
||||
assert_eq!(header.crit, None);
|
||||
assert_eq!(header.cty, None);
|
||||
assert_eq!(
|
||||
header.kid,
|
||||
Some(JsonWebKeyId::new(
|
||||
"bilbo.baggins@hobbiton.example".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(header.typ, None);
|
||||
}
|
||||
assert_eq!(jwt_access.unverified_payload_ref(), expected_payload);
|
||||
|
||||
assert_eq!(
|
||||
jwt_access
|
||||
.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, key)
|
||||
.expect("failed to validate payload")
|
||||
.to_string(),
|
||||
expected_payload
|
||||
);
|
||||
}
|
||||
|
||||
let key: CoreJsonWebKey =
|
||||
serde_json::from_str(TEST_RSA_PUB_KEY).expect("deserialization failed");
|
||||
|
||||
let jwt: JsonWebToken<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
String,
|
||||
JsonWebTokenStringPayloadSerde,
|
||||
> = serde_json::from_value(serde_json::Value::String(TEST_JWT.to_string()))
|
||||
.expect("failed to deserialize");
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&jwt).expect("failed to serialize"),
|
||||
serde_json::Value::String(TEST_JWT.to_string())
|
||||
);
|
||||
|
||||
verify_jwt(&jwt, &key, TEST_JWT_PAYLOAD);
|
||||
assert_eq!((&jwt).unverified_payload(), TEST_JWT_PAYLOAD);
|
||||
|
||||
verify_jwt(jwt, &key, TEST_JWT_PAYLOAD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_jwt() {
|
||||
let signing_key = CoreRsaPrivateSigningKey::from_pem(
|
||||
TEST_RSA_PRIV_KEY,
|
||||
Some(JsonWebKeyId::new(
|
||||
"bilbo.baggins@hobbiton.example".to_string(),
|
||||
)),
|
||||
)
|
||||
.unwrap();
|
||||
let new_jwt = JsonWebToken::<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
JsonWebTokenStringPayloadSerde,
|
||||
>::new(
|
||||
TEST_JWT_PAYLOAD.to_owned(),
|
||||
&signing_key,
|
||||
&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&new_jwt).expect("failed to serialize"),
|
||||
serde_json::Value::String(TEST_JWT.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_signature() {
|
||||
let corrupted_jwt_str = TEST_JWT
|
||||
.to_string()
|
||||
.chars()
|
||||
.take(TEST_JWT.len() - 1)
|
||||
.collect::<String>()
|
||||
+ "f";
|
||||
let jwt: JsonWebToken<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
String,
|
||||
JsonWebTokenStringPayloadSerde,
|
||||
> = serde_json::from_value(serde_json::Value::String(corrupted_jwt_str))
|
||||
.expect("failed to deserialize");
|
||||
let key: CoreJsonWebKey =
|
||||
serde_json::from_str(TEST_RSA_PUB_KEY).expect("deserialization failed");
|
||||
|
||||
// JsonWebTokenAccess for reference.
|
||||
(&jwt)
|
||||
.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)
|
||||
.expect_err("signature verification should have failed");
|
||||
|
||||
// JsonWebTokenAccess for owned value.
|
||||
jwt.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)
|
||||
.expect_err("signature verification should have failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_deserialization() {
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct TestPayload {
|
||||
foo: String,
|
||||
}
|
||||
|
||||
fn expect_deserialization_err<I: Into<String>>(jwt_str: I, pattern: &str) {
|
||||
let err = serde_json::from_value::<
|
||||
JsonWebToken<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
TestPayload,
|
||||
JsonWebTokenJsonPayloadSerde,
|
||||
>,
|
||||
>(serde_json::Value::String(jwt_str.into()))
|
||||
.expect_err("deserialization should have failed");
|
||||
|
||||
assert!(
|
||||
err.to_string().contains(pattern),
|
||||
"Error `{}` must contain string `{}`",
|
||||
err,
|
||||
pattern,
|
||||
);
|
||||
}
|
||||
|
||||
// Too many dots
|
||||
expect_deserialization_err("a.b.c.d", "found 4 parts (expected 3)");
|
||||
|
||||
// Invalid header base64
|
||||
expect_deserialization_err("a!.b.c", "Invalid base64url header encoding");
|
||||
|
||||
// Invalid header utf-8 (after base64 decoding)
|
||||
expect_deserialization_err("gA.b.c", "Error(\"expected value\", line: 1, column: 1)");
|
||||
|
||||
// Invalid header JSON
|
||||
expect_deserialization_err("bm90X2pzb24.b.c", "Failed to parse header JSON");
|
||||
|
||||
let valid_header =
|
||||
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9";
|
||||
|
||||
// Invalid payload base64
|
||||
expect_deserialization_err(
|
||||
format!("{}.b!.c", valid_header),
|
||||
"Invalid base64url payload encoding",
|
||||
);
|
||||
|
||||
// Invalid payload utf-8 (after base64 decoding)
|
||||
expect_deserialization_err(
|
||||
format!("{}.gA.c", valid_header),
|
||||
"Error(\"expected value\", line: 1, column: 1)",
|
||||
);
|
||||
|
||||
// Invalid payload JSON
|
||||
expect_deserialization_err(
|
||||
format!("{}.bm90X2pzb24.c", valid_header),
|
||||
"Failed to parse payload JSON",
|
||||
);
|
||||
|
||||
let valid_body = "eyJmb28iOiAiYmFyIn0";
|
||||
|
||||
// Invalid signature base64
|
||||
expect_deserialization_err(
|
||||
format!("{}.{}.c!", valid_header, valid_body),
|
||||
"Invalid base64url signature encoding",
|
||||
);
|
||||
|
||||
let deserialized = serde_json::from_value::<
|
||||
JsonWebToken<
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJwsSigningAlgorithm,
|
||||
CoreJsonWebKeyType,
|
||||
TestPayload,
|
||||
JsonWebTokenJsonPayloadSerde,
|
||||
>,
|
||||
>(serde_json::Value::String(format!(
|
||||
"{}.{}.e2FiY30",
|
||||
valid_header, valid_body
|
||||
)))
|
||||
.expect("failed to deserialize");
|
||||
assert_eq!(deserialized.unverified_payload().foo, "bar");
|
||||
}
|
||||
}
|
||||
1648
zeroidc/vendor/openidconnect/src/lib.rs
vendored
Normal file
1648
zeroidc/vendor/openidconnect/src/lib.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
895
zeroidc/vendor/openidconnect/src/macros.rs
vendored
Normal file
895
zeroidc/vendor/openidconnect/src/macros.rs
vendored
Normal file
@@ -0,0 +1,895 @@
|
||||
///
|
||||
/// Copied from oauth2-rs crate (not part of that crate's stable public interface).
|
||||
///
|
||||
macro_rules! new_type {
|
||||
// Convenience pattern without an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
)
|
||||
) => {
|
||||
new_type![
|
||||
@new_type_pub $(#[$attr])*,
|
||||
$name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
),
|
||||
concat!(
|
||||
"Create a new `",
|
||||
stringify!($name),
|
||||
"` to wrap the given `",
|
||||
stringify!($type),
|
||||
"`."
|
||||
),
|
||||
impl {}
|
||||
];
|
||||
};
|
||||
// Convenience pattern without an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
pub(crate) $name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
)
|
||||
) => {
|
||||
new_type![
|
||||
@new_type_pub_crate $(#[$attr])*,
|
||||
$name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
),
|
||||
concat!(
|
||||
"Create a new `",
|
||||
stringify!($name),
|
||||
"` to wrap the given `",
|
||||
stringify!($type),
|
||||
"`."
|
||||
),
|
||||
impl {}
|
||||
];
|
||||
};
|
||||
// Main entry point with an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
)
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
new_type![
|
||||
@new_type_pub $(#[$attr])*,
|
||||
$name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
),
|
||||
concat!(
|
||||
"Create a new `",
|
||||
stringify!($name),
|
||||
"` to wrap the given `",
|
||||
stringify!($type),
|
||||
"`."
|
||||
),
|
||||
impl {
|
||||
$($item)*
|
||||
}
|
||||
];
|
||||
};
|
||||
// Main entry point with an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
pub(crate) $name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
)
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
new_type![
|
||||
@new_type_pub_crate $(#[$attr])*,
|
||||
$name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
),
|
||||
concat!(
|
||||
"Create a new `",
|
||||
stringify!($name),
|
||||
"` to wrap the given `",
|
||||
stringify!($type),
|
||||
"`."
|
||||
),
|
||||
impl {
|
||||
$($item)*
|
||||
}
|
||||
];
|
||||
};
|
||||
// Actual implementation, after stringifying the #[doc] attr.
|
||||
(
|
||||
@new_type_pub $(#[$attr:meta])*,
|
||||
$name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
),
|
||||
$new_doc:expr,
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
$(#[$attr])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct $name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
);
|
||||
impl $name {
|
||||
$($item)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[doc = $new_doc]
|
||||
pub fn new(s: $type) -> Self {
|
||||
$name(s)
|
||||
}
|
||||
}
|
||||
impl Deref for $name {
|
||||
type Target = $type;
|
||||
fn deref(&self) -> &$type {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
// Actual implementation, after stringifying the #[doc] attr.
|
||||
(
|
||||
@new_type_pub_crate $(#[$attr:meta])*,
|
||||
$name:ident(
|
||||
$(#[$type_attr:meta])*
|
||||
$type:ty
|
||||
),
|
||||
$new_doc:expr,
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
$(#[$attr])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct $name(
|
||||
$(#[$type_attr])*
|
||||
$type
|
||||
);
|
||||
impl $name {
|
||||
$($item)*
|
||||
|
||||
#[doc = $new_doc]
|
||||
pub fn new(s: $type) -> Self {
|
||||
$name(s)
|
||||
}
|
||||
}
|
||||
impl Deref for $name {
|
||||
type Target = $type;
|
||||
fn deref(&self) -> &$type {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// Copied from oauth2-rs crate (not part of that crate's stable public interface).
|
||||
///
|
||||
macro_rules! new_secret_type {
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident($type:ty)
|
||||
) => {
|
||||
new_secret_type![
|
||||
$(#[$attr])*
|
||||
$name($type)
|
||||
impl {}
|
||||
];
|
||||
};
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident($type:ty)
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
new_secret_type![
|
||||
$(#[$attr])*,
|
||||
$name($type),
|
||||
concat!(
|
||||
"Create a new `",
|
||||
stringify!($name),
|
||||
"` to wrap the given `",
|
||||
stringify!($type),
|
||||
"`."
|
||||
),
|
||||
concat!("Get the secret contained within this `", stringify!($name), "`."),
|
||||
impl {
|
||||
$($item)*
|
||||
}
|
||||
];
|
||||
};
|
||||
(
|
||||
$(#[$attr:meta])*,
|
||||
$name:ident($type:ty),
|
||||
$new_doc:expr,
|
||||
$secret_doc:expr,
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
$(
|
||||
#[$attr]
|
||||
)*
|
||||
pub struct $name($type);
|
||||
impl $name {
|
||||
$($item)*
|
||||
|
||||
#[doc = $new_doc]
|
||||
pub fn new(s: $type) -> Self {
|
||||
$name(s)
|
||||
}
|
||||
///
|
||||
#[doc = $secret_doc]
|
||||
///
|
||||
/// # Security Warning
|
||||
///
|
||||
/// Leaking this value may compromise the security of the OAuth2 flow.
|
||||
///
|
||||
pub fn secret(&self) -> &$type { &self.0 }
|
||||
}
|
||||
impl Debug for $name {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
|
||||
write!(f, concat!(stringify!($name), "([redacted])"))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates a URL-specific new type
|
||||
///
|
||||
/// Types created by this macro enforce during construction that the contained value represents a
|
||||
/// syntactically valid URL. However, comparisons and hashes of these types are based on the string
|
||||
/// representation given during construction, disregarding any canonicalization performed by the
|
||||
/// underlying `Url` struct. OpenID Connect requires certain URLs (e.g., ID token issuers) to be
|
||||
/// compared exactly, without canonicalization.
|
||||
///
|
||||
/// In addition to the raw string representation, these types include a `url` method to retrieve a
|
||||
/// parsed `Url` struct.
|
||||
///
|
||||
macro_rules! new_url_type {
|
||||
// Convenience pattern without an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident
|
||||
) => {
|
||||
new_url_type![
|
||||
@new_type_pub $(#[$attr])*,
|
||||
$name,
|
||||
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
|
||||
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
|
||||
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
|
||||
impl {}
|
||||
];
|
||||
};
|
||||
// Main entry point with an impl.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
new_url_type![
|
||||
@new_type_pub $(#[$attr])*,
|
||||
$name,
|
||||
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
|
||||
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
|
||||
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
|
||||
impl {
|
||||
$($item)*
|
||||
}
|
||||
];
|
||||
};
|
||||
// Actual implementation, after stringifying the #[doc] attr.
|
||||
(
|
||||
@new_type_pub $(#[$attr:meta])*,
|
||||
$name:ident,
|
||||
$new_doc:expr,
|
||||
$from_url_doc:expr,
|
||||
$url_doc:expr,
|
||||
impl {
|
||||
$($item:tt)*
|
||||
}
|
||||
) => {
|
||||
$(#[$attr])*
|
||||
#[derive(Clone)]
|
||||
pub struct $name(Url, String);
|
||||
impl $name {
|
||||
#[doc = $new_doc]
|
||||
pub fn new(url: String) -> Result<Self, ::url::ParseError> {
|
||||
Ok($name(Url::parse(&url)?, url))
|
||||
}
|
||||
#[doc = $from_url_doc]
|
||||
pub fn from_url(url: Url) -> Self {
|
||||
let s = url.to_string();
|
||||
Self(url, s)
|
||||
} #[doc = $url_doc]
|
||||
pub fn url(&self) -> &Url {
|
||||
return &self.0;
|
||||
}
|
||||
$($item)*
|
||||
}
|
||||
impl Deref for $name {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &String {
|
||||
&self.1
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
|
||||
let mut debug_trait_builder = f.debug_tuple(stringify!($name));
|
||||
debug_trait_builder.field(&self.1);
|
||||
debug_trait_builder.finish()
|
||||
}
|
||||
}
|
||||
impl<'de> ::serde::Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: ::serde::de::Deserializer<'de>,
|
||||
{
|
||||
struct UrlVisitor;
|
||||
impl<'de> ::serde::de::Visitor<'de> for UrlVisitor {
|
||||
type Value = $name;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut ::std::fmt::Formatter
|
||||
) -> ::std::fmt::Result {
|
||||
formatter.write_str(stringify!($name))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: ::serde::de::Error,
|
||||
{
|
||||
$name::new(v.to_string()).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(UrlVisitor {})
|
||||
}
|
||||
}
|
||||
impl ::serde::Serialize for $name {
|
||||
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
|
||||
where
|
||||
SE: ::serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.1)
|
||||
}
|
||||
}
|
||||
impl ::std::hash::Hash for $name {
|
||||
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) -> () {
|
||||
::std::hash::Hash::hash(&(self.1), state);
|
||||
}
|
||||
}
|
||||
impl Ord for $name {
|
||||
fn cmp(&self, other: &$name) -> ::std::cmp::Ordering {
|
||||
self.1.cmp(&other.1)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for $name {
|
||||
fn partial_cmp(&self, other: &$name) -> Option<::std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl PartialEq for $name {
|
||||
fn eq(&self, other: &$name) -> bool {
|
||||
self.1 == other.1
|
||||
}
|
||||
}
|
||||
impl Eq for $name {}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! deserialize_fields {
|
||||
(@field_str Option(Seconds($field:ident))) => { stringify![$field] };
|
||||
(@field_str Option(DateTime(Seconds($field:ident)))) => { stringify![$field] };
|
||||
(@field_str Option($field:ident)) => { stringify![$field] };
|
||||
(@field_str LanguageTag($field:ident)) => { stringify![$field] };
|
||||
(@field_str $field:ident) => { stringify![$field] };
|
||||
(@let_none Option(Seconds($field:ident))) => { let mut $field = None; };
|
||||
(@let_none Option(DateTime(Seconds($field:ident)))) => { let mut $field = None; };
|
||||
(@let_none Option($field:ident)) => { let mut $field = None; };
|
||||
(@let_none LanguageTag($field:ident)) => { let mut $field = None; };
|
||||
(@let_none $field:ident) => { let mut $field = None; };
|
||||
(@case $map:ident $key:ident $language_tag_opt:ident Option(Seconds($field:ident))) => {
|
||||
if $field.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field(stringify!($field)));
|
||||
} else if let Some(language_tag) = $language_tag_opt {
|
||||
return Err(
|
||||
serde::de::Error::custom(
|
||||
format!(
|
||||
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
|
||||
language_tag.as_ref()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
let seconds = $map.next_value::<Option<u64>>()?;
|
||||
$field = seconds.map(Duration::from_secs);
|
||||
};
|
||||
(@case $map:ident $key:ident $language_tag_opt:ident
|
||||
Option(DateTime(Seconds($field:ident)))) => {
|
||||
if $field.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field(stringify!($field)));
|
||||
} else if let Some(language_tag) = $language_tag_opt {
|
||||
return Err(
|
||||
serde::de::Error::custom(
|
||||
format!(
|
||||
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
|
||||
language_tag.as_ref()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
let seconds = $map.next_value::<Option<Timestamp>>()?;
|
||||
$field = seconds
|
||||
.map(|sec| timestamp_to_utc(&sec).map_err(|_| serde::de::Error::custom(
|
||||
format!(
|
||||
concat!(
|
||||
"failed to parse `{}` as UTC datetime (in seconds) for key `",
|
||||
stringify!($field),
|
||||
"`"
|
||||
),
|
||||
sec,
|
||||
)
|
||||
))).transpose()?;
|
||||
};
|
||||
(@case $map:ident $key:ident $language_tag_opt:ident Option($field:ident)) => {
|
||||
if $field.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field(stringify!($field)));
|
||||
} else if let Some(language_tag) = $language_tag_opt {
|
||||
return Err(
|
||||
serde::de::Error::custom(
|
||||
format!(
|
||||
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
|
||||
language_tag.as_ref()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$field = $map.next_value()?;
|
||||
};
|
||||
(@case $map:ident $key:ident $language_tag_opt:ident LanguageTag($field:ident)) => {
|
||||
let localized_claim =
|
||||
if let Some(ref mut localized_claim) = $field {
|
||||
localized_claim
|
||||
} else {
|
||||
let new = LocalizedClaim::new();
|
||||
$field = Some(new);
|
||||
$field.as_mut().unwrap()
|
||||
};
|
||||
if localized_claim.contains_key($language_tag_opt.as_ref()) {
|
||||
return Err(serde::de::Error::custom(format!("duplicate field `{}`", $key)));
|
||||
}
|
||||
|
||||
localized_claim.insert($language_tag_opt, $map.next_value()?);
|
||||
};
|
||||
(@case $map:ident $key:ident $language_tag_opt:ident $field:ident) => {
|
||||
if $field.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field(stringify!($field)));
|
||||
} else if let Some(language_tag) = $language_tag_opt {
|
||||
return Err(
|
||||
serde::de::Error::custom(
|
||||
format!(
|
||||
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
|
||||
language_tag.as_ref()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$field = Some($map.next_value()?);
|
||||
};
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),* => [Option(Seconds($field_new:ident))] $([$($entry:tt)+])*
|
||||
}) => {
|
||||
deserialize_fields![
|
||||
@struct_recurs [$($struct_type)+] {
|
||||
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
|
||||
}
|
||||
]
|
||||
};
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),* => [Option(DateTime(Seconds($field_new:ident)))] $([$($entry:tt)+])*
|
||||
}) => {
|
||||
deserialize_fields![
|
||||
@struct_recurs [$($struct_type)+] {
|
||||
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
|
||||
}
|
||||
]
|
||||
};
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),* => [Option($field_new:ident)] $([$($entry:tt)+])*
|
||||
}) => {
|
||||
deserialize_fields![
|
||||
@struct_recurs [$($struct_type)+] {
|
||||
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
|
||||
}
|
||||
]
|
||||
};
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),* => [LanguageTag($field_new:ident)] $([$($entry:tt)+])*
|
||||
}) => {
|
||||
deserialize_fields![
|
||||
@struct_recurs [$($struct_type)+] {
|
||||
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
|
||||
}
|
||||
]
|
||||
};
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),* => [$field_new:ident] $([$($entry:tt)+])*
|
||||
}) => {
|
||||
deserialize_fields![
|
||||
@struct_recurs [$($struct_type)+] {
|
||||
$($name: $e,)* $field_new:
|
||||
$field_new
|
||||
.ok_or_else(|| serde::de::Error::missing_field(stringify!($field_new)))? =>
|
||||
$([$($entry)+])*
|
||||
}
|
||||
]
|
||||
};
|
||||
// Actually instantiate the struct.
|
||||
(@struct_recurs [$($struct_type:tt)+] {
|
||||
$($name:ident: $e:expr),+ =>
|
||||
}) => {
|
||||
#[allow(clippy::redundant_field_names)]
|
||||
$($struct_type)+ {
|
||||
$($name: $e),+
|
||||
}
|
||||
};
|
||||
// Main entry point
|
||||
(
|
||||
$map:ident {
|
||||
$([$($entry:tt)+])+
|
||||
}
|
||||
) => {
|
||||
// let mut field_name = None;
|
||||
$(deserialize_fields![@let_none $($entry)+];)+
|
||||
while let Some(key) = $map.next_key::<String>()? {
|
||||
let (field_name, language_tag_opt) = split_language_tag_key(&key);
|
||||
match field_name {
|
||||
$(
|
||||
// "field_name" => { ... }
|
||||
deserialize_fields![@field_str $($entry)+] => {
|
||||
deserialize_fields![@case $map key language_tag_opt $($entry)+];
|
||||
},
|
||||
)+
|
||||
// Ignore unknown fields.
|
||||
_ => {
|
||||
$map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(deserialize_fields![@struct_recurs [Self::Value] { => $([$($entry)+])* }])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serialize_fields {
|
||||
(@case $self:ident $map:ident Option(Seconds($field:ident))) => {
|
||||
if let Some(ref $field) = $self.$field {
|
||||
$map.serialize_entry(stringify!($field), &$field.as_secs())?;
|
||||
}
|
||||
};
|
||||
(@case $self:ident $map:ident Option(DateTime(Seconds($field:ident)))) => {
|
||||
if let Some(ref $field) = $self.$field {
|
||||
$map.serialize_entry(stringify!($field), &utc_to_seconds(&$field))?;
|
||||
}
|
||||
};
|
||||
(@case $self:ident $map:ident Option($field:ident)) => {
|
||||
if let Some(ref $field) = $self.$field {
|
||||
$map.serialize_entry(stringify!($field), $field)?;
|
||||
}
|
||||
};
|
||||
(@case $self:ident $map:ident LanguageTag($field:ident)) => {
|
||||
if let Some(ref field_map) = $self.$field {
|
||||
use itertools::sorted;
|
||||
let sorted_field_map = sorted(field_map.iter());
|
||||
for (language_tag_opt, $field) in sorted_field_map {
|
||||
if let Some(ref language_tag) = language_tag_opt {
|
||||
$map.serialize_entry(
|
||||
&format!(concat!(stringify!($field), "#{}"), language_tag.as_ref()),
|
||||
&$field
|
||||
)?;
|
||||
} else {
|
||||
$map.serialize_entry(stringify!($field), &$field)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@case $self:ident $map:ident $field:ident) => {
|
||||
$map.serialize_entry(stringify!($field), &$self.$field)?;
|
||||
};
|
||||
// Main entry point
|
||||
(
|
||||
$self:ident -> $serializer:ident {
|
||||
$([$($entry:tt)+])+
|
||||
}
|
||||
) => {
|
||||
let mut map = $serializer.serialize_map(None)?;
|
||||
$(
|
||||
serialize_fields![@case $self map $($entry)+];
|
||||
)+
|
||||
map.end()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! field_getters {
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool >) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<bool> {
|
||||
$zero.$field
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool > { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<bool> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >>) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<DateTime<Utc>> {
|
||||
$zero.$field
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >> { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<DateTime<Utc>> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty >) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<&$type> {
|
||||
$zero.$field.as_ref()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty > { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> Option<$type> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident DateTime < Utc >) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> DateTime<Utc> {
|
||||
$zero.$field
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> &$type {
|
||||
&$zero.$field
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $field(&$self) -> $type {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool >) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<bool> {
|
||||
$zero.$field()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool > { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<bool> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >>) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<DateTime<Utc>> {
|
||||
$zero.$field()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >> { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<DateTime<Utc>> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty >) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<&$type> {
|
||||
$zero.$field()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty > { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> Option<$type> {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() DateTime < Utc >) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> DateTime<Utc> {
|
||||
$zero.$field()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> &$type {
|
||||
&$zero.$field()
|
||||
}
|
||||
};
|
||||
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty { $($body:tt)+ }) => {
|
||||
#[doc = $doc]
|
||||
fn $field(&$self) -> $type {
|
||||
$($body)+
|
||||
}
|
||||
};
|
||||
// Main entry points
|
||||
(
|
||||
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
|
||||
$(
|
||||
$field:ident[$($entry:tt)+] [$doc_field:expr],
|
||||
)+
|
||||
}
|
||||
) => {
|
||||
$(
|
||||
field_getters![
|
||||
@case
|
||||
[concat!("Returns the `", $doc_field, "` ", $doc, ".")]
|
||||
$vis $self [$zero] $field $($entry)+
|
||||
];
|
||||
)+
|
||||
};
|
||||
(
|
||||
$vis:vis $self:ident [$zero:expr]() [$doc:expr] {
|
||||
$(
|
||||
$field:ident[$($entry:tt)+] [$doc_field:expr],
|
||||
)+
|
||||
}
|
||||
) => {
|
||||
$(
|
||||
field_getters![
|
||||
@case
|
||||
[concat!("Returns the `", $doc_field, "` ", $doc, ".")]
|
||||
$vis $self [$zero] $field() $($entry)+
|
||||
];
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! field_setters {
|
||||
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty [$doc_field:expr]) => {
|
||||
field_setters![
|
||||
@case2
|
||||
[concat!("Sets the `", $doc_field, "` ", $doc, ".")]
|
||||
$vis $self [$zero] $setter $field $type
|
||||
];
|
||||
};
|
||||
(@case2 [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty) => {
|
||||
#[doc = $doc]
|
||||
$vis fn $setter(
|
||||
mut $self,
|
||||
$field: $type
|
||||
) -> Self {
|
||||
$zero.$field = $field;
|
||||
$self
|
||||
}
|
||||
};
|
||||
// Main entry point
|
||||
(
|
||||
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
|
||||
$setter:ident -> $field:ident[$($entry:tt)+] [$doc_field:expr]
|
||||
}
|
||||
) => {
|
||||
field_setters![
|
||||
@case [$doc] $vis $self [$zero] $setter $field $($entry)+ [$doc_field]
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! field_getters_setters {
|
||||
(
|
||||
@single $vis:vis $self:ident [$zero:expr] [$doc:expr]
|
||||
[$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]
|
||||
) => {
|
||||
field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [$field_doc], }];
|
||||
field_setters![
|
||||
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }
|
||||
];
|
||||
field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];
|
||||
};
|
||||
(
|
||||
@single $vis:vis $self:ident [$zero:expr]() [$doc:expr]
|
||||
[$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]
|
||||
) => {
|
||||
field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [$field_doc], }];
|
||||
field_setters![
|
||||
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }
|
||||
];
|
||||
field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];
|
||||
};
|
||||
(
|
||||
@single $vis:vis $self:ident [$zero:expr] [$doc:expr]
|
||||
[$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]
|
||||
) => {
|
||||
field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [stringify!($field)], }];
|
||||
field_setters![
|
||||
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }
|
||||
];
|
||||
field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];
|
||||
};
|
||||
(
|
||||
@single $vis:vis $self:ident [$zero:expr]() [$doc:expr]
|
||||
[$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]
|
||||
) => {
|
||||
field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [stringify!($field)], }];
|
||||
field_setters![
|
||||
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }
|
||||
];
|
||||
field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];
|
||||
};
|
||||
// Base case.
|
||||
(@single $vis:vis $self:ident [$zero:expr] [$doc:expr] []) => {};
|
||||
// Main entry points.
|
||||
(
|
||||
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
|
||||
$setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*
|
||||
}
|
||||
) => {
|
||||
field_getters_setters![
|
||||
@single
|
||||
$vis $self [$zero] [$doc] [$setter -> $field[$($entry)+] $($rest)*]
|
||||
];
|
||||
};
|
||||
(
|
||||
$vis:vis $self:ident [$zero:expr]() [$doc:expr] {
|
||||
$setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*
|
||||
}
|
||||
) => {
|
||||
field_getters_setters![
|
||||
@single
|
||||
$vis $self [$zero]() [$doc] [$setter -> $field[$($entry)+] $($rest)*]
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! deserialize_from_str {
|
||||
($type:path) => {
|
||||
impl<'de> serde::Deserialize<'de> for $type {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let variant_str = String::deserialize(deserializer)?;
|
||||
Ok(Self::from_str(&variant_str))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! serialize_as_str {
|
||||
($type:path) => {
|
||||
impl serde::ser::Serialize for $type {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
1537
zeroidc/vendor/openidconnect/src/registration.rs
vendored
Normal file
1537
zeroidc/vendor/openidconnect/src/registration.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1342
zeroidc/vendor/openidconnect/src/types.rs
vendored
Normal file
1342
zeroidc/vendor/openidconnect/src/types.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
557
zeroidc/vendor/openidconnect/src/user_info.rs
vendored
Normal file
557
zeroidc/vendor/openidconnect/src/user_info.rs
vendored
Normal file
@@ -0,0 +1,557 @@
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::str;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use http::header::{HeaderValue, ACCEPT, CONTENT_TYPE};
|
||||
use http::method::Method;
|
||||
use http::status::StatusCode;
|
||||
use oauth2::AccessToken;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::helpers::FilteredFlatten;
|
||||
use crate::http_utils::{auth_bearer, content_type_has_essence, MIME_TYPE_JSON, MIME_TYPE_JWT};
|
||||
use crate::jwt::{JsonWebTokenError, JsonWebTokenJsonPayloadSerde};
|
||||
use crate::types::helpers::deserialize_string_or_vec_opt;
|
||||
use crate::types::LocalizedClaim;
|
||||
use crate::verification::UserInfoVerifier;
|
||||
use crate::{
|
||||
AdditionalClaims, AddressClaim, Audience, AudiencesClaim, ClaimsVerificationError,
|
||||
EndUserBirthday, EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName,
|
||||
EndUserName, EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl,
|
||||
EndUserTimezone, EndUserUsername, EndUserWebsiteUrl, GenderClaim, HttpRequest, HttpResponse,
|
||||
IssuerClaim, IssuerUrl, JsonWebKey, JsonWebKeyType, JsonWebKeyUse, JsonWebToken,
|
||||
JweContentEncryptionAlgorithm, JwsSigningAlgorithm, LanguageTag, PrivateSigningKey,
|
||||
StandardClaims, SubjectIdentifier,
|
||||
};
|
||||
|
||||
///
|
||||
/// User info request.
|
||||
///
|
||||
pub struct UserInfoRequest<'a, JE, JS, JT, JU, K>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
JU: JsonWebKeyUse,
|
||||
K: JsonWebKey<JS, JT, JU>,
|
||||
{
|
||||
pub(super) url: &'a UserInfoUrl,
|
||||
pub(super) access_token: AccessToken,
|
||||
pub(super) require_signed_response: bool,
|
||||
pub(super) signed_response_verifier: UserInfoVerifier<'static, JE, JS, JT, JU, K>,
|
||||
}
|
||||
impl<'a, JE, JS, JT, JU, K> UserInfoRequest<'a, JE, JS, JT, JU, K>
|
||||
where
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
JU: JsonWebKeyUse,
|
||||
K: JsonWebKey<JS, JT, JU>,
|
||||
{
|
||||
///
|
||||
/// Submits this request to the associated user info endpoint using the specified synchronous
|
||||
/// HTTP client.
|
||||
///
|
||||
pub fn request<AC, GC, HC, RE>(
|
||||
self,
|
||||
http_client: HC,
|
||||
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
HC: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
|
||||
RE: std::error::Error + 'static,
|
||||
{
|
||||
http_client(self.prepare_request())
|
||||
.map_err(UserInfoError::Request)
|
||||
.and_then(|http_response| self.user_info_response(http_response))
|
||||
}
|
||||
|
||||
///
|
||||
/// Submits this request to the associated user info endpoint using the specified asynchronous
|
||||
/// HTTP client.
|
||||
///
|
||||
pub async fn request_async<AC, C, F, GC, RE>(
|
||||
self,
|
||||
http_client: C,
|
||||
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
C: FnOnce(HttpRequest) -> F,
|
||||
F: Future<Output = Result<HttpResponse, RE>>,
|
||||
GC: GenderClaim,
|
||||
RE: std::error::Error + 'static,
|
||||
{
|
||||
let http_request = self.prepare_request();
|
||||
let http_response = http_client(http_request)
|
||||
.await
|
||||
.map_err(UserInfoError::Request)?;
|
||||
|
||||
self.user_info_response(http_response)
|
||||
}
|
||||
|
||||
fn prepare_request(&self) -> HttpRequest {
|
||||
let (auth_header, auth_value) = auth_bearer(&self.access_token);
|
||||
HttpRequest {
|
||||
url: self.url.url().clone(),
|
||||
method: Method::GET,
|
||||
headers: vec![
|
||||
(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON)),
|
||||
(auth_header, auth_value),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_info_response<AC, GC, RE>(
|
||||
self,
|
||||
http_response: HttpResponse,
|
||||
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
RE: std::error::Error + 'static,
|
||||
{
|
||||
if http_response.status_code != StatusCode::OK {
|
||||
return Err(UserInfoError::Response(
|
||||
http_response.status_code,
|
||||
http_response.body,
|
||||
"unexpected HTTP status code".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
match http_response
|
||||
.headers
|
||||
.get(CONTENT_TYPE)
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| HeaderValue::from_static(MIME_TYPE_JSON))
|
||||
{
|
||||
ref content_type if content_type_has_essence(content_type, MIME_TYPE_JSON) => {
|
||||
if self.require_signed_response {
|
||||
return Err(UserInfoError::ClaimsVerification(
|
||||
ClaimsVerificationError::NoSignature,
|
||||
));
|
||||
}
|
||||
UserInfoClaims::from_json(
|
||||
&http_response.body,
|
||||
self.signed_response_verifier.expected_subject(),
|
||||
)
|
||||
}
|
||||
ref content_type if content_type_has_essence(content_type, MIME_TYPE_JWT) => {
|
||||
let jwt_str = String::from_utf8(http_response.body).map_err(|_| {
|
||||
UserInfoError::Other("response body has invalid UTF-8 encoding".to_string())
|
||||
})?;
|
||||
serde_path_to_error::deserialize::<_, UserInfoJsonWebToken<AC, GC, JE, JS, JT>>(
|
||||
serde_json::Value::String(jwt_str),
|
||||
)
|
||||
.map_err(UserInfoError::Parse)?
|
||||
.claims(&self.signed_response_verifier)
|
||||
.map_err(UserInfoError::ClaimsVerification)
|
||||
}
|
||||
ref content_type => Err(UserInfoError::Response(
|
||||
http_response.status_code,
|
||||
http_response.body,
|
||||
format!("unexpected response Content-Type: `{:?}`", content_type),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Specifies whether to require the user info response to be a signed JSON Web Token (JWT).
|
||||
///
|
||||
pub fn require_signed_response(mut self, require_signed_response: bool) -> Self {
|
||||
self.require_signed_response = require_signed_response;
|
||||
self
|
||||
}
|
||||
|
||||
///
|
||||
/// Specifies whether to require the issuer of the signed JWT response to match the expected
|
||||
/// issuer URL for this provider.
|
||||
///
|
||||
/// This option has no effect on unsigned JSON responses.
|
||||
///
|
||||
pub fn require_issuer_match(mut self, iss_required: bool) -> Self {
|
||||
self.signed_response_verifier = self
|
||||
.signed_response_verifier
|
||||
.require_issuer_match(iss_required);
|
||||
self
|
||||
}
|
||||
|
||||
///
|
||||
/// Specifies whether to require the audience of the signed JWT response to match the expected
|
||||
/// audience (client ID).
|
||||
///
|
||||
/// This option has no effect on unsigned JSON responses.
|
||||
///
|
||||
pub fn require_audience_match(mut self, aud_required: bool) -> Self {
|
||||
self.signed_response_verifier = self
|
||||
.signed_response_verifier
|
||||
.require_audience_match(aud_required);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// User info claims.
|
||||
///
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct UserInfoClaims<AC: AdditionalClaims, GC: GenderClaim>(UserInfoClaimsImpl<AC, GC>);
|
||||
impl<AC, GC> UserInfoClaims<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
///
|
||||
/// Initializes user info claims.
|
||||
///
|
||||
pub fn new(standard_claims: StandardClaims<GC>, additional_claims: AC) -> Self {
|
||||
Self(UserInfoClaimsImpl {
|
||||
issuer: None,
|
||||
audiences: None,
|
||||
standard_claims,
|
||||
additional_claims: additional_claims.into(),
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes user info claims from the provided raw JSON response.
|
||||
///
|
||||
/// If an `expected_subject` is provided, this function verifies that the user info claims
|
||||
/// contain the expected subject and returns an error otherwise.
|
||||
///
|
||||
pub fn from_json<RE>(
|
||||
user_info_json: &[u8],
|
||||
expected_subject: Option<&SubjectIdentifier>,
|
||||
) -> Result<Self, UserInfoError<RE>>
|
||||
where
|
||||
RE: std::error::Error + 'static,
|
||||
{
|
||||
let user_info = serde_path_to_error::deserialize::<_, UserInfoClaimsImpl<AC, GC>>(
|
||||
&mut serde_json::Deserializer::from_slice(user_info_json),
|
||||
)
|
||||
.map_err(UserInfoError::Parse)?;
|
||||
|
||||
// This is the only verification we need to do for JSON-based user info claims, so don't
|
||||
// bother with the complexity of a separate verifier object.
|
||||
if expected_subject
|
||||
.iter()
|
||||
.all(|expected_subject| user_info.standard_claims.sub == **expected_subject)
|
||||
{
|
||||
Ok(Self(user_info))
|
||||
} else {
|
||||
Err(UserInfoError::ClaimsVerification(
|
||||
ClaimsVerificationError::InvalidSubject(format!(
|
||||
"expected `{}` (found `{}`)",
|
||||
// This can only happen when expected_subject is not None.
|
||||
expected_subject.unwrap().as_str(),
|
||||
user_info.standard_claims.sub.as_str(),
|
||||
)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
field_getters_setters![
|
||||
pub self [self.0] ["claim"] {
|
||||
set_issuer -> issuer[Option<IssuerUrl>],
|
||||
set_audiences -> audiences[Option<Vec<Audience>>] ["aud"],
|
||||
}
|
||||
];
|
||||
|
||||
///
|
||||
/// Returns the `sub` claim.
|
||||
///
|
||||
pub fn subject(&self) -> &SubjectIdentifier {
|
||||
&self.0.standard_claims.sub
|
||||
}
|
||||
///
|
||||
/// Sets the `sub` claim.
|
||||
///
|
||||
pub fn set_subject(&mut self, subject: SubjectIdentifier) {
|
||||
self.0.standard_claims.sub = subject
|
||||
}
|
||||
|
||||
field_getters_setters![
|
||||
pub self [self.0.standard_claims] ["claim"] {
|
||||
set_name -> name[Option<LocalizedClaim<EndUserName>>],
|
||||
set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],
|
||||
set_family_name ->
|
||||
family_name[Option<LocalizedClaim<EndUserFamilyName>>],
|
||||
set_middle_name ->
|
||||
middle_name[Option<LocalizedClaim<EndUserMiddleName>>],
|
||||
set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],
|
||||
set_preferred_username -> preferred_username[Option<EndUserUsername>],
|
||||
set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],
|
||||
set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],
|
||||
set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],
|
||||
set_email -> email[Option<EndUserEmail>],
|
||||
set_email_verified -> email_verified[Option<bool>],
|
||||
set_gender -> gender[Option<GC>],
|
||||
set_birthday -> birthday[Option<EndUserBirthday>],
|
||||
set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],
|
||||
set_locale -> locale[Option<LanguageTag>],
|
||||
set_phone_number -> phone_number[Option<EndUserPhoneNumber>],
|
||||
set_phone_number_verified -> phone_number_verified[Option<bool>],
|
||||
set_address -> address[Option<AddressClaim>],
|
||||
set_updated_at -> updated_at[Option<DateTime<Utc>>],
|
||||
}
|
||||
];
|
||||
|
||||
///
|
||||
/// Returns the standard claims as a `StandardClaims` object.
|
||||
///
|
||||
pub fn standard_claims(&self) -> &StandardClaims<GC> {
|
||||
&self.0.standard_claims
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns additional user info claims.
|
||||
///
|
||||
pub fn additional_claims(&self) -> &AC {
|
||||
self.0.additional_claims.as_ref()
|
||||
}
|
||||
///
|
||||
/// Returns mutable additional user info claims.
|
||||
///
|
||||
pub fn additional_claims_mut(&mut self) -> &mut AC {
|
||||
self.0.additional_claims.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct UserInfoClaimsImpl<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
#[serde(rename = "iss", skip_serializing_if = "Option::is_none")]
|
||||
pub issuer: Option<IssuerUrl>,
|
||||
// We always serialize as an array, which is valid according to the spec.
|
||||
#[serde(
|
||||
default,
|
||||
rename = "aud",
|
||||
deserialize_with = "deserialize_string_or_vec_opt",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub audiences: Option<Vec<Audience>>,
|
||||
|
||||
#[serde(bound = "GC: GenderClaim", flatten)]
|
||||
pub standard_claims: StandardClaims<GC>,
|
||||
|
||||
#[serde(bound = "AC: AdditionalClaims", flatten)]
|
||||
pub additional_claims: FilteredFlatten<StandardClaims<GC>, AC>,
|
||||
}
|
||||
impl<AC, GC> AudiencesClaim for UserInfoClaimsImpl<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
fn audiences(&self) -> Option<&Vec<Audience>> {
|
||||
self.audiences.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'a, AC, GC> AudiencesClaim for &'a UserInfoClaimsImpl<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
fn audiences(&self) -> Option<&Vec<Audience>> {
|
||||
self.audiences.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<AC, GC> IssuerClaim for UserInfoClaimsImpl<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
fn issuer(&self) -> Option<&IssuerUrl> {
|
||||
self.issuer.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'a, AC, GC> IssuerClaim for &'a UserInfoClaimsImpl<AC, GC>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
{
|
||||
fn issuer(&self) -> Option<&IssuerUrl> {
|
||||
self.issuer.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// JSON Web Token (JWT) containing user info claims.
|
||||
///
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct UserInfoJsonWebToken<
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
>(
|
||||
#[serde(bound = "AC: AdditionalClaims")]
|
||||
JsonWebToken<JE, JS, JT, UserInfoClaimsImpl<AC, GC>, JsonWebTokenJsonPayloadSerde>,
|
||||
);
|
||||
impl<AC, GC, JE, JS, JT> UserInfoJsonWebToken<AC, GC, JE, JS, JT>
|
||||
where
|
||||
AC: AdditionalClaims,
|
||||
GC: GenderClaim,
|
||||
JE: JweContentEncryptionAlgorithm<JT>,
|
||||
JS: JwsSigningAlgorithm<JT>,
|
||||
JT: JsonWebKeyType,
|
||||
{
|
||||
///
|
||||
/// Initializes a new signed JWT containing the specified claims, signed with the specified key
|
||||
/// and signing algorithm.
|
||||
///
|
||||
pub fn new<JU, K, S>(
|
||||
claims: UserInfoClaims<AC, GC>,
|
||||
signing_key: &S,
|
||||
alg: JS,
|
||||
) -> Result<Self, JsonWebTokenError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
K: JsonWebKey<JS, JT, JU>,
|
||||
S: PrivateSigningKey<JS, JT, JU, K>,
|
||||
{
|
||||
Ok(Self(JsonWebToken::new(claims.0, signing_key, &alg)?))
|
||||
}
|
||||
|
||||
///
|
||||
/// Verifies and returns the user info claims.
|
||||
///
|
||||
pub fn claims<JU, K>(
|
||||
self,
|
||||
verifier: &UserInfoVerifier<JE, JS, JT, JU, K>,
|
||||
) -> Result<UserInfoClaims<AC, GC>, ClaimsVerificationError>
|
||||
where
|
||||
JU: JsonWebKeyUse,
|
||||
K: JsonWebKey<JS, JT, JU>,
|
||||
{
|
||||
Ok(UserInfoClaims(verifier.verified_claims(self.0)?))
|
||||
}
|
||||
}
|
||||
|
||||
new_url_type![
|
||||
///
|
||||
/// URL for a provider's user info endpoint.
|
||||
///
|
||||
UserInfoUrl
|
||||
];
|
||||
|
||||
///
|
||||
/// Error retrieving user info.
|
||||
///
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum UserInfoError<RE>
|
||||
where
|
||||
RE: std::error::Error + 'static,
|
||||
{
|
||||
///
|
||||
/// Failed to verify user info claims.
|
||||
///
|
||||
#[error("Failed to verify claims")]
|
||||
ClaimsVerification(#[source] ClaimsVerificationError),
|
||||
///
|
||||
/// Failed to parse server response.
|
||||
///
|
||||
#[error("Failed to parse server response")]
|
||||
Parse(#[source] serde_path_to_error::Error<serde_json::Error>),
|
||||
///
|
||||
/// An error occurred while sending the request or receiving the response (e.g., network
|
||||
/// connectivity failed).
|
||||
///
|
||||
#[error("Request failed")]
|
||||
Request(#[source] RE),
|
||||
///
|
||||
/// Server returned an invalid response.
|
||||
///
|
||||
#[error("Server returned invalid response: {2}")]
|
||||
Response(StatusCode, Vec<u8>, String),
|
||||
///
|
||||
/// An unexpected error occurred.
|
||||
///
|
||||
#[error("Other error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::CoreGenderClaim;
|
||||
use crate::{AdditionalClaims, UserInfoClaims};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
struct TestClaims {
|
||||
pub tfa_method: String,
|
||||
}
|
||||
impl AdditionalClaims for TestClaims {}
|
||||
|
||||
#[test]
|
||||
fn test_additional_claims() {
|
||||
let claims = UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<
|
||||
crate::reqwest::HttpClientError,
|
||||
>(
|
||||
"{
|
||||
\"iss\": \"https://server.example.com\",
|
||||
\"sub\": \"24400320\",
|
||||
\"aud\": [\"s6BhdRkqt3\"],
|
||||
\"tfa_method\": \"u2f\"
|
||||
}"
|
||||
.as_bytes(),
|
||||
None,
|
||||
)
|
||||
.expect("failed to deserialize");
|
||||
assert_eq!(claims.additional_claims().tfa_method, "u2f");
|
||||
assert_eq!(
|
||||
serde_json::to_string(&claims).expect("failed to serialize"),
|
||||
"{\
|
||||
\"iss\":\"https://server.example.com\",\
|
||||
\"aud\":[\"s6BhdRkqt3\"],\
|
||||
\"sub\":\"24400320\",\
|
||||
\"tfa_method\":\"u2f\"\
|
||||
}",
|
||||
);
|
||||
|
||||
UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<crate::reqwest::HttpClientError>(
|
||||
"{
|
||||
\"iss\": \"https://server.example.com\",
|
||||
\"sub\": \"24400320\",
|
||||
\"aud\": [\"s6BhdRkqt3\"]
|
||||
}".as_bytes(),
|
||||
None,
|
||||
)
|
||||
.expect_err("missing claim should fail to deserialize");
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct AllOtherClaims(HashMap<String, serde_json::Value>);
|
||||
impl AdditionalClaims for AllOtherClaims {}
|
||||
|
||||
#[test]
|
||||
fn test_catch_all_additional_claims() {
|
||||
let claims = UserInfoClaims::<AllOtherClaims, CoreGenderClaim>::from_json::<
|
||||
crate::reqwest::HttpClientError,
|
||||
>(
|
||||
"{
|
||||
\"iss\": \"https://server.example.com\",
|
||||
\"sub\": \"24400320\",
|
||||
\"aud\": [\"s6BhdRkqt3\"],
|
||||
\"tfa_method\": \"u2f\",
|
||||
\"updated_at\": 1000
|
||||
}"
|
||||
.as_bytes(),
|
||||
None,
|
||||
)
|
||||
.expect("failed to deserialize");
|
||||
|
||||
assert_eq!(claims.additional_claims().0.len(), 1);
|
||||
assert_eq!(claims.additional_claims().0["tfa_method"], "u2f");
|
||||
}
|
||||
}
|
||||
2096
zeroidc/vendor/openidconnect/src/verification.rs
vendored
Normal file
2096
zeroidc/vendor/openidconnect/src/verification.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user