#[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),
}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
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.
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§
impl Eq for CimdError
Source§impl Error for CimdError
impl Error for CimdError
1.30.0 · Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()