Skip to main content

CimdError

Enum CimdError 

Source
#[non_exhaustive]
pub enum CimdError {
Show 19 variants NotHttps, NoHost, NotAscii, NoPath, DotSegment, Fragment, Userinfo, QueryString, SpecialUseAddress, UrlTooLong, DocumentTooLarge, NotJson, MissingClientId, ClientIdMismatch, ClientSecretPresent, SharedSecretAuthMethod, KeyMaterialPresent, RedirectUriNotSameOrigin, Metadata(RegistrationErrorResponse),
}
Available on crate feature cimd only.
Expand description

Why a client identifier URL, or a document fetched from one, was refused.

Each variant names the RULE it broke rather than echoing the offending value: this is attacker-supplied text that a host is likely to log, and the same reasoning already applies to crate::RegistrationErrorResponse’s descriptions. #[non_exhaustive]: this enum is a list of REFUSAL REASONS for a specification still in working-group draft, so it will gain variants as the draft gains rules, and a host that matches it exhaustively must not have its build broken by a patch release that tightens a check.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

NotHttps

Section 3: the client identifier MUST use the https scheme.

Spelled in lower case, and an upper-case HTTPS:// lands here too. Section 4.1 compares the document’s client_id to the fetch URL by RFC 3986 section 6.2.1 SIMPLE STRING COMPARISON, so normalising the scheme here would make two byte-distinct strings name one client and break that comparison rather than help it. Refusing is the only move that keeps both rules true at once.

§

NoHost

RFC 3986 section 3.2: the client identifier has no host. https:///app is not a URL anything can be fetched from, and accepting one would put an identifier in the client table that no fetch could ever be made against.

§

NotAscii

RFC 3986 section 2: the client identifier contains a byte outside printable ASCII — or a backslash or a percent sign in its AUTHORITY, which are printable ASCII and land here anyway.

A URI’s grammar is ASCII, and anything outside it MUST be percent-encoded, so a raw space, a control byte or a newline here is not a URI at all. This crate’s RFC 8707 resource indicator check makes the same refusal for the same reason. It matters more here than there: the client identifier is echoed into audit records, and a newline in one is log injection.

§The two printable-ASCII cases, and why they are here

