JWS in rust
| #!/usr/bin/env run-cargo-script | |
| //! ```cargo | |
| //! [package] | |
| //! edition = "2018" | |
| //! | |
| //! [dependencies] | |
| //! openssl = "*" | |
| //! base64 = "*" | |
| //! ``` | |
| use openssl::bn::BigNum; | |
| use openssl::ec::{EcGroup, EcKey}; | |
| use openssl::ecdsa::EcdsaSig; | |
| use openssl::nid::Nid; | |
| use openssl::sha::sha256; | |
| fn base64url<T: ?Sized + AsRef<[u8]>>(input: &T) -> String { | |
| base64::encode_config( | |
| input, | |
| base64::Config::new(base64::CharacterSet::UrlSafe, false), | |
| ) | |
| } | |
| fn unbase64url(input: &str) -> Result<Vec<u8>, String> { | |
| base64::decode_config( | |
| input, | |
| base64::Config::new(base64::CharacterSet::UrlSafe, false), | |
| ) | |
| .map_err(|_| "Decode error".into()) | |
| } | |
| pub fn main() -> Result<(), String> { | |
| // The values and comparisons are taken from https://tools.ietf.org/html/rfc7515 | |
| let prot = r#"{"alg":"ES256"}"#; | |
| let encoded_head = base64url(&prot); | |
| assert_eq!(encoded_head, "eyJhbGciOiJFUzI1NiJ9"); | |
| let payload = | |
| "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}"; | |
| const COMP_PAYLOAD: &[u8] = &[ | |
| 123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, | |
| 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, | |
| 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, | |
| 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125, | |
| ]; | |
| assert_eq!(payload.as_bytes(), COMP_PAYLOAD); | |
| let encoded_payload = base64url(payload); | |
| assert_eq!( | |
| encoded_payload, | |
| "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQog\ | |
| Imh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" | |
| ); | |
| let combined = format!("{}.{}", encoded_head, encoded_payload); | |
| assert_eq!( | |
| combined, | |
| "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODA\ | |
| sDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" | |
| ); | |
| const COMP_COMBINED: &[u8] = &[ | |
| 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 70, 85, 122, 73, 49, 78, 105, 74, 57, | |
| 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, 65, 48, | |
| 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, 77, 84, 107, | |
| 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, 54, 76, 121, 57, | |
| 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, 57, 112, 99, 49, 57, | |
| 121, 98, 50, 57, 48, 73, 106, 112, 48, 99, 110, 86, 108, 102, 81, | |
| ]; | |
| assert_eq!(combined.as_bytes(), COMP_COMBINED); | |
| // {"kty":"EC", | |
| // "crv":"P-256", | |
| // "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", | |
| // "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", | |
| // "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" | |
| // } | |
| let x_bytes = unbase64url("f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU").unwrap(); | |
| let y_bytes = unbase64url("x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0").unwrap(); | |
| let d_bytes = unbase64url("jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI").unwrap(); | |
| let x = BigNum::from_slice(&x_bytes).unwrap(); | |
| let y = BigNum::from_slice(&y_bytes).unwrap(); | |
| let d = BigNum::from_slice(&d_bytes).unwrap(); | |
| let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); | |
| let public_key = EcKey::from_public_key_affine_coordinates(&group, &x, &y).unwrap(); | |
| let private_key = | |
| EcKey::from_private_components(&group, &d, public_key.public_key()).unwrap(); | |
| public_key.check_key().unwrap(); | |
| private_key.check_key().unwrap(); | |
| let digest = sha256(combined.as_bytes()); | |
| let sig = EcdsaSig::sign(&digest, &private_key).unwrap(); | |
| // let r2 = sig.r().to_owned().unwrap(); | |
| // let s2 = sig.s().to_owned().unwrap(); | |
| let r = sig.r().to_vec(); | |
| let s = sig.s().to_vec(); | |
| let mut sign_bytes = Vec::with_capacity(r.len() + s.len()); | |
| sign_bytes.extend_from_slice(&r); | |
| sign_bytes.extend_from_slice(&s); | |
| let signature = base64url(&sign_bytes); | |
| // go backwards | |
| let unsign_bytes = unbase64url(&signature).unwrap(); | |
| let r2 = BigNum::from_slice(&unsign_bytes[0..unsign_bytes.len() / 2]).unwrap(); | |
| let s2 = BigNum::from_slice(&unsign_bytes[unsign_bytes.len() / 2..]).unwrap(); | |
| let sig2 = EcdsaSig::from_private_components(r2, s2).unwrap(); | |
| let ver = sig2.verify(&digest, &public_key).unwrap(); | |
| assert!(ver); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment