diff --git a/.lock b/.lock new file mode 100644 index 00000000..e69de29b diff --git a/crates.js b/crates.js new file mode 100644 index 00000000..e46a28d8 --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["jwt_compact"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 00000000..f5402616 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
Redirecting to ../../../jwt_compact/alg/struct.Ed25519.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/enum.ModulusBits.html b/jwt_compact/alg/enum.ModulusBits.html new file mode 100644 index 00000000..a35825c8 --- /dev/null +++ b/jwt_compact/alg/enum.ModulusBits.html @@ -0,0 +1,24 @@ +#[non_exhaustive]pub enum ModulusBits {
+ TwoKibibytes,
+ ThreeKibibytes,
+ FourKibibytes,
+}
rsa
only.Bit length of an RSA key modulus (aka RSA key length).
+2048 bits. This is the minimum recommended key length as of 2020.
+3072 bits.
+4096 bits.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.Redirecting to ../../../jwt_compact/alg/struct.Es256k.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/generic/struct.SecretBytes.html b/jwt_compact/alg/generic/struct.SecretBytes.html new file mode 100644 index 00000000..b599cf73 --- /dev/null +++ b/jwt_compact/alg/generic/struct.SecretBytes.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.SecretBytes.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/generic/trait.SigningKey.html b/jwt_compact/alg/generic/trait.SigningKey.html new file mode 100644 index 00000000..f0fb5c44 --- /dev/null +++ b/jwt_compact/alg/generic/trait.SigningKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/trait.SigningKey.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/generic/trait.VerifyingKey.html b/jwt_compact/alg/generic/trait.VerifyingKey.html new file mode 100644 index 00000000..8e6c151a --- /dev/null +++ b/jwt_compact/alg/generic/trait.VerifyingKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/trait.VerifyingKey.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs256.html b/jwt_compact/alg/hmacs/struct.Hs256.html new file mode 100644 index 00000000..97f1e7da --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs256.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs256.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs256Key.html b/jwt_compact/alg/hmacs/struct.Hs256Key.html new file mode 100644 index 00000000..45b8febf --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs256Key.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs256Key.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs256Signature.html b/jwt_compact/alg/hmacs/struct.Hs256Signature.html new file mode 100644 index 00000000..73c125ff --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs256Signature.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs256Signature.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs384.html b/jwt_compact/alg/hmacs/struct.Hs384.html new file mode 100644 index 00000000..713eeb83 --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs384.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs384.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs384Key.html b/jwt_compact/alg/hmacs/struct.Hs384Key.html new file mode 100644 index 00000000..a63c6c2f --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs384Key.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs384Key.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs384Signature.html b/jwt_compact/alg/hmacs/struct.Hs384Signature.html new file mode 100644 index 00000000..99ec666b --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs384Signature.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs384Signature.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs512.html b/jwt_compact/alg/hmacs/struct.Hs512.html new file mode 100644 index 00000000..3c519853 --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs512.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs512.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs512Key.html b/jwt_compact/alg/hmacs/struct.Hs512Key.html new file mode 100644 index 00000000..7669b6c0 --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs512Key.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs512Key.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/hmacs/struct.Hs512Signature.html b/jwt_compact/alg/hmacs/struct.Hs512Signature.html new file mode 100644 index 00000000..d2acf25e --- /dev/null +++ b/jwt_compact/alg/hmacs/struct.Hs512Signature.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Hs512Signature.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/index.html b/jwt_compact/alg/index.html new file mode 100644 index 00000000..8e023b97 --- /dev/null +++ b/jwt_compact/alg/index.html @@ -0,0 +1,7 @@ +Implementations of JWT signing / verification algorithms. Also contains generic traits +for signing and verifying keys.
+exonum-crypto
or ed25519-dalek
or ed25519-compact
p256
ES256
signing algorithm. Implements elliptic curve digital signatures (ECDSA)
+on the secp256r1 curve (aka P-256).es256k
or k256
HS256
signing algorithm.HS256
algorithm. Zeroed on drop.Hs256
algorithm.HS384
signing algorithm.HS384
algorithm. Zeroed on drop.Hs384
algorithm.HS512
signing algorithm.HS512
algorithm. Zeroed on drop.Hs512
algorithm.ModulusBits
fails.rsa
Rsa
algorithm from a string.rsa
rsa
StrongKey
s.StrongKey
.rsa
Redirecting to ../../../jwt_compact/alg/struct.Es256.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/enum.ModulusBits.html b/jwt_compact/alg/rsa/enum.ModulusBits.html new file mode 100644 index 00000000..b130153e --- /dev/null +++ b/jwt_compact/alg/rsa/enum.ModulusBits.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/enum.ModulusBits.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.ModulusBitsError.html b/jwt_compact/alg/rsa/struct.ModulusBitsError.html new file mode 100644 index 00000000..46f8fbce --- /dev/null +++ b/jwt_compact/alg/rsa/struct.ModulusBitsError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.ModulusBitsError.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.Rsa.html b/jwt_compact/alg/rsa/struct.Rsa.html new file mode 100644 index 00000000..273bee9f --- /dev/null +++ b/jwt_compact/alg/rsa/struct.Rsa.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.Rsa.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.RsaParseError.html b/jwt_compact/alg/rsa/struct.RsaParseError.html new file mode 100644 index 00000000..260c0c9d --- /dev/null +++ b/jwt_compact/alg/rsa/struct.RsaParseError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.RsaParseError.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.RsaPrivateKey.html b/jwt_compact/alg/rsa/struct.RsaPrivateKey.html new file mode 100644 index 00000000..a9a0cf02 --- /dev/null +++ b/jwt_compact/alg/rsa/struct.RsaPrivateKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.RsaPrivateKey.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.RsaPublicKey.html b/jwt_compact/alg/rsa/struct.RsaPublicKey.html new file mode 100644 index 00000000..9b2ebee0 --- /dev/null +++ b/jwt_compact/alg/rsa/struct.RsaPublicKey.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.RsaPublicKey.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/rsa/struct.RsaSignature.html b/jwt_compact/alg/rsa/struct.RsaSignature.html new file mode 100644 index 00000000..1e26aef2 --- /dev/null +++ b/jwt_compact/alg/rsa/struct.RsaSignature.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../jwt_compact/alg/struct.RsaSignature.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/alg/sidebar-items.js b/jwt_compact/alg/sidebar-items.js new file mode 100644 index 00000000..a7b9127d --- /dev/null +++ b/jwt_compact/alg/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ModulusBits"],"struct":["Ed25519","Es256","Es256k","Hs256","Hs256Key","Hs256Signature","Hs384","Hs384Key","Hs384Signature","Hs512","Hs512Key","Hs512Signature","ModulusBitsError","Rsa","RsaParseError","RsaPrivateKey","RsaPublicKey","RsaSignature","SecretBytes","StrongAlg","StrongKey","WeakKeyError"],"trait":["SigningKey","VerifyingKey"]}; \ No newline at end of file diff --git a/jwt_compact/alg/struct.Ed25519.html b/jwt_compact/alg/struct.Ed25519.html new file mode 100644 index 00000000..0f0dedb1 --- /dev/null +++ b/jwt_compact/alg/struct.Ed25519.html @@ -0,0 +1,61 @@ +pub struct Ed25519;
exonum-crypto
or ed25519-dalek
or ed25519-compact
only.Integrity algorithm using digital signatures on the Ed25519 elliptic curve.
+The name of the algorithm is specified as EdDSA
as per IANA registry.
+Use with_specific_name()
to switch to non-standard Ed25519
.
Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.raw
bytes. Returns an error if the bytes do not represent
+a valid key.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Es256;
p256
only.ES256
signing algorithm. Implements elliptic curve digital signatures (ECDSA)
+on the secp256r1 curve (aka P-256).
Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.raw
bytes. Returns an error if the bytes do not represent
+a valid key.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Es256k<D = Sha256> { /* private fields */ }
es256k
or k256
only.Algorithm implementing elliptic curve digital signatures (ECDSA) on the secp256k1 curve.
+The algorithm does not fix the choice of the message digest algorithm; instead, +it is provided as a type parameter. SHA-256 is the default parameter value, +but it can be set to any cryptographically secure hash function with 32-byte output +(e.g., SHA3-256).
+Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Hs256;
HS256
signing algorithm.
See RFC 7518 for the algorithm specification.
+Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.raw
bytes. Returns an error if the bytes do not represent
+a valid key.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Hs256Key(/* private fields */);
Signing / verifying key for HS256
algorithm. Zeroed on drop.
raw
bytes. Returns an error if the bytes do not represent
+a valid key.self
into the result. Lower case
+letters are used (e.g. f9b4ca
)self
into the result. Upper case
+letters are used (e.g. F9B4CA
)pub struct Hs256Signature(/* private fields */);
Signature produced by the Hs256
algorithm.
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct Hs384;
HS384
signing algorithm.
See RFC 7518 for the algorithm specification.
+Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.raw
bytes. Returns an error if the bytes do not represent
+a valid key.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Hs384Key(/* private fields */);
Signing / verifying key for HS384
algorithm. Zeroed on drop.
raw
bytes. Returns an error if the bytes do not represent
+a valid key.self
into the result. Lower case
+letters are used (e.g. f9b4ca
)self
into the result. Upper case
+letters are used (e.g. F9B4CA
)pub struct Hs384Signature(/* private fields */);
Signature produced by the Hs384
algorithm.
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct Hs512;
HS512
signing algorithm.
See RFC 7518 for the algorithm specification.
+Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.raw
bytes. Returns an error if the bytes do not represent
+a valid key.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct Hs512Key(/* private fields */);
Signing / verifying key for HS512
algorithm. Zeroed on drop.
raw
bytes. Returns an error if the bytes do not represent
+a valid key.self
into the result. Lower case
+letters are used (e.g. f9b4ca
)self
into the result. Upper case
+letters are used (e.g. F9B4CA
)pub struct Hs512Signature(/* private fields */);
Signature produced by the Hs512
algorithm.
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct ModulusBitsError(/* private fields */);
rsa
only.Error type returned when a conversion of an integer into ModulusBits
fails.
pub struct Rsa { /* private fields */ }
rsa
only.Integrity algorithm using RSA digital signatures.
+Depending on the variation, the algorithm employs PKCS#1 v1.5 or PSS padding and
+one of the hash functions from the SHA-2 family: SHA-256, SHA-384, or SHA-512.
+See RFC 7518 for more details. Depending on the chosen parameters,
+the name of the algorithm is one of RS256
, RS384
, RS512
, PS256
, PS384
, PS512
:
R
/ P
denote the padding scheme: PKCS#1 v1.5 for R
, PSS for P
256
/ 384
/ 512
denote the hash functionThe length of RSA keys is not unequivocally specified by the algorithm; nevertheless,
+it MUST be at least 2048 bits as per RFC 7518. To minimize risks of misconfiguration,
+use StrongAlg
wrapper around Rsa
:
const ALG: StrongAlg<Rsa> = StrongAlg(Rsa::rs256());
+// `ALG` will not support RSA keys with unsecure lengths by design!
Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct RsaParseError(/* private fields */);
rsa
only.Errors that can occur when parsing an Rsa
algorithm from a string.
pub struct RsaPrivateKey { /* private fields */ }
rsa
only.Represents a whole RSA key, public and private parts.
+Generate a new Rsa key pair of the given bit size using the passed in rng
.
Generate a new RSA key pair of the given bit size and the public exponent
+using the passed in rng
.
Unless you have specific needs, you should use RsaPrivateKey::new
instead.
Constructs an RSA key pair from individual components:
+n
: RSA moduluse
: public exponent (i.e. encrypting exponent)d
: private exponent (i.e. decrypting exponent)primes
: prime factors of n
: typically two primes p
and q
. More than two primes can
+be provided for multiprime RSA, however this is generally not recommended. If no primes
+are provided, a prime factor recovery algorithm will be employed to attempt to recover the
+factors (as described in NIST SP 800-56B Revision 2 Appendix C.2). This algorithm only
+works if there are just two prime factors p
and q
(as opposed to multiprime), and e
+is between 2^16 and 2^256.Constructs an RSA key pair from its two primes p and q.
+This will rebuild the private exponent and the modulus.
+Private exponent will be rebuilt using the method defined in +NIST 800-56B Section 6.2.1.
+Constructs an RSA key pair from its primes.
+This will rebuild the private exponent and the modulus.
+Get the public key from the private key, cloning n
and e
.
Generally this is not needed since RsaPrivateKey
implements the PublicKey
trait,
+but it can occasionally be useful to discard the private information entirely.
Performs some calculations to speed up private key operations.
+Clears precomputed values by setting to None
+Compute CRT coefficient: (1/q) mod p
.
Performs basic sanity checks on the key.
+Returns Ok(())
if everything is good, otherwise an appropriate error.
Decrypt the given message.
+Decrypt the given message.
+Uses rng
to blind the decryption process.
Sign the given digest.
+Sign the given digest using the provided rng
, which is used in the
+following ways depending on the SignatureScheme
:
Pkcs1v15Sign
padding: uses the RNG
+to mask the private key operation with random blinding, which helps
+mitigate sidechannel attacks.Pss
always requires randomness. Use
+Pss::new
for a standard RSASSA-PSS signature, or
+Pss::new_blinded
for RSA-BSSA blind
+signatures.source
. Read moreSecretDocument
] containing a PKCS#8-encoded private key.⚠ Warning. Contrary to RFC 7518, this implementation does not set dp
, dq
, and qi
+fields in the JWK root object, as well as d
and t
fields for additional factors
+(i.e., in the oth
array).
self
and other
values to be equal, and is used
+by ==
.⚠ Warning. Contrary to RFC 7518 (at least, in spirit), this conversion ignores
+dp
, dq
, and qi
fields from JWK, as well as d
and t
fields for additional factors.
SecretDocument
] containing a PKCS#1-encoded private key.pub struct RsaPublicKey { /* private fields */ }
rsa
only.Represents the public part of an RSA key.
+Encrypt the given message.
+Verify a signed message.
+hashed
must be the result of hashing the input using the hashing function
+passed in through hash
.
If the message is valid Ok(())
is returned, otherwise an Err
indicating failure.
Minimum value of the public exponent e
.
Maximum value of the public exponent e
.
Create a new public key from its components.
+This function accepts public keys with a modulus size up to 4096-bits,
+i.e. RsaPublicKey::MAX_SIZE
.
Create a new public key from its components.
+Create a new public key, bypassing checks around the modulus and public +exponent size.
+This method is not recommended, and only intended for unusual use cases.
+Most applications should use RsaPublicKey::new
or
+RsaPublicKey::new_with_max_size
instead.
source
. Read moreDocument
] containing a SPKI-encoded public key.self
and other
values to be equal, and is used
+by ==
.SubjectPublicKeyInfo
]
+(binary format).RsaPublicKey
]
+(binary format).Document
] containing a PKCS#1-encoded public key.pub struct RsaSignature(/* private fields */);
rsa
only.RSA signature.
+pub struct SecretBytes<'a>(/* private fields */);
Generic container for secret bytes, which can be either owned or borrowed. +If owned, bytes are zeroized on drop.
+Comparisons on SecretBytes
are constant-time, but other operations (e.g., deserialization)
+may be var-time.
Represented in human-readable formats (JSON, TOML, YAML, etc.) as a base64-url encoded string
+with no padding. For other formats (e.g., CBOR), SecretBytes
will be serialized directly
+as a byte sequence.
ascii_char
)Views this slice of ASCII characters as a UTF-8 str
.
ascii_char
)Views this slice of ASCII characters as a slice of u8
bytes.
Returns the number of elements in the slice.
+let a = [1, 2, 3];
+assert_eq!(a.len(), 3);
Returns true
if the slice has a length of 0.
let a = [1, 2, 3];
+assert!(!a.is_empty());
Returns the first element of the slice, or None
if it is empty.
let v = [10, 40, 30];
+assert_eq!(Some(&10), v.first());
+
+let w: &[i32] = &[];
+assert_eq!(None, w.first());
Returns the first and all the rest of the elements of the slice, or None
if it is empty.
let x = &[0, 1, 2];
+
+if let Some((first, elements)) = x.split_first() {
+ assert_eq!(first, &0);
+ assert_eq!(elements, &[1, 2]);
+}
Returns the last and all the rest of the elements of the slice, or None
if it is empty.
let x = &[0, 1, 2];
+
+if let Some((last, elements)) = x.split_last() {
+ assert_eq!(last, &2);
+ assert_eq!(elements, &[0, 1]);
+}
Returns the last element of the slice, or None
if it is empty.
let v = [10, 40, 30];
+assert_eq!(Some(&30), v.last());
+
+let w: &[i32] = &[];
+assert_eq!(None, w.last());
slice_first_last_chunk
)Returns the first N
elements of the slice, or None
if it has fewer than N
elements.
#![feature(slice_first_last_chunk)]
+
+let u = [10, 40, 30];
+assert_eq!(Some(&[10, 40]), u.first_chunk::<2>());
+
+let v: &[i32] = &[10];
+assert_eq!(None, v.first_chunk::<2>());
+
+let w: &[i32] = &[];
+assert_eq!(Some(&[]), w.first_chunk::<0>());
slice_first_last_chunk
)Returns the first N
elements of the slice and the remainder,
+or None
if it has fewer than N
elements.
#![feature(slice_first_last_chunk)]
+
+let x = &[0, 1, 2];
+
+if let Some((first, elements)) = x.split_first_chunk::<2>() {
+ assert_eq!(first, &[0, 1]);
+ assert_eq!(elements, &[2]);
+}
slice_first_last_chunk
)Returns the last N
elements of the slice and the remainder,
+or None
if it has fewer than N
elements.
#![feature(slice_first_last_chunk)]
+
+let x = &[0, 1, 2];
+
+if let Some((last, elements)) = x.split_last_chunk::<2>() {
+ assert_eq!(last, &[1, 2]);
+ assert_eq!(elements, &[0]);
+}
slice_first_last_chunk
)Returns the last element of the slice, or None
if it is empty.
#![feature(slice_first_last_chunk)]
+
+let u = [10, 40, 30];
+assert_eq!(Some(&[40, 30]), u.last_chunk::<2>());
+
+let v: &[i32] = &[10];
+assert_eq!(None, v.last_chunk::<2>());
+
+let w: &[i32] = &[];
+assert_eq!(Some(&[]), w.last_chunk::<0>());
Returns a reference to an element or subslice depending on the type of +index.
+None
if out of bounds.None
if out of bounds.let v = [10, 40, 30];
+assert_eq!(Some(&40), v.get(1));
+assert_eq!(Some(&[10, 40][..]), v.get(0..2));
+assert_eq!(None, v.get(3));
+assert_eq!(None, v.get(0..4));
Returns a reference to an element or subslice, without doing bounds +checking.
+For a safe alternative see get
.
Calling this method with an out-of-bounds index is undefined behavior +even if the resulting reference is not used.
+let x = &[1, 2, 4];
+
+unsafe {
+ assert_eq!(x.get_unchecked(1), &2);
+}
Returns a raw pointer to the slice’s buffer.
+The caller must ensure that the slice outlives the pointer this +function returns, or else it will end up pointing to garbage.
+The caller must also ensure that the memory the pointer (non-transitively) points to
+is never written to (except inside an UnsafeCell
) using this pointer or any pointer
+derived from it. If you need to mutate the contents of the slice, use as_mut_ptr
.
Modifying the container referenced by this slice may cause its buffer +to be reallocated, which would also make any pointers to it invalid.
+let x = &[1, 2, 4];
+let x_ptr = x.as_ptr();
+
+unsafe {
+ for i in 0..x.len() {
+ assert_eq!(x.get_unchecked(i), &*x_ptr.add(i));
+ }
+}
Returns the two raw pointers spanning the slice.
+The returned range is half-open, which means that the end pointer +points one past the last element of the slice. This way, an empty +slice is represented by two equal pointers, and the difference between +the two pointers represents the size of the slice.
+See as_ptr
for warnings on using these pointers. The end pointer
+requires extra caution, as it does not point to a valid element in the
+slice.
This function is useful for interacting with foreign interfaces which +use two pointers to refer to a range of elements in memory, as is +common in C++.
+It can also be useful to check if a pointer to an element refers to an +element of this slice:
+ +let a = [1, 2, 3];
+let x = &a[1] as *const _;
+let y = &5 as *const _;
+
+assert!(a.as_ptr_range().contains(&x));
+assert!(!a.as_ptr_range().contains(&y));
Returns an iterator over the slice.
+The iterator yields all items from start to end.
+let x = &[1, 2, 4];
+let mut iterator = x.iter();
+
+assert_eq!(iterator.next(), Some(&1));
+assert_eq!(iterator.next(), Some(&2));
+assert_eq!(iterator.next(), Some(&4));
+assert_eq!(iterator.next(), None);
Returns an iterator over all contiguous windows of length
+size
. The windows overlap. If the slice is shorter than
+size
, the iterator returns no values.
Panics if size
is 0.
let slice = ['r', 'u', 's', 't'];
+let mut iter = slice.windows(2);
+assert_eq!(iter.next().unwrap(), &['r', 'u']);
+assert_eq!(iter.next().unwrap(), &['u', 's']);
+assert_eq!(iter.next().unwrap(), &['s', 't']);
+assert!(iter.next().is_none());
If the slice is shorter than size
:
let slice = ['f', 'o', 'o'];
+let mut iter = slice.windows(4);
+assert!(iter.next().is_none());
There’s no windows_mut
, as that existing would let safe code violate the
+“only one &mut
at a time to the same thing” rule. However, you can sometimes
+use Cell::as_slice_of_cells
in
+conjunction with windows
to accomplish something similar:
use std::cell::Cell;
+
+let mut array = ['R', 'u', 's', 't', ' ', '2', '0', '1', '5'];
+let slice = &mut array[..];
+let slice_of_cells: &[Cell<char>] = Cell::from_mut(slice).as_slice_of_cells();
+for w in slice_of_cells.windows(3) {
+ Cell::swap(&w[0], &w[2]);
+}
+assert_eq!(array, ['s', 't', ' ', '2', '0', '1', '5', 'u', 'R']);
Returns an iterator over chunk_size
elements of the slice at a time, starting at the
+beginning of the slice.
The chunks are slices and do not overlap. If chunk_size
does not divide the length of the
+slice, then the last chunk will not have length chunk_size
.
See chunks_exact
for a variant of this iterator that returns chunks of always exactly
+chunk_size
elements, and rchunks
for the same iterator but starting at the end of the
+slice.
Panics if chunk_size
is 0.
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.chunks(2);
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert_eq!(iter.next().unwrap(), &['m']);
+assert!(iter.next().is_none());
Returns an iterator over chunk_size
elements of the slice at a time, starting at the
+beginning of the slice.
The chunks are slices and do not overlap. If chunk_size
does not divide the length of the
+slice, then the last up to chunk_size-1
elements will be omitted and can be retrieved
+from the remainder
function of the iterator.
Due to each chunk having exactly chunk_size
elements, the compiler can often optimize the
+resulting code better than in the case of chunks
.
See chunks
for a variant of this iterator that also returns the remainder as a smaller
+chunk, and rchunks_exact
for the same iterator but starting at the end of the slice.
Panics if chunk_size
is 0.
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.chunks_exact(2);
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['m']);
slice_as_chunks
)Splits the slice into a slice of N
-element arrays,
+assuming that there’s no remainder.
This may only be called when
+N
-element chunks (aka self.len() % N == 0
).N != 0
.#![feature(slice_as_chunks)]
+let slice: &[char] = &['l', 'o', 'r', 'e', 'm', '!'];
+let chunks: &[[char; 1]] =
+ // SAFETY: 1-element chunks never have remainder
+ unsafe { slice.as_chunks_unchecked() };
+assert_eq!(chunks, &[['l'], ['o'], ['r'], ['e'], ['m'], ['!']]);
+let chunks: &[[char; 3]] =
+ // SAFETY: The slice length (6) is a multiple of 3
+ unsafe { slice.as_chunks_unchecked() };
+assert_eq!(chunks, &[['l', 'o', 'r'], ['e', 'm', '!']]);
+
+// These would be unsound:
+// let chunks: &[[_; 5]] = slice.as_chunks_unchecked() // The slice length is not a multiple of 5
+// let chunks: &[[_; 0]] = slice.as_chunks_unchecked() // Zero-length chunks are never allowed
slice_as_chunks
)Splits the slice into a slice of N
-element arrays,
+starting at the beginning of the slice,
+and a remainder slice with length strictly less than N
.
Panics if N
is 0. This check will most probably get changed to a compile time
+error before this method gets stabilized.
#![feature(slice_as_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let (chunks, remainder) = slice.as_chunks();
+assert_eq!(chunks, &[['l', 'o'], ['r', 'e']]);
+assert_eq!(remainder, &['m']);
If you expect the slice to be an exact multiple, you can combine
+let
-else
with an empty slice pattern:
#![feature(slice_as_chunks)]
+let slice = ['R', 'u', 's', 't'];
+let (chunks, []) = slice.as_chunks::<2>() else {
+ panic!("slice didn't have even length")
+};
+assert_eq!(chunks, &[['R', 'u'], ['s', 't']]);
slice_as_chunks
)Splits the slice into a slice of N
-element arrays,
+starting at the end of the slice,
+and a remainder slice with length strictly less than N
.
Panics if N
is 0. This check will most probably get changed to a compile time
+error before this method gets stabilized.
#![feature(slice_as_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let (remainder, chunks) = slice.as_rchunks();
+assert_eq!(remainder, &['l']);
+assert_eq!(chunks, &[['o', 'r'], ['e', 'm']]);
array_chunks
)Returns an iterator over N
elements of the slice at a time, starting at the
+beginning of the slice.
The chunks are array references and do not overlap. If N
does not divide the
+length of the slice, then the last up to N-1
elements will be omitted and can be
+retrieved from the remainder
function of the iterator.
This method is the const generic equivalent of chunks_exact
.
Panics if N
is 0. This check will most probably get changed to a compile time
+error before this method gets stabilized.
#![feature(array_chunks)]
+let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.array_chunks();
+assert_eq!(iter.next().unwrap(), &['l', 'o']);
+assert_eq!(iter.next().unwrap(), &['r', 'e']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['m']);
array_windows
)Returns an iterator over overlapping windows of N
elements of a slice,
+starting at the beginning of the slice.
This is the const generic equivalent of windows
.
If N
is greater than the size of the slice, it will return no windows.
Panics if N
is 0. This check will most probably get changed to a compile time
+error before this method gets stabilized.
#![feature(array_windows)]
+let slice = [0, 1, 2, 3];
+let mut iter = slice.array_windows();
+assert_eq!(iter.next().unwrap(), &[0, 1]);
+assert_eq!(iter.next().unwrap(), &[1, 2]);
+assert_eq!(iter.next().unwrap(), &[2, 3]);
+assert!(iter.next().is_none());
Returns an iterator over chunk_size
elements of the slice at a time, starting at the end
+of the slice.
The chunks are slices and do not overlap. If chunk_size
does not divide the length of the
+slice, then the last chunk will not have length chunk_size
.
See rchunks_exact
for a variant of this iterator that returns chunks of always exactly
+chunk_size
elements, and chunks
for the same iterator but starting at the beginning
+of the slice.
Panics if chunk_size
is 0.
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.rchunks(2);
+assert_eq!(iter.next().unwrap(), &['e', 'm']);
+assert_eq!(iter.next().unwrap(), &['o', 'r']);
+assert_eq!(iter.next().unwrap(), &['l']);
+assert!(iter.next().is_none());
Returns an iterator over chunk_size
elements of the slice at a time, starting at the
+end of the slice.
The chunks are slices and do not overlap. If chunk_size
does not divide the length of the
+slice, then the last up to chunk_size-1
elements will be omitted and can be retrieved
+from the remainder
function of the iterator.
Due to each chunk having exactly chunk_size
elements, the compiler can often optimize the
+resulting code better than in the case of rchunks
.
See rchunks
for a variant of this iterator that also returns the remainder as a smaller
+chunk, and chunks_exact
for the same iterator but starting at the beginning of the
+slice.
Panics if chunk_size
is 0.
let slice = ['l', 'o', 'r', 'e', 'm'];
+let mut iter = slice.rchunks_exact(2);
+assert_eq!(iter.next().unwrap(), &['e', 'm']);
+assert_eq!(iter.next().unwrap(), &['o', 'r']);
+assert!(iter.next().is_none());
+assert_eq!(iter.remainder(), &['l']);
slice_group_by
)Returns an iterator over the slice producing non-overlapping runs +of elements using the predicate to separate them.
+The predicate is called on two elements following themselves,
+it means the predicate is called on slice[0]
and slice[1]
+then on slice[1]
and slice[2]
and so on.
#![feature(slice_group_by)]
+
+let slice = &[1, 1, 1, 3, 3, 2, 2, 2];
+
+let mut iter = slice.group_by(|a, b| a == b);
+
+assert_eq!(iter.next(), Some(&[1, 1, 1][..]));
+assert_eq!(iter.next(), Some(&[3, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 2, 2][..]));
+assert_eq!(iter.next(), None);
This method can be used to extract the sorted subslices:
+ +#![feature(slice_group_by)]
+
+let slice = &[1, 1, 2, 3, 2, 3, 2, 3, 4];
+
+let mut iter = slice.group_by(|a, b| a <= b);
+
+assert_eq!(iter.next(), Some(&[1, 1, 2, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 3][..]));
+assert_eq!(iter.next(), Some(&[2, 3, 4][..]));
+assert_eq!(iter.next(), None);
Divides one slice into two at an index.
+The first will contain all indices from [0, mid)
(excluding
+the index mid
itself) and the second will contain all
+indices from [mid, len)
(excluding the index len
itself).
Panics if mid > len
.
let v = [1, 2, 3, 4, 5, 6];
+
+{
+ let (left, right) = v.split_at(0);
+ assert_eq!(left, []);
+ assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_at(2);
+ assert_eq!(left, [1, 2]);
+ assert_eq!(right, [3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_at(6);
+ assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, []);
+}
slice_split_at_unchecked
)Divides one slice into two at an index, without doing bounds checking.
+The first will contain all indices from [0, mid)
(excluding
+the index mid
itself) and the second will contain all
+indices from [mid, len)
(excluding the index len
itself).
For a safe alternative see split_at
.
Calling this method with an out-of-bounds index is undefined behavior
+even if the resulting reference is not used. The caller has to ensure that
+0 <= mid <= self.len()
.
#![feature(slice_split_at_unchecked)]
+
+let v = [1, 2, 3, 4, 5, 6];
+
+unsafe {
+ let (left, right) = v.split_at_unchecked(0);
+ assert_eq!(left, []);
+ assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+unsafe {
+ let (left, right) = v.split_at_unchecked(2);
+ assert_eq!(left, [1, 2]);
+ assert_eq!(right, [3, 4, 5, 6]);
+}
+
+unsafe {
+ let (left, right) = v.split_at_unchecked(6);
+ assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, []);
+}
split_array
)Divides one slice into an array and a remainder slice at an index.
+The array will contain all indices from [0, N)
(excluding
+the index N
itself) and the slice will contain all
+indices from [N, len)
(excluding the index len
itself).
Panics if N > len
.
#![feature(split_array)]
+
+let v = &[1, 2, 3, 4, 5, 6][..];
+
+{
+ let (left, right) = v.split_array_ref::<0>();
+ assert_eq!(left, &[]);
+ assert_eq!(right, [1, 2, 3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_array_ref::<2>();
+ assert_eq!(left, &[1, 2]);
+ assert_eq!(right, [3, 4, 5, 6]);
+}
+
+{
+ let (left, right) = v.split_array_ref::<6>();
+ assert_eq!(left, &[1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, []);
+}
split_array
)Divides one slice into an array and a remainder slice at an index from +the end.
+The slice will contain all indices from [0, len - N)
(excluding
+the index len - N
itself) and the array will contain all
+indices from [len - N, len)
(excluding the index len
itself).
Panics if N > len
.
#![feature(split_array)]
+
+let v = &[1, 2, 3, 4, 5, 6][..];
+
+{
+ let (left, right) = v.rsplit_array_ref::<0>();
+ assert_eq!(left, [1, 2, 3, 4, 5, 6]);
+ assert_eq!(right, &[]);
+}
+
+{
+ let (left, right) = v.rsplit_array_ref::<2>();
+ assert_eq!(left, [1, 2, 3, 4]);
+ assert_eq!(right, &[5, 6]);
+}
+
+{
+ let (left, right) = v.rsplit_array_ref::<6>();
+ assert_eq!(left, []);
+ assert_eq!(right, &[1, 2, 3, 4, 5, 6]);
+}
Returns an iterator over subslices separated by elements that match
+pred
. The matched element is not contained in the subslices.
let slice = [10, 40, 33, 20];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
If the first element is matched, an empty slice will be the first item +returned by the iterator. Similarly, if the last element in the slice +is matched, an empty slice will be the last item returned by the +iterator:
+ +let slice = [10, 40, 33];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40]);
+assert_eq!(iter.next().unwrap(), &[]);
+assert!(iter.next().is_none());
If two matched elements are directly adjacent, an empty slice will be +present between them:
+ +let slice = [10, 6, 33, 20];
+let mut iter = slice.split(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10]);
+assert_eq!(iter.next().unwrap(), &[]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
Returns an iterator over subslices separated by elements that match
+pred
. The matched element is contained in the end of the previous
+subslice as a terminator.
let slice = [10, 40, 33, 20];
+let mut iter = slice.split_inclusive(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
+assert_eq!(iter.next().unwrap(), &[20]);
+assert!(iter.next().is_none());
If the last element of the slice is matched, +that element will be considered the terminator of the preceding slice. +That slice will be the last item returned by the iterator.
+ +let slice = [3, 10, 40, 33];
+let mut iter = slice.split_inclusive(|num| num % 3 == 0);
+
+assert_eq!(iter.next().unwrap(), &[3]);
+assert_eq!(iter.next().unwrap(), &[10, 40, 33]);
+assert!(iter.next().is_none());
Returns an iterator over subslices separated by elements that match
+pred
, starting at the end of the slice and working backwards.
+The matched element is not contained in the subslices.
let slice = [11, 22, 33, 0, 44, 55];
+let mut iter = slice.rsplit(|num| *num == 0);
+
+assert_eq!(iter.next().unwrap(), &[44, 55]);
+assert_eq!(iter.next().unwrap(), &[11, 22, 33]);
+assert_eq!(iter.next(), None);
As with split()
, if the first or last element is matched, an empty
+slice will be the first (or last) item returned by the iterator.
let v = &[0, 1, 1, 2, 3, 5, 8];
+let mut it = v.rsplit(|n| *n % 2 == 0);
+assert_eq!(it.next().unwrap(), &[]);
+assert_eq!(it.next().unwrap(), &[3, 5]);
+assert_eq!(it.next().unwrap(), &[1, 1]);
+assert_eq!(it.next().unwrap(), &[]);
+assert_eq!(it.next(), None);
Returns an iterator over subslices separated by elements that match
+pred
, limited to returning at most n
items. The matched element is
+not contained in the subslices.
The last element returned, if any, will contain the remainder of the +slice.
+Print the slice split once by numbers divisible by 3 (i.e., [10, 40]
,
+[20, 60, 50]
):
let v = [10, 40, 30, 20, 60, 50];
+
+for group in v.splitn(2, |num| *num % 3 == 0) {
+ println!("{group:?}");
+}
Returns an iterator over subslices separated by elements that match
+pred
limited to returning at most n
items. This starts at the end of
+the slice and works backwards. The matched element is not contained in
+the subslices.
The last element returned, if any, will contain the remainder of the +slice.
+Print the slice split once, starting from the end, by numbers divisible
+by 3 (i.e., [50]
, [10, 40, 30, 20]
):
let v = [10, 40, 30, 20, 60, 50];
+
+for group in v.rsplitn(2, |num| *num % 3 == 0) {
+ println!("{group:?}");
+}
Returns true
if the slice contains an element with the given value.
This operation is O(n).
+Note that if you have a sorted slice, binary_search
may be faster.
let v = [10, 40, 30];
+assert!(v.contains(&30));
+assert!(!v.contains(&50));
If you do not have a &T
, but some other value that you can compare
+with one (for example, String
implements PartialEq<str>
), you can
+use iter().any
:
let v = [String::from("hello"), String::from("world")]; // slice of `String`
+assert!(v.iter().any(|e| e == "hello")); // search with `&str`
+assert!(!v.iter().any(|e| e == "hi"));
Returns true
if needle
is a prefix of the slice.
let v = [10, 40, 30];
+assert!(v.starts_with(&[10]));
+assert!(v.starts_with(&[10, 40]));
+assert!(!v.starts_with(&[50]));
+assert!(!v.starts_with(&[10, 50]));
Always returns true
if needle
is an empty slice:
let v = &[10, 40, 30];
+assert!(v.starts_with(&[]));
+let v: &[u8] = &[];
+assert!(v.starts_with(&[]));
Returns true
if needle
is a suffix of the slice.
let v = [10, 40, 30];
+assert!(v.ends_with(&[30]));
+assert!(v.ends_with(&[40, 30]));
+assert!(!v.ends_with(&[50]));
+assert!(!v.ends_with(&[50, 30]));
Always returns true
if needle
is an empty slice:
let v = &[10, 40, 30];
+assert!(v.ends_with(&[]));
+let v: &[u8] = &[];
+assert!(v.ends_with(&[]));
Returns a subslice with the prefix removed.
+If the slice starts with prefix
, returns the subslice after the prefix, wrapped in Some
.
+If prefix
is empty, simply returns the original slice.
If the slice does not start with prefix
, returns None
.
let v = &[10, 40, 30];
+assert_eq!(v.strip_prefix(&[10]), Some(&[40, 30][..]));
+assert_eq!(v.strip_prefix(&[10, 40]), Some(&[30][..]));
+assert_eq!(v.strip_prefix(&[50]), None);
+assert_eq!(v.strip_prefix(&[10, 50]), None);
+
+let prefix : &str = "he";
+assert_eq!(b"hello".strip_prefix(prefix.as_bytes()),
+ Some(b"llo".as_ref()));
Returns a subslice with the suffix removed.
+If the slice ends with suffix
, returns the subslice before the suffix, wrapped in Some
.
+If suffix
is empty, simply returns the original slice.
If the slice does not end with suffix
, returns None
.
let v = &[10, 40, 30];
+assert_eq!(v.strip_suffix(&[30]), Some(&[10, 40][..]));
+assert_eq!(v.strip_suffix(&[40, 30]), Some(&[10][..]));
+assert_eq!(v.strip_suffix(&[50]), None);
+assert_eq!(v.strip_suffix(&[50, 30]), None);
Binary searches this slice for a given element. +If the slice is not sorted, the returned result is unspecified and +meaningless.
+If the value is found then Result::Ok
is returned, containing the
+index of the matching element. If there are multiple matches, then any
+one of the matches could be returned. The index is chosen
+deterministically, but is subject to change in future versions of Rust.
+If the value is not found then Result::Err
is returned, containing
+the index where a matching element could be inserted while maintaining
+sorted order.
See also binary_search_by
, binary_search_by_key
, and partition_point
.
Looks up a series of four elements. The first is found, with a
+uniquely determined position; the second and third are not
+found; the fourth could match any position in [1, 4]
.
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+assert_eq!(s.binary_search(&13), Ok(9));
+assert_eq!(s.binary_search(&4), Err(7));
+assert_eq!(s.binary_search(&100), Err(13));
+let r = s.binary_search(&1);
+assert!(match r { Ok(1..=4) => true, _ => false, });
If you want to find that whole range of matching items, rather than
+an arbitrary matching one, that can be done using partition_point
:
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+let low = s.partition_point(|x| x < &1);
+assert_eq!(low, 1);
+let high = s.partition_point(|x| x <= &1);
+assert_eq!(high, 5);
+let r = s.binary_search(&1);
+assert!((low..high).contains(&r.unwrap()));
+
+assert!(s[..low].iter().all(|&x| x < 1));
+assert!(s[low..high].iter().all(|&x| x == 1));
+assert!(s[high..].iter().all(|&x| x > 1));
+
+// For something not found, the "range" of equal items is empty
+assert_eq!(s.partition_point(|x| x < &11), 9);
+assert_eq!(s.partition_point(|x| x <= &11), 9);
+assert_eq!(s.binary_search(&11), Err(9));
If you want to insert an item to a sorted vector, while maintaining
+sort order, consider using partition_point
:
let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+let num = 42;
+let idx = s.partition_point(|&x| x < num);
+// The above is equivalent to `let idx = s.binary_search(&num).unwrap_or_else(|x| x);`
+s.insert(idx, num);
+assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
Binary searches this slice with a comparator function.
+The comparator function should return an order code that indicates
+whether its argument is Less
, Equal
or Greater
the desired
+target.
+If the slice is not sorted or if the comparator function does not
+implement an order consistent with the sort order of the underlying
+slice, the returned result is unspecified and meaningless.
If the value is found then Result::Ok
is returned, containing the
+index of the matching element. If there are multiple matches, then any
+one of the matches could be returned. The index is chosen
+deterministically, but is subject to change in future versions of Rust.
+If the value is not found then Result::Err
is returned, containing
+the index where a matching element could be inserted while maintaining
+sorted order.
See also binary_search
, binary_search_by_key
, and partition_point
.
Looks up a series of four elements. The first is found, with a
+uniquely determined position; the second and third are not
+found; the fourth could match any position in [1, 4]
.
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+
+let seek = 13;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Ok(9));
+let seek = 4;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(7));
+let seek = 100;
+assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(13));
+let seek = 1;
+let r = s.binary_search_by(|probe| probe.cmp(&seek));
+assert!(match r { Ok(1..=4) => true, _ => false, });
Binary searches this slice with a key extraction function.
+Assumes that the slice is sorted by the key, for instance with
+sort_by_key
using the same key extraction function.
+If the slice is not sorted by the key, the returned result is
+unspecified and meaningless.
If the value is found then Result::Ok
is returned, containing the
+index of the matching element. If there are multiple matches, then any
+one of the matches could be returned. The index is chosen
+deterministically, but is subject to change in future versions of Rust.
+If the value is not found then Result::Err
is returned, containing
+the index where a matching element could be inserted while maintaining
+sorted order.
See also binary_search
, binary_search_by
, and partition_point
.
Looks up a series of four elements in a slice of pairs sorted by
+their second elements. The first is found, with a uniquely
+determined position; the second and third are not found; the
+fourth could match any position in [1, 4]
.
let s = [(0, 0), (2, 1), (4, 1), (5, 1), (3, 1),
+ (1, 2), (2, 3), (4, 5), (5, 8), (3, 13),
+ (1, 21), (2, 34), (4, 55)];
+
+assert_eq!(s.binary_search_by_key(&13, |&(a, b)| b), Ok(9));
+assert_eq!(s.binary_search_by_key(&4, |&(a, b)| b), Err(7));
+assert_eq!(s.binary_search_by_key(&100, |&(a, b)| b), Err(13));
+let r = s.binary_search_by_key(&1, |&(a, b)| b);
+assert!(match r { Ok(1..=4) => true, _ => false, });
Transmute the slice to a slice of another type, ensuring alignment of the types is +maintained.
+This method splits the slice into three distinct slices: prefix, correctly aligned middle +slice of a new type, and the suffix slice. How exactly the slice is split up is not +specified; the middle part may be smaller than necessary. However, if this fails to return a +maximal middle part, that is because code is running in a context where performance does not +matter, such as a sanitizer attempting to find alignment bugs. Regular code running +in a default (debug or release) execution will return a maximal middle part.
+This method has no purpose when either input element T
or output element U
are
+zero-sized and will return the original slice without splitting anything.
This method is essentially a transmute
with respect to the elements in the returned
+middle slice, so all the usual caveats pertaining to transmute::<T, U>
also apply here.
Basic usage:
+ +unsafe {
+ let bytes: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
+ let (prefix, shorts, suffix) = bytes.align_to::<u16>();
+ // less_efficient_algorithm_for_bytes(prefix);
+ // more_efficient_algorithm_for_aligned_shorts(shorts);
+ // less_efficient_algorithm_for_bytes(suffix);
+}
portable_simd
)Split a slice into a prefix, a middle of aligned SIMD types, and a suffix.
+This is a safe wrapper around slice::align_to
, so has the same weak
+postconditions as that method. You’re only assured that
+self.len() == prefix.len() + middle.len() * LANES + suffix.len()
.
Notably, all of the following are possible:
+prefix.len() >= LANES
.middle.is_empty()
despite self.len() >= 3 * LANES
.suffix.len() >= LANES
.That said, this is a safe method, so if you’re only writing safe code, +then this can at most cause incorrect logic, not unsoundness.
+This will panic if the size of the SIMD type is different from
+LANES
times that of the scalar.
At the time of writing, the trait restrictions on Simd<T, LANES>
keeps
+that from ever happening, as only power-of-two numbers of lanes are
+supported. It’s possible that, in the future, those restrictions might
+be lifted in a way that would make it possible to see panics from this
+method for something like LANES == 3
.
#![feature(portable_simd)]
+use core::simd::SimdFloat;
+
+let short = &[1, 2, 3];
+let (prefix, middle, suffix) = short.as_simd::<4>();
+assert_eq!(middle, []); // Not enough elements for anything in the middle
+
+// They might be split in any possible way between prefix and suffix
+let it = prefix.iter().chain(suffix).copied();
+assert_eq!(it.collect::<Vec<_>>(), vec![1, 2, 3]);
+
+fn basic_simd_sum(x: &[f32]) -> f32 {
+ use std::ops::Add;
+ use std::simd::f32x4;
+ let (prefix, middle, suffix) = x.as_simd();
+ let sums = f32x4::from_array([
+ prefix.iter().copied().sum(),
+ 0.0,
+ 0.0,
+ suffix.iter().copied().sum(),
+ ]);
+ let sums = middle.iter().copied().fold(sums, f32x4::add);
+ sums.reduce_sum()
+}
+
+let numbers: Vec<f32> = (1..101).map(|x| x as _).collect();
+assert_eq!(basic_simd_sum(&numbers[1..99]), 4949.0);
is_sorted
)Checks if the elements of this slice are sorted.
+That is, for each element a
and its following element b
, a <= b
must hold. If the
+slice yields exactly zero or one element, true
is returned.
Note that if Self::Item
is only PartialOrd
, but not Ord
, the above definition
+implies that this function returns false
if any two consecutive items are not
+comparable.
#![feature(is_sorted)]
+let empty: [i32; 0] = [];
+
+assert!([1, 2, 2, 9].is_sorted());
+assert!(![1, 3, 2, 4].is_sorted());
+assert!([0].is_sorted());
+assert!(empty.is_sorted());
+assert!(![0.0, 1.0, f32::NAN].is_sorted());
is_sorted
)Checks if the elements of this slice are sorted using the given comparator function.
+Instead of using PartialOrd::partial_cmp
, this function uses the given compare
+function to determine the ordering of two elements. Apart from that, it’s equivalent to
+is_sorted
; see its documentation for more information.
is_sorted
)Checks if the elements of this slice are sorted using the given key extraction function.
+Instead of comparing the slice’s elements directly, this function compares the keys of the
+elements, as determined by f
. Apart from that, it’s equivalent to is_sorted
; see its
+documentation for more information.
#![feature(is_sorted)]
+
+assert!(["c", "bb", "aaa"].is_sorted_by_key(|s| s.len()));
+assert!(![-2i32, -1, 0, 3].is_sorted_by_key(|n| n.abs()));
Returns the index of the partition point according to the given predicate +(the index of the first element of the second partition).
+The slice is assumed to be partitioned according to the given predicate.
+This means that all elements for which the predicate returns true are at the start of the slice
+and all elements for which the predicate returns false are at the end.
+For example, [7, 15, 3, 5, 4, 12, 6]
is partitioned under the predicate x % 2 != 0
+(all odd numbers are at the start, all even at the end).
If this slice is not partitioned, the returned result is unspecified and meaningless, +as this method performs a kind of binary search.
+See also binary_search
, binary_search_by
, and binary_search_by_key
.
let v = [1, 2, 3, 3, 5, 6, 7];
+let i = v.partition_point(|&x| x < 5);
+
+assert_eq!(i, 4);
+assert!(v[..i].iter().all(|&x| x < 5));
+assert!(v[i..].iter().all(|&x| !(x < 5)));
If all elements of the slice match the predicate, including if the slice +is empty, then the length of the slice will be returned:
+ +let a = [2, 4, 8];
+assert_eq!(a.partition_point(|x| x < &100), a.len());
+let a: [i32; 0] = [];
+assert_eq!(a.partition_point(|x| x < &100), 0);
If you want to insert an item to a sorted vector, while maintaining +sort order:
+ +let mut s = vec![0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
+let num = 42;
+let idx = s.partition_point(|&x| x < num);
+s.insert(idx, num);
+assert_eq!(s, [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 42, 55]);
Checks if all bytes in this slice are within the ASCII range.
+ascii_char
)If this slice is_ascii
, returns it as a slice of
+ASCII characters, otherwise returns None
.
ascii_char
)Converts this slice of bytes into a slice of ASCII characters, +without checking whether they’re valid.
+Every byte in the slice must be in 0..=127
, or else this is UB.
Checks that two slices are an ASCII case-insensitive match.
+Same as to_ascii_lowercase(a) == to_ascii_lowercase(b)
,
+but without allocating and copying temporaries.
Returns an iterator that produces an escaped version of this slice, +treating it as an ASCII string.
+
+let s = b"0\t\r\n'\"\\\x9d";
+let escaped = s.escape_ascii().to_string();
+assert_eq!(escaped, "0\\t\\r\\n\\'\\\"\\\\\\x9d");
byte_slice_trim_ascii
)Returns a byte slice with leading ASCII whitespace bytes removed.
+‘Whitespace’ refers to the definition used by
+u8::is_ascii_whitespace
.
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b" \t hello world\n".trim_ascii_start(), b"hello world\n");
+assert_eq!(b" ".trim_ascii_start(), b"");
+assert_eq!(b"".trim_ascii_start(), b"");
byte_slice_trim_ascii
)Returns a byte slice with trailing ASCII whitespace bytes removed.
+‘Whitespace’ refers to the definition used by
+u8::is_ascii_whitespace
.
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b"\r hello world\n ".trim_ascii_end(), b"\r hello world");
+assert_eq!(b" ".trim_ascii_end(), b"");
+assert_eq!(b"".trim_ascii_end(), b"");
byte_slice_trim_ascii
)Returns a byte slice with leading and trailing ASCII whitespace bytes +removed.
+‘Whitespace’ refers to the definition used by
+u8::is_ascii_whitespace
.
#![feature(byte_slice_trim_ascii)]
+
+assert_eq!(b"\r hello world\n ".trim_ascii(), b"hello world");
+assert_eq!(b" ".trim_ascii(), b"");
+assert_eq!(b"".trim_ascii(), b"");
slice_flatten
)Takes a &[[T; N]]
, and flattens it to a &[T]
.
This panics if the length of the resulting slice would overflow a usize
.
This is only possible when flattening a slice of arrays of zero-sized
+types, and thus tends to be irrelevant in practice. If
+size_of::<T>() > 0
, this will never panic.
#![feature(slice_flatten)]
+
+assert_eq!([[1, 2, 3], [4, 5, 6]].flatten(), &[1, 2, 3, 4, 5, 6]);
+
+assert_eq!(
+ [[1, 2, 3], [4, 5, 6]].flatten(),
+ [[1, 2], [3, 4], [5, 6]].flatten(),
+);
+
+let slice_of_empty_arrays: &[[i32; 0]] = &[[], [], [], [], []];
+assert!(slice_of_empty_arrays.flatten().is_empty());
+
+let empty_slice_of_arrays: &[[u32; 10]] = &[];
+assert!(empty_slice_of_arrays.flatten().is_empty());
Copies self
into a new Vec
.
let s = [10, 40, 30];
+let x = s.to_vec();
+// Here, `s` and `x` can be modified independently.
allocator_api
)Copies self
into a new Vec
with an allocator.
#![feature(allocator_api)]
+
+use std::alloc::System;
+
+let s = [10, 40, 30];
+let x = s.to_vec_in(System);
+// Here, `s` and `x` can be modified independently.
Flattens a slice of T
into a single value Self::Output
.
assert_eq!(["hello", "world"].concat(), "helloworld");
+assert_eq!([[1, 2], [3, 4]].concat(), [1, 2, 3, 4]);
Flattens a slice of T
into a single value Self::Output
, placing a
+given separator between each.
assert_eq!(["hello", "world"].join(" "), "hello world");
+assert_eq!([[1, 2], [3, 4]].join(&0), [1, 2, 0, 3, 4]);
+assert_eq!([[1, 2], [3, 4]].join(&[0, 0][..]), [1, 2, 0, 0, 3, 4]);
Flattens a slice of T
into a single value Self::Output
, placing a
+given separator between each.
assert_eq!(["hello", "world"].connect(" "), "hello world");
+assert_eq!([[1, 2], [3, 4]].connect(&0), [1, 2, 0, 3, 4]);
Returns a vector containing a copy of this slice where each byte +is mapped to its ASCII upper case equivalent.
+ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.
+To uppercase the value in-place, use make_ascii_uppercase
.
Returns a vector containing a copy of this slice where each byte +is mapped to its ASCII lower case equivalent.
+ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.
+To lowercase the value in-place, use make_ascii_lowercase
.
source
. Read moreself
into the result. Lower case
+letters are used (e.g. f9b4ca
)self
into the result. Upper case
+letters are used (e.g. F9B4CA
)pub struct StrongAlg<T>(pub T);
Wrapper around a JWT algorithm signalling that it supports only StrongKey
s.
The wrapper will implement Algorithm
if the wrapped value is an Algorithm
with both
+signing and verifying keys convertible to StrongKey
s.
let weak_key = Hs256Key::new(b"too short!");
+assert!(StrongKey::try_from(weak_key).is_err());
+// There is no way to create a `StrongKey` from `weak_key`!
+
+let strong_key: StrongKey<_> = Hs256Key::generate(&mut thread_rng());
+let claims = // ...
+let token = StrongAlg(Hs256)
+ .token(&Header::empty(), &claims, &strong_key)?;
0: T
Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read morepub struct StrongKey<T>(/* private fields */);
Wrapper around keys allowing to enforce key strength requirements.
+The wrapper signifies that the key has supported strength as per the corresponding
+algorithm spec. For example, RSA keys must have length at least 2,048 bits per RFC 7518.
+Likewise, HS*
keys must have at least the length of the hash output
+(e.g., 32 bytes for HS256
). Since these requirements sometimes clash with backward
+compatibility (and sometimes a lesser level of security is enough),
+notion of key strength is implemented in such an opt-in, composable way.
It’s easy to convert a StrongKey<T>
to T
via into_inner()
or to
+access &T
via AsRef
impl. In contrast, the reverse transformation is fallible, and
+is defined with the help of TryFrom
. The error type for TryFrom
is WeakKeyError
,
+a simple wrapper around a weak key.
See StrongAlg
docs for an example of usage.
Converts this private key to a public key.
+Returns the wrapped value.
+self
into the result. Lower case
+letters are used (e.g. f9b4ca
)self
into the result. Upper case
+letters are used (e.g. F9B4CA
)pub struct WeakKeyError<T>(pub T);
Error type used for fallible conversion into a StrongKey
.
The error wraps around a weak key, which can be extracted for further use.
+0: T
pub trait SigningKey<T>: Sizedwhere
+ T: Algorithm<SigningKey = Self>,{
+ // Required methods
+ fn from_slice(raw: &[u8]) -> Result<Self>;
+ fn to_verifying_key(&self) -> T::VerifyingKey;
+ fn as_bytes(&self) -> SecretBytes<'_>;
+}
Signing key for a specific signature cryptosystem. In the case of public-key cryptosystems, +this is a private key.
+This trait provides a uniform interface for different backends / implementations +of the same cryptosystem.
+Creates a key from raw
bytes. Returns an error if the bytes do not represent
+a valid key.
Converts a signing key to a verification key.
+Returns the key as raw bytes.
+Implementations should return Cow::Borrowed
whenever possible (that is, if the bytes
+are actually stored within the implementing data structure).
pub trait VerifyingKey<T>: Sizedwhere
+ T: Algorithm<VerifyingKey = Self>,{
+ // Required methods
+ fn from_slice(raw: &[u8]) -> Result<Self>;
+ fn as_bytes(&self) -> Cow<'_, [u8]>;
+}
Verifying key for a specific signature cryptosystem. In the case of public-key cryptosystems, +this is a public key.
+This trait provides a uniform interface for different backends / implementations +of the same cryptosystem.
+Creates a key from raw
bytes. Returns an error if the bytes do not represent
+a valid key.
Redirecting to ../../jwt_compact/struct.Claims.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/claims/struct.Empty.html b/jwt_compact/claims/struct.Empty.html new file mode 100644 index 00000000..cf59a24c --- /dev/null +++ b/jwt_compact/claims/struct.Empty.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.Empty.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/claims/struct.TimeOptions.html b/jwt_compact/claims/struct.TimeOptions.html new file mode 100644 index 00000000..bd946457 --- /dev/null +++ b/jwt_compact/claims/struct.TimeOptions.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.TimeOptions.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/enum.Claim.html b/jwt_compact/enum.Claim.html new file mode 100644 index 00000000..087b8eac --- /dev/null +++ b/jwt_compact/enum.Claim.html @@ -0,0 +1,22 @@ +#[non_exhaustive]pub enum Claim {
+ Expiration,
+ NotBefore,
+}
Identifier of a claim in Claims
.
#[non_exhaustive]pub enum CreationError {
+ Header(Error),
+ Claims(Error),
+ CborClaims(Error<Infallible>),
+}
Errors that can occur during token creation.
+Token header cannot be serialized.
+Token claims cannot be serialized into JSON.
+ciborium
only.Token claims cannot be serialized into CBOR.
+#[non_exhaustive]pub enum ParseError {
+ InvalidTokenStructure,
+ InvalidBase64Encoding,
+ MalformedHeader(Error),
+ UnsupportedContentType(String),
+}
Errors that may occur during token parsing.
+Token has invalid structure.
+Valid tokens must consist of 3 base64url-encoded parts (header, claims, and signature) +separated by periods.
+Cannot decode base64.
+Token header cannot be parsed.
+Content type mentioned in the token header is not supported.
+Supported content types are JSON (used by default) and CBOR (only if the ciborium
+crate feature is enabled, which it is by default).
#[non_exhaustive]pub enum Thumbprint<const N: usize> {
+ Bytes([u8; N]),
+ String(String),
+}
Representation of a X.509 certificate thumbprint (x5t
and x5t#S256
fields in
+the JWT Header
).
As per the JWS spec in RFC 7515, a certificate thumbprint (i.e., the SHA-1 / SHA-256 +digest of the certificate) must be base64url-encoded. Some JWS implementations however +encode not the thumbprint itself, but rather its hex encoding, sometimes even +with additional chars spliced within. To account for these implementations, +a thumbprint is represented as an enum – either a properly encoded hash digest, +or an opaque base64-encoded string.
+let key = Hs256Key::new(b"super_secret_key_donut_steel");
+
+// Creates a token with a custom-encoded SHA-1 thumbprint.
+let thumbprint = "65:AF:69:09:B1:B0:75:8E:06:C6:E0:48:C4:60:02:B5:C6:95:E3:6B";
+let header = Header::empty()
+ .with_key_id("my_key")
+ .with_certificate_sha1_thumbprint(thumbprint);
+let token = Hs256.token(&header, &Claims::empty(), &key)?;
+println!("{token}");
+
+// Deserialize the token and check that its header fields are readable.
+let token = UntrustedToken::new(&token)?;
+let deserialized_thumbprint =
+ token.header().certificate_sha1_thumbprint.as_ref();
+assert_matches!(
+ deserialized_thumbprint,
+ Some(Thumbprint::String(s)) if s == thumbprint
+);
Byte representation of a SHA-1 or SHA-256 digest.
+Opaque string representation of the thumbprint. It is the responsibility +of an application to verify that this value is valid.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.#[non_exhaustive]pub enum ValidationError {
+ AlgorithmMismatch {
+ expected: String,
+ actual: String,
+ },
+ InvalidSignatureLen {
+ expected: usize,
+ actual: usize,
+ },
+ MalformedSignature(Error),
+ InvalidSignature,
+ MalformedClaims(Error),
+ MalformedCborClaims(Error<Error>),
+ NoClaim(Claim),
+ Expired,
+ NotMature,
+}
Errors that can occur during token validation.
+Algorithm mentioned in the token header differs from invoked one.
+Token signature has invalid byte length.
+Token signature is malformed.
+Token signature has failed verification.
+Token claims cannot be deserialized from JSON.
+ciborium
only.Token claims cannot be deserialized from CBOR.
+Claim requested during validation is not present in the token.
+Token has expired.
+Token is not yet valid as per nbf
claim.
Redirecting to ../../jwt_compact/enum.Claim.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/error/enum.CreationError.html b/jwt_compact/error/enum.CreationError.html new file mode 100644 index 00000000..5822c7e2 --- /dev/null +++ b/jwt_compact/error/enum.CreationError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/enum.CreationError.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/error/enum.ParseError.html b/jwt_compact/error/enum.ParseError.html new file mode 100644 index 00000000..b345b4ef --- /dev/null +++ b/jwt_compact/error/enum.ParseError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/enum.ParseError.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/error/enum.ValidationError.html b/jwt_compact/error/enum.ValidationError.html new file mode 100644 index 00000000..d663cde0 --- /dev/null +++ b/jwt_compact/error/enum.ValidationError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/enum.ValidationError.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/index.html b/jwt_compact/index.html new file mode 100644 index 00000000..13e0f3fc --- /dev/null +++ b/jwt_compact/index.html @@ -0,0 +1,172 @@ +Minimalistic JSON web token (JWT) implementation with focus on type safety +and secure cryptographic primitives.
+Algorithm
trait, which uses fully typed keys and signatures.Header
struct. Notably, Header
does not
+expose the alg
field.
+Instead, alg
is filled automatically during token creation, and is compared to the
+expected value during verification. (If you do not know the JWT signature algorithm during
+verification, you’re doing something wrong.) This eliminates the possibility
+of algorithm switching attacks.ciborium
feature.EdDSA
algorithm with the Ed25519 elliptic curve, and ES256K
algorithm
+with the secp256k1 elliptic curve.Algorithm(s) | Feature | Description |
---|---|---|
HS256 , HS384 , HS512 | - | Uses pure Rust sha2 crate |
EdDSA (Ed25519) | exonum-crypto | libsodium binding |
EdDSA (Ed25519) | ed25519-dalek | Pure Rust implementation |
EdDSA (Ed25519) | ed25519-compact | Compact pure Rust implementation, WASM-compatible |
ES256K | es256k | Rust binding for libsecp256k1 |
ES256K | k256 | Pure Rust implementation |
ES256 | p256 | Pure Rust implementation |
RS* , PS* (RSA) | rsa | Uses pure Rust rsa crate with blinding |
Beware that the rsa
crate (along with other RSA implementations) may be susceptible to
+the “Marvin” timing side-channel attack
+at the time of writing; use with caution.
EdDSA
and ES256K
algorithms are somewhat less frequently supported by JWT implementations
+than others since they are recent additions to the JSON Web Algorithms (JWA) suit.
+They both work with elliptic curves
+(Curve25519 and secp256k1; both are widely used in crypto community and believed to be
+securely generated). These algs have 128-bit security, making them an alternative
+to ES256
.
RSA support requires a system-wide RNG retrieved via the getrandom
crate.
+In case of a compilation failure in the getrandom
crate, you may want
+to include it as a direct dependency and specify one of its features
+to assist getrandom
with choosing an appropriate RNG implementation; consult getrandom
docs
+for more details. See also WASM and bare-metal E2E tests included
+in the source code repository of this crate.
If the ciborium
crate feature is enabled (and it is enabled by default), token claims can
+be encoded using CBOR with the AlgorithmExt::compact_token()
method.
+The compactly encoded JWTs have the cty
field (content type) in their header
+set to "CBOR"
. Tokens with such encoding can be verified in the same way as ordinary tokens;
+see examples below.
If the ciborium
feature is disabled, AlgorithmExt::compact_token()
is not available.
+Verifying CBOR-encoded tokens in this case is not supported either;
+a ParseError::UnsupportedContentType
will be returned when creating an UntrustedToken
+from the token string.
no_std
supportThe crate supports a no_std
compilation mode. This is controlled by two features:
+clock
and std
; both are on by default.
clock
feature enables getting the current time using Utc::now()
from chrono
.
+Without it, some TimeOptions
constructors, such as the Default
impl,
+are not available. It is still possible to create TimeOptions
with an explicitly specified
+clock function, or to set / verify time-related Claims
fields manually.std
feature is propagated to the core dependencies and enables std
-specific
+functionality (such as error types implementing the standard Error
trait).Some alloc
types are still used in the no_std
mode, such as String
, Vec
and Cow
.
Note that not all crypto backends are no_std
-compatible.
Basic JWT lifecycle:
+ +use chrono::{Duration, Utc};
+use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
+use serde::{Serialize, Deserialize};
+
+/// Custom claims encoded in the token.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct CustomClaims {
+ /// `sub` is a standard claim which denotes claim subject:
+ /// https://tools.ietf.org/html/rfc7519#section-4.1.2
+ #[serde(rename = "sub")]
+ subject: String,
+}
+
+// Choose time-related options for token creation / validation.
+let time_options = TimeOptions::default();
+// Create a symmetric HMAC key, which will be used both to create and verify tokens.
+let key = Hs256Key::new(b"super_secret_key_donut_steel");
+// Create a token.
+let header = Header::empty().with_key_id("my-key");
+let claims = Claims::new(CustomClaims { subject: "alice".to_owned() })
+ .set_duration_and_issuance(&time_options, Duration::days(7))
+ .set_not_before(Utc::now() - Duration::hours(1));
+let token_string = Hs256.token(&header, &claims, &key)?;
+println!("token: {token_string}");
+
+// Parse the token.
+let token = UntrustedToken::new(&token_string)?;
+// Before verifying the token, we might find the key which has signed the token
+// using the `Header.key_id` field.
+assert_eq!(token.header().key_id, Some("my-key".to_owned()));
+// Validate the token integrity.
+let token: Token<CustomClaims> = Hs256.validator(&key).validate(&token)?;
+// Validate additional conditions.
+token.claims()
+ .validate_expiration(&time_options)?
+ .validate_maturity(&time_options)?;
+// Now, we can extract information from the token (e.g., its subject).
+let subject = &token.claims().custom.subject;
+assert_eq!(subject, "alice");
/// Custom claims encoded in the token.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct CustomClaims {
+ /// `sub` is a standard claim which denotes claim subject:
+ /// https://tools.ietf.org/html/rfc7519#section-4.1.2
+ /// The custom serializer we use allows to efficiently
+ /// encode the subject in CBOR.
+ #[serde(rename = "sub", with = "HexForm")]
+ subject: [u8; 32],
+}
+
+let time_options = TimeOptions::default();
+let key = Hs256Key::new(b"super_secret_key_donut_steel");
+let claims = Claims::new(CustomClaims { subject: [111; 32] })
+ .set_duration_and_issuance(&time_options, Duration::days(7));
+let token = Hs256.token(&Header::empty(), &claims, &key)?;
+println!("token: {token}");
+let compact_token = Hs256.compact_token(&Header::empty(), &claims, &key)?;
+println!("compact token: {compact_token}");
+// The compact token should be ~40 chars shorter.
+
+// Parse the compact token.
+let token = UntrustedToken::new(&compact_token)?;
+let token: Token<CustomClaims> = Hs256.validator(&key).validate(&token)?;
+token.claims().validate_expiration(&time_options)?;
+// Now, we can extract information from the token (e.g., its subject).
+assert_eq!(token.claims().custom.subject, [111; 32]);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct CustomClaims { subject: [u8; 32] }
+
+/// Additional fields in the token header.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct HeaderExtensions { custom: bool }
+
+let time_options = TimeOptions::default();
+let key = Hs256Key::new(b"super_secret_key_donut_steel");
+let claims = Claims::new(CustomClaims { subject: [111; 32] })
+ .set_duration_and_issuance(&time_options, Duration::days(7));
+let header = Header::new(HeaderExtensions { custom: true })
+ .with_key_id("my-key");
+let token = Hs256.token(&header, &claims, &key)?;
+print!("token: {token}");
+
+// Parse the token.
+let token: UntrustedToken<HeaderExtensions> =
+ token.as_str().try_into()?;
+// Token header (incl. custom fields) can be accessed right away.
+assert_eq!(token.header().key_id.as_deref(), Some("my-key"));
+assert!(token.header().other_fields.custom);
+// Token can then be validated as usual.
+let token = Hs256.validator::<CustomClaims>(&key).validate(&token)?;
+assert_eq!(token.claims().custom.subject, [111; 32]);
Claims
.Token
together with the validated token signature.Algorithm
associated with a specific verifying key
+and a claims type. Produced by the AlgorithmExt::validator()
method.Claims
.Algorithm
trait.Algorithm
.#[non_exhaustive]pub enum JsonWebKey<'a> {
+ Rsa {
+ modulus: Cow<'a, [u8]>,
+ public_exponent: Cow<'a, [u8]>,
+ private_parts: Option<RsaPrivateParts<'a>>,
+ },
+ EllipticCurve {
+ curve: Cow<'a, str>,
+ x: Cow<'a, [u8]>,
+ y: Cow<'a, [u8]>,
+ secret: Option<SecretBytes<'a>>,
+ },
+ Symmetric {
+ secret: SecretBytes<'a>,
+ },
+ KeyPair {
+ curve: Cow<'a, str>,
+ x: Cow<'a, [u8]>,
+ secret: Option<SecretBytes<'a>>,
+ },
+}
Basic JWK functionality: (de)serialization and creating thumbprints.
+See RFC 7518 for the details about the fields for various key types.
+Self::thumbprint()
and the Display
implementation
+allow to get the overall presentation of the key. The latter returns JSON serialization
+of the key with fields ordered alphabetically. That is, this output for verifying keys
+can be used to compute key thumbprints.
For human-readable formats (e.g., JSON, TOML, YAML), byte fields in JsonWebKey
+and embedded types (SecretBytes
, RsaPrivateParts
, RsaPrimeFactor
) will be
+serialized in base64-url encoding with no padding, as per the JWK spec.
+For other formats (e.g., CBOR), byte fields will be serialized as byte sequences.
Because of the limitations
+of the CBOR support in serde
, a JsonWebKey
serialized in CBOR is not compliant
+with the CBOR Object Signing and Encryption spec (COSE). It can still be a good
+way to decrease the serialized key size.
A JWK can be obtained from signing and verifying keys defined in the alg
+module via From
/ Into
traits. Conversion from a JWK to a specific key is fallible
+and can be performed via TryFrom
with JwkError
as an error
+type.
As a part of conversion for asymmetric signing keys, it is checked whether
+the signing and verifying parts of the JWK match; JwkError::MismatchedKeys
is returned
+otherwise. This check is not performed for verifying keys even if the necessary data
+is present in the provided JWK.
⚠ Warning. Conversions for private RSA keys are not fully compliant with RFC 7518.
+See the docs for the relevant impl
s for more details.
private_parts: Option<RsaPrivateParts<'a>>
Private RSA parameters. Only present for private keys.
+Public or private RSA key. Has kty
field set to RSA
.
secret: Option<SecretBytes<'a>>
Secret scalar (d
); not present for public keys.
Public or private key in an ECDSA crypto system. Has kty
field set to EC
.
secret: SecretBytes<'a>
Bytes representing this key.
+Generic symmetric key, e.g. for HS256
algorithm. Has kty
field set to oct
.
x: Cow<'a, [u8]>
Public key. For Ed25519, this is the standard 32-byte public key presentation
+(x
coordinate of a point on the curve + sign).
secret: Option<SecretBytes<'a>>
Secret key (d
). For Ed25519, this is the seed.
Generic asymmetric keypair. This key type is used e.g. for Ed25519 keys.
+Returns true
if this key can be used for signing (has SecretBytes
fields).
Returns a copy of this key with parts not necessary for signature verification removed.
+Computes a thumbprint of this JWK. The result complies with the key thumbprint defined +in RFC 7638.
+source
. Read more⚠ Warning. Contrary to RFC 7518, this implementation does not set dp
, dq
, and qi
+fields in the JWK root object, as well as d
and t
fields for additional factors
+(i.e., in the oth
array).
self
and other
values to be equal, and is used
+by ==
.⚠ Warning. Contrary to RFC 7518 (at least, in spirit), this conversion ignores
+dp
, dq
, and qi
fields from JWK, as well as d
and t
fields for additional factors.
#[non_exhaustive]pub enum JwkError {
+ NoField(String),
+ UnexpectedKeyType {
+ expected: KeyType,
+ actual: KeyType,
+ },
+ UnexpectedValue {
+ field: String,
+ expected: String,
+ actual: String,
+ },
+ UnexpectedLen {
+ field: String,
+ expected: usize,
+ actual: usize,
+ },
+ MismatchedKeys,
+ Custom(Error),
+}
Errors that can occur when transforming a JsonWebKey
into the presentation specific for
+a crypto backend, using the TryFrom
trait.
Required field is absent from JWK.
+Key type (the kty
field) is not as expected.
JWK field has an unexpected value.
+JWK field has an unexpected byte length.
+Signing and verifying keys do not match.
+Custom error specific to a crypto backend.
+#[non_exhaustive]pub enum KeyType {
+ Rsa,
+ EllipticCurve,
+ Symmetric,
+ KeyPair,
+}
Type of a JsonWebKey
.
Public or private RSA key. Corresponds to the RSA
value of the kty
field for JWKs.
Public or private key in an ECDSA crypto system. Corresponds to the EC
value
+of the kty
field for JWKs.
Symmetric key. Corresponds to the oct
value of the kty
field for JWKs.
Generic asymmetric keypair. Corresponds to the OKP
value of the kty
field for JWKs.
Basic support of JSON Web Keys (JWK).
+The functionality defined in this module allows converting between
+the generic JWK format and key presentation specific for the crypto backend.
+JsonWebKey
s can be (de)serialized using serde
infrastructure, and can be used
+to compute key thumbprint as per RFC 7638.
use jwt_compact::{alg::Hs256Key, jwk::JsonWebKey};
+use sha2::Sha256;
+
+// Load a key from the JWK presentation.
+let json_str = r#"
+ { "kty": "oct", "k": "t-bdv41MJXExXnpquHBuDn7n1YGyX7gLQchVHAoNu50" }
+"#;
+let jwk: JsonWebKey<'_> = serde_json::from_str(json_str)?;
+let key = Hs256Key::try_from(&jwk)?;
+
+// Convert `key` back to JWK.
+let jwk_from_key = JsonWebKey::from(&key);
+assert_eq!(jwk_from_key, jwk);
+println!("{}", serde_json::to_string(&jwk)?);
+
+// Compute the key thumbprint.
+let thumbprint = jwk_from_key.thumbprint::<Sha256>();
RsaPrivateParts
.JsonWebKey::Rsa
that are specific to private keys.JsonWebKey
into the presentation specific for
+a crypto backend, using the TryFrom
trait.JsonWebKey
.pub struct RsaPrimeFactor<'a> {
+ pub factor: SecretBytes<'a>,
+ pub crt_exponent: Option<SecretBytes<'a>>,
+ pub crt_coefficient: Option<SecretBytes<'a>>,
+}
Block for an additional prime factor in RsaPrivateParts
.
Fields of this struct are serialized using the big endian presentation
+with the minimum necessary number of bytes. See JsonWebKey
notes
+on encoding.
factor: SecretBytes<'a>
Prime factor (r
).
crt_exponent: Option<SecretBytes<'a>>
Factor CRT exponent (d
).
crt_coefficient: Option<SecretBytes<'a>>
Factor CRT coefficient (t
).
source
. Read moreself
and other
values to be equal, and is used
+by ==
.pub struct RsaPrivateParts<'a> {
+ pub private_exponent: SecretBytes<'a>,
+ pub prime_factor_p: SecretBytes<'a>,
+ pub prime_factor_q: SecretBytes<'a>,
+ pub p_crt_exponent: Option<SecretBytes<'a>>,
+ pub q_crt_exponent: Option<SecretBytes<'a>>,
+ pub q_crt_coefficient: Option<SecretBytes<'a>>,
+ pub other_prime_factors: Vec<RsaPrimeFactor<'a>>,
+}
Parts of JsonWebKey::Rsa
that are specific to private keys.
Fields of this struct are serialized using the big endian presentation
+with the minimum necessary number of bytes. See JsonWebKey
notes
+on encoding.
private_exponent: SecretBytes<'a>
Private exponent (d
).
prime_factor_p: SecretBytes<'a>
First prime factor (p
).
prime_factor_q: SecretBytes<'a>
Second prime factor (q
).
p_crt_exponent: Option<SecretBytes<'a>>
First factor CRT exponent (dp
).
q_crt_exponent: Option<SecretBytes<'a>>
Second factor CRT exponent (dq
).
q_crt_coefficient: Option<SecretBytes<'a>>
CRT coefficient of the second factor (qi
).
other_prime_factors: Vec<RsaPrimeFactor<'a>>
Other prime factors.
+source
. Read moreself
and other
values to be equal, and is used
+by ==
.Prelude to neatly import all necessary stuff from the crate.
+pub use crate::AlgorithmExt as _;
pub use crate::Claims;
pub use crate::Header;
pub use crate::TimeOptions;
pub use crate::Token;
pub use crate::UntrustedToken;
#[non_exhaustive]pub struct Claims<T> {
+ pub expiration: Option<DateTime<Utc>>,
+ pub not_before: Option<DateTime<Utc>>,
+ pub issued_at: Option<DateTime<Utc>>,
+ pub custom: T,
+}
Claims encoded in a token.
+Claims are comprised of a “standard” part (exp
, nbf
and iat
claims as per JWT spec),
+and custom fields. iss
, sub
and aud
claims are not in the standard part
+due to a variety of data types they can be reasonably represented by.
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.expiration: Option<DateTime<Utc>>
Expiration time of the token.
+not_before: Option<DateTime<Utc>>
Minimum time at which token is valid.
+issued_at: Option<DateTime<Utc>>
Time of token issuance.
+custom: T
Custom claims.
+Sets the expiration
claim so that the token has the specified duration
.
+The current timestamp is taken from options
.
Atomically sets issued_at
and expiration
claims: first to the current time
+(taken from options
), and the second to match the specified duration
of the token.
Sets the nbf
claim.
Validates the expiration claim.
+This method will return an error if the claims do not feature an expiration time,
+or if it is in the past (subject to the provided options
).
Validates the maturity time (nbf
claim).
This method will return an error if the claims do not feature a maturity time,
+or if it is in the future (subject to the provided options
).
pub struct Empty {}
A structure with no fields that can be used as a type parameter to Claims
.
#[non_exhaustive]pub struct Header<T = Empty> {
+ pub key_set_url: Option<String>,
+ pub key_id: Option<String>,
+ pub certificate_url: Option<String>,
+ pub certificate_sha1_thumbprint: Option<Thumbprint<20>>,
+ pub certificate_thumbprint: Option<Thumbprint<32>>,
+ pub token_type: Option<String>,
+ pub other_fields: T,
+}
JWT header.
+See RFC 7515 for the description
+of the fields. The purpose of all fields except token_type
is to determine
+the verifying key. Since these values will be provided by the adversary in the case of
+an attack, they require additional verification (e.g., a provided certificate might
+be checked against the list of “acceptable” certificate authorities).
A Header
can be created using Default
implementation, which does not set any fields.
+For added fluency, you may use with_*
methods:
use sha2::{digest::Digest, Sha256};
+
+let my_key_cert = // DER-encoded key certificate
+let thumbprint: [u8; 32] = Sha256::digest(my_key_cert).into();
+let header = Header::empty()
+ .with_key_id("my-key-id")
+ .with_certificate_thumbprint(thumbprint);
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.key_set_url: Option<String>
URL of the JSON Web Key Set containing the key that has signed the token.
+This field is renamed to jku
for serialization.
key_id: Option<String>
Identifier of the key that has signed the token. This field is renamed to kid
+for serialization.
certificate_url: Option<String>
URL of the X.509 certificate for the signing key. This field is renamed to x5u
+for serialization.
certificate_sha1_thumbprint: Option<Thumbprint<20>>
SHA-1 thumbprint of the X.509 certificate for the signing key.
+This field is renamed to x5t
for serialization.
certificate_thumbprint: Option<Thumbprint<32>>
SHA-256 thumbprint of the X.509 certificate for the signing key.
+This field is renamed to x5t#S256
for serialization.
token_type: Option<String>
Application-specific token type. This field is renamed to typ
for serialization.
other_fields: T
Other fields encoded in the header. These fields may be used by agreement between +the producer and consumer of the token to pass additional information. +See Sections 4.2 and 4.3 of RFC 7515 +for details.
+For the token creation and validation to work properly, the fields type must Serialize
+to a JSON object.
Note that these fields do not include the signing algorithm (alg
) and the token
+content type (cty
) since both these fields have predefined semantics and are used
+internally by the crate logic.
Sets the key_set_url
field for this header.
Sets the key_id
field for this header.
Sets the certificate_url
field for this header.
Sets the certificate_sha1_thumbprint
field for this header.
Sets the certificate_thumbprint
field for this header.
Sets the token_type
field for this header.
pub struct Renamed<A> { /* private fields */ }
Algorithm that uses a custom name when creating and validating tokens.
+use jwt_compact::{alg::{Hs256, Hs256Key}, prelude::*, Empty, Renamed};
+
+let alg = Renamed::new(Hs256, "HS2");
+let key = Hs256Key::new(b"super_secret_key_donut_steel");
+let token_string = alg.token(&Header::empty(), &Claims::empty(), &key)?;
+
+let token = UntrustedToken::new(&token_string)?;
+assert_eq!(token.algorithm(), "HS2");
+// Note that the created token cannot be verified against the original algorithm
+// since the algorithm name recorded in the token header doesn't match.
+assert!(Hs256.validator::<Empty>(&key).validate(&token).is_err());
+
+// ...but the modified alg is working as expected.
+assert!(alg.validator::<Empty>(&key).validate(&token).is_ok());
Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).alg
field of the JWT header.message
with the signing_key
.message
against the signature
and verifying_key
.ciborium
only..validator().validate()
for added flexibilityverifying_key
..validator().validate_for_signed_token()
for added flexibilityverifying_key
. Read more#[non_exhaustive]pub struct SignedToken<A: Algorithm + ?Sized, T, H = Empty> {
+ pub signature: A::Signature,
+ pub token: Token<T, H>,
+}
Token
together with the validated token signature.
#[derive(Serialize, Deserialize)]
+struct MyClaims {
+ // Custom claims in the token...
+}
+
+let token_string: String = // token from an external source
+let token = UntrustedToken::new(&token_string)?;
+let signed = Hs256.validator::<MyClaims>(&key)
+ .validate_for_signed_token(&token)?;
+
+// `signature` is strongly typed.
+let signature: Hs256Signature = signed.signature;
+// Token itself is available via `token` field.
+let claims = signed.token.claims();
+claims.validate_expiration(&TimeOptions::default())?;
+// Process the claims...
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.signature: A::Signature
Token signature.
+token: Token<T, H>
Verified token.
+#[non_exhaustive]pub struct TimeOptions<F = fn() -> DateTime<Utc>> {
+ pub leeway: Duration,
+ pub clock_fn: F,
+}
Time-related options for token creation and validation.
+If the clock
crate feature is on (and it’s on by default), TimeOptions
can be created
+using the Default
impl or Self::from_leeway()
. If the feature is off,
+you can still create options using a generic constructor.
// Default options.
+let default_options = TimeOptions::default();
+let options_with_custom_leeway =
+ TimeOptions::from_leeway(Duration::seconds(5));
+// Options that have a fixed time. Can be useful for testing.
+let clock_time = Utc::now();
+let options_with_stopped_clock =
+ TimeOptions::new(Duration::seconds(10), move || clock_time);
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.leeway: Duration
Leeway to use during validation.
+clock_fn: F
Source of the current timestamps.
+clock
only.Creates options based on the specified time leeway. The clock source is Utc::now()
.
source
. Read moreCreates options with a default leeway (60 seconds) and the Utc::now()
clock.
This impl is supported on crate feature clock
only.
pub struct Token<T, H = Empty> { /* private fields */ }
Token with validated integrity.
+Claims encoded in the token can be verified by invoking Claims
methods
+via Self::claims()
.
pub struct UntrustedToken<'a, H = Empty> { /* private fields */ }
Parsed, but unvalidated token.
+The type param (Empty
by default) corresponds to the additional information enclosed
+in the token Header
.
An UntrustedToken
can be parsed from a string using the TryFrom
implementation.
+This checks that a token is well-formed (has a header, claims and a signature),
+but does not validate the signature.
+As a shortcut, a token without additional header info can be created using Self::new()
.
let token_str = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJp\
+ c3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leG\
+ FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJ\
+ U1p1r_wW1gFWFOEjXk";
+let token: UntrustedToken = token_str.try_into()?;
+// The same operation using a shortcut:
+let same_token = UntrustedToken::new(token_str)?;
+// Token header can be accessed to select the verifying key etc.
+let key_id: Option<&str> = token.header().key_id.as_deref();
#[derive(Debug, Clone, Deserialize)]
+struct HeaderExtensions {
+ custom: String,
+}
+
+let token_str = "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3Rfa2V5Iiwid\
+ HlwIjoiSldUIiwiY3VzdG9tIjoiY3VzdG9tIn0.eyJzdWIiOiIxMjM0NTY\
+ 3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9._27Fb6nF\
+ Tg-HSt3vO4ylaLGcU_ZV2VhMJR4HL7KaQik";
+let token: UntrustedToken<HeaderExtensions> = token_str.try_into()?;
+let extensions = &token.header().other_fields;
+println!("{}", extensions.custom);
Converts this token to an owned form.
+Returns signature bytes from the token. These bytes are not guaranteed to form a valid +signature.
+Deserializes claims from this token without checking token integrity. The resulting +claims are thus not guaranteed to be valid.
+source
. Read morepub struct Validator<'a, A: Algorithm + ?Sized, T> { /* private fields */ }
Validator for a certain signing Algorithm
associated with a specific verifying key
+and a claims type. Produced by the AlgorithmExt::validator()
method.
Validates the token integrity against a verifying key enclosed in this validator.
+Validates the token integrity against a verifying key enclosed in this validator,
+and returns the validated Token
together with its signature.
Redirecting to ../../jwt_compact/enum.Thumbprint.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/token/struct.Header.html b/jwt_compact/token/struct.Header.html new file mode 100644 index 00000000..88bc535b --- /dev/null +++ b/jwt_compact/token/struct.Header.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.Header.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/token/struct.SignedToken.html b/jwt_compact/token/struct.SignedToken.html new file mode 100644 index 00000000..1ed36159 --- /dev/null +++ b/jwt_compact/token/struct.SignedToken.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.SignedToken.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/token/struct.Token.html b/jwt_compact/token/struct.Token.html new file mode 100644 index 00000000..9cf524ce --- /dev/null +++ b/jwt_compact/token/struct.Token.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.Token.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/token/struct.UntrustedToken.html b/jwt_compact/token/struct.UntrustedToken.html new file mode 100644 index 00000000..b9b730ca --- /dev/null +++ b/jwt_compact/token/struct.UntrustedToken.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.UntrustedToken.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/trait.Algorithm.html b/jwt_compact/trait.Algorithm.html new file mode 100644 index 00000000..fc89845f --- /dev/null +++ b/jwt_compact/trait.Algorithm.html @@ -0,0 +1,39 @@ +pub trait Algorithm {
+ type SigningKey;
+ type VerifyingKey;
+ type Signature: AlgorithmSignature;
+
+ // Required methods
+ fn name(&self) -> Cow<'static, str>;
+ fn sign(
+ &self,
+ signing_key: &Self::SigningKey,
+ message: &[u8]
+ ) -> Self::Signature;
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8]
+ ) -> bool;
+}
JWT signing algorithm.
+Key used when issuing new tokens.
+Key used when verifying tokens. May coincide with Self::SigningKey
for symmetric
+algorithms (e.g., HS*
).
Signature produced by the algorithm.
+Returns the name of this algorithm, as mentioned in the alg
field of the JWT header.
Signs a message
with the signing_key
.
Verifies the message
against the signature
and verifying_key
.
pub trait AlgorithmExt: Algorithm {
+ // Required methods
+ fn token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey
+ ) -> Result<String, CreationError>
+ where T: Serialize;
+ fn compact_token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey
+ ) -> Result<String, CreationError>
+ where T: Serialize;
+ fn validator<'a, T>(
+ &'a self,
+ verifying_key: &'a Self::VerifyingKey
+ ) -> Validator<'a, Self, T>;
+ fn validate_integrity<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey
+ ) -> Result<Token<T>, ValidationError>
+ where T: DeserializeOwned;
+ fn validate_for_signed_token<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey
+ ) -> Result<SignedToken<Self, T>, ValidationError>
+ where T: DeserializeOwned;
+}
Automatically implemented extensions of the Algorithm
trait.
Creates a new token and serializes it to string.
+ciborium
only.Creates a new token with CBOR-encoded claims and serializes it to string.
+Creates a JWT validator for the specified verifying key and the claims type. +The validator can then be used to validate integrity of one or more tokens.
+.validator().validate()
for added flexibilityValidates the token integrity against the provided verifying_key
.
.validator().validate_for_signed_token()
for added flexibilityValidates the token integrity against the provided verifying_key
.
Unlike validate_integrity
, this method retains more
+information about the original token, in particular, its signature.
pub trait AlgorithmSignature: Sized {
+ const LENGTH: Option<NonZeroUsize> = None;
+
+ // Required methods
+ fn try_from_slice(slice: &[u8]) -> Result<Self>;
+ fn as_bytes(&self) -> Cow<'_, [u8]>;
+}
Signature for a certain JWT signing Algorithm
.
We require that signature can be restored from a byte slice, +and can be represented as a byte slice.
+Constant byte length of signatures supported by the Algorithm
, or None
if
+the signature length is variable.
Some(_)
, the signature will be first checked for its length
+during token verification. An InvalidSignatureLen
error will be raised if the length
+is invalid. Self::try_from_slice()
will thus always receive a slice with
+the expected length.None
, no length check is performed before calling
+Self::try_from_slice()
.Attempts to restore a signature from a byte slice. This method may fail +if the slice is malformed.
+Redirecting to ../../jwt_compact/struct.Renamed.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/traits/struct.Validator.html b/jwt_compact/traits/struct.Validator.html new file mode 100644 index 00000000..03dae562 --- /dev/null +++ b/jwt_compact/traits/struct.Validator.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/struct.Validator.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/traits/trait.Algorithm.html b/jwt_compact/traits/trait.Algorithm.html new file mode 100644 index 00000000..c1631c5d --- /dev/null +++ b/jwt_compact/traits/trait.Algorithm.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/trait.Algorithm.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/traits/trait.AlgorithmExt.html b/jwt_compact/traits/trait.AlgorithmExt.html new file mode 100644 index 00000000..987ad6f9 --- /dev/null +++ b/jwt_compact/traits/trait.AlgorithmExt.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/trait.AlgorithmExt.html...
+ + + \ No newline at end of file diff --git a/jwt_compact/traits/trait.AlgorithmSignature.html b/jwt_compact/traits/trait.AlgorithmSignature.html new file mode 100644 index 00000000..28610d17 --- /dev/null +++ b/jwt_compact/traits/trait.AlgorithmSignature.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../jwt_compact/trait.AlgorithmSignature.html...
+ + + \ No newline at end of file diff --git a/search-index.js b/search-index.js new file mode 100644 index 00000000..446f247e --- /dev/null +++ b/search-index.js @@ -0,0 +1,5 @@ +var searchIndex = JSON.parse('{\ +"jwt_compact":{"doc":"Minimalistic JSON web token (JWT) implementation with …","t":"IININNEDNEDNNDNNNNNSSNNNNNNNEDQDQNEDDNDEDQALKLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMLMLLLLLLLLLLLLLLLLLLLLLLKLMLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMAMMMKLLLLLLMMALLLLLLLKLMLLLLLLLLLLLLLLLLLLLKLMMLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLKLLKLKLLLLLLLLLLLLLLLLLLLLLMMMMDDDNDDDDDDDDDSSSEDDDDDDDIDDNNIDKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNEENNENNNNDDNNNNNLLLLLLLLLLLLLLLLLLMMLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMMCCCCCC","n":["Algorithm","AlgorithmExt","AlgorithmMismatch","AlgorithmSignature","Bytes","CborClaims","Claim","Claims","Claims","CreationError","Empty","Expiration","Expired","Header","Header","InvalidBase64Encoding","InvalidSignature","InvalidSignatureLen","InvalidTokenStructure","LENGTH","LENGTH","MalformedCborClaims","MalformedClaims","MalformedHeader","MalformedSignature","NoClaim","NotBefore","NotMature","ParseError","Renamed","Signature","SignedToken","SigningKey","String","Thumbprint","TimeOptions","Token","UnsupportedContentType","UntrustedToken","ValidationError","Validator","VerifyingKey","alg","algorithm","as_bytes","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","certificate_sha1_thumbprint","certificate_thumbprint","certificate_url","claims","clock_fn","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","compact_token","compact_token","custom","default","default","default","deserialize","deserialize","deserialize","deserialize","deserialize_claims_unchecked","empty","empty","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","expiration","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_leeway","hash","hash","header","header","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_owned","into_parts","issued_at","jwk","key_id","key_set_url","leeway","name","name","new","new","new","new","new","not_before","other_fields","prelude","serialize","serialize","serialize","serialize","set_duration","set_duration_and_issuance","set_not_before","sign","sign","signature","signature_bytes","source","source","source","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","token","token","token","token_type","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from_slice","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","validate","validate_expiration","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_integrity","validate_integrity","validate_maturity","validator","validator","verify_signature","verify_signature","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","with_certificate_sha1_thumbprint","with_certificate_thumbprint","with_certificate_url","with_key_id","with_key_set_url","with_token_type","actual","actual","expected","expected","Ed25519","Es256","Es256k","FourKibibytes","Hs256","Hs256Key","Hs256Signature","Hs384","Hs384Key","Hs384Signature","Hs512","Hs512Key","Hs512Signature","MAX_PUB_EXPONENT","MAX_SIZE","MIN_PUB_EXPONENT","ModulusBits","ModulusBitsError","Rsa","RsaParseError","RsaPrivateKey","RsaPublicKey","RsaSignature","SecretBytes","SigningKey","StrongAlg","StrongKey","ThreeKibibytes","TwoKibibytes","VerifyingKey","WeakKeyError","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_bytes","as_mut","as_mut","as_mut","as_ref","as_ref","as_ref","as_ref","as_ref","as_ref","bits","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrowed","clear_precomputed","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","compact_token","compact_token","compact_token","compact_token","compact_token","compact_token","compact_token","compact_token","crt_coefficient","crt_values","d","decrypt","decrypt_blinded","default","default","default","default","default","default","default","deref","deserialize","dp","dq","drop","drop","e","e","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encode_hex_upper","encrypt","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_components","from_p_q","from_pkcs1_der","from_pkcs1_der","from_pkcs8_der","from_primes","from_public_key_der","from_slice","from_slice","from_slice","from_slice","from_slice","from_slice","from_slice","from_slice","from_str","generate","generate","generate","generate","hash","hash","hash","hash","hash","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into_inner","n","n","name","name","name","name","name","name","name","name","new","new","new","new","new","new","new_unchecked","new_with_exp","new_with_max_size","owned","precompute","primes","ps256","ps384","ps512","qinv","rs256","rs384","rs512","serialize","sign","sign","sign","sign","sign","sign","sign","sign","sign","sign_with_rng","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_pkcs1_der","to_pkcs1_der","to_pkcs8_der","to_public_key","to_public_key","to_public_key_der","to_string","to_string","to_string","to_verifying_key","to_verifying_key","to_verifying_key","to_verifying_key","token","token","token","token","token","token","token","token","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from_slice","try_from_slice","try_from_slice","try_from_slice","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","validate","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_for_signed_token","validate_integrity","validate_integrity","validate_integrity","validate_integrity","validate_integrity","validate_integrity","validate_integrity","validate_integrity","validator","validator","validator","validator","validator","validator","validator","validator","verify","verify_signature","verify_signature","verify_signature","verify_signature","verify_signature","verify_signature","verify_signature","verify_signature","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","with_name","with_specific_name","zeroize","zeroize","zeroize","Custom","EllipticCurve","EllipticCurve","JsonWebKey","JwkError","KeyPair","KeyPair","KeyType","MismatchedKeys","NoField","Rsa","Rsa","RsaPrimeFactor","RsaPrivateParts","Symmetric","Symmetric","UnexpectedKeyType","UnexpectedLen","UnexpectedValue","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","crt_coefficient","crt_exponent","custom","deserialize","deserialize","deserialize","eq","eq","eq","eq","equivalent","factor","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","hash","into","into","into","into","into","is_signing_key","key_type","other_prime_factors","p_crt_exponent","prime_factor_p","prime_factor_q","private_exponent","q_crt_coefficient","q_crt_exponent","serialize","serialize","serialize","source","thumbprint","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_verifying_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","curve","curve","modulus","private_parts","public_exponent","secret","secret","secret","x","x","y","actual","actual","actual","expected","expected","expected","field","field","Claims","Header","TimeOptions","Token","UntrustedToken","_"],"q":[[0,"jwt_compact"],[302,"jwt_compact::ValidationError"],[306,"jwt_compact::alg"],[840,"jwt_compact::jwk"],[961,"jwt_compact::jwk::JsonWebKey"],[972,"jwt_compact::jwk::JwkError"],[980,"jwt_compact::prelude"],[986,"alloc::borrow"],[987,"core::clone"],[988,"core::marker"],[989,"serde::ser"],[990,"alloc::string"],[991,"core::result"],[992,"core::default"],[993,"serde::de"],[994,"serde::de"],[995,"core::fmt"],[996,"core::fmt"],[997,"core::hash"],[998,"core::ops::function"],[999,"core::convert"],[1000,"serde::ser"],[1001,"chrono::datetime"],[1002,"core::error"],[1003,"core::option"],[1004,"anyhow"],[1005,"core::any"],[1006,"core::convert"],[1007,"rsa::traits::keys"],[1008,"rsa::traits::padding"],[1009,"alloc::alloc"],[1010,"alloc::vec"],[1011,"rsa::errors"],[1012,"rand_core"],[1013,"digest"],[1014,"crypto_common"],[1015,"digest::digest"],[1016,"core::iter::traits::collect"],[1017,"core::fmt"],[1018,"rsa::pss::verifying_key"],[1019,"rsa::pss::signing_key"],[1020,"rsa::pkcs1v15::signing_key"],[1021,"rsa::pss::blinded_signing_key"],[1022,"pkcs1::error"],[1023,"pkcs8::error"],[1024,"spki::error"],[1025,"rand_core"],[1026,"secp256k1"],[1027,"num_bigint_dig::bigint"],[1028,"rsa::traits::padding"],[1029,"der::document"],[1030,"der::asn1::bit_string"],[1031,"spki::spki"],[1032,"pkcs8::private_key_info"],[1033,"anyhow"],[1034,"p256::ecdsa"],[1035,"exonum_crypto"],[1036,"p256::ecdsa"]],"d":["JWT signing algorithm.","Automatically implemented extensions of theAlgorithm
…","Algorithm mentioned in the token header differs from …","Signature for a certain JWT signing Algorithm
.","Byte representation of a SHA-1 or SHA-256 digest.","Token claims cannot be serialized into CBOR.","Identifier of a claim in Claims
.","Claims encoded in a token.","Token claims cannot be serialized into JSON.","Errors that can occur during token creation.","A structure with no fields that can be used as a type …","exp
claim (expiration time).","Token has expired.","JWT header.","Token header cannot be serialized.","Cannot decode base64.","Token signature has failed verification.","Token signature has invalid byte length.","Token has invalid structure.","Constant byte length of signatures supported by the …","Constant byte length of signatures supported by the …","Token claims cannot be deserialized from CBOR.","Token claims cannot be deserialized from JSON.","Token header cannot be parsed.","Token signature is malformed.","Claim requested during validation is not present in the …","nbf
claim (valid not before).","Token is not yet valid as per nbf
claim.","Errors that may occur during token parsing.","Algorithm that uses a custom name when creating and …","Signature produced by the algorithm.","Token
together with the validated token signature.","Key used when issuing new tokens.","Opaque string representation of the thumbprint. It is the …","Representation of a X.509 certificate thumbprint (x5t
and …","Time-related options for token creation and validation.","Token with validated integrity.","Content type mentioned in the token header is not …","Parsed, but unvalidated token.","Errors that can occur during token validation.","Validator for a certain signing Algorithm
associated with …","Key used when verifying tokens. May coincide with …","Implementations of JWT signing / verification algorithms. …","Gets the integrity algorithm used to secure the token.","Represents this signature as bytes.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","SHA-1 thumbprint of the X.509 certificate for the signing …","SHA-256 thumbprint of the X.509 certificate for the …","URL of the X.509 certificate for the signing key. This …","Gets token claims.","Source of the current timestamps.","","","","","","","","","","","","","","","","","","","","","","","Creates a new token with CBOR-encoded claims and …","","Custom claims.","","","","","","","","Deserializes claims from this token without checking token …","Creates an empty claims instance.","Creates an empty header.","","","","","","","","","Expiration time of the token.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Creates options based on the specified time leeway. The …","","","Gets the token header.","Gets token header.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Converts this token to an owned form.","Splits the Token
into the respective Header
and Claims
…","Time of token issuance.","Basic support of JSON Web Keys (JWK).","Identifier of the key that has signed the token. This …","URL of the JSON Web Key Set containing the key that has …","Leeway to use during validation.","Returns the name of this algorithm, as mentioned in the alg
…","","Creates options based on the specified time leeway and …","Creates a new instance with the provided custom claims.","Creates a header with the specified custom fields.","Creates an untrusted token from a string. This is a …","Creates a renamed algorithm.","Minimum time at which token is valid.","Other fields encoded in the header. These fields may be …","Prelude to neatly import all necessary stuff from the …","","","","","Sets the expiration
claim so that the token has the …","Atomically sets issued_at
and expiration
claims: first to …","Sets the nbf
claim.","Signs a message
with the signing_key
.","","Token signature.","Returns signature bytes from the token. These bytes are not…","","","","","","","","","","","","","","","","","","","Creates a new token and serializes it to string.","","Verified token.","Application-specific token type. This field is renamed to …","","","","","","","","","","","","","","","","Attempts to restore a signature from a byte slice. This …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Validates the token integrity against a verifying key …","Validates the expiration claim.","Validates the token integrity against the provided …","","Validates the token integrity against a verifying key …","Validates the token integrity against the provided …","","Validates the maturity time (nbf
claim).","Creates a JWT validator for the specified verifying key …","","Verifies the message
against the signature
and …","","","","","","","","","","","","","","","","Sets the certificate_sha1_thumbprint
field for this header.","Sets the certificate_thumbprint
field for this header.","Sets the certificate_url
field for this header.","Sets the key_id
field for this header.","Sets the key_set_url
field for this header.","Sets the token_type
field for this header.","Actual algorithm in the token.","Actual signature length.","Expected algorithm name.","Expected signature length.","Integrity algorithm using digital signatures on the …","ES256
signing algorithm. Implements elliptic curve digital …","Algorithm implementing elliptic curve digital signatures …","4096 bits.","HS256
signing algorithm.","Signing / verifying key for HS256
algorithm. Zeroed on …","Signature produced by the Hs256
algorithm.","HS384
signing algorithm.","Signing / verifying key for HS384
algorithm. Zeroed on …","Signature produced by the Hs384
algorithm.","HS512
signing algorithm.","Signing / verifying key for HS512
algorithm. Zeroed on …","Signature produced by the Hs512
algorithm.","Maximum value of the public exponent e
.","Maximum size of the modulus n
in bits.","Minimum value of the public exponent e
.","Bit length of an RSA key modulus (aka RSA key length).","Error type returned when a conversion of an integer into …","Integrity algorithm using RSA digital signatures.","Errors that can occur when parsing an Rsa
algorithm from a …","Represents a whole RSA key, public and private parts.","Represents the public part of an RSA key.","RSA signature.","Generic container for secret bytes, which can be either …","Signing key for a specific signature cryptosystem. In the …","Wrapper around a JWT algorithm signalling that it supports …","Wrapper around keys allowing to enforce key strength …","3072 bits.","2048 bits. This is the minimum recommended key length as …","Verifying key for a specific signature cryptosystem. In …","Error type used for fallible conversion into a StrongKey
.","Returns the key as raw bytes.","Returns the key as raw bytes.","","","","","","","","","","","","","","","","","","","","Converts this length to the numeric value.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Creates secret bytes from a borrowed slice.","Clears precomputed values by setting to None","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Compute CRT coefficient: (1/q) mod p
.","","","Decrypt the given message.","Decrypt the given message.","","","","","","","","","","","","","","","","","","","","","","","","","","Encrypt the given message.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","","Constructs an RSA key pair from individual components:","Constructs an RSA key pair from its two primes p and q.","","","","Constructs an RSA key pair from its primes.","","Creates a key from raw
bytes. Returns an error if the …","Creates a key from raw
bytes. Returns an error if the …","","","","","","","","Generates a random key using a cryptographically secure …","Generates a random key using a cryptographically secure …","Generates a random key using a cryptographically secure …","Generates a new key pair with the specified modulus bit …","","","","","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Returns the wrapped value.","","","","","","","","","","","Creates a key from the specified bytes
.","Creates a key from the specified bytes
.","Creates a key from the specified bytes
.","Creates a new algorithm instance. This is a (moderately) …","Create a new public key from its components.","Generate a new Rsa key pair of the given bit size using …","Create a new public key, bypassing checks around the …","Generate a new RSA key pair of the given bit size and the …","Create a new public key from its components.","Creates secret bytes from an owned Vec
.","Performs some calculations to speed up private key …","","RSA with SHA-256 and PSS padding.","RSA with SHA-384 and PSS padding.","RSA with SHA-512 and PSS padding.","","RSA with SHA-256 and PKCS#1 v1.5 padding.","RSA with SHA-384 and PKCS#1 v1.5 padding.","RSA with SHA-512 and PKCS#1 v1.5 padding.","","","","","","","","","","Sign the given digest.","Sign the given digest using the provided rng
, which is …","","","","","","","","","","","","","","","","","","","","","Converts this private key to a public key.","Get the public key from the private key, cloning n
and e
.","","","","","Converts a signing key to a verification key.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Performs basic sanity checks on the key. Returns Ok(())
if …","","","","","","","","","","","","","","","","","","","","","","","","","Verify a signed message.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","RSA based on the specified algorithm name.","Creates an algorithm instance with the algorithm name …","","","","Custom error specific to a crypto backend.","Public or private key in an ECDSA crypto system. …","Public or private key in an ECDSA crypto system. Has kty
…","Basic JWK functionality: (de)serialization and creating …","Errors that can occur when transforming a JsonWebKey
into …","Generic asymmetric keypair. Corresponds to the OKP
value …","Generic asymmetric keypair. This key type is used e.g. for …","Type of a JsonWebKey
.","Signing and verifying keys do not match.","Required field is absent from JWK.","Public or private RSA key. Corresponds to the RSA
value of …","Public or private RSA key. Has kty
field set to RSA
.","Block for an additional prime factor in RsaPrivateParts
.","Parts of JsonWebKey::Rsa
that are specific to private keys.","Symmetric key. Corresponds to the oct
value of the kty
…","Generic symmetric key, e.g. for HS256
algorithm. Has kty
…","Key type (the kty
field) is not as expected.","JWK field has an unexpected byte length.","JWK field has an unexpected value.","","","","","","","","","","","","","","","","","","","Factor CRT coefficient (t
).","Factor CRT exponent (d
).","Creates a Custom
error variant.","","","","","","","","","Prime factor (r
).","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Returns true
if this key can be used for signing (has …","Gets the type of this key.","Other prime factors.","First factor CRT exponent (dp
).","First prime factor (p
).","Second prime factor (q
).","Private exponent (d
).","CRT coefficient of the second factor (qi
).","Second factor CRT exponent (dq
).","","","","","Computes a thumbprint of this JWK. The result complies …","","","","","","","","Returns a copy of this key with parts not necessary for …","","","","","","","","","","","","","","","","","","","","","Curve name (crv
), such as secp256k1
.","Curve name (crv
), such as Ed25519
.","Key modulus (n
).","Private RSA parameters. Only present for private keys.","Public exponent (e
).","Secret scalar (d
); not present for public keys.","Bytes representing this key.","Secret key (d
). For Ed25519, this is the seed.","x
coordinate of the curve point.","Public key. For Ed25519, this is the standard 32-byte …","y
coordinate of the curve point.","Actual key type.","Actual value of the field.","Actual byte length of the field.","Expected key type.","Expected value of the field.","Expected byte length of the field.","Field name.","Field name.","","","","","",""],"i":[0,0,27,0,14,21,0,0,21,0,0,13,27,0,21,33,27,27,33,119,119,27,27,33,27,27,13,27,0,0,8,0,8,14,0,0,0,33,0,0,0,8,0,1,119,10,11,12,7,33,27,13,21,14,15,1,6,16,18,10,11,12,7,33,27,13,21,14,15,1,6,16,18,15,15,15,6,11,10,11,12,7,13,14,15,1,6,16,18,10,11,12,7,13,14,15,1,6,16,18,120,16,7,11,12,15,12,7,14,15,1,7,15,12,7,13,14,12,7,13,14,7,10,11,12,7,33,33,27,27,13,13,21,21,14,15,1,6,16,18,10,11,12,7,33,27,13,21,14,14,14,14,15,1,6,16,18,11,12,14,1,6,10,11,12,7,33,27,13,21,14,15,1,6,16,18,1,6,7,0,15,15,11,8,16,11,7,15,1,16,7,15,0,12,7,14,15,7,7,7,8,16,10,1,33,27,21,10,11,12,7,13,14,15,1,6,16,18,33,27,13,21,120,16,10,15,10,11,12,7,33,27,13,21,14,15,1,1,6,16,18,119,10,11,12,7,33,27,13,21,14,15,1,6,16,18,10,11,12,7,33,27,13,21,14,15,1,6,16,18,18,7,120,16,18,120,16,7,120,16,8,16,10,11,12,7,33,27,13,21,14,15,1,6,16,18,15,15,15,15,15,15,121,122,121,122,0,0,0,58,0,0,0,0,0,0,0,0,0,57,57,57,0,0,0,0,0,0,0,0,0,0,0,58,58,0,0,123,124,48,49,50,51,51,52,52,53,53,54,51,52,53,47,51,52,53,55,56,58,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,47,56,47,48,49,50,51,52,53,60,61,62,63,58,64,55,65,57,56,47,48,49,50,51,52,53,60,61,62,63,58,64,55,65,57,56,60,61,62,76,63,77,64,65,56,56,56,56,56,60,61,62,76,63,77,65,47,47,56,56,47,56,57,56,47,51,52,53,55,47,51,52,53,55,57,47,48,49,50,60,61,62,63,58,64,55,57,56,48,49,50,60,61,62,63,58,64,55,57,56,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,80,64,81,81,55,82,82,65,57,56,47,48,49,50,51,51,52,52,53,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,57,57,57,57,56,56,56,56,56,56,57,56,56,56,57,123,124,51,51,52,52,53,53,64,51,52,53,64,60,61,62,63,57,56,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,55,57,56,60,61,62,76,63,77,64,65,51,52,53,76,57,56,57,56,57,47,56,56,64,64,64,56,64,64,64,47,60,61,62,76,63,77,64,65,56,56,47,48,49,50,51,52,53,60,61,62,63,58,64,55,65,57,56,57,56,56,55,56,57,80,81,82,124,51,52,53,60,61,62,76,63,77,64,65,47,48,49,50,51,51,52,52,53,53,60,61,62,76,63,77,54,58,58,80,64,81,55,55,55,55,55,55,82,65,57,57,57,56,56,56,48,49,50,54,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,56,60,61,62,76,63,77,64,65,60,61,62,76,63,77,64,65,60,61,62,76,63,77,64,65,57,60,61,62,76,63,77,64,65,47,48,49,50,51,52,53,60,61,62,76,63,77,54,58,80,64,81,55,82,65,57,56,64,63,51,52,53,111,107,102,0,0,107,102,0,111,111,107,102,0,0,107,102,111,111,111,107,111,102,108,109,107,111,102,108,109,107,102,108,109,107,102,108,109,109,109,111,102,108,109,107,102,108,109,107,109,107,107,111,111,102,102,108,109,107,111,102,102,102,102,102,102,102,102,102,102,102,102,108,109,107,107,111,102,108,109,102,102,108,108,108,108,108,108,108,102,108,109,111,102,107,102,108,109,107,111,102,102,107,111,102,108,109,107,111,102,108,109,107,111,102,108,109,107,111,102,108,109,125,126,127,127,127,125,128,126,125,126,125,129,130,131,129,130,131,130,131,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[[],[[5,[[4,[3]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[6,7],0,[[[10,[8,9,9]]],[[10,[8,9,9]]]],[[[11,[9]]],[[11,[9]]]],[12,12],[[[7,[9]]],[[7,[9]]]],[13,13],[14,14],[[[15,[9]]],[[15,[9]]]],[[[1,[9]]],[[1,[9]]]],[[[6,[9,9]]],[[6,[9,9]]]],[[[16,[9]]],[[16,[9]]]],[[[18,[[0,[8,17]]]]],[[18,[[0,[8,17]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],0,[[],11],[[],12],[[],[[15,[23]]]],[24,[[22,[12]]]],[24,[[22,[[7,[25]]]]]],[24,[[22,[14]]]],[24,[[22,[[15,[25]]]]]],[1,[[22,[[7,[26]],27]]]],[[],[[7,[12]]]],[[],15],[[12,12],28],[[[7,[29]],[7,[29]]],28],[[13,13],28],[[14,14],28],[[],28],[[],28],[[],28],[[],28],0,[[[10,[8,30,30]],31],32],[[[11,[30]],31],32],[[12,31],32],[[[7,[30]],31],32],[[33,31],32],[[33,31],32],[[27,31],32],[[27,31],32],[[13,31],32],[[13,31],32],[[21,31],32],[[21,31],32],[[14,31],32],[[[15,[30]],31],32],[[[1,[30]],31],32],[[[6,[30,30]],31],32],[[[16,[30]],31],32],[[[18,[[0,[30,8,17]],30]],31],32],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,14],[[]],[20,14],[[[34,[3]]],14],[[]],[[]],[[]],[[]],[[]],[35,11],[[12,36]],[[14,36]],[1,15],[6,15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[6],0,0,0,0,0,[[],[[5,[2]]]],[[[16,[8]]],[[5,[2]]]],[[35,37],[[11,[37]]]],[[],7],[[],15],[[[0,[[38,[2]],17]]],[[22,[1,33]]]],[[8,2],[[16,[8]]]],0,0,0,[[12,39],22],[[[7,[19]],39],22],[[14,39],22],[[[15,[19]],39],22],[[7,[11,[37]],35],7],[[7,[11,[37]],35],7],[[7,[41,[40]]],7],[[[4,[3]]]],[[[16,[8]],[4,[3]]]],0,[1,[[4,[3]]]],[33,[[43,[42]]]],[27,[[43,[42]]]],[21,[[43,[42]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],20],[[],20],[[],20],[[],20],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],0,0,[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[2,[[22,[[1,[26]]]]]],[[],22],[[],22],[[],22],[[],22],[[[4,[3]]],44],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[[18,[[0,[8,17]],26]],[1,[9]]],[[22,[[6,[26,9]],27]]]],[[7,[11,[37]]],[[22,[7,27]]]],[1,[[22,[[10,[26]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[18,[[0,[8,17]],26]],[1,[9]]],[[22,[[10,[[0,[8,17]],26,9]],27]]]],[1,[[22,[[6,[26]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[7,[11,[37]]],[[22,[7,27]]]],[[],18],[[],18],[[[4,[3]]],28],[[[16,[8]],[4,[3]]],28],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[15,[46,[14]]],15],[[15,[46,[14]]],15],[[15,[46,[20]]],15],[[15,[46,[20]]],15],[[15,[46,[20]]],15],[[15,[46,[20]]],15],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[5,[[4,[3]]]]]],[[],47],[48,[[5,[[4,[3]]]]]],[49,[[5,[[4,[3]]]]]],[50,[[5,[[4,[3]]]]]],[51,47],[51,[[5,[[4,[3]]]]]],[52,47],[52,[[5,[[4,[3]]]]]],[53,47],[53,[[5,[[4,[3]]]]]],[54,[[5,[[4,[3]]]]]],[51,[[4,[3]]]],[52,[[4,[3]]]],[53,[[4,[3]]]],[47,[[4,[3]]]],[51,[[4,[3]]]],[52,[[4,[3]]]],[53,[[4,[3]]]],[55],[56,57],[58,59],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[4,[3]]],47],[56],[47,47],[48,48],[49,49],[50,50],[51,51],[52,52],[53,53],[60,60],[61,61],[62,62],[63,63],[58,58],[64,64],[[[55,[9]]],[[55,[9]]]],[[[65,[9]]],[[65,[9]]]],[57,57],[56,56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[56,[[43,[66]]]],[56,[[43,[[4,[67]]]]]],[56,66],[[56,68,[4,[3]]],[[22,[[70,[3,69]],71]]]],[[56,72,68,[4,[3]]],[[22,[[70,[3,69]],71]]]],[[],60],[[],61],[[],62],[[],[[76,[[0,[73,74,9,23,75]]]]]],[[],63],[[],77],[[],[[65,[23]]]],[47],[24,[[22,[47]]]],[56,[[43,[66]]]],[56,[[43,[66]]]],[47],[56],[57,66],[56,66],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[],[[79,[78]]]],[[57,72,68,[4,[3]]],[[22,[[70,[3,69]],71]]]],[[47,47],28],[[48,48],28],[[49,49],28],[[50,50],28],[[60,60],28],[[61,61],28],[[62,62],28],[[63,63],28],[[58,58],28],[[64,64],28],[[[55,[29]],[55,[29]]],28],[[57,57],28],[[56,56],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[],28],[[47,31],32],[[48,31],32],[[49,31],32],[[50,31],32],[[51,31],32],[[52,31],32],[[53,31],32],[[60,31],32],[[61,31],32],[[62,31],32],[[[76,[30]],31],32],[[63,31],32],[[77,31],32],[[54,31],32],[[58,31],32],[[80,31],32],[[80,31],32],[[64,31],32],[[81,31],32],[[81,31],32],[[[55,[30]],31],32],[[[82,[30]],31],32],[[82,31],32],[[[65,[30]],31],32],[[57,31],[[22,[83]]]],[[56,31],[[22,[83]]]],[[]],[[]],[[]],[[]],[[]],[[[4,[3]]],51],[[[4,[3]]],52],[[]],[[]],[[[4,[3]]],53],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[56,57],[[[85,[84]]],57],[[[86,[84]]],57],[56,57],[[]],[[[87,[84]]],56],[[[88,[84]]],56],[[]],[[[89,[84]]],56],[[66,66,66,[70,[66,69]]],[[22,[56,71]]]],[[66,66,66],[[22,[56,71]]]],[[[4,[3]]],[[22,[90]]]],[[[4,[3]]],[[22,[90]]]],[[[4,[3]]],[[22,[91]]]],[[[70,[66,69]],66],[[22,[56,71]]]],[[[4,[3]]],[[22,[92]]]],[[[4,[3]]],44],[[[4,[3]]],44],[[[4,[3]]],[[44,[51]]]],[[[4,[3]]],[[44,[51]]]],[[[4,[3]]],[[44,[52]]]],[[[4,[3]]],[[44,[52]]]],[[[4,[3]]],[[44,[53]]]],[[[4,[3]]],[[44,[53]]]],[2,[[22,[64]]]],[[[0,[93,94]]],[[55,[51]]]],[[[0,[93,94]]],[[55,[52]]]],[[[0,[93,94]]],[[55,[53]]]],[[[0,[93,94]],58],95],[[60,36]],[[61,36]],[[62,36]],[[63,36]],[[57,36]],[[56,36]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[55],[57,66],[56,66],[60,[[5,[2]]]],[61,[[5,[2]]]],[62,[[5,[2]]]],[[[76,[[0,[73,74,9,23,75]]]]],[[5,[2]]]],[63,[[5,[2]]]],[77,[[5,[2]]]],[64,[[5,[2]]]],[[[65,[8]]],[[5,[2]]]],[[[38,[[4,[3]]]]],51],[[[38,[[4,[3]]]]],52],[[[38,[[4,[3]]]]],53],[[[97,[96]]],[[76,[[0,[73,74,9,23,75]]]]]],[[66,66],[[22,[57,71]]]],[[[0,[72,17]],59],[[22,[56,71]]]],[[66,66],57],[[[0,[72,17]],59,66],[[22,[56,71]]]],[[66,66,59],[[22,[57,71]]]],[[[70,[3]]],47],[56,[[22,[71]]]],[56,[[4,[66]]]],[[],64],[[],64],[[],64],[56,[[43,[98]]]],[[],64],[[],64],[[],64],[[47,39],22],[[60,[4,[3]]]],[[61,[4,[3]]]],[[62,[4,[3]]]],[[[76,[[0,[73,74,9,23,75]]]],[4,[3]]]],[[63,[4,[3]]]],[[77,[4,[3]]]],[[64,[4,[3]]]],[[[65,[8]],[4,[3]]]],[[56,99,[4,[3]]],[[22,[[70,[3,69]],71]]]],[[56,72,99,[4,[3]]],[[22,[[70,[3,69]],71]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[22,[100,90]]]],[[],[[22,[101,90]]]],[56,[[22,[101,91]]]],[[[55,[56]]],[[55,[57]]]],[56,57],[57,[[22,[100,92]]]],[[],20],[[],20],[[],20],[[]],[51,51],[52,52],[53,53],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[[15,[19]],[7,[19]]],[[22,[20,21]]]],[[],22],[[],22],[[],22],[[],22],[102,[[22,[51]]]],[[],22],[102,[[22,[52]]]],[[],22],[[],22],[102,[[22,[53]]]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[59,[[22,[58]]]],[[],22],[[],22],[[],22],[[],22],[56,[[22,[[55,[56]]]]]],[51,[[22,[[55,[51]]]]]],[53,[[22,[[55,[53]]]]]],[57,[[22,[[55,[57]]]]]],[52,[[22,[[55,[52]]]]]],[[],22],[[],22],[[[105,[103,104]]],[[22,[57,92]]]],[102,[[22,[57]]]],[[],22],[102,[[22,[56]]]],[106,[[22,[56,91]]]],[[],22],[[[4,[3]]],[[44,[48]]]],[[[4,[3]]],[[44,[49]]]],[[[4,[3]]],[[44,[50]]]],[[[4,[3]]],[[44,[54]]]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[[],45],[56,[[22,[71]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[10,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[[1,[12]]],[[22,[[6,[26,12]],27]]]],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[],18],[[57,99,[4,[3]],[4,[3]]],[[22,[71]]]],[[60,[4,[3]]],28],[[61,[4,[3]]],28],[[62,[4,[3]]],28],[[[76,[[0,[73,74,9,23,75]]]],[4,[3]]],28],[[63,[4,[3]]],28],[[77,[4,[3]]],28],[[64,[4,[3]]],28],[[[65,[8]],[4,[3]]],28],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[2,64],[[],[[16,[63]]]],[51],[52],[53],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[107,107],[102,102],[108,108],[109,109],[[]],[[]],[[]],[[]],0,0,[[[46,[110]]],111],[24,[[22,[102]]]],[24,[[22,[108]]]],[24,[[22,[109]]]],[[107,107],28],[[102,102],28],[[108,108],28],[[109,109],28],[[],28],0,[[107,31],32],[[107,31],32],[[111,31],32],[[111,31],32],[[102,31],32],[[102,31],32],[[108,31],32],[[109,31],32],[[]],[[]],[112,102],[57,102],[56,102],[[]],[113,102],[114,102],[53,102],[115,102],[51,102],[116,102],[52,102],[117,102],[[]],[[]],[[107,36]],[[]],[[]],[[]],[[]],[[]],[102,28],[102,107],0,0,0,0,0,0,0,[[102,39],22],[[108,39],22],[[109,39],22],[111,[[43,[42]]]],[102,[[118,[84]]]],[[]],[[]],[[]],[[]],[[],20],[[],20],[[],20],[102,102],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],45],[[],45],[[],45],[[],45],[[],45],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"c":[272,275],"p":[[3,"UntrustedToken",0],[15,"str"],[15,"u8"],[15,"slice"],[4,"Cow",986],[3,"Token",0],[3,"Claims",0],[8,"Algorithm",0],[8,"Clone",987],[3,"SignedToken",0],[3,"TimeOptions",0],[3,"Empty",0],[4,"Claim",0],[4,"Thumbprint",0],[3,"Header",0],[3,"Renamed",0],[8,"Sized",988],[3,"Validator",0],[8,"Serialize",989],[3,"String",990],[4,"CreationError",0],[4,"Result",991],[8,"Default",992],[8,"Deserializer",993],[8,"Deserialize",993],[8,"DeserializeOwned",993],[4,"ValidationError",0],[15,"bool"],[8,"PartialEq",994],[8,"Debug",995],[3,"Formatter",995],[6,"Result",995],[4,"ParseError",0],[15,"array"],[3,"Duration",996],[8,"Hasher",997],[8,"Fn",998],[8,"AsRef",999],[8,"Serializer",989],[3,"Utc",1000],[3,"DateTime",1001],[8,"Error",1002],[4,"Option",1003],[6,"Result",1004],[3,"TypeId",1005],[8,"Into",999],[3,"SecretBytes",306],[3,"Hs256Signature",306],[3,"Hs384Signature",306],[3,"Hs512Signature",306],[3,"Hs256Key",306],[3,"Hs384Key",306],[3,"Hs512Key",306],[3,"RsaSignature",306],[3,"StrongKey",306],[3,"RsaPrivateKey",306],[3,"RsaPublicKey",306],[4,"ModulusBits",306],[15,"usize"],[3,"Hs256",306],[3,"Hs384",306],[3,"Hs512",306],[3,"Ed25519",306],[3,"Rsa",306],[3,"StrongAlg",306],[3,"BigUint",1006],[3,"CrtValue",1007],[8,"PaddingScheme",1008],[3,"Global",1009],[3,"Vec",1010],[4,"Error",1011],[8,"CryptoRngCore",1012],[8,"FixedOutputReset",1013],[8,"BlockSizeUser",1014],[8,"HashMarker",1015],[3,"Es256k",306],[3,"Es256",306],[15,"char"],[8,"FromIterator",1016],[3,"ModulusBitsError",306],[3,"RsaParseError",306],[3,"WeakKeyError",306],[3,"Error",995],[8,"Digest",1015],[3,"VerifyingKey",1017],[3,"VerifyingKey",1018],[3,"SigningKey",1019],[3,"SigningKey",1020],[3,"BlindedSigningKey",1021],[4,"Error",1022],[4,"Error",1023],[4,"Error",1024],[8,"CryptoRng",1012],[8,"RngCore",1012],[6,"Result",1011],[4,"All",1025],[3,"Secp256k1",1026],[3,"BigInt",1027],[8,"SignatureScheme",1008],[3,"Document",1028],[3,"SecretDocument",1028],[4,"JsonWebKey",840],[3,"AnyRef",1029],[3,"BitStringRef",1030],[3,"SubjectPublicKeyInfo",1031],[3,"PrivateKeyInfo",1032],[4,"KeyType",840],[3,"RsaPrivateParts",840],[3,"RsaPrimeFactor",840],[3,"Error",1004],[4,"JwkError",840],[3,"SecretKey",1033],[6,"SigningKey",1034],[3,"PublicKey",1033],[3,"PublicKey",1035],[6,"VerifyingKey",1034],[3,"SecretKey",1035],[6,"Output",1014],[8,"AlgorithmSignature",0],[8,"AlgorithmExt",0],[13,"AlgorithmMismatch",302],[13,"InvalidSignatureLen",302],[8,"VerifyingKey",306],[8,"SigningKey",306],[13,"EllipticCurve",961],[13,"KeyPair",961],[13,"Rsa",961],[13,"Symmetric",961],[13,"UnexpectedKeyType",972],[13,"UnexpectedValue",972],[13,"UnexpectedLen",972]]}\
+}');
+if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
+if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
diff --git a/settings.html b/settings.html
new file mode 100644
index 00000000..88cede9c
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +
//! Implementations of JWT signing / verification algorithms. Also contains generic traits
+//! for signing and verifying keys.
+
+use core::fmt;
+
+use crate::{alloc::Cow, Algorithm};
+
+mod generic;
+mod hmacs;
+// Alternative ES256K implementations.
+#[cfg(feature = "secp256k1")]
+mod es256k;
+#[cfg(feature = "k256")]
+mod k256;
+// Alternative EdDSA implementations.
+#[cfg(feature = "ed25519-compact")]
+mod eddsa_compact;
+#[cfg(feature = "ed25519-dalek")]
+mod eddsa_dalek;
+#[cfg(feature = "exonum-crypto")]
+mod eddsa_sodium;
+// ES256 implemenation.
+#[cfg(feature = "p256")]
+mod p256;
+// RSA implementation.
+#[cfg(feature = "rsa")]
+mod rsa;
+
+#[cfg(feature = "ed25519-compact")]
+pub use self::eddsa_compact::*;
+#[cfg(feature = "ed25519-dalek")]
+pub use self::eddsa_dalek::Ed25519;
+#[cfg(feature = "exonum-crypto")]
+pub use self::eddsa_sodium::Ed25519;
+#[cfg(feature = "es256k")]
+pub use self::es256k::Es256k;
+pub use self::generic::{SecretBytes, SigningKey, VerifyingKey};
+pub use self::hmacs::*;
+#[cfg(feature = "k256")]
+pub use self::k256::Es256k;
+#[cfg(feature = "p256")]
+pub use self::p256::Es256;
+#[cfg(feature = "rsa")]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub use self::rsa::{
+ ModulusBits, ModulusBitsError, Rsa, RsaParseError, RsaPrivateKey, RsaPublicKey, RsaSignature,
+};
+
+/// Wrapper around keys allowing to enforce key strength requirements.
+///
+/// The wrapper signifies that the key has supported strength as per the corresponding
+/// algorithm spec. For example, RSA keys must have length at least 2,048 bits per [RFC 7518].
+/// Likewise, `HS*` keys must have at least the length of the hash output
+/// (e.g., 32 bytes for `HS256`). Since these requirements sometimes clash with backward
+/// compatibility (and sometimes a lesser level of security is enough),
+/// notion of key strength is implemented in such an opt-in, composable way.
+///
+/// It's easy to convert a `StrongKey<T>` to `T` via [`into_inner()`](Self::into_inner()) or to
+/// access `&T` via `AsRef` impl. In contrast, the reverse transformation is fallible, and
+/// is defined with the help of [`TryFrom`]. The error type for `TryFrom` is [`WeakKeyError`],
+/// a simple wrapper around a weak key.
+///
+/// # Examples
+///
+/// See [`StrongAlg`] docs for an example of usage.
+///
+/// [RFC 7518]: https://www.rfc-editor.org/rfc/rfc7518.html
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct StrongKey<T>(T);
+
+impl<T> StrongKey<T> {
+ /// Returns the wrapped value.
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
+
+impl<T> AsRef<T> for StrongKey<T> {
+ fn as_ref(&self) -> &T {
+ &self.0
+ }
+}
+
+/// Error type used for fallible conversion into a [`StrongKey`].
+///
+/// The error wraps around a weak key, which can be extracted for further use.
+#[derive(Debug)]
+pub struct WeakKeyError<T>(pub T);
+
+impl<T> fmt::Display for WeakKeyError<T> {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("Weak cryptographic key")
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T: fmt::Debug + 'static> std::error::Error for WeakKeyError<T> {}
+
+/// Wrapper around a JWT algorithm signalling that it supports only [`StrongKey`]s.
+///
+/// The wrapper will implement `Algorithm` if the wrapped value is an `Algorithm` with both
+/// signing and verifying keys convertible to `StrongKey`s.
+///
+/// # Examples
+///
+/// ```
+/// # use rand::thread_rng;
+/// # use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key, StrongAlg, StrongKey}};
+/// # fn main() -> anyhow::Result<()> {
+/// let weak_key = Hs256Key::new(b"too short!");
+/// assert!(StrongKey::try_from(weak_key).is_err());
+/// // There is no way to create a `StrongKey` from `weak_key`!
+///
+/// let strong_key: StrongKey<_> = Hs256Key::generate(&mut thread_rng());
+/// let claims = // ...
+/// # Claims::empty();
+/// let token = StrongAlg(Hs256)
+/// .token(&Header::empty(), &claims, &strong_key)?;
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone, Copy, Default)]
+pub struct StrongAlg<T>(pub T);
+
+#[allow(clippy::trait_duplication_in_bounds)] // false positive
+impl<T: Algorithm> Algorithm for StrongAlg<T>
+where
+ StrongKey<T::SigningKey>: TryFrom<T::SigningKey>,
+ StrongKey<T::VerifyingKey>: TryFrom<T::VerifyingKey>,
+{
+ type SigningKey = StrongKey<T::SigningKey>;
+ type VerifyingKey = StrongKey<T::VerifyingKey>;
+ type Signature = T::Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ self.0.name()
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ self.0.sign(&signing_key.0, message)
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ self.0
+ .verify_signature(signature, &verifying_key.0, message)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +
//! `EdDSA` algorithm implementation using the `exonum-crypto` crate.
+
+use anyhow::format_err;
+use exonum_crypto::{
+ gen_keypair_from_seed, sign, verify, PublicKey, SecretKey, Seed, Signature, PUBLIC_KEY_LENGTH,
+ SEED_LENGTH, SIGNATURE_LENGTH,
+};
+
+use core::num::NonZeroUsize;
+
+use crate::{
+ alg::{SecretBytes, SigningKey, VerifyingKey},
+ alloc::Cow,
+ jwk::{JsonWebKey, JwkError, KeyType},
+ Algorithm, AlgorithmSignature, Renamed,
+};
+
+impl AlgorithmSignature for Signature {
+ const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(SIGNATURE_LENGTH);
+
+ fn try_from_slice(bytes: &[u8]) -> anyhow::Result<Self> {
+ // There are no checks other than by signature length in `from_slice`,
+ // so the `unwrap()` below is safe.
+ Ok(Self::from_slice(bytes).unwrap())
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(self.as_ref())
+ }
+}
+
+/// Integrity algorithm using digital signatures on the Ed25519 elliptic curve.
+///
+/// The name of the algorithm is specified as `EdDSA` as per [IANA registry].
+/// Use `with_specific_name()` to switch to non-standard `Ed25519`.
+///
+/// [IANA registry]: https://www.iana.org/assignments/jose/jose.xhtml
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "exonum-crypto",
+ feature = "ed25519-dalek",
+ feature = "ed25519-compact"
+ )))
+)]
+pub struct Ed25519;
+
+impl Ed25519 {
+ /// Creates an algorithm instance with the algorithm name specified as `Ed25519`.
+ /// This is a non-standard name, but it is used in some apps.
+ pub fn with_specific_name() -> Renamed<Self> {
+ Renamed::new(Self, "Ed25519")
+ }
+}
+
+impl Algorithm for Ed25519 {
+ type SigningKey = SecretKey;
+ type VerifyingKey = PublicKey;
+ type Signature = Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("EdDSA")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ sign(message, signing_key)
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ verify(signature, message, verifying_key)
+ }
+}
+
+impl VerifyingKey<Ed25519> for PublicKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_slice(raw).ok_or_else(|| format_err!("Invalid public key length"))
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(self.as_ref())
+ }
+}
+
+impl SigningKey<Ed25519> for SecretKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_slice(raw).ok_or_else(|| format_err!("Invalid secret key bytes"))
+ }
+
+ fn to_verifying_key(&self) -> PublicKey {
+ // Slightly hacky. The backend does not expose functions for converting secret keys
+ // to public ones, and we don't want to use `KeyPair` instead of `SecretKey`
+ // for this single purpose.
+ PublicKey::from_slice(&self[SEED_LENGTH..]).unwrap()
+ }
+
+ fn as_bytes(&self) -> SecretBytes<'_> {
+ SecretBytes::borrowed(&self[..])
+ }
+}
+
+impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
+ fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
+ JsonWebKey::KeyPair {
+ curve: Cow::Borrowed("Ed25519"),
+ x: Cow::Borrowed(key.as_ref()),
+ secret: None,
+ }
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for PublicKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::KeyPair { curve, x, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::KeyPair));
+ };
+
+ JsonWebKey::ensure_curve(curve, "Ed25519")?;
+ JsonWebKey::ensure_len("x", x, PUBLIC_KEY_LENGTH)?;
+ Ok(PublicKey::from_slice(x).unwrap())
+ // ^ unlike some other impls, libsodium does not check public key validity on creation
+ }
+}
+
+impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
+ fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
+ JsonWebKey::KeyPair {
+ curve: Cow::Borrowed("Ed25519"),
+ x: Cow::Borrowed(&key[SEED_LENGTH..]),
+ secret: Some(SecretBytes::borrowed(&key[..SEED_LENGTH])),
+ }
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for SecretKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::KeyPair { secret, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::KeyPair));
+ };
+ let seed_bytes = secret.as_deref();
+ let seed_bytes = seed_bytes.ok_or_else(|| JwkError::NoField("d".to_owned()))?;
+
+ JsonWebKey::ensure_len("d", seed_bytes, SEED_LENGTH)?;
+ let seed = Seed::from_slice(seed_bytes).unwrap();
+ let (_, sk) = gen_keypair_from_seed(&seed);
+ jwk.ensure_key_match(sk)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +
//! `ES256K` algorithm implementation using the `secp256k1` crate.
+
+use lazy_static::lazy_static;
+use secp256k1::{
+ constants::{
+ COMPACT_SIGNATURE_SIZE, FIELD_SIZE, SECRET_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE,
+ },
+ ecdsa::Signature,
+ All, Message, PublicKey, Secp256k1, SecretKey,
+};
+use sha2::{
+ digest::{
+ crypto_common::BlockSizeUser, generic_array::typenum::U32, FixedOutputReset, HashMarker,
+ },
+ Digest, Sha256,
+};
+
+use core::{marker::PhantomData, num::NonZeroUsize};
+
+use crate::{
+ alg::{SecretBytes, SigningKey, VerifyingKey},
+ alloc::Cow,
+ jwk::{JsonWebKey, JwkError, KeyType},
+ Algorithm, AlgorithmSignature,
+};
+
+/// Byte size of a serialized EC coordinate.
+const COORDINATE_SIZE: usize = FIELD_SIZE.len();
+
+impl AlgorithmSignature for Signature {
+ const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(COMPACT_SIGNATURE_SIZE);
+
+ fn try_from_slice(slice: &[u8]) -> anyhow::Result<Self> {
+ Signature::from_compact(slice).map_err(Into::into)
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(self.serialize_compact()[..].to_vec())
+ }
+}
+
+/// Algorithm implementing elliptic curve digital signatures (ECDSA) on the secp256k1 curve.
+///
+/// The algorithm does not fix the choice of the message digest algorithm; instead,
+/// it is provided as a type parameter. SHA-256 is the default parameter value,
+/// but it can be set to any cryptographically secure hash function with 32-byte output
+/// (e.g., SHA3-256).
+#[derive(Debug)]
+#[cfg_attr(docsrs, doc(cfg(any(feature = "es256k", feature = "k256"))))]
+pub struct Es256k<D = Sha256> {
+ context: Secp256k1<All>,
+ _digest: PhantomData<D>,
+}
+
+impl<D> Default for Es256k<D>
+where
+ D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
+{
+ fn default() -> Self {
+ Es256k {
+ context: Secp256k1::new(),
+ _digest: PhantomData,
+ }
+ }
+}
+
+impl<D> Es256k<D>
+where
+ D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
+{
+ /// Creates a new algorithm instance.
+ /// This is a (moderately) expensive operation, so if necessary, the algorithm should
+ /// be `clone()`d rather than created anew.
+ #[cfg_attr(docsrs, doc(cfg(feature = "es256k")))]
+ pub fn new(context: Secp256k1<All>) -> Self {
+ Es256k {
+ context,
+ _digest: PhantomData,
+ }
+ }
+}
+
+impl<D> Algorithm for Es256k<D>
+where
+ D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
+{
+ type SigningKey = SecretKey;
+ type VerifyingKey = PublicKey;
+ type Signature = Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("ES256K")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ let mut digest = D::default();
+ digest.update(message);
+ let message = Message::from_digest(digest.finalize().into());
+
+ self.context.sign_ecdsa(&message, signing_key)
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ let mut digest = D::default();
+ digest.update(message);
+ let message = Message::from_digest(digest.finalize().into());
+
+ // Some implementations (e.g., OpenSSL) produce high-S signatures, which
+ // are considered invalid by this implementation. Hence, we perform normalization here.
+ //
+ // See also: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
+ let mut normalized_signature = *signature;
+ normalized_signature.normalize_s();
+
+ self.context
+ .verify_ecdsa(&message, &normalized_signature, verifying_key)
+ .is_ok()
+ }
+}
+
+/// This implementation initializes a `libsecp256k1` context once on the first call to
+/// `to_verifying_key` if it was not initialized previously.
+impl SigningKey<Es256k> for SecretKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_slice(raw).map_err(From::from)
+ }
+
+ fn to_verifying_key(&self) -> PublicKey {
+ lazy_static! {
+ static ref CONTEXT: Secp256k1<All> = Secp256k1::new();
+ }
+ PublicKey::from_secret_key(&CONTEXT, self)
+ }
+
+ fn as_bytes(&self) -> SecretBytes<'_> {
+ SecretBytes::borrowed(&self[..])
+ }
+}
+
+impl VerifyingKey<Es256k> for PublicKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_slice(raw).map_err(From::from)
+ }
+
+ /// Serializes the key as a 33-byte compressed form, as per [`Self::serialize()`].
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(self.serialize().to_vec())
+ }
+}
+
+fn create_jwk<'a>(pk: &PublicKey, sk: Option<&'a SecretKey>) -> JsonWebKey<'a> {
+ let uncompressed = pk.serialize_uncompressed();
+ JsonWebKey::EllipticCurve {
+ curve: "secp256k1".into(),
+ x: Cow::Owned(uncompressed[1..=COORDINATE_SIZE].to_vec()),
+ y: Cow::Owned(uncompressed[(1 + COORDINATE_SIZE)..].to_vec()),
+ secret: sk.map(|sk| SecretBytes::borrowed(&sk.as_ref()[..])),
+ }
+}
+
+impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
+ fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
+ create_jwk(key, None)
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for PublicKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::EllipticCurve { curve, x, y, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
+ };
+ JsonWebKey::ensure_curve(curve, "secp256k1")?;
+ JsonWebKey::ensure_len("x", x, COORDINATE_SIZE)?;
+ JsonWebKey::ensure_len("y", y, COORDINATE_SIZE)?;
+
+ let mut key_bytes = [0_u8; UNCOMPRESSED_PUBLIC_KEY_SIZE];
+ key_bytes[0] = 4; // uncompressed key marker
+ key_bytes[1..=COORDINATE_SIZE].copy_from_slice(x);
+ key_bytes[(1 + COORDINATE_SIZE)..].copy_from_slice(y);
+ PublicKey::from_slice(&key_bytes[..]).map_err(JwkError::custom)
+ }
+}
+
+impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
+ fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
+ create_jwk(&key.to_verifying_key(), Some(key))
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for SecretKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::EllipticCurve { secret, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
+ };
+ let sk_bytes = secret.as_deref();
+ let sk_bytes = sk_bytes.ok_or_else(|| JwkError::NoField("d".into()))?;
+ JsonWebKey::ensure_len("d", sk_bytes, SECRET_KEY_SIZE)?;
+
+ let sk = SecretKey::from_slice(sk_bytes).map_err(JwkError::custom)?;
+ jwk.ensure_key_match(sk)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +
//! Generic traits providing uniform interfaces for a certain cryptosystem
+//! across different backends.
+
+use zeroize::Zeroize;
+
+use core::{fmt, ops};
+
+use crate::{
+ alloc::{Cow, Vec},
+ Algorithm,
+};
+
+/// Verifying key for a specific signature cryptosystem. In the case of public-key cryptosystems,
+/// this is a public key.
+///
+/// This trait provides a uniform interface for different backends / implementations
+/// of the same cryptosystem.
+pub trait VerifyingKey<T>: Sized
+where
+ T: Algorithm<VerifyingKey = Self>,
+{
+ /// Creates a key from `raw` bytes. Returns an error if the bytes do not represent
+ /// a valid key.
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self>;
+
+ /// Returns the key as raw bytes.
+ ///
+ /// Implementations should return `Cow::Borrowed` whenever possible (that is, if the bytes
+ /// are actually stored within the implementing data structure).
+ fn as_bytes(&self) -> Cow<'_, [u8]>;
+}
+
+/// Signing key for a specific signature cryptosystem. In the case of public-key cryptosystems,
+/// this is a private key.
+///
+/// This trait provides a uniform interface for different backends / implementations
+/// of the same cryptosystem.
+pub trait SigningKey<T>: Sized
+where
+ T: Algorithm<SigningKey = Self>,
+{
+ /// Creates a key from `raw` bytes. Returns an error if the bytes do not represent
+ /// a valid key.
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self>;
+
+ /// Converts a signing key to a verification key.
+ fn to_verifying_key(&self) -> T::VerifyingKey;
+
+ /// Returns the key as raw bytes.
+ ///
+ /// Implementations should return `Cow::Borrowed` whenever possible (that is, if the bytes
+ /// are actually stored within the implementing data structure).
+ fn as_bytes(&self) -> SecretBytes<'_>;
+}
+
+/// Generic container for secret bytes, which can be either owned or borrowed.
+/// If owned, bytes are zeroized on drop.
+///
+/// Comparisons on `SecretBytes` are constant-time, but other operations (e.g., deserialization)
+/// may be var-time.
+///
+/// # Serialization
+///
+/// Represented in human-readable formats (JSON, TOML, YAML, etc.) as a base64-url encoded string
+/// with no padding. For other formats (e.g., CBOR), `SecretBytes` will be serialized directly
+/// as a byte sequence.
+#[derive(Clone)]
+pub struct SecretBytes<'a>(Cow<'a, [u8]>);
+
+impl<'a> SecretBytes<'a> {
+ pub(crate) fn new(inner: Cow<'a, [u8]>) -> Self {
+ Self(inner)
+ }
+
+ /// Creates secret bytes from a borrowed slice.
+ pub fn borrowed(bytes: &'a [u8]) -> Self {
+ Self(Cow::Borrowed(bytes))
+ }
+
+ /// Creates secret bytes from an owned `Vec`.
+ pub fn owned(bytes: Vec<u8>) -> Self {
+ Self(Cow::Owned(bytes))
+ }
+}
+
+impl fmt::Debug for SecretBytes<'_> {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("SecretBytes")
+ .field("len", &self.0.len())
+ .finish()
+ }
+}
+
+impl Drop for SecretBytes<'_> {
+ fn drop(&mut self) {
+ // if bytes are borrowed, we don't need to perform any special cleaning.
+ if let Cow::Owned(bytes) = &mut self.0 {
+ Zeroize::zeroize(bytes);
+ }
+ }
+}
+
+impl ops::Deref for SecretBytes<'_> {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl AsRef<[u8]> for SecretBytes<'_> {
+ fn as_ref(&self) -> &[u8] {
+ self
+ }
+}
+
+impl PartialEq for SecretBytes<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ subtle::ConstantTimeEq::ct_eq(self.as_ref(), other.as_ref()).into()
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +
//! JWT algorithms based on HMACs.
+
+use hmac::digest::generic_array::{typenum::Unsigned, GenericArray};
+use hmac::{digest::CtOutput, Hmac, Mac as _};
+use rand_core::{CryptoRng, RngCore};
+use sha2::{
+ digest::{core_api::BlockSizeUser, OutputSizeUser},
+ Sha256, Sha384, Sha512,
+};
+use smallvec::{smallvec, SmallVec};
+use zeroize::Zeroize;
+
+use core::{fmt, num::NonZeroUsize};
+
+use crate::{
+ alg::{SecretBytes, SigningKey, StrongKey, VerifyingKey, WeakKeyError},
+ alloc::Cow,
+ jwk::{JsonWebKey, JwkError, KeyType},
+ Algorithm, AlgorithmSignature,
+};
+
+macro_rules! define_hmac_signature {
+ (
+ $(#[$($attr:meta)+])*
+ struct $name:ident<$digest:ident>;
+ ) => {
+ $(#[$($attr)+])*
+ #[derive(Clone, PartialEq, Eq)]
+ pub struct $name(CtOutput<Hmac<$digest>>);
+
+ impl fmt::Debug for $name {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.debug_tuple(stringify!($name)).field(&"_").finish()
+ }
+ }
+
+ impl AlgorithmSignature for $name {
+ const LENGTH: Option<NonZeroUsize> =
+ NonZeroUsize::new(<$digest as OutputSizeUser>::OutputSize::USIZE);
+
+ fn try_from_slice(bytes: &[u8]) -> anyhow::Result<Self> {
+ let bytes = GenericArray::clone_from_slice(bytes);
+ Ok(Self(CtOutput::new(bytes)))
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(self.0.clone().into_bytes().to_vec())
+ }
+ }
+ };
+}
+
+define_hmac_signature!(
+ /// Signature produced by the [`Hs256`] algorithm.
+ struct Hs256Signature<Sha256>;
+);
+define_hmac_signature!(
+ /// Signature produced by the [`Hs384`] algorithm.
+ struct Hs384Signature<Sha384>;
+);
+define_hmac_signature!(
+ /// Signature produced by the [`Hs512`] algorithm.
+ struct Hs512Signature<Sha512>;
+);
+
+macro_rules! define_hmac_key {
+ (
+ $(#[$($attr:meta)+])*
+ struct $name:ident<$digest:ident>([u8; $buffer_size:expr]);
+ ) => {
+ $(#[$($attr)+])*
+ #[derive(Clone, Zeroize)]
+ #[zeroize(drop)]
+ pub struct $name(pub(crate) SmallVec<[u8; $buffer_size]>);
+
+ impl fmt::Debug for $name {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.debug_tuple(stringify!($name)).field(&"_").finish()
+ }
+ }
+
+ impl $name {
+ /// Generates a random key using a cryptographically secure RNG.
+ pub fn generate<R: CryptoRng + RngCore>(rng: &mut R) -> StrongKey<Self> {
+ let mut key = $name(smallvec![0; <$digest as BlockSizeUser>::BlockSize::to_usize()]);
+ rng.fill_bytes(&mut key.0);
+ StrongKey(key)
+ }
+
+ /// Creates a key from the specified `bytes`.
+ pub fn new(bytes: impl AsRef<[u8]>) -> Self {
+ Self(bytes.as_ref().into())
+ }
+
+ /// Computes HMAC with this key and the specified `message`.
+ fn hmac(&self, message: impl AsRef<[u8]>) -> CtOutput<Hmac<$digest>> {
+ let mut hmac = Hmac::<$digest>::new_from_slice(&self.0)
+ .expect("HMACs work with any key size");
+ hmac.update(message.as_ref());
+ hmac.finalize()
+ }
+ }
+
+ impl From<&[u8]> for $name {
+ fn from(bytes: &[u8]) -> Self {
+ $name(bytes.into())
+ }
+ }
+
+ impl AsRef<[u8]> for $name {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+ }
+
+ impl AsMut<[u8]> for $name {
+ fn as_mut(&mut self) -> &mut [u8] {
+ &mut self.0
+ }
+ }
+
+ impl TryFrom<$name> for StrongKey<$name> {
+ type Error = WeakKeyError<$name>;
+
+ fn try_from(value: $name) -> Result<Self, Self::Error> {
+ if value.0.len() >= <$digest as BlockSizeUser>::BlockSize::to_usize() {
+ Ok(StrongKey(value))
+ } else {
+ Err(WeakKeyError(value))
+ }
+ }
+ }
+ };
+}
+
+define_hmac_key! {
+ /// Signing / verifying key for `HS256` algorithm. Zeroed on drop.
+ struct Hs256Key<Sha256>([u8; 64]);
+}
+define_hmac_key! {
+ /// Signing / verifying key for `HS384` algorithm. Zeroed on drop.
+ struct Hs384Key<Sha384>([u8; 128]);
+}
+define_hmac_key! {
+ /// Signing / verifying key for `HS512` algorithm. Zeroed on drop.
+ struct Hs512Key<Sha512>([u8; 128]);
+}
+
+/// `HS256` signing algorithm.
+///
+/// See [RFC 7518] for the algorithm specification.
+///
+/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-3.2
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+pub struct Hs256;
+
+impl Algorithm for Hs256 {
+ type SigningKey = Hs256Key;
+ type VerifyingKey = Hs256Key;
+ type Signature = Hs256Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("HS256")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ Hs256Signature(signing_key.hmac(message))
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ verifying_key.hmac(message) == signature.0
+ }
+}
+
+/// `HS384` signing algorithm.
+///
+/// See [RFC 7518] for the algorithm specification.
+///
+/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-3.2
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+pub struct Hs384;
+
+impl Algorithm for Hs384 {
+ type SigningKey = Hs384Key;
+ type VerifyingKey = Hs384Key;
+ type Signature = Hs384Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("HS384")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ Hs384Signature(signing_key.hmac(message))
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ verifying_key.hmac(message) == signature.0
+ }
+}
+
+/// `HS512` signing algorithm.
+///
+/// See [RFC 7518] for the algorithm specification.
+///
+/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-3.2
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+pub struct Hs512;
+
+impl Algorithm for Hs512 {
+ type SigningKey = Hs512Key;
+ type VerifyingKey = Hs512Key;
+ type Signature = Hs512Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("HS512")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ Hs512Signature(signing_key.hmac(message))
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ verifying_key.hmac(message) == signature.0
+ }
+}
+
+macro_rules! impl_key_traits {
+ ($key:ident<$alg:ident>) => {
+ impl SigningKey<$alg> for $key {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Ok(Self::from(raw))
+ }
+
+ fn to_verifying_key(&self) -> Self {
+ self.clone()
+ }
+
+ fn as_bytes(&self) -> SecretBytes<'_> {
+ SecretBytes::borrowed(self.as_ref())
+ }
+ }
+
+ impl VerifyingKey<$alg> for $key {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Ok(Self::from(raw))
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(self.as_ref())
+ }
+ }
+
+ impl<'a> From<&'a $key> for JsonWebKey<'a> {
+ fn from(key: &'a $key) -> JsonWebKey<'a> {
+ JsonWebKey::Symmetric {
+ secret: SecretBytes::borrowed(key.as_ref()),
+ }
+ }
+ }
+
+ impl TryFrom<&JsonWebKey<'_>> for $key {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ match jwk {
+ JsonWebKey::Symmetric { secret } => Ok(Self::new(secret)),
+ _ => Err(JwkError::key_type(jwk, KeyType::Symmetric)),
+ }
+ }
+ }
+ };
+}
+
+impl_key_traits!(Hs256Key<Hs256>);
+impl_key_traits!(Hs384Key<Hs384>);
+impl_key_traits!(Hs512Key<Hs512>);
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +
//! `ES256` algorithm implementation using the `p256` crate.
+
+use p256::ecdsa::{
+ signature::{DigestSigner, DigestVerifier},
+ Signature, SigningKey, VerifyingKey,
+};
+use sha2::{Digest, Sha256};
+
+use core::num::NonZeroUsize;
+
+use crate::{
+ alg::{self, SecretBytes},
+ alloc::Cow,
+ jwk::{JsonWebKey, JwkError, KeyType},
+ Algorithm, AlgorithmSignature,
+};
+
+impl AlgorithmSignature for Signature {
+ const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(64);
+
+ fn try_from_slice(slice: &[u8]) -> anyhow::Result<Self> {
+ Signature::try_from(slice).map_err(|err| anyhow::anyhow!(err))
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(self.to_bytes().to_vec())
+ }
+}
+
+/// `ES256` signing algorithm. Implements elliptic curve digital signatures (ECDSA)
+/// on the secp256r1 curve (aka P-256).
+#[derive(Debug, Default)]
+#[cfg_attr(docsrs, doc(cfg(feature = "p256")))]
+pub struct Es256;
+
+impl Algorithm for Es256 {
+ type SigningKey = SigningKey;
+ type VerifyingKey = VerifyingKey;
+ type Signature = Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed("ES256")
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ let mut digest = Sha256::default();
+ digest.update(message);
+ signing_key.sign_digest(digest)
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ let mut digest = Sha256::default();
+ digest.update(message);
+
+ verifying_key.verify_digest(digest, signature).is_ok()
+ }
+}
+
+impl alg::SigningKey<Es256> for SigningKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_slice(raw).map_err(|err| anyhow::anyhow!(err))
+ }
+
+ fn to_verifying_key(&self) -> VerifyingKey {
+ *self.verifying_key()
+ }
+
+ fn as_bytes(&self) -> SecretBytes<'_> {
+ SecretBytes::owned(self.to_bytes().to_vec())
+ }
+}
+
+impl alg::VerifyingKey<Es256> for VerifyingKey {
+ fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
+ Self::from_sec1_bytes(raw).map_err(|err| anyhow::anyhow!(err))
+ }
+
+ /// Serializes the key as a 33-byte compressed form.
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ let bytes = self.to_encoded_point(true).as_bytes().to_vec();
+ Cow::Owned(bytes)
+ }
+}
+
+fn create_jwk<'a>(pk: &VerifyingKey, sk: Option<&'a SigningKey>) -> JsonWebKey<'a> {
+ let uncompressed = pk.to_encoded_point(false);
+ JsonWebKey::EllipticCurve {
+ curve: "P-256".into(),
+ x: Cow::Owned(uncompressed.x().expect("x coord").to_vec()),
+ y: Cow::Owned(uncompressed.y().expect("y coord").to_vec()),
+ secret: sk.map(|sk| SecretBytes::owned(sk.to_bytes().to_vec())),
+ }
+}
+
+impl<'a> From<&'a VerifyingKey> for JsonWebKey<'a> {
+ fn from(key: &'a VerifyingKey) -> JsonWebKey<'a> {
+ create_jwk(key, None)
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for VerifyingKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ const COORDINATE_SIZE: usize = 32;
+
+ let JsonWebKey::EllipticCurve { curve, x, y, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
+ };
+ JsonWebKey::ensure_curve(curve, "P-256")?;
+ JsonWebKey::ensure_len("x", x, COORDINATE_SIZE)?;
+ JsonWebKey::ensure_len("y", y, COORDINATE_SIZE)?;
+
+ let mut key_bytes = [0_u8; 2 * COORDINATE_SIZE + 1];
+ key_bytes[0] = 4; // uncompressed key marker
+ key_bytes[1..=COORDINATE_SIZE].copy_from_slice(x);
+ key_bytes[(1 + COORDINATE_SIZE)..].copy_from_slice(y);
+ VerifyingKey::from_sec1_bytes(&key_bytes[..])
+ .map_err(|err| JwkError::custom(anyhow::anyhow!(err)))
+ }
+}
+
+impl<'a> From<&'a SigningKey> for JsonWebKey<'a> {
+ fn from(key: &'a SigningKey) -> JsonWebKey<'a> {
+ create_jwk(key.verifying_key(), Some(key))
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for SigningKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::EllipticCurve { secret, .. } = jwk else {
+ return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
+ };
+ let sk_bytes = secret.as_deref();
+ let sk_bytes = sk_bytes.ok_or_else(|| JwkError::NoField("d".into()))?;
+ JsonWebKey::ensure_len("d", sk_bytes, 32)?;
+
+ let sk =
+ Self::from_slice(sk_bytes).map_err(|err| JwkError::custom(anyhow::anyhow!(err)))?;
+ jwk.ensure_key_match(sk)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +
//! RSA-based JWT algorithms: `RS*` and `PS*`.
+
+pub use rsa::{errors::Error as RsaError, RsaPrivateKey, RsaPublicKey};
+
+use rand_core::{CryptoRng, RngCore};
+use rsa::{
+ traits::{PrivateKeyParts, PublicKeyParts},
+ BigUint, Pkcs1v15Sign, Pss,
+};
+use sha2::{Digest, Sha256, Sha384, Sha512};
+
+use core::{fmt, str::FromStr};
+
+use crate::{
+ alg::{SecretBytes, StrongKey, WeakKeyError},
+ alloc::{Box, Cow, String, ToOwned, Vec},
+ jwk::{JsonWebKey, JwkError, KeyType, RsaPrimeFactor, RsaPrivateParts},
+ Algorithm, AlgorithmSignature,
+};
+
+/// RSA signature.
+#[derive(Debug)]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub struct RsaSignature(Vec<u8>);
+
+impl AlgorithmSignature for RsaSignature {
+ fn try_from_slice(bytes: &[u8]) -> anyhow::Result<Self> {
+ Ok(RsaSignature(bytes.to_vec()))
+ }
+
+ fn as_bytes(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(&self.0)
+ }
+}
+
+/// RSA hash algorithm.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum HashAlg {
+ Sha256,
+ Sha384,
+ Sha512,
+}
+
+impl HashAlg {
+ fn digest(self, message: &[u8]) -> Box<[u8]> {
+ match self {
+ Self::Sha256 => {
+ let digest: [u8; 32] = *(Sha256::digest(message).as_ref());
+ Box::new(digest)
+ }
+ Self::Sha384 => {
+ let mut digest = [0_u8; 48];
+ digest.copy_from_slice(Sha384::digest(message).as_ref());
+ Box::new(digest)
+ }
+ Self::Sha512 => {
+ let mut digest = [0_u8; 64];
+ digest.copy_from_slice(Sha512::digest(message).as_ref());
+ Box::new(digest)
+ }
+ }
+ }
+}
+
+/// RSA padding algorithm.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+enum Padding {
+ Pkcs1v15,
+ Pss,
+}
+
+#[derive(Debug)]
+enum PaddingScheme {
+ Pkcs1v15(Pkcs1v15Sign),
+ Pss(Pss),
+}
+
+/// Bit length of an RSA key modulus (aka RSA key length).
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub enum ModulusBits {
+ /// 2048 bits. This is the minimum recommended key length as of 2020.
+ TwoKibibytes,
+ /// 3072 bits.
+ ThreeKibibytes,
+ /// 4096 bits.
+ FourKibibytes,
+}
+
+impl ModulusBits {
+ /// Converts this length to the numeric value.
+ pub fn bits(self) -> usize {
+ match self {
+ Self::TwoKibibytes => 2_048,
+ Self::ThreeKibibytes => 3_072,
+ Self::FourKibibytes => 4_096,
+ }
+ }
+
+ fn is_valid_bits(bits: usize) -> bool {
+ matches!(bits, 2_048 | 3_072 | 4_096)
+ }
+}
+
+impl TryFrom<usize> for ModulusBits {
+ type Error = ModulusBitsError;
+
+ fn try_from(value: usize) -> Result<Self, Self::Error> {
+ match value {
+ 2_048 => Ok(Self::TwoKibibytes),
+ 3_072 => Ok(Self::ThreeKibibytes),
+ 4_096 => Ok(Self::FourKibibytes),
+ _ => Err(ModulusBitsError(())),
+ }
+ }
+}
+
+/// Error type returned when a conversion of an integer into `ModulusBits` fails.
+#[derive(Debug)]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub struct ModulusBitsError(());
+
+impl fmt::Display for ModulusBitsError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(
+ "Unsupported bit length of RSA modulus; only lengths 2048, 3072 and 4096 \
+ are supported.",
+ )
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ModulusBitsError {}
+
+/// Integrity algorithm using [RSA] digital signatures.
+///
+/// Depending on the variation, the algorithm employs PKCS#1 v1.5 or PSS padding and
+/// one of the hash functions from the SHA-2 family: SHA-256, SHA-384, or SHA-512.
+/// See [RFC 7518] for more details. Depending on the chosen parameters,
+/// the name of the algorithm is one of `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`:
+///
+/// - `R` / `P` denote the padding scheme: PKCS#1 v1.5 for `R`, PSS for `P`
+/// - `256` / `384` / `512` denote the hash function
+///
+/// The length of RSA keys is not unequivocally specified by the algorithm; nevertheless,
+/// it **MUST** be at least 2048 bits as per RFC 7518. To minimize risks of misconfiguration,
+/// use [`StrongAlg`](super::StrongAlg) wrapper around `Rsa`:
+///
+/// ```
+/// # use jwt_compact::alg::{StrongAlg, Rsa};
+/// const ALG: StrongAlg<Rsa> = StrongAlg(Rsa::rs256());
+/// // `ALG` will not support RSA keys with unsecure lengths by design!
+/// ```
+///
+/// [RSA]: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
+/// [RFC 7518]: https://www.rfc-editor.org/rfc/rfc7518.html
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub struct Rsa {
+ hash_alg: HashAlg,
+ padding_alg: Padding,
+}
+
+impl Algorithm for Rsa {
+ type SigningKey = RsaPrivateKey;
+ type VerifyingKey = RsaPublicKey;
+ type Signature = RsaSignature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed(self.alg_name())
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ let digest = self.hash_alg.digest(message);
+ let signing_result = match self.padding_scheme() {
+ PaddingScheme::Pkcs1v15(padding) => {
+ signing_key.sign_with_rng(&mut rand_core::OsRng, padding, &digest)
+ }
+ PaddingScheme::Pss(padding) => {
+ signing_key.sign_with_rng(&mut rand_core::OsRng, padding, &digest)
+ }
+ };
+ RsaSignature(signing_result.expect("Unexpected RSA signature failure"))
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ let digest = self.hash_alg.digest(message);
+ let verify_result = match self.padding_scheme() {
+ PaddingScheme::Pkcs1v15(padding) => {
+ verifying_key.verify(padding, &digest, &signature.0)
+ }
+ PaddingScheme::Pss(padding) => verifying_key.verify(padding, &digest, &signature.0),
+ };
+ verify_result.is_ok()
+ }
+}
+
+impl Rsa {
+ const fn new(hash_alg: HashAlg, padding_alg: Padding) -> Self {
+ Rsa {
+ hash_alg,
+ padding_alg,
+ }
+ }
+
+ /// RSA with SHA-256 and PKCS#1 v1.5 padding.
+ pub const fn rs256() -> Rsa {
+ Rsa::new(HashAlg::Sha256, Padding::Pkcs1v15)
+ }
+
+ /// RSA with SHA-384 and PKCS#1 v1.5 padding.
+ pub const fn rs384() -> Rsa {
+ Rsa::new(HashAlg::Sha384, Padding::Pkcs1v15)
+ }
+
+ /// RSA with SHA-512 and PKCS#1 v1.5 padding.
+ pub const fn rs512() -> Rsa {
+ Rsa::new(HashAlg::Sha512, Padding::Pkcs1v15)
+ }
+
+ /// RSA with SHA-256 and PSS padding.
+ pub const fn ps256() -> Rsa {
+ Rsa::new(HashAlg::Sha256, Padding::Pss)
+ }
+
+ /// RSA with SHA-384 and PSS padding.
+ pub const fn ps384() -> Rsa {
+ Rsa::new(HashAlg::Sha384, Padding::Pss)
+ }
+
+ /// RSA with SHA-512 and PSS padding.
+ pub const fn ps512() -> Rsa {
+ Rsa::new(HashAlg::Sha512, Padding::Pss)
+ }
+
+ /// RSA based on the specified algorithm name.
+ ///
+ /// # Panics
+ ///
+ /// - Panics if the name is not one of the six RSA-based JWS algorithms. Prefer using
+ /// the [`FromStr`] trait if the conversion is potentially fallible.
+ pub fn with_name(name: &str) -> Self {
+ name.parse().unwrap()
+ }
+
+ fn padding_scheme(self) -> PaddingScheme {
+ match self.padding_alg {
+ Padding::Pkcs1v15 => PaddingScheme::Pkcs1v15(match self.hash_alg {
+ HashAlg::Sha256 => Pkcs1v15Sign::new::<Sha256>(),
+ HashAlg::Sha384 => Pkcs1v15Sign::new::<Sha384>(),
+ HashAlg::Sha512 => Pkcs1v15Sign::new::<Sha512>(),
+ }),
+ Padding::Pss => {
+ // The salt length needs to be set to the size of hash function output;
+ // see https://www.rfc-editor.org/rfc/rfc7518.html#section-3.5.
+ PaddingScheme::Pss(match self.hash_alg {
+ HashAlg::Sha256 => Pss::new_with_salt::<Sha256>(Sha256::output_size()),
+ HashAlg::Sha384 => Pss::new_with_salt::<Sha384>(Sha384::output_size()),
+ HashAlg::Sha512 => Pss::new_with_salt::<Sha512>(Sha512::output_size()),
+ })
+ }
+ }
+ }
+
+ fn alg_name(self) -> &'static str {
+ match (self.padding_alg, self.hash_alg) {
+ (Padding::Pkcs1v15, HashAlg::Sha256) => "RS256",
+ (Padding::Pkcs1v15, HashAlg::Sha384) => "RS384",
+ (Padding::Pkcs1v15, HashAlg::Sha512) => "RS512",
+ (Padding::Pss, HashAlg::Sha256) => "PS256",
+ (Padding::Pss, HashAlg::Sha384) => "PS384",
+ (Padding::Pss, HashAlg::Sha512) => "PS512",
+ }
+ }
+
+ /// Generates a new key pair with the specified modulus bit length (aka key length).
+ pub fn generate<R: CryptoRng + RngCore>(
+ rng: &mut R,
+ modulus_bits: ModulusBits,
+ ) -> rsa::errors::Result<(StrongKey<RsaPrivateKey>, StrongKey<RsaPublicKey>)> {
+ let signing_key = RsaPrivateKey::new(rng, modulus_bits.bits())?;
+ let verifying_key = signing_key.to_public_key();
+ Ok((StrongKey(signing_key), StrongKey(verifying_key)))
+ }
+}
+
+impl FromStr for Rsa {
+ type Err = RsaParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "RS256" => Self::rs256(),
+ "RS384" => Self::rs384(),
+ "RS512" => Self::rs512(),
+ "PS256" => Self::ps256(),
+ "PS384" => Self::ps384(),
+ "PS512" => Self::ps512(),
+ _ => return Err(RsaParseError(s.to_owned())),
+ })
+ }
+}
+
+/// Errors that can occur when parsing an [`Rsa`] algorithm from a string.
+#[derive(Debug)]
+#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
+pub struct RsaParseError(String);
+
+impl fmt::Display for RsaParseError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "Invalid RSA algorithm name: {}", self.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RsaParseError {}
+
+impl StrongKey<RsaPrivateKey> {
+ /// Converts this private key to a public key.
+ pub fn to_public_key(&self) -> StrongKey<RsaPublicKey> {
+ StrongKey(self.0.to_public_key())
+ }
+}
+
+impl TryFrom<RsaPrivateKey> for StrongKey<RsaPrivateKey> {
+ type Error = WeakKeyError<RsaPrivateKey>;
+
+ fn try_from(key: RsaPrivateKey) -> Result<Self, Self::Error> {
+ if ModulusBits::is_valid_bits(key.n().bits()) {
+ Ok(StrongKey(key))
+ } else {
+ Err(WeakKeyError(key))
+ }
+ }
+}
+
+impl TryFrom<RsaPublicKey> for StrongKey<RsaPublicKey> {
+ type Error = WeakKeyError<RsaPublicKey>;
+
+ fn try_from(key: RsaPublicKey) -> Result<Self, Self::Error> {
+ if ModulusBits::is_valid_bits(key.n().bits()) {
+ Ok(StrongKey(key))
+ } else {
+ Err(WeakKeyError(key))
+ }
+ }
+}
+
+impl<'a> From<&'a RsaPublicKey> for JsonWebKey<'a> {
+ fn from(key: &'a RsaPublicKey) -> JsonWebKey<'a> {
+ JsonWebKey::Rsa {
+ modulus: Cow::Owned(key.n().to_bytes_be()),
+ public_exponent: Cow::Owned(key.e().to_bytes_be()),
+ private_parts: None,
+ }
+ }
+}
+
+impl TryFrom<&JsonWebKey<'_>> for RsaPublicKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::Rsa {
+ modulus,
+ public_exponent,
+ ..
+ } = jwk
+ else {
+ return Err(JwkError::key_type(jwk, KeyType::Rsa));
+ };
+
+ let e = BigUint::from_bytes_be(public_exponent);
+ let n = BigUint::from_bytes_be(modulus);
+ Self::new(n, e).map_err(|err| JwkError::custom(anyhow::anyhow!(err)))
+ }
+}
+
+/// ⚠ **Warning.** Contrary to [RFC 7518], this implementation does not set `dp`, `dq`, and `qi`
+/// fields in the JWK root object, as well as `d` and `t` fields for additional factors
+/// (i.e., in the `oth` array).
+///
+/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-6.3.2
+impl<'a> From<&'a RsaPrivateKey> for JsonWebKey<'a> {
+ fn from(key: &'a RsaPrivateKey) -> JsonWebKey<'a> {
+ const MSG: &str = "RsaPrivateKey must have at least 2 prime factors";
+
+ let p = key.primes().get(0).expect(MSG);
+ let q = key.primes().get(1).expect(MSG);
+
+ let private_parts = RsaPrivateParts {
+ private_exponent: SecretBytes::owned(key.d().to_bytes_be()),
+ prime_factor_p: SecretBytes::owned(p.to_bytes_be()),
+ prime_factor_q: SecretBytes::owned(q.to_bytes_be()),
+ p_crt_exponent: None,
+ q_crt_exponent: None,
+ q_crt_coefficient: None,
+ other_prime_factors: key.primes()[2..]
+ .iter()
+ .map(|factor| RsaPrimeFactor {
+ factor: SecretBytes::owned(factor.to_bytes_be()),
+ crt_exponent: None,
+ crt_coefficient: None,
+ })
+ .collect(),
+ };
+
+ JsonWebKey::Rsa {
+ modulus: Cow::Owned(key.n().to_bytes_be()),
+ public_exponent: Cow::Owned(key.e().to_bytes_be()),
+ private_parts: Some(private_parts),
+ }
+ }
+}
+
+/// ⚠ **Warning.** Contrary to [RFC 7518] (at least, in spirit), this conversion ignores
+/// `dp`, `dq`, and `qi` fields from JWK, as well as `d` and `t` fields for additional factors.
+///
+/// [RFC 7518]: https://www.rfc-editor.org/rfc/rfc7518.html
+impl TryFrom<&JsonWebKey<'_>> for RsaPrivateKey {
+ type Error = JwkError;
+
+ fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
+ let JsonWebKey::Rsa {
+ modulus,
+ public_exponent,
+ private_parts,
+ } = jwk
+ else {
+ return Err(JwkError::key_type(jwk, KeyType::Rsa));
+ };
+
+ let RsaPrivateParts {
+ private_exponent: d,
+ prime_factor_p,
+ prime_factor_q,
+ other_prime_factors,
+ ..
+ } = private_parts
+ .as_ref()
+ .ok_or_else(|| JwkError::NoField("d".into()))?;
+
+ let e = BigUint::from_bytes_be(public_exponent);
+ let n = BigUint::from_bytes_be(modulus);
+ let d = BigUint::from_bytes_be(d);
+
+ let mut factors = Vec::with_capacity(2 + other_prime_factors.len());
+ factors.push(BigUint::from_bytes_be(prime_factor_p));
+ factors.push(BigUint::from_bytes_be(prime_factor_q));
+ factors.extend(
+ other_prime_factors
+ .iter()
+ .map(|prime| BigUint::from_bytes_be(&prime.factor)),
+ );
+
+ let key = Self::from_components(n, e, d, factors);
+ let key = key.map_err(|err| JwkError::custom(anyhow::anyhow!(err)))?;
+ key.validate()
+ .map_err(|err| JwkError::custom(anyhow::anyhow!(err)))?;
+ Ok(key)
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +
use chrono::{DateTime, Duration, Utc};
+use serde::{Deserialize, Serialize};
+
+use crate::{Claim, ValidationError};
+
+/// Time-related options for token creation and validation.
+///
+/// If the `clock` crate feature is on (and it's on by default), `TimeOptions` can be created
+/// using the `Default` impl or [`Self::from_leeway()`]. If the feature is off,
+/// you can still create options using [a generic constructor](Self::new).
+///
+/// # Examples
+///
+/// ```
+/// # use chrono::{Duration, Utc};
+/// # use jwt_compact::TimeOptions;
+/// // Default options.
+/// let default_options = TimeOptions::default();
+/// let options_with_custom_leeway =
+/// TimeOptions::from_leeway(Duration::seconds(5));
+/// // Options that have a fixed time. Can be useful for testing.
+/// let clock_time = Utc::now();
+/// let options_with_stopped_clock =
+/// TimeOptions::new(Duration::seconds(10), move || clock_time);
+/// ```
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+pub struct TimeOptions<F = fn() -> DateTime<Utc>> {
+ /// Leeway to use during validation.
+ pub leeway: Duration,
+ /// Source of the current timestamps.
+ pub clock_fn: F,
+}
+
+impl<F: Fn() -> DateTime<Utc>> TimeOptions<F> {
+ /// Creates options based on the specified time leeway and clock function.
+ pub fn new(leeway: Duration, clock_fn: F) -> Self {
+ Self { leeway, clock_fn }
+ }
+}
+
+impl TimeOptions {
+ /// Creates options based on the specified time leeway. The clock source is [`Utc::now()`].
+ #[cfg(feature = "clock")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "clock")))]
+ pub fn from_leeway(leeway: Duration) -> Self {
+ Self {
+ leeway,
+ clock_fn: Utc::now,
+ }
+ }
+}
+
+/// Creates options with a default leeway (60 seconds) and the [`Utc::now()`] clock.
+///
+/// This impl is supported on **crate feature `clock`** only.
+#[cfg(feature = "clock")]
+impl Default for TimeOptions {
+ fn default() -> Self {
+ Self::from_leeway(Duration::seconds(60))
+ }
+}
+
+/// A structure with no fields that can be used as a type parameter to `Claims`.
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct Empty {}
+
+/// Claims encoded in a token.
+///
+/// Claims are comprised of a "standard" part (`exp`, `nbf` and `iat` claims as per [JWT spec]),
+/// and custom fields. `iss`, `sub` and `aud` claims are not in the standard part
+/// due to a variety of data types they can be reasonably represented by.
+///
+/// [JWT spec]: https://tools.ietf.org/html/rfc7519#section-4.1
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[non_exhaustive]
+pub struct Claims<T> {
+ /// Expiration time of the token.
+ #[serde(
+ rename = "exp",
+ default,
+ skip_serializing_if = "Option::is_none",
+ with = "self::serde_timestamp"
+ )]
+ pub expiration: Option<DateTime<Utc>>,
+
+ /// Minimum time at which token is valid.
+ #[serde(
+ rename = "nbf",
+ default,
+ skip_serializing_if = "Option::is_none",
+ with = "self::serde_timestamp"
+ )]
+ pub not_before: Option<DateTime<Utc>>,
+
+ /// Time of token issuance.
+ #[serde(
+ rename = "iat",
+ default,
+ skip_serializing_if = "Option::is_none",
+ with = "self::serde_timestamp"
+ )]
+ pub issued_at: Option<DateTime<Utc>>,
+
+ /// Custom claims.
+ #[serde(flatten)]
+ pub custom: T,
+}
+
+impl Claims<Empty> {
+ /// Creates an empty claims instance.
+ pub fn empty() -> Self {
+ Self {
+ expiration: None,
+ not_before: None,
+ issued_at: None,
+ custom: Empty {},
+ }
+ }
+}
+
+impl<T> Claims<T> {
+ /// Creates a new instance with the provided custom claims.
+ pub fn new(custom_claims: T) -> Self {
+ Self {
+ expiration: None,
+ not_before: None,
+ issued_at: None,
+ custom: custom_claims,
+ }
+ }
+
+ /// Sets the `expiration` claim so that the token has the specified `duration`.
+ /// The current timestamp is taken from `options`.
+ #[must_use]
+ pub fn set_duration<F>(self, options: &TimeOptions<F>, duration: Duration) -> Self
+ where
+ F: Fn() -> DateTime<Utc>,
+ {
+ Self {
+ expiration: Some((options.clock_fn)() + duration),
+ ..self
+ }
+ }
+
+ /// Atomically sets `issued_at` and `expiration` claims: first to the current time
+ /// (taken from `options`), and the second to match the specified `duration` of the token.
+ #[must_use]
+ pub fn set_duration_and_issuance<F>(self, options: &TimeOptions<F>, duration: Duration) -> Self
+ where
+ F: Fn() -> DateTime<Utc>,
+ {
+ let issued_at = (options.clock_fn)();
+ Self {
+ expiration: Some(issued_at + duration),
+ issued_at: Some(issued_at),
+ ..self
+ }
+ }
+
+ /// Sets the `nbf` claim.
+ #[must_use]
+ pub fn set_not_before(self, moment: DateTime<Utc>) -> Self {
+ Self {
+ not_before: Some(moment),
+ ..self
+ }
+ }
+
+ /// Validates the expiration claim.
+ ///
+ /// This method will return an error if the claims do not feature an expiration time,
+ /// or if it is in the past (subject to the provided `options`).
+ pub fn validate_expiration<F>(&self, options: &TimeOptions<F>) -> Result<&Self, ValidationError>
+ where
+ F: Fn() -> DateTime<Utc>,
+ {
+ self.expiration.map_or(
+ Err(ValidationError::NoClaim(Claim::Expiration)),
+ |expiration| {
+ let expiration_with_leeway = expiration
+ .checked_add_signed(options.leeway)
+ .unwrap_or(DateTime::<Utc>::MAX_UTC);
+ if (options.clock_fn)() > expiration_with_leeway {
+ Err(ValidationError::Expired)
+ } else {
+ Ok(self)
+ }
+ },
+ )
+ }
+
+ /// Validates the maturity time (`nbf` claim).
+ ///
+ /// This method will return an error if the claims do not feature a maturity time,
+ /// or if it is in the future (subject to the provided `options`).
+ pub fn validate_maturity<F>(&self, options: &TimeOptions<F>) -> Result<&Self, ValidationError>
+ where
+ F: Fn() -> DateTime<Utc>,
+ {
+ self.not_before.map_or(
+ Err(ValidationError::NoClaim(Claim::NotBefore)),
+ |not_before| {
+ if (options.clock_fn)() < not_before - options.leeway {
+ Err(ValidationError::NotMature)
+ } else {
+ Ok(self)
+ }
+ },
+ )
+ }
+}
+
+mod serde_timestamp {
+ use chrono::{offset::TimeZone, DateTime, Utc};
+ use serde::{
+ de::{Error as DeError, Visitor},
+ Deserializer, Serializer,
+ };
+
+ use core::fmt;
+
+ struct TimestampVisitor;
+
+ impl<'de> Visitor<'de> for TimestampVisitor {
+ type Value = DateTime<Utc>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("UTC timestamp")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: DeError,
+ {
+ Utc.timestamp_opt(value, 0)
+ .single()
+ .ok_or_else(|| E::custom("UTC timestamp overflow"))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: DeError,
+ {
+ let value = i64::try_from(value).map_err(DeError::custom)?;
+ Utc.timestamp_opt(value, 0)
+ .single()
+ .ok_or_else(|| E::custom("UTC timestamp overflow"))
+ }
+
+ #[allow(clippy::cast_possible_truncation)]
+ // ^ If truncation occurs, the `timestamp_opt()` won't return a single value anyway
+ fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
+ where
+ E: DeError,
+ {
+ Utc.timestamp_opt(value as i64, 0)
+ .single()
+ .ok_or_else(|| E::custom("UTC timestamp overflow"))
+ }
+ }
+
+ pub fn serialize<S: Serializer>(
+ time: &Option<DateTime<Utc>>,
+ serializer: S,
+ ) -> Result<S::Ok, S::Error> {
+ // `unwrap` is safe due to `skip_serializing_if` option
+ serializer.serialize_i64(time.unwrap().timestamp())
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(
+ deserializer: D,
+ ) -> Result<Option<DateTime<Utc>>, D::Error> {
+ deserializer.deserialize_i64(TimestampVisitor).map(Some)
+ }
+}
+
+#[cfg(all(test, feature = "clock"))]
+mod tests {
+ use super::*;
+ use assert_matches::assert_matches;
+ use chrono::TimeZone;
+
+ #[test]
+ fn empty_claims_can_be_serialized() {
+ let mut claims = Claims::empty();
+ assert!(serde_json::to_string(&claims).is_ok());
+ claims.expiration = Some(Utc::now());
+ assert!(serde_json::to_string(&claims).is_ok());
+ claims.not_before = Some(Utc::now());
+ assert!(serde_json::to_string(&claims).is_ok());
+ }
+
+ #[test]
+ #[cfg(feature = "ciborium")]
+ fn empty_claims_can_be_serialized_to_cbor() {
+ let mut claims = Claims::empty();
+ assert!(ciborium::into_writer(&claims, &mut vec![]).is_ok());
+ claims.expiration = Some(Utc::now());
+ assert!(ciborium::into_writer(&claims, &mut vec![]).is_ok());
+ claims.not_before = Some(Utc::now());
+ assert!(ciborium::into_writer(&claims, &mut vec![]).is_ok());
+ }
+
+ #[test]
+ fn expired_claim() {
+ let mut claims = Claims::empty();
+ let time_options = TimeOptions::default();
+ assert_matches!(
+ claims.validate_expiration(&time_options).unwrap_err(),
+ ValidationError::NoClaim(Claim::Expiration)
+ );
+
+ claims.expiration = Some(DateTime::<Utc>::MAX_UTC);
+ assert!(claims.validate_expiration(&time_options).is_ok());
+
+ claims.expiration = Some(Utc::now() - Duration::hours(1));
+ assert_matches!(
+ claims.validate_expiration(&time_options).unwrap_err(),
+ ValidationError::Expired
+ );
+
+ claims.expiration = Some(Utc::now() - Duration::seconds(10));
+ // With the default leeway, this claim is still valid.
+ assert!(claims.validate_expiration(&time_options).is_ok());
+ // If we set leeway lower, then the claim will be considered expired.
+ assert_matches!(
+ claims
+ .validate_expiration(&TimeOptions::from_leeway(Duration::seconds(5)))
+ .unwrap_err(),
+ ValidationError::Expired
+ );
+ // Same if we set the current time in the past.
+ let expiration = claims.expiration.unwrap();
+ assert!(claims
+ .validate_expiration(&TimeOptions::new(Duration::seconds(3), move || {
+ expiration
+ }))
+ .is_ok());
+ }
+
+ #[test]
+ fn immature_claim() {
+ let mut claims = Claims::empty();
+ let time_options = TimeOptions::default();
+ assert_matches!(
+ claims.validate_maturity(&time_options).unwrap_err(),
+ ValidationError::NoClaim(Claim::NotBefore)
+ );
+
+ claims.not_before = Some(Utc::now() + Duration::hours(1));
+ assert_matches!(
+ claims.validate_maturity(&time_options).unwrap_err(),
+ ValidationError::NotMature
+ );
+
+ claims.not_before = Some(Utc::now() + Duration::seconds(10));
+ // With the default leeway, this claim is still valid.
+ assert!(claims.validate_maturity(&time_options).is_ok());
+ // If we set leeway lower, then the claim will be considered expired.
+ assert_matches!(
+ claims
+ .validate_maturity(&TimeOptions::from_leeway(Duration::seconds(5)))
+ .unwrap_err(),
+ ValidationError::NotMature
+ );
+ }
+ #[test]
+ fn float_timestamp() {
+ let claims = "{\"exp\": 1.691203462e+9}";
+ let claims: Claims<Empty> = serde_json::from_str(claims).unwrap();
+ let timestamp = Utc.timestamp_opt(1_691_203_462, 0).single().unwrap();
+ assert_eq!(claims.expiration, Some(timestamp));
+ }
+
+ #[test]
+ fn float_timestamp_errors() {
+ let invalid_claims = ["{\"exp\": 1e20}", "{\"exp\": -1e20}"];
+ for claims in invalid_claims {
+ let err = serde_json::from_str::<Claims<Empty>>(claims).unwrap_err();
+ let err = err.to_string();
+ assert!(err.contains("UTC timestamp overflow"), "{err}");
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +
//! Error handling.
+
+#[cfg(feature = "ciborium")]
+use core::convert::Infallible;
+use core::fmt;
+
+use crate::alloc::String;
+
+#[cfg(feature = "ciborium")]
+pub(crate) type CborDeError<E = anyhow::Error> = ciborium::de::Error<E>;
+#[cfg(feature = "ciborium")]
+pub(crate) type CborSerError<E = Infallible> = ciborium::ser::Error<E>;
+
+/// Errors that may occur during token parsing.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ParseError {
+ /// Token has invalid structure.
+ ///
+ /// Valid tokens must consist of 3 base64url-encoded parts (header, claims, and signature)
+ /// separated by periods.
+ InvalidTokenStructure,
+ /// Cannot decode base64.
+ InvalidBase64Encoding,
+ /// Token header cannot be parsed.
+ MalformedHeader(serde_json::Error),
+ /// [Content type][cty] mentioned in the token header is not supported.
+ ///
+ /// Supported content types are JSON (used by default) and CBOR (only if the `ciborium`
+ /// crate feature is enabled, which it is by default).
+ ///
+ /// [cty]: https://tools.ietf.org/html/rfc7515#section-4.1.10
+ UnsupportedContentType(String),
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InvalidTokenStructure => formatter.write_str("invalid token structure"),
+ Self::InvalidBase64Encoding => write!(formatter, "invalid base64 decoding"),
+ Self::MalformedHeader(err) => write!(formatter, "malformed token header: {err}"),
+ Self::UnsupportedContentType(ty) => {
+ write!(formatter, "unsupported content type: {ty}")
+ }
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::MalformedHeader(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+/// Errors that can occur during token validation.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ValidationError {
+ /// Algorithm mentioned in the token header differs from invoked one.
+ AlgorithmMismatch {
+ /// Expected algorithm name.
+ expected: String,
+ /// Actual algorithm in the token.
+ actual: String,
+ },
+ /// Token signature has invalid byte length.
+ InvalidSignatureLen {
+ /// Expected signature length.
+ expected: usize,
+ /// Actual signature length.
+ actual: usize,
+ },
+ /// Token signature is malformed.
+ MalformedSignature(anyhow::Error),
+ /// Token signature has failed verification.
+ InvalidSignature,
+ /// Token claims cannot be deserialized from JSON.
+ MalformedClaims(serde_json::Error),
+ /// Token claims cannot be deserialized from CBOR.
+ #[cfg(feature = "ciborium")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "ciborium")))]
+ MalformedCborClaims(CborDeError),
+ /// Claim requested during validation is not present in the token.
+ NoClaim(Claim),
+ /// Token has expired.
+ Expired,
+ /// Token is not yet valid as per `nbf` claim.
+ NotMature,
+}
+
+/// Identifier of a claim in `Claims`.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum Claim {
+ /// `exp` claim (expiration time).
+ Expiration,
+ /// `nbf` claim (valid not before).
+ NotBefore,
+}
+
+impl fmt::Display for Claim {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(match self {
+ Self::Expiration => "exp",
+ Self::NotBefore => "nbf",
+ })
+ }
+}
+
+impl fmt::Display for ValidationError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::AlgorithmMismatch { expected, actual } => write!(
+ formatter,
+ "token algorithm ({actual}) differs from expected ({expected})"
+ ),
+ Self::InvalidSignatureLen { expected, actual } => write!(
+ formatter,
+ "invalid signature length: expected {expected} bytes, got {actual} bytes"
+ ),
+ Self::MalformedSignature(err) => write!(formatter, "malformed token signature: {err}"),
+ Self::InvalidSignature => formatter.write_str("signature has failed verification"),
+ Self::MalformedClaims(err) => write!(formatter, "cannot deserialize claims: {err}"),
+ #[cfg(feature = "ciborium")]
+ Self::MalformedCborClaims(err) => write!(formatter, "cannot deserialize claims: {err}"),
+ Self::NoClaim(claim) => write!(
+ formatter,
+ "claim `{claim}` requested during validation is not present in the token"
+ ),
+ Self::Expired => formatter.write_str("token has expired"),
+ Self::NotMature => formatter.write_str("token is not yet ready"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ValidationError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::MalformedSignature(err) => Some(err.as_ref()),
+ Self::MalformedClaims(err) => Some(err),
+ #[cfg(feature = "ciborium")]
+ Self::MalformedCborClaims(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+/// Errors that can occur during token creation.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum CreationError {
+ /// Token header cannot be serialized.
+ Header(serde_json::Error),
+ /// Token claims cannot be serialized into JSON.
+ Claims(serde_json::Error),
+ /// Token claims cannot be serialized into CBOR.
+ #[cfg(feature = "ciborium")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "ciborium")))]
+ CborClaims(CborSerError),
+}
+
+impl fmt::Display for CreationError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Header(err) => write!(formatter, "cannot serialize header: {err}"),
+ Self::Claims(err) => write!(formatter, "cannot serialize claims: {err}"),
+ #[cfg(feature = "ciborium")]
+ Self::CborClaims(err) => write!(formatter, "cannot serialize claims into CBOR: {err}"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for CreationError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Header(err) | Self::Claims(err) => Some(err),
+ #[cfg(feature = "ciborium")]
+ Self::CborClaims(err) => Some(err),
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +
//! Basic support of [JSON Web Keys](https://tools.ietf.org/html/rfc7517.html) (JWK).
+//!
+//! The functionality defined in this module allows converting between
+//! the [generic JWK format](JsonWebKey) and key presentation specific for the crypto backend.
+//! [`JsonWebKey`]s can be (de)serialized using [`serde`] infrastructure, and can be used
+//! to compute key thumbprint as per [RFC 7638].
+//!
+//! [`serde`]: https://crates.io/crates/serde
+//! [RFC 7638]: https://tools.ietf.org/html/rfc7638
+//!
+//! # Examples
+//!
+//! ```
+//! use jwt_compact::{alg::Hs256Key, jwk::JsonWebKey};
+//! use sha2::Sha256;
+//!
+//! # fn main() -> anyhow::Result<()> {
+//! // Load a key from the JWK presentation.
+//! let json_str = r#"
+//! { "kty": "oct", "k": "t-bdv41MJXExXnpquHBuDn7n1YGyX7gLQchVHAoNu50" }
+//! "#;
+//! let jwk: JsonWebKey<'_> = serde_json::from_str(json_str)?;
+//! let key = Hs256Key::try_from(&jwk)?;
+//!
+//! // Convert `key` back to JWK.
+//! let jwk_from_key = JsonWebKey::from(&key);
+//! assert_eq!(jwk_from_key, jwk);
+//! println!("{}", serde_json::to_string(&jwk)?);
+//!
+//! // Compute the key thumbprint.
+//! let thumbprint = jwk_from_key.thumbprint::<Sha256>();
+//! # Ok(())
+//! # }
+//! ```
+
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use sha2::digest::{Digest, Output};
+
+use core::fmt;
+
+use crate::{
+ alg::SecretBytes,
+ alloc::{Cow, String, ToString, Vec},
+};
+
+/// Type of a [`JsonWebKey`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+pub enum KeyType {
+ /// Public or private RSA key. Corresponds to the `RSA` value of the `kty` field for JWKs.
+ Rsa,
+ /// Public or private key in an ECDSA crypto system. Corresponds to the `EC` value
+ /// of the `kty` field for JWKs.
+ EllipticCurve,
+ /// Symmetric key. Corresponds to the `oct` value of the `kty` field for JWKs.
+ Symmetric,
+ /// Generic asymmetric keypair. Corresponds to the `OKP` value of the `kty` field for JWKs.
+ KeyPair,
+}
+
+impl fmt::Display for KeyType {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(match self {
+ Self::Rsa => "RSA",
+ Self::EllipticCurve => "EC",
+ Self::Symmetric => "oct",
+ Self::KeyPair => "OKP",
+ })
+ }
+}
+
+/// Errors that can occur when transforming a [`JsonWebKey`] into the presentation specific for
+/// a crypto backend, using the [`TryFrom`] trait.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum JwkError {
+ /// Required field is absent from JWK.
+ NoField(String),
+ /// Key type (the `kty` field) is not as expected.
+ UnexpectedKeyType {
+ /// Expected key type.
+ expected: KeyType,
+ /// Actual key type.
+ actual: KeyType,
+ },
+ /// JWK field has an unexpected value.
+ UnexpectedValue {
+ /// Field name.
+ field: String,
+ /// Expected value of the field.
+ expected: String,
+ /// Actual value of the field.
+ actual: String,
+ },
+ /// JWK field has an unexpected byte length.
+ UnexpectedLen {
+ /// Field name.
+ field: String,
+ /// Expected byte length of the field.
+ expected: usize,
+ /// Actual byte length of the field.
+ actual: usize,
+ },
+ /// Signing and verifying keys do not match.
+ MismatchedKeys,
+ /// Custom error specific to a crypto backend.
+ Custom(anyhow::Error),
+}
+
+impl fmt::Display for JwkError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::UnexpectedKeyType { expected, actual } => {
+ write!(
+ formatter,
+ "unexpected key type: {actual} (expected {expected})"
+ )
+ }
+ Self::NoField(field) => write!(formatter, "field `{field}` is absent from JWK"),
+ Self::UnexpectedValue {
+ field,
+ expected,
+ actual,
+ } => {
+ write!(
+ formatter,
+ "field `{field}` has unexpected value (expected: {expected}, got: {actual})"
+ )
+ }
+ Self::UnexpectedLen {
+ field,
+ expected,
+ actual,
+ } => {
+ write!(
+ formatter,
+ "field `{field}` has unexpected length (expected: {expected}, got: {actual})"
+ )
+ }
+ Self::MismatchedKeys => {
+ formatter.write_str("private and public keys encoded in JWK do not match")
+ }
+ Self::Custom(err) => fmt::Display::fmt(err, formatter),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for JwkError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Custom(err) => Some(err.as_ref()),
+ _ => None,
+ }
+ }
+}
+
+impl JwkError {
+ /// Creates a `Custom` error variant.
+ pub fn custom(err: impl Into<anyhow::Error>) -> Self {
+ Self::Custom(err.into())
+ }
+
+ pub(crate) fn key_type(jwk: &JsonWebKey<'_>, expected: KeyType) -> Self {
+ let actual = jwk.key_type();
+ debug_assert_ne!(actual, expected);
+ Self::UnexpectedKeyType { actual, expected }
+ }
+}
+
+impl Serialize for SecretBytes<'_> {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ base64url::serialize(self.as_ref(), serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for SecretBytes<'_> {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ base64url::deserialize(deserializer).map(SecretBytes::new)
+ }
+}
+
+/// Basic [JWK] functionality: (de)serialization and creating thumbprints.
+///
+/// See [RFC 7518] for the details about the fields for various key types.
+///
+/// [`Self::thumbprint()`] and the [`Display`](fmt::Display) implementation
+/// allow to get the overall presentation of the key. The latter returns JSON serialization
+/// of the key with fields ordered alphabetically. That is, this output for verifying keys
+/// can be used to compute key thumbprints.
+///
+/// # Serialization
+///
+/// For human-readable formats (e.g., JSON, TOML, YAML), byte fields in `JsonWebKey`
+/// and embedded types ([`SecretBytes`], [`RsaPrivateParts`], [`RsaPrimeFactor`]) will be
+/// serialized in base64-url encoding with no padding, as per the JWK spec.
+/// For other formats (e.g., CBOR), byte fields will be serialized as byte sequences.
+///
+/// Because of [the limitations](https://github.com/pyfisch/cbor/issues/3)
+/// of the CBOR support in `serde`, a `JsonWebKey` serialized in CBOR is **not** compliant
+/// with the [CBOR Object Signing and Encryption spec][COSE] (COSE). It can still be a good
+/// way to decrease the serialized key size.
+///
+/// # Conversions
+///
+/// A JWK can be obtained from signing and verifying keys defined in the [`alg`](crate::alg)
+/// module via [`From`] / [`Into`] traits. Conversion from a JWK to a specific key is fallible
+/// and can be performed via [`TryFrom`] with [`JwkError`] as an error
+/// type.
+///
+/// As a part of conversion for asymmetric signing keys, it is checked whether
+/// the signing and verifying parts of the JWK match; [`JwkError::MismatchedKeys`] is returned
+/// otherwise. This check is **not** performed for verifying keys even if the necessary data
+/// is present in the provided JWK.
+///
+/// ⚠ **Warning.** Conversions for private RSA keys are not fully compliant with [RFC 7518].
+/// See the docs for the relevant `impl`s for more details.
+///
+/// [RFC 7518]: https://tools.ietf.org/html/rfc7518#section-6
+/// [JWK]: https://tools.ietf.org/html/rfc7517.html
+/// [COSE]: https://tools.ietf.org/html/rfc8152
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "kty")]
+#[non_exhaustive]
+pub enum JsonWebKey<'a> {
+ /// Public or private RSA key. Has `kty` field set to `RSA`.
+ #[serde(rename = "RSA")]
+ Rsa {
+ /// Key modulus (`n`).
+ #[serde(rename = "n", with = "base64url")]
+ modulus: Cow<'a, [u8]>,
+ /// Public exponent (`e`).
+ #[serde(rename = "e", with = "base64url")]
+ public_exponent: Cow<'a, [u8]>,
+ /// Private RSA parameters. Only present for private keys.
+ #[serde(flatten)]
+ private_parts: Option<RsaPrivateParts<'a>>,
+ },
+ /// Public or private key in an ECDSA crypto system. Has `kty` field set to `EC`.
+ #[serde(rename = "EC")]
+ EllipticCurve {
+ /// Curve name (`crv`), such as `secp256k1`.
+ #[serde(rename = "crv")]
+ curve: Cow<'a, str>,
+ /// `x` coordinate of the curve point.
+ #[serde(with = "base64url")]
+ x: Cow<'a, [u8]>,
+ /// `y` coordinate of the curve point.
+ #[serde(with = "base64url")]
+ y: Cow<'a, [u8]>,
+ /// Secret scalar (`d`); not present for public keys.
+ #[serde(rename = "d", default, skip_serializing_if = "Option::is_none")]
+ secret: Option<SecretBytes<'a>>,
+ },
+ /// Generic symmetric key, e.g. for `HS256` algorithm. Has `kty` field set to `oct`.
+ #[serde(rename = "oct")]
+ Symmetric {
+ /// Bytes representing this key.
+ #[serde(rename = "k")]
+ secret: SecretBytes<'a>,
+ },
+ /// Generic asymmetric keypair. This key type is used e.g. for Ed25519 keys.
+ #[serde(rename = "OKP")]
+ KeyPair {
+ /// Curve name (`crv`), such as `Ed25519`.
+ #[serde(rename = "crv")]
+ curve: Cow<'a, str>,
+ /// Public key. For Ed25519, this is the standard 32-byte public key presentation
+ /// (`x` coordinate of a point on the curve + sign).
+ #[serde(with = "base64url")]
+ x: Cow<'a, [u8]>,
+ /// Secret key (`d`). For Ed25519, this is the seed.
+ #[serde(rename = "d", default, skip_serializing_if = "Option::is_none")]
+ secret: Option<SecretBytes<'a>>,
+ },
+}
+
+impl JsonWebKey<'_> {
+ /// Gets the type of this key.
+ pub fn key_type(&self) -> KeyType {
+ match self {
+ Self::Rsa { .. } => KeyType::Rsa,
+ Self::EllipticCurve { .. } => KeyType::EllipticCurve,
+ Self::Symmetric { .. } => KeyType::Symmetric,
+ Self::KeyPair { .. } => KeyType::KeyPair,
+ }
+ }
+
+ /// Returns `true` if this key can be used for signing (has [`SecretBytes`] fields).
+ pub fn is_signing_key(&self) -> bool {
+ match self {
+ Self::Rsa { private_parts, .. } => private_parts.is_some(),
+ Self::EllipticCurve { secret, .. } | Self::KeyPair { secret, .. } => secret.is_some(),
+ Self::Symmetric { .. } => true,
+ }
+ }
+
+ /// Returns a copy of this key with parts not necessary for signature verification removed.
+ #[must_use]
+ pub fn to_verifying_key(&self) -> Self {
+ match self {
+ Self::Rsa {
+ modulus,
+ public_exponent,
+ ..
+ } => Self::Rsa {
+ modulus: modulus.clone(),
+ public_exponent: public_exponent.clone(),
+ private_parts: None,
+ },
+
+ Self::EllipticCurve { curve, x, y, .. } => Self::EllipticCurve {
+ curve: curve.clone(),
+ x: x.clone(),
+ y: y.clone(),
+ secret: None,
+ },
+
+ Self::Symmetric { secret } => Self::Symmetric {
+ secret: secret.clone(),
+ },
+
+ Self::KeyPair { curve, x, .. } => Self::KeyPair {
+ curve: curve.clone(),
+ x: x.clone(),
+ secret: None,
+ },
+ }
+ }
+
+ /// Computes a thumbprint of this JWK. The result complies with the key thumbprint defined
+ /// in [RFC 7638].
+ ///
+ /// [RFC 7638]: https://tools.ietf.org/html/rfc7638
+ pub fn thumbprint<D: Digest>(&self) -> Output<D> {
+ let hashed_key = if self.is_signing_key() {
+ Cow::Owned(self.to_verifying_key())
+ } else {
+ Cow::Borrowed(self)
+ };
+ D::digest(hashed_key.to_string().as_bytes())
+ }
+}
+
+impl fmt::Display for JsonWebKey<'_> {
+ // TODO: Not the most efficient approach
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let json_value = serde_json::to_value(self).expect("Cannot convert JsonWebKey to JSON");
+ let json_value = json_value.as_object().unwrap();
+ // ^ unwrap() is safe: `JsonWebKey` serialization is always an object.
+
+ let mut json_entries: Vec<_> = json_value.iter().collect();
+ json_entries.sort_unstable_by(|(x, _), (y, _)| x.cmp(y));
+
+ formatter.write_str("{")?;
+ let field_count = json_entries.len();
+ for (i, (name, value)) in json_entries.into_iter().enumerate() {
+ write!(formatter, "\"{name}\":{value}")?;
+ if i + 1 < field_count {
+ formatter.write_str(",")?;
+ }
+ }
+ formatter.write_str("}")
+ }
+}
+
+/// Parts of [`JsonWebKey::Rsa`] that are specific to private keys.
+///
+/// # Serialization
+///
+/// Fields of this struct are serialized using the big endian presentation
+/// with the minimum necessary number of bytes. See [`JsonWebKey` notes](JsonWebKey#serialization)
+/// on encoding.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct RsaPrivateParts<'a> {
+ /// Private exponent (`d`).
+ #[serde(rename = "d")]
+ pub private_exponent: SecretBytes<'a>,
+ /// First prime factor (`p`).
+ #[serde(rename = "p")]
+ pub prime_factor_p: SecretBytes<'a>,
+ /// Second prime factor (`q`).
+ #[serde(rename = "q")]
+ pub prime_factor_q: SecretBytes<'a>,
+ /// First factor CRT exponent (`dp`).
+ #[serde(rename = "dp", default, skip_serializing_if = "Option::is_none")]
+ pub p_crt_exponent: Option<SecretBytes<'a>>,
+ /// Second factor CRT exponent (`dq`).
+ #[serde(rename = "dq", default, skip_serializing_if = "Option::is_none")]
+ pub q_crt_exponent: Option<SecretBytes<'a>>,
+ /// CRT coefficient of the second factor (`qi`).
+ #[serde(rename = "qi", default, skip_serializing_if = "Option::is_none")]
+ pub q_crt_coefficient: Option<SecretBytes<'a>>,
+ /// Other prime factors.
+ #[serde(rename = "oth", default, skip_serializing_if = "Vec::is_empty")]
+ pub other_prime_factors: Vec<RsaPrimeFactor<'a>>,
+}
+
+/// Block for an additional prime factor in [`RsaPrivateParts`].
+///
+/// # Serialization
+///
+/// Fields of this struct are serialized using the big endian presentation
+/// with the minimum necessary number of bytes. See [`JsonWebKey` notes](JsonWebKey#serialization)
+/// on encoding.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct RsaPrimeFactor<'a> {
+ /// Prime factor (`r`).
+ #[serde(rename = "r")]
+ pub factor: SecretBytes<'a>,
+ /// Factor CRT exponent (`d`).
+ #[serde(rename = "d", default, skip_serializing_if = "Option::is_none")]
+ pub crt_exponent: Option<SecretBytes<'a>>,
+ /// Factor CRT coefficient (`t`).
+ #[serde(rename = "t", default, skip_serializing_if = "Option::is_none")]
+ pub crt_coefficient: Option<SecretBytes<'a>>,
+}
+
+#[cfg(any(
+ feature = "es256k",
+ feature = "k256",
+ feature = "exonum-crypto",
+ feature = "ed25519-dalek",
+ feature = "ed25519-compact"
+))]
+mod helpers {
+ use super::{JsonWebKey, JwkError};
+ use crate::{alg::SigningKey, alloc::ToOwned, Algorithm};
+
+ impl JsonWebKey<'_> {
+ pub(crate) fn ensure_curve(curve: &str, expected: &str) -> Result<(), JwkError> {
+ if curve == expected {
+ Ok(())
+ } else {
+ Err(JwkError::UnexpectedValue {
+ field: "crv".to_owned(),
+ expected: expected.to_owned(),
+ actual: curve.to_owned(),
+ })
+ }
+ }
+
+ pub(crate) fn ensure_len(
+ field: &str,
+ bytes: &[u8],
+ expected_len: usize,
+ ) -> Result<(), JwkError> {
+ if bytes.len() == expected_len {
+ Ok(())
+ } else {
+ Err(JwkError::UnexpectedLen {
+ field: field.to_owned(),
+ expected: expected_len,
+ actual: bytes.len(),
+ })
+ }
+ }
+
+ /// Ensures that the provided signing key matches the verifying key restored from the same JWK.
+ /// This is useful when implementing [`TryFrom`] conversion from `JsonWebKey` for private keys.
+ pub(crate) fn ensure_key_match<Alg, K>(&self, signing_key: K) -> Result<K, JwkError>
+ where
+ Alg: Algorithm<SigningKey = K>,
+ K: SigningKey<Alg>,
+ Alg::VerifyingKey: for<'jwk> TryFrom<&'jwk Self, Error = JwkError> + PartialEq,
+ {
+ let verifying_key = <Alg::VerifyingKey>::try_from(self)?;
+ if verifying_key == signing_key.to_verifying_key() {
+ Ok(signing_key)
+ } else {
+ Err(JwkError::MismatchedKeys)
+ }
+ }
+ }
+}
+
+mod base64url {
+ use base64ct::{Base64UrlUnpadded, Encoding};
+ use serde::{
+ de::{Error as DeError, Unexpected, Visitor},
+ Deserializer, Serializer,
+ };
+
+ use core::fmt;
+
+ use crate::alloc::{Cow, Vec};
+
+ pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ if serializer.is_human_readable() {
+ serializer.serialize_str(&Base64UrlUnpadded::encode_string(value))
+ } else {
+ serializer.serialize_bytes(value)
+ }
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result<Cow<'static, [u8]>, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct Base64Visitor;
+
+ impl Visitor<'_> for Base64Visitor {
+ type Value = Vec<u8>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("base64url-encoded data")
+ }
+
+ fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
+ Base64UrlUnpadded::decode_vec(value)
+ .map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
+ }
+
+ fn visit_bytes<E: DeError>(self, value: &[u8]) -> Result<Self::Value, E> {
+ Ok(value.to_vec())
+ }
+
+ fn visit_byte_buf<E: DeError>(self, value: Vec<u8>) -> Result<Self::Value, E> {
+ Ok(value)
+ }
+ }
+
+ struct BytesVisitor;
+
+ impl<'de> Visitor<'de> for BytesVisitor {
+ type Value = Vec<u8>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("byte buffer")
+ }
+
+ fn visit_bytes<E: DeError>(self, value: &[u8]) -> Result<Self::Value, E> {
+ Ok(value.to_vec())
+ }
+
+ fn visit_byte_buf<E: DeError>(self, value: Vec<u8>) -> Result<Self::Value, E> {
+ Ok(value)
+ }
+ }
+
+ let maybe_bytes = if deserializer.is_human_readable() {
+ deserializer.deserialize_str(Base64Visitor)
+ } else {
+ deserializer.deserialize_bytes(BytesVisitor)
+ };
+ maybe_bytes.map(Cow::Owned)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::alg::Hs256Key;
+
+ use assert_matches::assert_matches;
+
+ fn create_jwk() -> JsonWebKey<'static> {
+ JsonWebKey::KeyPair {
+ curve: Cow::Borrowed("Ed25519"),
+ x: Cow::Borrowed(b"test"),
+ secret: None,
+ }
+ }
+
+ #[test]
+ fn serializing_jwk() {
+ let jwk = create_jwk();
+
+ let json = serde_json::to_value(&jwk).unwrap();
+ assert_eq!(
+ json,
+ serde_json::json!({ "crv": "Ed25519", "kty": "OKP", "x": "dGVzdA" })
+ );
+
+ let restored: JsonWebKey<'_> = serde_json::from_value(json).unwrap();
+ assert_eq!(restored, jwk);
+ }
+
+ #[test]
+ fn jwk_deserialization_errors() {
+ let missing_field_json = r#"{"crv":"Ed25519"}"#;
+ let missing_field_err = serde_json::from_str::<JsonWebKey<'_>>(missing_field_json)
+ .unwrap_err()
+ .to_string();
+ assert!(
+ missing_field_err.contains("missing field `kty`"),
+ "{missing_field_err}"
+ );
+
+ let base64_json = r#"{"crv":"Ed25519","kty":"OKP","x":"??"}"#;
+ let base64_err = serde_json::from_str::<JsonWebKey<'_>>(base64_json)
+ .unwrap_err()
+ .to_string();
+ assert!(
+ base64_err.contains("invalid value: string \"??\""),
+ "{base64_err}"
+ );
+ assert!(
+ base64_err.contains("base64url-encoded data"),
+ "{base64_err}"
+ );
+ }
+
+ #[test]
+ fn extra_jwk_fields() {
+ #[derive(Debug, Serialize, Deserialize)]
+ struct ExtendedJsonWebKey<'a, T> {
+ #[serde(flatten)]
+ base: JsonWebKey<'a>,
+ #[serde(flatten)]
+ extra: T,
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct Extra {
+ #[serde(rename = "kid")]
+ key_id: String,
+ #[serde(rename = "use")]
+ key_use: KeyUse,
+ }
+
+ #[derive(Debug, Deserialize, PartialEq)]
+ enum KeyUse {
+ #[serde(rename = "sig")]
+ Signature,
+ #[serde(rename = "enc")]
+ Encryption,
+ }
+
+ let json_str = r#"
+ { "kty": "oct", "kid": "my-unique-key", "k": "dGVzdA", "use": "sig" }
+ "#;
+ let jwk: ExtendedJsonWebKey<'_, Extra> = serde_json::from_str(json_str).unwrap();
+
+ assert_matches!(&jwk.base, JsonWebKey::Symmetric { secret } if secret.as_ref() == b"test");
+ assert_eq!(jwk.extra.key_id, "my-unique-key");
+ assert_eq!(jwk.extra.key_use, KeyUse::Signature);
+
+ let key = Hs256Key::try_from(&jwk.base).unwrap();
+ let jwk_from_key = JsonWebKey::from(&key);
+
+ assert_matches!(
+ jwk_from_key,
+ JsonWebKey::Symmetric { secret } if secret.as_ref() == b"test"
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "ciborium")]
+ fn jwk_with_cbor() {
+ let key = JsonWebKey::KeyPair {
+ curve: Cow::Borrowed("Ed25519"),
+ x: Cow::Borrowed(b"public"),
+ secret: Some(SecretBytes::borrowed(b"private")),
+ };
+ let mut bytes = vec![];
+ ciborium::into_writer(&key, &mut bytes).unwrap();
+ assert!(bytes.windows(6).any(|window| window == b"public"));
+ assert!(bytes.windows(7).any(|window| window == b"private"));
+
+ let restored: JsonWebKey<'_> = ciborium::from_reader(&bytes[..]).unwrap();
+ assert_eq!(restored, key);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +
//! Minimalistic [JSON web token (JWT)][JWT] implementation with focus on type safety
+//! and secure cryptographic primitives.
+//!
+//! # Design choices
+//!
+//! - JWT signature algorithms (i.e., cryptographic algorithms providing JWT integrity)
+//! are expressed via the [`Algorithm`] trait, which uses fully typed keys and signatures.
+//! - [JWT header] is represented by the [`Header`] struct. Notably, `Header` does not
+//! expose the [`alg` field].
+//! Instead, `alg` is filled automatically during token creation, and is compared to the
+//! expected value during verification. (If you do not know the JWT signature algorithm during
+//! verification, you're doing something wrong.) This eliminates the possibility
+//! of [algorithm switching attacks][switching].
+//!
+//! # Additional features
+//!
+//! - The crate supports more compact [CBOR] encoding of the claims. This feature is enabled
+//! via the [`ciborium` feature](#cbor-support).
+//! - The crate supports `EdDSA` algorithm with the Ed25519 elliptic curve, and `ES256K` algorithm
+//! with the secp256k1 elliptic curve.
+//! - Supports basic [JSON Web Key](https://tools.ietf.org/html/rfc7517.html) functionality,
+//! e.g., for converting keys to / from JSON or computing
+//! [a key thumbprint](https://tools.ietf.org/html/rfc7638).
+//!
+//! ## Supported algorithms
+//!
+//! | Algorithm(s) | Feature | Description |
+//! |--------------|---------|-------------|
+//! | `HS256`, `HS384`, `HS512` | - | Uses pure Rust [`sha2`] crate |
+//! | `EdDSA` (Ed25519) | [`exonum-crypto`] | [`libsodium`] binding |
+//! | `EdDSA` (Ed25519) | [`ed25519-dalek`] | Pure Rust implementation |
+//! | `EdDSA` (Ed25519) | [`ed25519-compact`] | Compact pure Rust implementation, WASM-compatible |
+//! | `ES256K` | `es256k` | [Rust binding][`secp256k1`] for [`libsecp256k1`] |
+//! | `ES256K` | [`k256`] | Pure Rust implementation |
+//! | `ES256` | [`p256`] | Pure Rust implementation |
+//! | `RS*`, `PS*` (RSA) | `rsa` | Uses pure Rust [`rsa`] crate with blinding |
+//!
+//! Beware that the `rsa` crate (along with other RSA implementations) may be susceptible to
+//! [the "Marvin" timing side-channel attack](https://github.com/RustCrypto/RSA/security/advisories/GHSA-c38w-74pg-36hr)
+//! at the time of writing; use with caution.
+//!
+//! `EdDSA` and `ES256K` algorithms are somewhat less frequently supported by JWT implementations
+//! than others since they are recent additions to the JSON Web Algorithms (JWA) suit.
+//! They both work with elliptic curves
+//! (Curve25519 and secp256k1; both are widely used in crypto community and believed to be
+//! securely generated). These algs have 128-bit security, making them an alternative
+//! to `ES256`.
+//!
+//! RSA support requires a system-wide RNG retrieved via the [`getrandom`] crate.
+//! In case of a compilation failure in the `getrandom` crate, you may want
+//! to include it as a direct dependency and specify one of its features
+//! to assist `getrandom` with choosing an appropriate RNG implementation; consult `getrandom` docs
+//! for more details. See also WASM and bare-metal E2E tests included
+//! in the [source code repository] of this crate.
+//!
+//! ## CBOR support
+//!
+//! If the `ciborium` crate feature is enabled (and it is enabled by default), token claims can
+//! be encoded using [CBOR] with the [`AlgorithmExt::compact_token()`] method.
+//! The compactly encoded JWTs have the [`cty` field] (content type) in their header
+//! set to `"CBOR"`. Tokens with such encoding can be verified in the same way as ordinary tokens;
+//! see [examples below](#examples).
+//!
+//! If the `ciborium` feature is disabled, `AlgorithmExt::compact_token()` is not available.
+//! Verifying CBOR-encoded tokens in this case is not supported either;
+//! a [`ParseError::UnsupportedContentType`] will be returned when creating an [`UntrustedToken`]
+//! from the token string.
+//!
+//! # `no_std` support
+//!
+//! The crate supports a `no_std` compilation mode. This is controlled by two features:
+//! `clock` and `std`; both are on by default.
+//!
+//! - The `clock` feature enables getting the current time using `Utc::now()` from [`chrono`].
+//! Without it, some [`TimeOptions`] constructors, such as the `Default` impl,
+//! are not available. It is still possible to create `TimeOptions` with an explicitly specified
+//! clock function, or to set / verify time-related [`Claims`] fields manually.
+//! - The `std` feature is propagated to the core dependencies and enables `std`-specific
+//! functionality (such as error types implementing the standard `Error` trait).
+//!
+//! Some `alloc` types are still used in the `no_std` mode, such as `String`, `Vec` and `Cow`.
+//!
+//! Note that not all crypto backends are `no_std`-compatible.
+//!
+//! [JWT]: https://jwt.io/
+//! [switching]: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
+//! [JWT header]: https://tools.ietf.org/html/rfc7519#section-5
+//! [`alg` field]: https://tools.ietf.org/html/rfc7515#section-4.1.1
+//! [`cty` field]: https://tools.ietf.org/html/rfc7515#section-4.1.10
+//! [CBOR]: https://tools.ietf.org/html/rfc7049
+//! [`sha2`]: https://docs.rs/sha2/
+//! [`libsodium`]: https://download.libsodium.org/doc/
+//! [`exonum-crypto`]: https://docs.rs/exonum-crypto/
+//! [`ed25519-dalek`]: https://doc.dalek.rs/ed25519_dalek/
+//! [`ed25519-compact`]: https://crates.io/crates/ed25519-compact
+//! [`secp256k1`]: https://docs.rs/secp256k1/
+//! [`libsecp256k1`]: https://github.com/bitcoin-core/secp256k1
+//! [`k256`]: https://docs.rs/k256/
+//! [`p256`]: https://docs.rs/p256/
+//! [`rsa`]: https://docs.rs/rsa/
+//! [`chrono`]: https://docs.rs/chrono/
+//! [`getrandom`]: https://docs.rs/getrandom/
+//! [source code repository]: https://github.com/slowli/jwt-compact
+//!
+//! # Examples
+//!
+//! Basic JWT lifecycle:
+//!
+//! ```
+//! use chrono::{Duration, Utc};
+//! use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
+//! use serde::{Serialize, Deserialize};
+//!
+//! /// Custom claims encoded in the token.
+//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
+//! struct CustomClaims {
+//! /// `sub` is a standard claim which denotes claim subject:
+//! /// https://tools.ietf.org/html/rfc7519#section-4.1.2
+//! #[serde(rename = "sub")]
+//! subject: String,
+//! }
+//!
+//! # fn main() -> anyhow::Result<()> {
+//! // Choose time-related options for token creation / validation.
+//! let time_options = TimeOptions::default();
+//! // Create a symmetric HMAC key, which will be used both to create and verify tokens.
+//! let key = Hs256Key::new(b"super_secret_key_donut_steel");
+//! // Create a token.
+//! let header = Header::empty().with_key_id("my-key");
+//! let claims = Claims::new(CustomClaims { subject: "alice".to_owned() })
+//! .set_duration_and_issuance(&time_options, Duration::days(7))
+//! .set_not_before(Utc::now() - Duration::hours(1));
+//! let token_string = Hs256.token(&header, &claims, &key)?;
+//! println!("token: {token_string}");
+//!
+//! // Parse the token.
+//! let token = UntrustedToken::new(&token_string)?;
+//! // Before verifying the token, we might find the key which has signed the token
+//! // using the `Header.key_id` field.
+//! assert_eq!(token.header().key_id, Some("my-key".to_owned()));
+//! // Validate the token integrity.
+//! let token: Token<CustomClaims> = Hs256.validator(&key).validate(&token)?;
+//! // Validate additional conditions.
+//! token.claims()
+//! .validate_expiration(&time_options)?
+//! .validate_maturity(&time_options)?;
+//! // Now, we can extract information from the token (e.g., its subject).
+//! let subject = &token.claims().custom.subject;
+//! assert_eq!(subject, "alice");
+//! # Ok(())
+//! # } // end main()
+//! ```
+//!
+//! ## Compact JWT
+//!
+//! ```
+//! # use chrono::Duration;
+//! # use hex_buffer_serde::{Hex as _, HexForm};
+//! # use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
+//! # use serde::{Serialize, Deserialize};
+//! /// Custom claims encoded in the token.
+//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
+//! struct CustomClaims {
+//! /// `sub` is a standard claim which denotes claim subject:
+//! /// https://tools.ietf.org/html/rfc7519#section-4.1.2
+//! /// The custom serializer we use allows to efficiently
+//! /// encode the subject in CBOR.
+//! #[serde(rename = "sub", with = "HexForm")]
+//! subject: [u8; 32],
+//! }
+//!
+//! # fn main() -> anyhow::Result<()> {
+//! let time_options = TimeOptions::default();
+//! let key = Hs256Key::new(b"super_secret_key_donut_steel");
+//! let claims = Claims::new(CustomClaims { subject: [111; 32] })
+//! .set_duration_and_issuance(&time_options, Duration::days(7));
+//! let token = Hs256.token(&Header::empty(), &claims, &key)?;
+//! println!("token: {token}");
+//! let compact_token = Hs256.compact_token(&Header::empty(), &claims, &key)?;
+//! println!("compact token: {compact_token}");
+//! // The compact token should be ~40 chars shorter.
+//!
+//! // Parse the compact token.
+//! let token = UntrustedToken::new(&compact_token)?;
+//! let token: Token<CustomClaims> = Hs256.validator(&key).validate(&token)?;
+//! token.claims().validate_expiration(&time_options)?;
+//! // Now, we can extract information from the token (e.g., its subject).
+//! assert_eq!(token.claims().custom.subject, [111; 32]);
+//! # Ok(())
+//! # } // end main()
+//! ```
+//!
+//! ## JWT with custom header fields
+//!
+//! ```
+//! # use chrono::Duration;
+//! # use jwt_compact::{prelude::*, alg::{Hs256, Hs256Key}};
+//! # use serde::{Deserialize, Serialize};
+//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
+//! struct CustomClaims { subject: [u8; 32] }
+//!
+//! /// Additional fields in the token header.
+//! #[derive(Debug, Clone, Serialize, Deserialize)]
+//! struct HeaderExtensions { custom: bool }
+//!
+//! # fn main() -> anyhow::Result<()> {
+//! let time_options = TimeOptions::default();
+//! let key = Hs256Key::new(b"super_secret_key_donut_steel");
+//! let claims = Claims::new(CustomClaims { subject: [111; 32] })
+//! .set_duration_and_issuance(&time_options, Duration::days(7));
+//! let header = Header::new(HeaderExtensions { custom: true })
+//! .with_key_id("my-key");
+//! let token = Hs256.token(&header, &claims, &key)?;
+//! print!("token: {token}");
+//!
+//! // Parse the token.
+//! let token: UntrustedToken<HeaderExtensions> =
+//! token.as_str().try_into()?;
+//! // Token header (incl. custom fields) can be accessed right away.
+//! assert_eq!(token.header().key_id.as_deref(), Some("my-key"));
+//! assert!(token.header().other_fields.custom);
+//! // Token can then be validated as usual.
+//! let token = Hs256.validator::<CustomClaims>(&key).validate(&token)?;
+//! assert_eq!(token.claims().custom.subject, [111; 32]);
+//! # Ok(())
+//! # } // end main()
+//! ```
+
+#![cfg_attr(not(feature = "std"), no_std)]
+// Documentation settings.
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![doc(html_root_url = "https://docs.rs/jwt-compact/0.8.0")]
+// Linter settings.
+#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(
+ clippy::missing_errors_doc,
+ clippy::must_use_candidate,
+ clippy::module_name_repetitions
+)]
+
+pub mod alg;
+mod claims;
+mod error;
+pub mod jwk;
+mod token;
+mod traits;
+
+// Polyfill for `alloc` types.
+mod alloc {
+ #[cfg(not(feature = "std"))]
+ extern crate alloc as std;
+
+ pub use std::{
+ borrow::{Cow, ToOwned},
+ boxed::Box,
+ format,
+ string::{String, ToString},
+ vec::Vec,
+ };
+}
+
+/// Prelude to neatly import all necessary stuff from the crate.
+pub mod prelude {
+ #[doc(no_inline)]
+ pub use crate::{AlgorithmExt as _, Claims, Header, TimeOptions, Token, UntrustedToken};
+}
+
+pub use crate::{
+ claims::{Claims, Empty, TimeOptions},
+ error::{Claim, CreationError, ParseError, ValidationError},
+ token::{Header, SignedToken, Thumbprint, Token, UntrustedToken},
+ traits::{Algorithm, AlgorithmExt, AlgorithmSignature, Renamed, Validator},
+};
+
+#[cfg(doctest)]
+doc_comment::doctest!("../README.md");
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +
//! `Token` and closely related types.
+
+use base64ct::{Base64UrlUnpadded, Encoding};
+use serde::{
+ de::{DeserializeOwned, Error as DeError, Visitor},
+ Deserialize, Deserializer, Serialize, Serializer,
+};
+use smallvec::{smallvec, SmallVec};
+
+use core::{cmp, fmt};
+
+#[cfg(feature = "ciborium")]
+use crate::error::CborDeError;
+use crate::{
+ alloc::{format, Cow, String, Vec},
+ Algorithm, Claims, Empty, ParseError, ValidationError,
+};
+
+/// Maximum "reasonable" signature size in bytes.
+const SIGNATURE_SIZE: usize = 128;
+
+/// Representation of a X.509 certificate thumbprint (`x5t` and `x5t#S256` fields in
+/// the JWT [`Header`]).
+///
+/// As per the JWS spec in [RFC 7515], a certificate thumbprint (i.e., the SHA-1 / SHA-256
+/// digest of the certificate) must be base64url-encoded. Some JWS implementations however
+/// encode not the thumbprint itself, but rather its hex encoding, sometimes even
+/// with additional chars spliced within. To account for these implementations,
+/// a thumbprint is represented as an enum – either a properly encoded hash digest,
+/// or an opaque base64-encoded string.
+///
+/// [RFC 7515]: https://www.rfc-editor.org/rfc/rfc7515.html
+///
+/// # Examples
+///
+/// ```
+/// # use assert_matches::assert_matches;
+/// # use jwt_compact::{
+/// # alg::{Hs256, Hs256Key}, AlgorithmExt, Claims, Header, Thumbprint, UntrustedToken,
+/// # };
+/// # fn main() -> anyhow::Result<()> {
+/// let key = Hs256Key::new(b"super_secret_key_donut_steel");
+///
+/// // Creates a token with a custom-encoded SHA-1 thumbprint.
+/// let thumbprint = "65:AF:69:09:B1:B0:75:8E:06:C6:E0:48:C4:60:02:B5:C6:95:E3:6B";
+/// let header = Header::empty()
+/// .with_key_id("my_key")
+/// .with_certificate_sha1_thumbprint(thumbprint);
+/// let token = Hs256.token(&header, &Claims::empty(), &key)?;
+/// println!("{token}");
+///
+/// // Deserialize the token and check that its header fields are readable.
+/// let token = UntrustedToken::new(&token)?;
+/// let deserialized_thumbprint =
+/// token.header().certificate_sha1_thumbprint.as_ref();
+/// assert_matches!(
+/// deserialized_thumbprint,
+/// Some(Thumbprint::String(s)) if s == thumbprint
+/// );
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+pub enum Thumbprint<const N: usize> {
+ /// Byte representation of a SHA-1 or SHA-256 digest.
+ Bytes([u8; N]),
+ /// Opaque string representation of the thumbprint. It is the responsibility
+ /// of an application to verify that this value is valid.
+ String(String),
+}
+
+impl<const N: usize> From<[u8; N]> for Thumbprint<N> {
+ fn from(value: [u8; N]) -> Self {
+ Self::Bytes(value)
+ }
+}
+
+impl<const N: usize> From<String> for Thumbprint<N> {
+ fn from(s: String) -> Self {
+ Self::String(s)
+ }
+}
+
+impl<const N: usize> From<&str> for Thumbprint<N> {
+ fn from(s: &str) -> Self {
+ Self::String(s.into())
+ }
+}
+
+impl<const N: usize> Serialize for Thumbprint<N> {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ let input = match self {
+ Self::Bytes(bytes) => bytes.as_slice(),
+ Self::String(s) => s.as_bytes(),
+ };
+ serializer.serialize_str(&Base64UrlUnpadded::encode_string(input))
+ }
+}
+
+impl<'de, const N: usize> Deserialize<'de> for Thumbprint<N> {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ struct Base64Visitor<const L: usize>;
+
+ impl<const L: usize> Visitor<'_> for Base64Visitor<L> {
+ type Value = Thumbprint<L>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "base64url-encoded thumbprint")
+ }
+
+ fn visit_str<E: DeError>(self, mut value: &str) -> Result<Self::Value, E> {
+ // Allow for padding. RFC 7515 defines base64url encoding as one without padding:
+ //
+ // > Base64url Encoding: Base64 encoding using the URL- and filename-safe
+ // > character set defined in Section 5 of RFC 4648 [RFC4648], with all trailing '='
+ // > characters omitted [...]
+ //
+ // ...but it's easy to trim the padding, so we support it anyway.
+ //
+ // See: https://www.rfc-editor.org/rfc/rfc7515.html#section-2
+ for _ in 0..2 {
+ if value.as_bytes().last() == Some(&b'=') {
+ value = &value[..value.len() - 1];
+ }
+ }
+
+ let decoded_len = value.len() * 3 / 4;
+ match decoded_len.cmp(&L) {
+ cmp::Ordering::Less => Err(E::custom(format!(
+ "thumbprint must contain at least {L} bytes"
+ ))),
+ cmp::Ordering::Equal => {
+ let mut bytes = [0_u8; L];
+ let len = Base64UrlUnpadded::decode(value, &mut bytes)
+ .map_err(E::custom)?
+ .len();
+ debug_assert_eq!(len, L);
+ Ok(bytes.into())
+ }
+ cmp::Ordering::Greater => {
+ let decoded = Base64UrlUnpadded::decode_vec(value).map_err(E::custom)?;
+ let decoded = String::from_utf8(decoded)
+ .map_err(|err| E::custom(err.utf8_error()))?;
+ Ok(decoded.into())
+ }
+ }
+ }
+ }
+
+ deserializer.deserialize_str(Base64Visitor)
+ }
+}
+
+/// JWT header.
+///
+/// See [RFC 7515](https://tools.ietf.org/html/rfc7515#section-4.1) for the description
+/// of the fields. The purpose of all fields except `token_type` is to determine
+/// the verifying key. Since these values will be provided by the adversary in the case of
+/// an attack, they require additional verification (e.g., a provided certificate might
+/// be checked against the list of "acceptable" certificate authorities).
+///
+/// A `Header` can be created using `Default` implementation, which does not set any fields.
+/// For added fluency, you may use `with_*` methods:
+///
+/// ```
+/// # use jwt_compact::Header;
+/// use sha2::{digest::Digest, Sha256};
+///
+/// let my_key_cert = // DER-encoded key certificate
+/// # b"Hello, world!";
+/// let thumbprint: [u8; 32] = Sha256::digest(my_key_cert).into();
+/// let header = Header::empty()
+/// .with_key_id("my-key-id")
+/// .with_certificate_thumbprint(thumbprint);
+/// ```
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[non_exhaustive]
+pub struct Header<T = Empty> {
+ /// URL of the JSON Web Key Set containing the key that has signed the token.
+ /// This field is renamed to [`jku`] for serialization.
+ ///
+ /// [`jku`]: https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.2
+ #[serde(rename = "jku", default, skip_serializing_if = "Option::is_none")]
+ pub key_set_url: Option<String>,
+
+ /// Identifier of the key that has signed the token. This field is renamed to [`kid`]
+ /// for serialization.
+ ///
+ /// [`kid`]: https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.4
+ #[serde(rename = "kid", default, skip_serializing_if = "Option::is_none")]
+ pub key_id: Option<String>,
+
+ /// URL of the X.509 certificate for the signing key. This field is renamed to [`x5u`]
+ /// for serialization.
+ ///
+ /// [`x5u`]: https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.5
+ #[serde(rename = "x5u", default, skip_serializing_if = "Option::is_none")]
+ pub certificate_url: Option<String>,
+
+ /// SHA-1 thumbprint of the X.509 certificate for the signing key.
+ /// This field is renamed to [`x5t`] for serialization.
+ ///
+ /// [`x5t`]: https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.7
+ #[serde(rename = "x5t", default, skip_serializing_if = "Option::is_none")]
+ pub certificate_sha1_thumbprint: Option<Thumbprint<20>>,
+
+ /// SHA-256 thumbprint of the X.509 certificate for the signing key.
+ /// This field is renamed to [`x5t#S256`] for serialization.
+ ///
+ /// [`x5t#S256`]: https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.8
+ #[serde(rename = "x5t#S256", default, skip_serializing_if = "Option::is_none")]
+ pub certificate_thumbprint: Option<Thumbprint<32>>,
+
+ /// Application-specific [token type]. This field is renamed to `typ` for serialization.
+ ///
+ /// [token type]: https://tools.ietf.org/html/rfc7519#section-5.1
+ #[serde(rename = "typ", default, skip_serializing_if = "Option::is_none")]
+ pub token_type: Option<String>,
+
+ /// Other fields encoded in the header. These fields may be used by agreement between
+ /// the producer and consumer of the token to pass additional information.
+ /// See Sections 4.2 and 4.3 of [RFC 7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.2)
+ /// for details.
+ ///
+ /// For the token creation and validation to work properly, the fields type must [`Serialize`]
+ /// to a JSON object.
+ ///
+ /// Note that these fields do not include the signing algorithm (`alg`) and the token
+ /// content type (`cty`) since both these fields have predefined semantics and are used
+ /// internally by the crate logic.
+ #[serde(flatten)]
+ pub other_fields: T,
+}
+
+impl Header {
+ /// Creates an empty header.
+ pub const fn empty() -> Self {
+ Self {
+ key_set_url: None,
+ key_id: None,
+ certificate_url: None,
+ certificate_sha1_thumbprint: None,
+ certificate_thumbprint: None,
+ token_type: None,
+ other_fields: Empty {},
+ }
+ }
+}
+
+impl<T> Header<T> {
+ /// Creates a header with the specified custom fields.
+ pub const fn new(fields: T) -> Header<T> {
+ Header {
+ key_set_url: None,
+ key_id: None,
+ certificate_url: None,
+ certificate_sha1_thumbprint: None,
+ certificate_thumbprint: None,
+ token_type: None,
+ other_fields: fields,
+ }
+ }
+
+ /// Sets the `key_set_url` field for this header.
+ #[must_use]
+ pub fn with_key_set_url(mut self, key_set_url: impl Into<String>) -> Self {
+ self.key_set_url = Some(key_set_url.into());
+ self
+ }
+
+ /// Sets the `key_id` field for this header.
+ #[must_use]
+ pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
+ self.key_id = Some(key_id.into());
+ self
+ }
+
+ /// Sets the `certificate_url` field for this header.
+ #[must_use]
+ pub fn with_certificate_url(mut self, certificate_url: impl Into<String>) -> Self {
+ self.certificate_url = Some(certificate_url.into());
+ self
+ }
+
+ /// Sets the `certificate_sha1_thumbprint` field for this header.
+ #[must_use]
+ pub fn with_certificate_sha1_thumbprint(
+ mut self,
+ certificate_thumbprint: impl Into<Thumbprint<20>>,
+ ) -> Self {
+ self.certificate_sha1_thumbprint = Some(certificate_thumbprint.into());
+ self
+ }
+
+ /// Sets the `certificate_thumbprint` field for this header.
+ #[must_use]
+ pub fn with_certificate_thumbprint(
+ mut self,
+ certificate_thumbprint: impl Into<Thumbprint<32>>,
+ ) -> Self {
+ self.certificate_thumbprint = Some(certificate_thumbprint.into());
+ self
+ }
+
+ /// Sets the `token_type` field for this header.
+ #[must_use]
+ pub fn with_token_type(mut self, token_type: impl Into<String>) -> Self {
+ self.token_type = Some(token_type.into());
+ self
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub(crate) struct CompleteHeader<'a, T> {
+ #[serde(rename = "alg")]
+ pub algorithm: Cow<'a, str>,
+ #[serde(rename = "cty", default, skip_serializing_if = "Option::is_none")]
+ pub content_type: Option<String>,
+ #[serde(flatten)]
+ pub inner: T,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum ContentType {
+ Json,
+ #[cfg(feature = "ciborium")]
+ Cbor,
+}
+
+/// Parsed, but unvalidated token.
+///
+/// The type param ([`Empty`] by default) corresponds to the [additional information] enclosed
+/// in the token [`Header`].
+///
+/// An `UntrustedToken` can be parsed from a string using the [`TryFrom`] implementation.
+/// This checks that a token is well-formed (has a header, claims and a signature),
+/// but does not validate the signature.
+/// As a shortcut, a token without additional header info can be created using [`Self::new()`].
+///
+/// [additional information]: Header#other_fields
+///
+/// # Examples
+///
+/// ```
+/// # use jwt_compact::UntrustedToken;
+/// let token_str = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJp\
+/// c3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leG\
+/// FtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJ\
+/// U1p1r_wW1gFWFOEjXk";
+/// let token: UntrustedToken = token_str.try_into()?;
+/// // The same operation using a shortcut:
+/// let same_token = UntrustedToken::new(token_str)?;
+/// // Token header can be accessed to select the verifying key etc.
+/// let key_id: Option<&str> = token.header().key_id.as_deref();
+/// # Ok::<_, anyhow::Error>(())
+/// ```
+///
+/// ## Handling tokens with custom header fields
+///
+/// ```
+/// # use serde::Deserialize;
+/// # use jwt_compact::UntrustedToken;
+/// #[derive(Debug, Clone, Deserialize)]
+/// struct HeaderExtensions {
+/// custom: String,
+/// }
+///
+/// let token_str = "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3Rfa2V5Iiwid\
+/// HlwIjoiSldUIiwiY3VzdG9tIjoiY3VzdG9tIn0.eyJzdWIiOiIxMjM0NTY\
+/// 3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9._27Fb6nF\
+/// Tg-HSt3vO4ylaLGcU_ZV2VhMJR4HL7KaQik";
+/// let token: UntrustedToken<HeaderExtensions> = token_str.try_into()?;
+/// let extensions = &token.header().other_fields;
+/// println!("{}", extensions.custom);
+/// # Ok::<_, anyhow::Error>(())
+/// ```
+#[derive(Debug, Clone)]
+pub struct UntrustedToken<'a, H = Empty> {
+ pub(crate) signed_data: Cow<'a, [u8]>,
+ header: Header<H>,
+ algorithm: String,
+ content_type: ContentType,
+ serialized_claims: Vec<u8>,
+ signature: SmallVec<[u8; SIGNATURE_SIZE]>,
+}
+
+/// Token with validated integrity.
+///
+/// Claims encoded in the token can be verified by invoking [`Claims`] methods
+/// via [`Self::claims()`].
+#[derive(Debug, Clone)]
+pub struct Token<T, H = Empty> {
+ header: Header<H>,
+ claims: Claims<T>,
+}
+
+impl<T, H> Token<T, H> {
+ pub(crate) fn new(header: Header<H>, claims: Claims<T>) -> Self {
+ Self { header, claims }
+ }
+
+ /// Gets token header.
+ pub fn header(&self) -> &Header<H> {
+ &self.header
+ }
+
+ /// Gets token claims.
+ pub fn claims(&self) -> &Claims<T> {
+ &self.claims
+ }
+
+ /// Splits the `Token` into the respective `Header` and `Claims` while consuming it.
+ pub fn into_parts(self) -> (Header<H>, Claims<T>) {
+ (self.header, self.claims)
+ }
+}
+
+/// `Token` together with the validated token signature.
+///
+/// # Examples
+///
+/// ```
+/// # use jwt_compact::{alg::{Hs256, Hs256Key, Hs256Signature}, prelude::*};
+/// # use chrono::Duration;
+/// # use serde::{Deserialize, Serialize};
+/// #
+/// #[derive(Serialize, Deserialize)]
+/// struct MyClaims {
+/// // Custom claims in the token...
+/// }
+///
+/// # fn main() -> anyhow::Result<()> {
+/// # let key = Hs256Key::new(b"super_secret_key");
+/// # let claims = Claims::new(MyClaims {})
+/// # .set_duration_and_issuance(&TimeOptions::default(), Duration::days(7));
+/// let token_string: String = // token from an external source
+/// # Hs256.token(&Header::empty(), &claims, &key)?;
+/// let token = UntrustedToken::new(&token_string)?;
+/// let signed = Hs256.validator::<MyClaims>(&key)
+/// .validate_for_signed_token(&token)?;
+///
+/// // `signature` is strongly typed.
+/// let signature: Hs256Signature = signed.signature;
+/// // Token itself is available via `token` field.
+/// let claims = signed.token.claims();
+/// claims.validate_expiration(&TimeOptions::default())?;
+/// // Process the claims...
+/// # Ok(())
+/// # } // end main()
+/// ```
+#[non_exhaustive]
+pub struct SignedToken<A: Algorithm + ?Sized, T, H = Empty> {
+ /// Token signature.
+ pub signature: A::Signature,
+ /// Verified token.
+ pub token: Token<T, H>,
+}
+
+impl<A, T, H> fmt::Debug for SignedToken<A, T, H>
+where
+ A: Algorithm,
+ A::Signature: fmt::Debug,
+ T: fmt::Debug,
+ H: fmt::Debug,
+{
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("SignedToken")
+ .field("token", &self.token)
+ .field("signature", &self.signature)
+ .finish()
+ }
+}
+
+impl<A, T, H> Clone for SignedToken<A, T, H>
+where
+ A: Algorithm,
+ A::Signature: Clone,
+ T: Clone,
+ H: Clone,
+{
+ fn clone(&self) -> Self {
+ Self {
+ signature: self.signature.clone(),
+ token: self.token.clone(),
+ }
+ }
+}
+
+impl<'a, H: DeserializeOwned> TryFrom<&'a str> for UntrustedToken<'a, H> {
+ type Error = ParseError;
+
+ fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+ let token_parts: Vec<_> = s.splitn(4, '.').collect();
+ match &token_parts[..] {
+ [header, claims, signature] => {
+ let header = Base64UrlUnpadded::decode_vec(header)
+ .map_err(|_| ParseError::InvalidBase64Encoding)?;
+ let serialized_claims = Base64UrlUnpadded::decode_vec(claims)
+ .map_err(|_| ParseError::InvalidBase64Encoding)?;
+
+ let mut decoded_signature = smallvec![0; 3 * (signature.len() + 3) / 4];
+ let signature_len =
+ Base64UrlUnpadded::decode(signature, &mut decoded_signature[..])
+ .map_err(|_| ParseError::InvalidBase64Encoding)?
+ .len();
+ decoded_signature.truncate(signature_len);
+
+ let header: CompleteHeader<_> =
+ serde_json::from_slice(&header).map_err(ParseError::MalformedHeader)?;
+ let content_type = match header.content_type {
+ None => ContentType::Json,
+ Some(s) if s.eq_ignore_ascii_case("json") => ContentType::Json,
+ #[cfg(feature = "ciborium")]
+ Some(s) if s.eq_ignore_ascii_case("cbor") => ContentType::Cbor,
+ Some(s) => return Err(ParseError::UnsupportedContentType(s)),
+ };
+ let signed_data = s.rsplit_once('.').unwrap().0.as_bytes();
+ Ok(Self {
+ signed_data: Cow::Borrowed(signed_data),
+ header: header.inner,
+ algorithm: header.algorithm.into_owned(),
+ content_type,
+ serialized_claims,
+ signature: decoded_signature,
+ })
+ }
+ _ => Err(ParseError::InvalidTokenStructure),
+ }
+ }
+}
+
+impl<'a> UntrustedToken<'a> {
+ /// Creates an untrusted token from a string. This is a shortcut for calling the [`TryFrom`]
+ /// conversion.
+ pub fn new<S: AsRef<str> + ?Sized>(s: &'a S) -> Result<Self, ParseError> {
+ Self::try_from(s.as_ref())
+ }
+}
+
+impl<H> UntrustedToken<'_, H> {
+ /// Converts this token to an owned form.
+ pub fn into_owned(self) -> UntrustedToken<'static, H> {
+ UntrustedToken {
+ signed_data: Cow::Owned(self.signed_data.into_owned()),
+ header: self.header,
+ algorithm: self.algorithm,
+ content_type: self.content_type,
+ serialized_claims: self.serialized_claims,
+ signature: self.signature,
+ }
+ }
+
+ /// Gets the token header.
+ pub fn header(&self) -> &Header<H> {
+ &self.header
+ }
+
+ /// Gets the integrity algorithm used to secure the token.
+ pub fn algorithm(&self) -> &str {
+ &self.algorithm
+ }
+
+ /// Returns signature bytes from the token. These bytes are **not** guaranteed to form a valid
+ /// signature.
+ pub fn signature_bytes(&self) -> &[u8] {
+ &self.signature
+ }
+
+ /// Deserializes claims from this token without checking token integrity. The resulting
+ /// claims are thus **not** guaranteed to be valid.
+ pub fn deserialize_claims_unchecked<T>(&self) -> Result<Claims<T>, ValidationError>
+ where
+ T: DeserializeOwned,
+ {
+ match self.content_type {
+ ContentType::Json => serde_json::from_slice(&self.serialized_claims)
+ .map_err(ValidationError::MalformedClaims),
+
+ #[cfg(feature = "ciborium")]
+ ContentType::Cbor => {
+ ciborium::from_reader(&self.serialized_claims[..]).map_err(|err| {
+ ValidationError::MalformedCborClaims(match err {
+ CborDeError::Io(err) => CborDeError::Io(anyhow::anyhow!(err)),
+ // ^ In order to be able to use `anyhow!` in both std and no-std envs,
+ // we inline the error transform directly here.
+ CborDeError::Syntax(offset) => CborDeError::Syntax(offset),
+ CborDeError::Semantic(offset, description) => {
+ CborDeError::Semantic(offset, description)
+ }
+ CborDeError::RecursionLimitExceeded => CborDeError::RecursionLimitExceeded,
+ })
+ })
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use assert_matches::assert_matches;
+ use base64ct::{Base64UrlUnpadded, Encoding};
+
+ use super::*;
+ use crate::{
+ alg::{Hs256, Hs256Key},
+ alloc::{ToOwned, ToString},
+ AlgorithmExt, Empty,
+ };
+
+ type Obj = serde_json::Map<String, serde_json::Value>;
+
+ const HS256_TOKEN: &str = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.\
+ eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
+ cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.\
+ dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
+ const HS256_KEY: &str = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75\
+ aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
+
+ #[test]
+ fn invalid_token_structure() {
+ let mangled_str = HS256_TOKEN.replace('.', "");
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::InvalidTokenStructure
+ );
+
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ let signature_start = mangled_str.rfind('.').unwrap();
+ mangled_str.truncate(signature_start);
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::InvalidTokenStructure
+ );
+
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ mangled_str.push('.');
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::InvalidTokenStructure
+ );
+ }
+
+ #[test]
+ fn base64_error_during_parsing() {
+ let mangled_str = HS256_TOKEN.replace('0', "+");
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::InvalidBase64Encoding
+ );
+ }
+
+ #[test]
+ fn base64_padding_error_during_parsing() {
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ mangled_str.pop();
+ mangled_str.push('_'); // leads to non-zero padding for the last encoded byte
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::InvalidBase64Encoding
+ );
+ }
+
+ #[test]
+ fn header_fields_are_not_serialized_if_not_present() {
+ let header = Header::empty();
+ let json = serde_json::to_string(&header).unwrap();
+ assert_eq!(json, "{}");
+ }
+
+ #[test]
+ fn header_with_x5t_field() {
+ let header = r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#;
+ let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
+ let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
+ let Thumbprint::Bytes(thumbprint) = thumbprint else {
+ unreachable!();
+ };
+
+ assert_eq!(thumbprint[0], 0x94);
+ assert_eq!(thumbprint[19], 0x99);
+
+ let json = serde_json::to_value(header).unwrap();
+ assert_eq!(
+ json,
+ serde_json::json!({
+ "alg": "HS256",
+ "x5t": "lDpwLQbzRZmu4fjajvn3KWAx1pk",
+ })
+ );
+ }
+
+ #[test]
+ fn header_with_padded_x5t_field() {
+ let header = r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk=="}"#;
+ let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
+ let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
+ let Thumbprint::Bytes(thumbprint) = thumbprint else {
+ unreachable!()
+ };
+
+ assert_eq!(thumbprint[0], 0x94);
+ assert_eq!(thumbprint[19], 0x99);
+ }
+
+ #[test]
+ fn header_with_hex_x5t_field() {
+ let header =
+ r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg"}"#;
+ let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
+ let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
+ let Thumbprint::String(thumbprint) = thumbprint else {
+ unreachable!()
+ };
+
+ assert_eq!(thumbprint, "65AF6909B1B0758E06C6E048C46002B5C695E36B");
+
+ let json = serde_json::to_value(header).unwrap();
+ assert_eq!(
+ json,
+ serde_json::json!({
+ "alg": "HS256",
+ "x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg",
+ })
+ );
+ }
+
+ #[test]
+ fn header_with_padded_hex_x5t_field() {
+ let header =
+ r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg=="}"#;
+ let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
+ let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
+ let Thumbprint::String(thumbprint) = thumbprint else {
+ unreachable!()
+ };
+
+ assert_eq!(thumbprint, "65AF6909B1B0758E06C6E048C46002B5C695E36B");
+ }
+
+ #[test]
+ fn header_with_overly_short_x5t_field() {
+ let header = r#"{"alg":"HS256","x5t":"aGk="}"#;
+ let err = serde_json::from_str::<CompleteHeader<Header<Empty>>>(header).unwrap_err();
+ let err = err.to_string();
+ assert!(
+ err.contains("thumbprint must contain at least 20 bytes"),
+ "{err}"
+ );
+ }
+
+ #[test]
+ fn header_with_non_base64_x5t_field() {
+ let headers = [
+ r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1p?"}"#,
+ r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk!RTM2Qg"}"#,
+ ];
+ for header in headers {
+ let err = serde_json::from_str::<CompleteHeader<Header<Empty>>>(header).unwrap_err();
+ let err = err.to_string();
+ assert!(err.contains("Base64"), "{err}");
+ }
+ }
+
+ #[test]
+ fn header_with_x5t_sha256_field() {
+ let header = r#"{"alg":"HS256","x5t#S256":"MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM"}"#;
+ let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
+ let thumbprint = header.inner.certificate_thumbprint.as_ref().unwrap();
+ let Thumbprint::Bytes(thumbprint) = thumbprint else {
+ unreachable!()
+ };
+
+ assert_eq!(thumbprint[0], 0x31);
+ assert_eq!(thumbprint[31], 0xd3);
+
+ let json = serde_json::to_value(header).unwrap();
+ assert_eq!(
+ json,
+ serde_json::json!({
+ "alg": "HS256",
+ "x5t#S256": "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM",
+ })
+ );
+ }
+
+ #[test]
+ fn malformed_header() {
+ let mangled_headers = [
+ // Missing closing brace
+ r#"{"alg":"HS256""#,
+ // Missing necessary `alg` field
+ "{}",
+ // `alg` field is not a string
+ r#"{"alg":5}"#,
+ r#"{"alg":[1,"foo"]}"#,
+ r#"{"alg":false}"#,
+ // Duplicate `alg` field
+ r#"{"alg":"HS256","alg":"none"}"#,
+ // Invalid thumbprint fields
+ r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1p"}"#,
+ r#"{"alg":"HS256","x5t":["lDpwLQbzRZmu4fjajvn3KWAx1pk"]}"#,
+ r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1 k"}"#,
+ r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk==="}"#,
+ r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pkk"}"#,
+ r#"{"alg":"HS256","x5t":"MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM"}"#,
+ r#"{"alg":"HS256","x5t#S256":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#,
+ ];
+
+ for mangled_header in &mangled_headers {
+ let mangled_header = Base64UrlUnpadded::encode_string(mangled_header.as_bytes());
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ mangled_str.replace_range(..mangled_str.find('.').unwrap(), &mangled_header);
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::MalformedHeader(_)
+ );
+ }
+ }
+
+ #[test]
+ fn unsupported_content_type() {
+ let mangled_header = br#"{"alg":"HS256","cty":"txt"}"#;
+ let mangled_header = Base64UrlUnpadded::encode_string(mangled_header);
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ mangled_str.replace_range(..mangled_str.find('.').unwrap(), &mangled_header);
+ assert_matches!(
+ UntrustedToken::new(&mangled_str).unwrap_err(),
+ ParseError::UnsupportedContentType(s) if s == "txt"
+ );
+ }
+
+ #[test]
+ fn extracting_custom_header_fields() {
+ let header = r#"{"alg":"HS256","custom":[1,"field"],"x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#;
+ let header: CompleteHeader<Header<Obj>> = serde_json::from_str(header).unwrap();
+ assert_eq!(header.algorithm, "HS256");
+ assert!(header.inner.certificate_sha1_thumbprint.is_some());
+ assert_eq!(header.inner.other_fields.len(), 1);
+ assert!(header.inner.other_fields["custom"].is_array());
+ }
+
+ #[test]
+ fn malformed_json_claims() {
+ let malformed_claims = [
+ // Missing closing brace
+ r#"{"exp":1500000000"#,
+ // `exp` claim is not a number
+ r#"{"exp":"1500000000"}"#,
+ r#"{"exp":false}"#,
+ // Duplicate `exp` claim
+ r#"{"exp":1500000000,"nbf":1400000000,"exp":1510000000}"#,
+ // Too large `exp` value
+ r#"{"exp":1500000000000000000000000000000000}"#,
+ ];
+
+ let claims_start = HS256_TOKEN.find('.').unwrap() + 1;
+ let claims_end = HS256_TOKEN.rfind('.').unwrap();
+ let key = Base64UrlUnpadded::decode_vec(HS256_KEY).unwrap();
+ let key = Hs256Key::new(key);
+
+ for claims in &malformed_claims {
+ let encoded_claims = Base64UrlUnpadded::encode_string(claims.as_bytes());
+ let mut mangled_str = HS256_TOKEN.to_owned();
+ mangled_str.replace_range(claims_start..claims_end, &encoded_claims);
+ let token = UntrustedToken::new(&mangled_str).unwrap();
+ assert_matches!(
+ Hs256.validator::<Obj>(&key).validate(&token).unwrap_err(),
+ ValidationError::MalformedClaims(_),
+ "Failing claims: {claims}"
+ );
+ }
+ }
+
+ fn test_invalid_signature_len(mangled_str: &str, actual_len: usize) {
+ let token = UntrustedToken::new(&mangled_str).unwrap();
+ let key = Base64UrlUnpadded::decode_vec(HS256_KEY).unwrap();
+ let key = Hs256Key::new(key);
+
+ let err = Hs256.validator::<Empty>(&key).validate(&token).unwrap_err();
+ assert_matches!(
+ err,
+ ValidationError::InvalidSignatureLen { actual, expected: 32 }
+ if actual == actual_len
+ );
+ }
+
+ #[test]
+ fn short_signature_error() {
+ test_invalid_signature_len(&HS256_TOKEN[..HS256_TOKEN.len() - 3], 30);
+ }
+
+ #[test]
+ fn long_signature_error() {
+ let mut mangled_string = HS256_TOKEN.to_owned();
+ mangled_string.push('a');
+ test_invalid_signature_len(&mangled_string, 33);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +
//! Key traits defined by the crate.
+
+use base64ct::{Base64UrlUnpadded, Encoding};
+use serde::{de::DeserializeOwned, Serialize};
+
+use core::{marker::PhantomData, num::NonZeroUsize};
+
+#[cfg(feature = "ciborium")]
+use crate::error::CborSerError;
+use crate::{
+ alloc::{Cow, String, ToOwned, Vec},
+ token::CompleteHeader,
+ Claims, CreationError, Header, SignedToken, Token, UntrustedToken, ValidationError,
+};
+
+/// Signature for a certain JWT signing [`Algorithm`].
+///
+/// We require that signature can be restored from a byte slice,
+/// and can be represented as a byte slice.
+pub trait AlgorithmSignature: Sized {
+ /// Constant byte length of signatures supported by the [`Algorithm`], or `None` if
+ /// the signature length is variable.
+ ///
+ /// - If this value is `Some(_)`, the signature will be first checked for its length
+ /// during token verification. An [`InvalidSignatureLen`] error will be raised if the length
+ /// is invalid. [`Self::try_from_slice()`] will thus always receive a slice with
+ /// the expected length.
+ /// - If this value is `None`, no length check is performed before calling
+ /// [`Self::try_from_slice()`].
+ ///
+ /// [`InvalidSignatureLen`]: crate::ValidationError::InvalidSignatureLen
+ const LENGTH: Option<NonZeroUsize> = None;
+
+ /// Attempts to restore a signature from a byte slice. This method may fail
+ /// if the slice is malformed.
+ fn try_from_slice(slice: &[u8]) -> anyhow::Result<Self>;
+
+ /// Represents this signature as bytes.
+ fn as_bytes(&self) -> Cow<'_, [u8]>;
+}
+
+/// JWT signing algorithm.
+pub trait Algorithm {
+ /// Key used when issuing new tokens.
+ type SigningKey;
+ /// Key used when verifying tokens. May coincide with [`Self::SigningKey`] for symmetric
+ /// algorithms (e.g., `HS*`).
+ type VerifyingKey;
+ /// Signature produced by the algorithm.
+ type Signature: AlgorithmSignature;
+
+ /// Returns the name of this algorithm, as mentioned in the `alg` field of the JWT header.
+ fn name(&self) -> Cow<'static, str>;
+
+ /// Signs a `message` with the `signing_key`.
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature;
+
+ /// Verifies the `message` against the `signature` and `verifying_key`.
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool;
+}
+
+/// Algorithm that uses a custom name when creating and validating tokens.
+///
+/// # Examples
+///
+/// ```
+/// use jwt_compact::{alg::{Hs256, Hs256Key}, prelude::*, Empty, Renamed};
+///
+/// # fn main() -> anyhow::Result<()> {
+/// let alg = Renamed::new(Hs256, "HS2");
+/// let key = Hs256Key::new(b"super_secret_key_donut_steel");
+/// let token_string = alg.token(&Header::empty(), &Claims::empty(), &key)?;
+///
+/// let token = UntrustedToken::new(&token_string)?;
+/// assert_eq!(token.algorithm(), "HS2");
+/// // Note that the created token cannot be verified against the original algorithm
+/// // since the algorithm name recorded in the token header doesn't match.
+/// assert!(Hs256.validator::<Empty>(&key).validate(&token).is_err());
+///
+/// // ...but the modified alg is working as expected.
+/// assert!(alg.validator::<Empty>(&key).validate(&token).is_ok());
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone, Copy)]
+pub struct Renamed<A> {
+ inner: A,
+ name: &'static str,
+}
+
+impl<A: Algorithm> Renamed<A> {
+ /// Creates a renamed algorithm.
+ pub fn new(algorithm: A, new_name: &'static str) -> Self {
+ Self {
+ inner: algorithm,
+ name: new_name,
+ }
+ }
+}
+
+impl<A: Algorithm> Algorithm for Renamed<A> {
+ type SigningKey = A::SigningKey;
+ type VerifyingKey = A::VerifyingKey;
+ type Signature = A::Signature;
+
+ fn name(&self) -> Cow<'static, str> {
+ Cow::Borrowed(self.name)
+ }
+
+ fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
+ self.inner.sign(signing_key, message)
+ }
+
+ fn verify_signature(
+ &self,
+ signature: &Self::Signature,
+ verifying_key: &Self::VerifyingKey,
+ message: &[u8],
+ ) -> bool {
+ self.inner
+ .verify_signature(signature, verifying_key, message)
+ }
+}
+
+/// Automatically implemented extensions of the `Algorithm` trait.
+pub trait AlgorithmExt: Algorithm {
+ /// Creates a new token and serializes it to string.
+ fn token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey,
+ ) -> Result<String, CreationError>
+ where
+ T: Serialize;
+
+ /// Creates a new token with CBOR-encoded claims and serializes it to string.
+ #[cfg(feature = "ciborium")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "ciborium")))]
+ fn compact_token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey,
+ ) -> Result<String, CreationError>
+ where
+ T: Serialize;
+
+ /// Creates a JWT validator for the specified verifying key and the claims type.
+ /// The validator can then be used to validate integrity of one or more tokens.
+ fn validator<'a, T>(&'a self, verifying_key: &'a Self::VerifyingKey) -> Validator<'a, Self, T>;
+
+ /// Validates the token integrity against the provided `verifying_key`.
+ #[deprecated = "Use `.validator().validate()` for added flexibility"]
+ fn validate_integrity<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey,
+ ) -> Result<Token<T>, ValidationError>
+ where
+ T: DeserializeOwned;
+
+ /// Validates the token integrity against the provided `verifying_key`.
+ ///
+ /// Unlike [`validate_integrity`](#tymethod.validate_integrity), this method retains more
+ /// information about the original token, in particular, its signature.
+ #[deprecated = "Use `.validator().validate_for_signed_token()` for added flexibility"]
+ fn validate_for_signed_token<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey,
+ ) -> Result<SignedToken<Self, T>, ValidationError>
+ where
+ T: DeserializeOwned;
+}
+
+impl<A: Algorithm> AlgorithmExt for A {
+ fn token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey,
+ ) -> Result<String, CreationError>
+ where
+ T: Serialize,
+ {
+ let complete_header = CompleteHeader {
+ algorithm: self.name(),
+ content_type: None,
+ inner: header,
+ };
+ let header = serde_json::to_string(&complete_header).map_err(CreationError::Header)?;
+ let mut buffer = Vec::new();
+ encode_base64_buf(&header, &mut buffer);
+
+ let claims = serde_json::to_string(claims).map_err(CreationError::Claims)?;
+ buffer.push(b'.');
+ encode_base64_buf(&claims, &mut buffer);
+
+ let signature = self.sign(signing_key, &buffer);
+ buffer.push(b'.');
+ encode_base64_buf(signature.as_bytes(), &mut buffer);
+
+ // SAFETY: safe by construction: base64 alphabet and `.` char are valid UTF-8.
+ Ok(unsafe { String::from_utf8_unchecked(buffer) })
+ }
+
+ #[cfg(feature = "ciborium")]
+ fn compact_token<T>(
+ &self,
+ header: &Header<impl Serialize>,
+ claims: &Claims<T>,
+ signing_key: &Self::SigningKey,
+ ) -> Result<String, CreationError>
+ where
+ T: Serialize,
+ {
+ let complete_header = CompleteHeader {
+ algorithm: self.name(),
+ content_type: Some("CBOR".to_owned()),
+ inner: header,
+ };
+ let header = serde_json::to_string(&complete_header).map_err(CreationError::Header)?;
+ let mut buffer = Vec::new();
+ encode_base64_buf(&header, &mut buffer);
+
+ let mut serialized_claims = vec![];
+ ciborium::into_writer(claims, &mut serialized_claims).map_err(|err| {
+ CreationError::CborClaims(match err {
+ CborSerError::Value(message) => CborSerError::Value(message),
+ CborSerError::Io(_) => unreachable!(), // writing to a `Vec` always succeeds
+ })
+ })?;
+ buffer.push(b'.');
+ encode_base64_buf(&serialized_claims, &mut buffer);
+
+ let signature = self.sign(signing_key, &buffer);
+ buffer.push(b'.');
+ encode_base64_buf(signature.as_bytes(), &mut buffer);
+
+ // SAFETY: safe by construction: base64 alphabet and `.` char are valid UTF-8.
+ Ok(unsafe { String::from_utf8_unchecked(buffer) })
+ }
+
+ fn validator<'a, T>(&'a self, verifying_key: &'a Self::VerifyingKey) -> Validator<'a, Self, T> {
+ Validator {
+ algorithm: self,
+ verifying_key,
+ _claims: PhantomData,
+ }
+ }
+
+ fn validate_integrity<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey,
+ ) -> Result<Token<T>, ValidationError>
+ where
+ T: DeserializeOwned,
+ {
+ self.validator::<T>(verifying_key).validate(token)
+ }
+
+ fn validate_for_signed_token<T>(
+ &self,
+ token: &UntrustedToken<'_>,
+ verifying_key: &Self::VerifyingKey,
+ ) -> Result<SignedToken<Self, T>, ValidationError>
+ where
+ T: DeserializeOwned,
+ {
+ self.validator::<T>(verifying_key)
+ .validate_for_signed_token(token)
+ }
+}
+
+/// Validator for a certain signing [`Algorithm`] associated with a specific verifying key
+/// and a claims type. Produced by the [`AlgorithmExt::validator()`] method.
+#[derive(Debug)]
+pub struct Validator<'a, A: Algorithm + ?Sized, T> {
+ algorithm: &'a A,
+ verifying_key: &'a A::VerifyingKey,
+ _claims: PhantomData<fn() -> T>,
+}
+
+impl<A: Algorithm + ?Sized, T> Clone for Validator<'_, A, T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<A: Algorithm + ?Sized, T> Copy for Validator<'_, A, T> {}
+
+impl<A: Algorithm + ?Sized, T: DeserializeOwned> Validator<'_, A, T> {
+ /// Validates the token integrity against a verifying key enclosed in this validator.
+ pub fn validate<H: Clone>(
+ self,
+ token: &UntrustedToken<'_, H>,
+ ) -> Result<Token<T, H>, ValidationError> {
+ self.validate_for_signed_token(token)
+ .map(|signed| signed.token)
+ }
+
+ /// Validates the token integrity against a verifying key enclosed in this validator,
+ /// and returns the validated [`Token`] together with its signature.
+ pub fn validate_for_signed_token<H: Clone>(
+ self,
+ token: &UntrustedToken<'_, H>,
+ ) -> Result<SignedToken<A, T, H>, ValidationError> {
+ let expected_alg = self.algorithm.name();
+ if expected_alg != token.algorithm() {
+ return Err(ValidationError::AlgorithmMismatch {
+ expected: expected_alg.into_owned(),
+ actual: token.algorithm().to_owned(),
+ });
+ }
+
+ let signature = token.signature_bytes();
+ if let Some(expected_len) = A::Signature::LENGTH {
+ if signature.len() != expected_len.get() {
+ return Err(ValidationError::InvalidSignatureLen {
+ expected: expected_len.get(),
+ actual: signature.len(),
+ });
+ }
+ }
+
+ let signature =
+ A::Signature::try_from_slice(signature).map_err(ValidationError::MalformedSignature)?;
+ // We assume that parsing claims is less computationally demanding than
+ // validating a signature.
+ let claims = token.deserialize_claims_unchecked::<T>()?;
+ if !self
+ .algorithm
+ .verify_signature(&signature, self.verifying_key, &token.signed_data)
+ {
+ return Err(ValidationError::InvalidSignature);
+ }
+
+ Ok(SignedToken {
+ signature,
+ token: Token::new(token.header().clone(), claims),
+ })
+ }
+}
+
+fn encode_base64_buf(source: impl AsRef<[u8]>, buffer: &mut Vec<u8>) {
+ let source = source.as_ref();
+ let previous_len = buffer.len();
+ let claims_len = Base64UrlUnpadded::encoded_len(source);
+ buffer.resize(previous_len + claims_len, 0);
+ Base64UrlUnpadded::encode(source, &mut buffer[previous_len..])
+ .expect("miscalculated base64-encoded length; this should never happen");
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`