A backslash in the authority (https://good.example\.evil.com/app) and a percent-escape in the authority (https://127.0.0.1%2e/app) are both refused as NotAscii. Neither is a non-ASCII byte, and the name is a variant REUSED rather than earned: it is one refusal short of honest, and it is said here because docs.rs is where a host reads what this variant means.

They share the reason. A WHATWG URL parser — which is what every mainstream HTTP client puts between the host and the socket — reads a backslash as a path separator and percent-DECODES the host before parsing it, while this crate reads the raw bytes. So the crate and the fetcher would derive DIFFERENT hosts from one string, which is the entire class of defect CimdError::SpecialUseAddress’s literal check exists to close. Refused rather than decoded, for the reason the module refuses everywhere else: decoding would mean two parties each deriving a host by their own rules. See ClientIdUrl::parse for the worked examples.

§

NoPath

Section 3: the client identifier MUST contain a path component. https://client.example has none; https://client.example/ has one.

§

DotSegment

Section 3: the client identifier MUST NOT contain single-dot or double-dot path segments.

Refused rather than resolved, for the reason in CimdError::NotHttps: resolving .. would let two distinct strings name one client while section 4.1 still compares them byte for byte.

§

Fragment

Section 3: the client identifier MUST NOT contain a fragment component.

§

Userinfo

Section 3: the client identifier MUST NOT contain a userinfo component. https://a@b/c is a URL whose HOST is b, which is not what most readers of it see.

§

QueryString

Section 3 SHOULD NOT: the client identifier carries a query string and this deployment did not set CimdPolicy::allow_query_string.

§

SpecialUseAddress

Section 6.5: the host of the client identifier is an IP LITERAL in a special-use range (RFC 6890), so dereferencing it would be a request to this deployment’s own network.

This is only HALF of section 6.5. See ClientIdUrl::parse for the half that is the host’s, and for why an address that passes here can still be a rebinding attack.

§

UrlTooLong

The client identifier is longer than MAX_CLIENT_ID_URL_BYTES.

§

DocumentTooLarge

Section 6.6: the document is larger than the policy’s max_document_bytes.

§

NotJson

Section 4.1: the document is not the JSON object RFC 8259 defines.

§

MissingClientId

Section 4.1: the document has no client_id member. It is REQUIRED, and its absence is not the same as a mismatch: nothing was claimed at all.

§

ClientIdMismatch

Section 4.1: the document’s client_id is not, byte for byte, the URL it was fetched from.

THIS IS THE CHECK THE WHOLE MECHANISM RESTS ON. Without it any document authorizes any client: an attacker publishes a document at a URL they control that claims somebody else’s client identifier, and an authorization server that skipped this comparison hands them that client’s redirect URIs.

§

ClientSecretPresent

Section 4.1: the document carries client_secret or client_secret_expires_at.

The document is world-readable by construction, so a shared secret in one is a secret published to the internet. Refused rather than dropped, on the same reasoning crate::registration gives for software_statement: dropping a member the client believes is being honoured registers a client on terms nobody agreed to.

§

SharedSecretAuthMethod

Section 4.1: token_endpoint_auth_method names a method that rests on a SHARED SYMMETRIC secret (client_secret_basic, client_secret_post, client_secret_jwt), which a public document cannot hold. See CimdError::ClientSecretPresent.

§

KeyMaterialPresent

Section 4.1: the document carries jwks or jwks_uri.

REFUSED, NOT DROPPED, and this is the one refusal in the list that is a property of THIS BUILD rather than of the draft. The draft PERMITS these two: a public key is the only credential a world-readable document can carry, so it is the sanctioned way for a client identifier metadata document to say “I authenticate, and here is what with”.

This crate cannot honour that yet. crate::registration models neither member (it records the gap in its own module docs), which is why ValidatedClientIdDocument::to_client is unconditionally ClientAuth::Public, and why crate::registration’s own validator refuses private_key_jwt outright. A document offering a key would therefore have been accepted as a PUBLIC client with no word said about the credential its author believes is in force — the exact outcome CimdError::ClientSecretPresent exists to prevent, and there is no reading on which the same facts deserve opposite treatment because one member is a secret and the other is not.

It also happens to be the only place the draft’s MUST NOT on PRIVATE key material is reachable at all: a private JWK arrives inside jwks, so a document that publishes one is refused here rather than parsed and silently dropped. Nothing else in this module looks inside the member, and nothing needs to: the whole member is refused either way.

This variant is where the gap closes. When jwks/jwks_uri become registrable, a document carrying one stops being a refusal and starts being a confidential client — and this enum is #[non_exhaustive] precisely so that removing a refusal is a patch release.

§

RedirectUriNotSameOrigin

Section 6.1: a redirect_uris entry is not same-origin with the client identifier, and this deployment left CimdPolicy::redirect_uris_same_origin on.

Section 6.1 PERMITS rather than requires this, and it is on by default here because without it anyone who can host a document can name any redirect URI in it.

§

Metadata(RegistrationErrorResponse)

The document’s metadata failed the same RFC 7591 section 2 validation a dynamic registration does, and this is that refusal, unchanged.

Section 4.1 says the members come from the OAuth Dynamic Client Registration Metadata registry, so they are checked by crate::registration’s validator rather than by a second copy of it that would drift.

Trait Implementations§

Source§

impl Clone for CimdError

Source§

fn clone(&self) -> CimdError

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for CimdError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for CimdError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for CimdError

Source§

impl Error for CimdError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for CimdError

Source§

fn eq(&self, other: &CimdError) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for CimdError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.