Skip to content

Instantly share code, notes, and snippets.

@lolgesten
Last active December 30, 2018 12:17
Show Gist options
  • Save lolgesten/db8996319d9260d2aaeb355f3464a51a to your computer and use it in GitHub Desktop.
Save lolgesten/db8996319d9260d2aaeb355f3464a51a to your computer and use it in GitHub Desktop.
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