Last active
December 30, 2018 12:17
-
-
Save lolgesten/db8996319d9260d2aaeb355f3464a51a to your computer and use it in GitHub Desktop.
JWS in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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