|
@@ -15,3 +15,85 @@ impl<'l> Into<String> for JWTData<'l> {
|
|
|
serde_json::to_string(&self).unwrap()
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+pub const DEFAULT_HEADER: &'static str = r#"{"alg": "EdDSA", "typ": "JWT"}"#;
|
|
|
+
|
|
|
+pub struct JWT {
|
|
|
+ pub header: String,
|
|
|
+ pub data: String,
|
|
|
+ pub signature: String,
|
|
|
+}
|
|
|
+
|
|
|
+impl JWT {
|
|
|
+ pub fn verify<B: std::convert::AsRef<[u8]>>(with: &ring::signature::UnparsedPublicKey<B>, from: &str) -> Option<Self> {
|
|
|
+ let header_split = from.find(".")?;
|
|
|
+ let header = &from[0..header_split];
|
|
|
+ let data_split = header_split + 1 + from[header_split+1..].find(".")?;
|
|
|
+ let data = &from[header_split + 1 .. data_split];
|
|
|
+ let signature = &from[data_split + 1 ..];
|
|
|
+
|
|
|
+ let mut to_verify = vec![];
|
|
|
+ to_verify.extend(header.as_bytes());
|
|
|
+ to_verify.extend(".".as_bytes());
|
|
|
+ to_verify.extend(data.as_bytes());
|
|
|
+
|
|
|
+ let decoded_signature = base64::decode_config(signature.as_bytes(), base64::URL_SAFE_NO_PAD).ok()?;
|
|
|
+ with.verify(to_verify.as_ref(), decoded_signature.as_ref()).ok()?;
|
|
|
+
|
|
|
+ // if we got this far, the verification passed
|
|
|
+ Some(Self {
|
|
|
+ header: header.into(), data: data.into(), signature: signature.into()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn sign(with: &ring::signature::Ed25519KeyPair, data: JWTData) -> Self {
|
|
|
+ let header = base64::encode_config(DEFAULT_HEADER, base64::URL_SAFE_NO_PAD);
|
|
|
+ let data = base64::encode_config(<JWTData as Into::<String>>::into(data), base64::URL_SAFE_NO_PAD);
|
|
|
+
|
|
|
+ let mut to_sign = vec![];
|
|
|
+ to_sign.extend(header.as_bytes());
|
|
|
+ to_sign.extend(".".as_bytes());
|
|
|
+ to_sign.extend(data.as_bytes());
|
|
|
+ let signature = base64::encode_config(with.sign(&to_sign).as_ref(), base64::URL_SAFE_NO_PAD);
|
|
|
+
|
|
|
+ Self {
|
|
|
+ header,
|
|
|
+ data,
|
|
|
+ signature
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn into_string(self) -> String {
|
|
|
+ self.header + "." + self.data.as_str() + "." + self.signature.as_str()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod test {
|
|
|
+ use ring::signature::KeyPair;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn simple_round_trip() {
|
|
|
+ let rng = ring::rand::SystemRandom::new();
|
|
|
+ let kpair_raw = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).expect("couldn't generate ephemeral keypair");
|
|
|
+ let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(kpair_raw.as_ref()).expect("couldn't load keypair");
|
|
|
+
|
|
|
+ let jdata = super::JWTData {
|
|
|
+ sub: "sub",
|
|
|
+ iss: "iss",
|
|
|
+ aud: "aud",
|
|
|
+ iat: 1,
|
|
|
+ exp: 2,
|
|
|
+ extras: Default::default(),
|
|
|
+ };
|
|
|
+
|
|
|
+ let generated = super::JWT::sign(&kpair, jdata).into_string();
|
|
|
+ println!("generated: {:?}", generated);
|
|
|
+
|
|
|
+ let pubkey = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, kpair.public_key().as_ref());
|
|
|
+
|
|
|
+ let vresult = super::JWT::verify(&pubkey, generated.as_str());
|
|
|
+
|
|
|
+ assert!(vresult.is_some());
|
|
|
+ }
|
|
|
+}
|