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
use jaws::Compact;
// JAWS provides JWT format for printing JWTs in a style similar to the example above,
// which is directly inspired by the way the ACME standard shows JWTs.
use jaws::JWTFormat;
// JAWS provides a single token type which is generic over the state of the token.
// The states are defined in the `state` module, and are used to track the
// signing and verification status.
use jaws::Token;
// The unverified token state, used like `Token<.., Unverified<..>, ..>`.
// It is generic over the type of the custom header parameters.
use jaws::token::Unverified;
// JAWS provides type-safe support for JWT claims.
use jaws::{Claims, RegisteredClaims};
// We are going to use an RSA private key to sign our JWT, provided by
// the `rsa` crate in the RustCrypto suite.
use rsa::pkcs8::DecodePrivateKey;
// The signing algorithm we will use (`RS256`) relies on the SHA-256 hash
// function, so we get it here from the `sha2` crate in the RustCrypto suite.
use sha2::Sha256;
// Using serde_json allows us to quickly construct a serializable payload,
// but applications may want to instead define a struct and use serde to
// derive serialize and deserialize for added type safety.
use serde_json::json;
// Trait to convert a SigningKey into a VerifyingKey.
use signature::Keypair;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// This key is from RFC 7515, Appendix A.2. Provide your own key instead!
// The key here is stored as a PKCS#8 PEM file, but you can leverage
// RustCrypto to load a variety of other formats.
let key = rsa::RsaPrivateKey::from_pkcs8_pem(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/rfc7515a2.pem"
)))
.unwrap();
// We will sign the JWT with the RS256 algorithm: RSA with SHA-256.
// RsaPkcs1v15 is really an alias to the digital signature algorithm
// implementation in the `rsa` crate, but provided in JAWS to make
// it clear which types are compatible with JWTs.
let alg = rsa::pkcs1v15::SigningKey::<Sha256>::new(key);
// Claims can combine registered and custom fields. The claims object
// can be any type which implements [serde::Serialize].
let claims: Claims<serde_json::Value, (), String, (), ()> = Claims {
registered: RegisteredClaims {
subject: "1234567890".to_string().into(),
..Default::default()
},
claims: json!({
"name": "John Doe",
"admin": true,
}),
};
// Create a token with the default headers, and no custom headers.
// The unit type can be used here because it implements [serde::Serialize],
// but a custom type could be passed if we wanted to have custom header
// fields.
let mut token = Token::compact((), claims);
// We can modify the headers freely before signing the JWT. In this case,
// we provide the `typ` header, which is optional in the JWT spec.
*token.header_mut().r#type() = Some("JWT".to_string());
// We can also ask that some fields be derived from the signing key, for example,
// this will derive the JWK field in the header from the signing key.
token.header_mut().key().derived();
println!("Initial JWT");
// Initially the JWT has no defined signature:
println!("JWT:");
println!("{}", token.formatted());
// Sign the token with the algorithm, and print the result.
let signed = token.sign(&alg).unwrap();
println!("Signed JWT");
println!("JWT:");
println!("{}", signed.formatted());
println!("Token: {}", signed.rendered().unwrap());
// We can't modify the token after signing it (that would change the signature)
// but we can access fields and read from them:
println!(
"Type: {:?}, Algorithm: {:?}",
signed.header().r#type(),
signed.header().algorithm(),
);
// We can also verify tokens.
let token: Token<Claims<serde_json::Value>, Unverified<()>, Compact> =
signed.rendered().unwrap().parse().unwrap();
println!("Parsed JWT");
// Unverified tokens can be printed for debugging, but there is deliberately
// no access to the payload, only to the header fields.
println!("JWT:");
println!("{}", token.formatted());
// We can use the JWK to verify that the token is signed with the correct key.
let hdr = token.header();
let jwk = hdr.key().unwrap();
let key = rsa_jwk_reader::rsa_pub(&serde_json::to_value(jwk).unwrap());
assert_eq!(&key, alg.verifying_key().as_ref());
let alg: rsa::pkcs1v15::VerifyingKey<Sha256> = rsa::pkcs1v15::VerifyingKey::new(key);
// We can't access the claims until we verify the token.
let verified = token.verify(&alg).unwrap();
println!("Verified JWT");
println!("JWT:");
println!("{}", verified.formatted());
println!(
"Payload: \n{}",
serde_json::to_string_pretty(&verified.payload()).unwrap()
);
Ok(())
}
mod rsa_jwk_reader {
use base64ct::Encoding;
fn strip_whitespace(s: &str) -> String {
s.chars().filter(|c| !c.is_whitespace()).collect()
}
fn to_biguint(v: &serde_json::Value) -> Option<rsa::BigUint> {
let val = strip_whitespace(v.as_str()?);
Some(rsa::BigUint::from_bytes_be(
base64ct::Base64UrlUnpadded::decode_vec(&val)
.ok()?
.as_slice(),
))
}
pub(crate) fn rsa_pub(key: &serde_json::Value) -> rsa::RsaPublicKey {
let n = to_biguint(&key["n"]).expect("decode n");
let e = to_biguint(&key["e"]).expect("decode e");
rsa::RsaPublicKey::new(n, e).expect("valid key parameters")
}
}