feat: Add initial public API skeleton
This commit is contained in:
		
							parent
							
								
									d0a52de5e8
								
							
						
					
					
						commit
						0f8231e990
					
				
					 3 changed files with 200 additions and 0 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| 
 | ||||
| /target/ | ||||
| **/*.rs.bk | ||||
| Cargo.lock | ||||
							
								
								
									
										11
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| [package] | ||||
| name = "alcoholic_jwt" | ||||
| version = "0.1.0" | ||||
| authors = ["Vincent Ambo <vincent@aprila.no>"] | ||||
| 
 | ||||
| [dependencies] | ||||
| openssl = "0.10" | ||||
| serde = "1.0" | ||||
| serde_json = "1.0" | ||||
| serde_derive = "1.0" | ||||
| base64 = "0.9" | ||||
							
								
								
									
										185
									
								
								src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,185 @@ | |||
| //! Implements a library for verifying JSON Web Tokens using the
 | ||||
| //! `RS256` signature algorithm.
 | ||||
| //!
 | ||||
| //! This library is specifically aimed at developers that consume
 | ||||
| //! tokens from services which provide their RSA public keys in
 | ||||
| //! [JWKS][] format.
 | ||||
| //!
 | ||||
| //! ## Usage example (token with `kid`-claim)
 | ||||
| //!
 | ||||
| //! ```rust
 | ||||
| //! extern crate alcoholic_jwt;
 | ||||
| //!
 | ||||
| //! use alcoholic_jwt::{JWKS, Validation, validate, token_kid};
 | ||||
| //!
 | ||||
| //! fn validate_token() {
 | ||||
| //!     // serde instances provided
 | ||||
| //!     let jwks: JWKS = some_http_client(jwks_url).json();
 | ||||
| //!
 | ||||
| //!     let token: String = some_token_fetcher();
 | ||||
| //!
 | ||||
| //!     // Several types of built-in validations are provided:
 | ||||
| //!     let validations = vec![
 | ||||
| //!       Validation::Issuer("some-issuer"),
 | ||||
| //!       Validation::Audience("some-audience"),
 | ||||
| //!       Validation::SubjectPresent,
 | ||||
| //!     ];
 | ||||
| //!
 | ||||
| //!     // Extracting a KID is about the only safe operation that can be
 | ||||
| //!     // done on a JWT before validating it.
 | ||||
| //!     let kid = token_kid(token).expect("No 'kid' claim present in token");
 | ||||
| //!
 | ||||
| //!     let jwk = jwks.find(kid).expect("Specified key not found in set");
 | ||||
| //!
 | ||||
| //!     match validate(token, jwk, validations) {
 | ||||
| //!       Valid => println!("Token is valid!"),
 | ||||
| //!       InvalidSignature(reason) => println!("Token signature invalid: {}", reason),
 | ||||
| //!       InvalidClaims(reasons) => {
 | ||||
| //!           println!("Token claims are totally invalid!");
 | ||||
| //!           for reason in reasons {
 | ||||
| //!               println!("Validation failure: {}", reason);
 | ||||
| //!           }
 | ||||
| //!       },
 | ||||
| //!     }
 | ||||
| //! }
 | ||||
| //! ```
 | ||||
| //!
 | ||||
| //! [JWKS]: https://tools.ietf.org/html/rfc7517
 | ||||
| 
 | ||||
| #[macro_use] extern crate serde_derive; | ||||
| 
 | ||||
| extern crate base64; | ||||
| extern crate openssl; | ||||
| extern crate serde; | ||||
| extern crate serde_json; | ||||
| 
 | ||||
| use base64::{decode_config, URL_SAFE}; | ||||
| use openssl::bn::BigNum; | ||||
| use openssl::pkey::Public; | ||||
| use openssl::rsa::{Rsa}; | ||||
| 
 | ||||
| /// JWT algorithm used. The only supported algorithm is currently
 | ||||
| /// RS256.
 | ||||
| #[derive(Deserialize, Debug)] | ||||
| enum KeyAlgorithm { RS256 } | ||||
| 
 | ||||
| /// Type of key contained in a JWT. The only supported key type is
 | ||||
| /// currently RSA.
 | ||||
| #[derive(Deserialize, Debug)] | ||||
| enum KeyType { RSA } | ||||
| 
 | ||||
| /// Representation of a single JSON Web Key. See [RFC
 | ||||
| /// 7517](https://tools.ietf.org/html/rfc7517#section-4).
 | ||||
| #[derive(Deserialize)] | ||||
| pub struct JWK { | ||||
|     kty: KeyType, | ||||
|     alg: Option<KeyAlgorithm>, | ||||
|     kid: Option<String>, | ||||
| 
 | ||||
|     // Shared modulus
 | ||||
|     n: String, | ||||
| 
 | ||||
|     // Public key exponent
 | ||||
|     e: String, | ||||
| } | ||||
| 
 | ||||
| /// Representation of a collection ("set") of JSON Web Keys. See
 | ||||
| /// [RFC 7517](https://tools.ietf.org/html/rfc7517#section-5).
 | ||||
| pub struct JWKS { | ||||
|     // This is a vector instead of some kind of map-like structure
 | ||||
|     // because key IDs are in fact optional.
 | ||||
|     //
 | ||||
|     // Technically having multiple keys with the same KID would not
 | ||||
|     // violate the JWKS-definition either, but behaviour in that case
 | ||||
|     // is unspecified.
 | ||||
|     keys: Vec<JWK>, | ||||
| } | ||||
| 
 | ||||
| impl JWKS { | ||||
|     /// Attempt to find a JWK by its key ID.
 | ||||
|     pub fn find(&self, kid: &str) -> Option<&JWK> { | ||||
|         self.keys.iter().find(|jwk| jwk.kid == Some(kid.into())) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Representation of a JSON Web Token. See [RFC
 | ||||
| /// 7519](https://tools.ietf.org/html/rfc7519).
 | ||||
| pub struct JWT {} | ||||
| 
 | ||||
| /// Possible token claim validations. This enumeration only covers
 | ||||
| /// common use-cases, for other types of validations the user is
 | ||||
| /// encouraged to inspect the claim set manually.
 | ||||
| pub enum Validation {} | ||||
| 
 | ||||
| /// Possible results of a token validation.
 | ||||
| pub enum ValidationResult { | ||||
|     /// Signature and claim validation succeeded.
 | ||||
|     Valid, | ||||
| 
 | ||||
|     /// Decoding of the provided JWK failed.
 | ||||
|     InvalidJWK(String), | ||||
| 
 | ||||
|     /// Signature validation failed, i.e. because of a non-matching
 | ||||
|     /// public key.
 | ||||
|     InvalidSignature, | ||||
| 
 | ||||
|     /// One or more claim validations failed.
 | ||||
|     // TODO: Provide reasons?
 | ||||
|     InvalidClaims, | ||||
| } | ||||
| 
 | ||||
| /// Attempt to extract the `kid`-claim out of a JWT's header claims.
 | ||||
| ///
 | ||||
| /// This function is normally used when a token provider has multiple
 | ||||
| /// public keys in rotation at the same time that could all still have
 | ||||
| /// valid tokens issued under them.
 | ||||
| ///
 | ||||
| /// This is only safe if the key set containing the currently allowed
 | ||||
| /// key IDs is fetched from a trusted source.
 | ||||
| pub fn token_kid(jwt: JWT) -> Option<String> { | ||||
|     unimplemented!() | ||||
| } | ||||
| 
 | ||||
| /// Validate the signature of a JSON Web Token and optionally apply
 | ||||
| /// claim validations. Signatures are always verified before claims,
 | ||||
| /// and if a signature verification passes *all* claim validations are
 | ||||
| /// run and returned.
 | ||||
| ///
 | ||||
| /// It is the user's task to ensure that the correct JWK is passed in
 | ||||
| /// for validation.
 | ||||
| pub fn validate(jwt: JWT, jwk: JWK, validations: Vec<Validation>) -> ValidationResult { | ||||
|     unimplemented!() | ||||
| } | ||||
| 
 | ||||
| // Internal implementation
 | ||||
| //
 | ||||
| // The functions in the following section are not part of the public
 | ||||
| // API of this library.
 | ||||
| 
 | ||||
| /// Decode a single key fragment to an OpenSSL BigNum.
 | ||||
| fn decode_fragment(fragment: &str) -> Option<BigNum> { | ||||
|     let bytes = decode_config(fragment, URL_SAFE).ok()?; | ||||
|     BigNum::from_slice(&bytes).ok() | ||||
| } | ||||
| 
 | ||||
| /// Decode an RSA public key from a JWK by constructing it directly
 | ||||
| /// from the public RSA key fragments.
 | ||||
| fn public_key_from_jwk(jwk: &JWK) -> Option<Rsa<Public>> { | ||||
|     let jwk_n = decode_fragment(&jwk.n)?; | ||||
|     let jwk_e = decode_fragment(&jwk.e)?; | ||||
|     Rsa::from_public_components(jwk_n, jwk_e).ok() | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_fragment_decoding() { | ||||
|         let fragment = "ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ"; | ||||
|         let bignum = decode_fragment(fragment).expect("Failed to decode fragment"); | ||||
| 
 | ||||
|         let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289"; | ||||
|         assert_eq!(expected, format!("{}", bignum), "Decoded fragment should match "); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue