284 lines
11 KiB
Rust
284 lines
11 KiB
Rust
//! RFC vectors and public codecs for pure signing.
|
|
use base64::{
|
|
Engine,
|
|
engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
|
|
};
|
|
use sausage::cred::sign::*;
|
|
fn raw(key: &[u8], alg: &str, payload: &[u8]) -> Vec<u8> {
|
|
sign(&STANDARD.encode(key), None, alg, payload, "raw", None, None)
|
|
.unwrap()
|
|
.bytes
|
|
.unwrap()
|
|
}
|
|
#[test]
|
|
fn ed25519_readers_and_wire() {
|
|
use ed25519_dalek::pkcs8::EncodePrivateKey;
|
|
let key = ed25519_dalek::SigningKey::from_bytes(&[7; 32]);
|
|
let pem = key.to_pkcs8_pem(Default::default()).unwrap();
|
|
let jwk = serde_json::json!({"kty":"OKP","crv":"Ed25519","d":URL_SAFE_NO_PAD.encode(key.to_bytes()),"x":URL_SAFE_NO_PAD.encode(key.verifying_key().as_bytes())}).to_string();
|
|
let ssh = ssh_key::PrivateKey::new(
|
|
ssh_key::private::Ed25519Keypair::from(&key).into(),
|
|
"synthetic",
|
|
)
|
|
.unwrap()
|
|
.to_openssh(Default::default())
|
|
.unwrap();
|
|
let expected = raw(&key.to_bytes(), "ed25519", b"message");
|
|
for value in [pem.as_str(), jwk.as_str(), ssh.as_str()] {
|
|
let sig = sign(value, None, "ed25519", b"message", "ssh", None, None).unwrap();
|
|
let wire = STANDARD.decode(sig.signature).unwrap();
|
|
assert_eq!(&wire[..19], b"\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x40");
|
|
assert_eq!(&wire[19..], expected);
|
|
for output in ["pem", "jwk", "ssh", "hex", "base64"] {
|
|
assert!(public_key(value, None, "ed25519", output).is_ok());
|
|
}
|
|
}
|
|
assert!(public_key(&STANDARD.encode([7; 32]), None, "hmac-sha256", "hex").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn rfc8032_and_4231() {
|
|
let seed =
|
|
hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap();
|
|
assert_eq!(
|
|
hex::encode(raw(&seed, "ed25519", b"")),
|
|
concat!(
|
|
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155",
|
|
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
|
|
)
|
|
);
|
|
for (alg, expected) in [
|
|
(
|
|
"hmac-sha256",
|
|
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
|
|
),
|
|
(
|
|
"hmac-sha384",
|
|
"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
|
|
),
|
|
(
|
|
"hmac-sha512",
|
|
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
|
|
),
|
|
] {
|
|
assert_eq!(
|
|
hex::encode(raw(&[0x0b; 20], alg, b"Hi There")),
|
|
expected.replace(' ', "")
|
|
);
|
|
}
|
|
}
|
|
#[test]
|
|
fn ec_readers() {
|
|
use p256::pkcs8::EncodePrivateKey;
|
|
let secret = p256::SecretKey::from_slice(&[7; 32]).unwrap();
|
|
let public = secret.public_key();
|
|
use p256::elliptic_curve::sec1::ToEncodedPoint;
|
|
let point = public.to_encoded_point(false);
|
|
let jwk = serde_json::json!({"kty":"EC","crv":"P-256","d":URL_SAFE_NO_PAD.encode(secret.to_bytes()),"x":URL_SAFE_NO_PAD.encode(point.x().unwrap()),"y":URL_SAFE_NO_PAD.encode(point.y().unwrap())}).to_string();
|
|
let pkcs8 = secret.to_pkcs8_pem(Default::default()).unwrap();
|
|
let sec1 = secret.to_sec1_pem(Default::default()).unwrap();
|
|
let expected = raw(&[7; 32], "es256", b"message");
|
|
let ssh = ssh_key::PrivateKey::from(ssh_key::private::EcdsaKeypair::NistP256 {
|
|
public: point,
|
|
private: ssh_key::private::EcdsaPrivateKey::from(secret.clone()),
|
|
})
|
|
.to_openssh(Default::default())
|
|
.unwrap();
|
|
for value in [jwk.as_str(), pkcs8.as_str(), sec1.as_str(), ssh.as_str()] {
|
|
assert_eq!(
|
|
sign(value, None, "es256", b"message", "raw", None, None)
|
|
.unwrap()
|
|
.bytes
|
|
.unwrap(),
|
|
expected
|
|
);
|
|
let public = ssh_key::PublicKey::from_bytes(
|
|
&STANDARD
|
|
.decode(public_key(value, None, "es256", "ssh").unwrap())
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
let wire = STANDARD
|
|
.decode(
|
|
sign(value, None, "es256", b"message", "ssh", None, None)
|
|
.unwrap()
|
|
.signature,
|
|
)
|
|
.unwrap();
|
|
let signature = ssh_key::Signature::try_from(wire.as_slice()).unwrap();
|
|
ed25519_dalek::Verifier::verify(&public, b"message", &signature).unwrap();
|
|
}
|
|
let mut bad: serde_json::Value = serde_json::from_str(&jwk).unwrap();
|
|
bad["x"] = serde_json::json!(URL_SAFE_NO_PAD.encode([0; 32]));
|
|
assert!(
|
|
sign(
|
|
&bad.to_string(),
|
|
None,
|
|
"es256",
|
|
b"message",
|
|
"raw",
|
|
None,
|
|
None
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rfc6979_and_formats() {
|
|
let key =
|
|
hex::decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap();
|
|
let expected = concat!(
|
|
"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
|
|
"f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8"
|
|
);
|
|
assert_eq!(hex::encode(raw(&key, "es256", b"sample")), expected);
|
|
let key = STANDARD.encode(key);
|
|
for output in ["hex", "base64", "jws", "ssh"] {
|
|
let result = sign(&key, None, "es256", b"sample", output, None, None).unwrap();
|
|
assert!(!result.signature.is_empty());
|
|
}
|
|
for output in ["hex", "base64", "pem", "jwk", "ssh"] {
|
|
assert!(!public_key(&key, None, "es256", output).unwrap().is_empty());
|
|
}
|
|
let pem = generate_es256().unwrap();
|
|
assert!(public_key(&pem, None, "es256", "jwk").is_ok());
|
|
for bad in [vec![], vec![1; 31], vec![0; 32], vec![255; 32]] {
|
|
assert!(sign(&STANDARD.encode(bad), None, "es256", b"", "raw", None, None).is_err());
|
|
}
|
|
}
|
|
#[test]
|
|
fn rsa_readers_and_ssh_wire() {
|
|
use rsa::{
|
|
pkcs1::EncodeRsaPrivateKey,
|
|
pkcs8::EncodePrivateKey,
|
|
traits::{PrivateKeyParts, PublicKeyParts},
|
|
};
|
|
use sha2::{Digest, Sha256, Sha512};
|
|
let key = rsa::RsaPrivateKey::new(&mut rsa::rand_core::OsRng, 2048).unwrap();
|
|
let pem = key.to_pkcs8_pem(Default::default()).unwrap();
|
|
let pkcs1 = key.to_pkcs1_pem(Default::default()).unwrap();
|
|
let ssh = ssh_key::PrivateKey::new(
|
|
ssh_key::private::RsaKeypair::try_from(&key).unwrap().into(),
|
|
"synthetic",
|
|
)
|
|
.unwrap();
|
|
let openssh = ssh.to_openssh(Default::default()).unwrap();
|
|
let encrypted = ssh
|
|
.encrypt(&mut rsa::rand_core::OsRng, "synthetic")
|
|
.unwrap()
|
|
.to_openssh(Default::default())
|
|
.unwrap();
|
|
let jwk = serde_json::json!({"kty":"RSA", "n":URL_SAFE_NO_PAD.encode(key.n().to_bytes_be()), "e":URL_SAFE_NO_PAD.encode(key.e().to_bytes_be()), "d":URL_SAFE_NO_PAD.encode(key.d().to_bytes_be()), "p":URL_SAFE_NO_PAD.encode(key.primes()[0].to_bytes_be()), "q":URL_SAFE_NO_PAD.encode(key.primes()[1].to_bytes_be())}).to_string();
|
|
for (value, pass) in [
|
|
(pem.as_str(), None),
|
|
(pkcs1.as_str(), None),
|
|
(openssh.as_str(), None),
|
|
(encrypted.as_str(), Some("synthetic")),
|
|
(jwk.as_str(), None),
|
|
] {
|
|
for (alg, hash) in [
|
|
("rs256", "sha256"),
|
|
("rsa-sha2-256", "sha256"),
|
|
("rsa-sha2-512", "sha512"),
|
|
] {
|
|
let result = sign(
|
|
value,
|
|
pass,
|
|
alg,
|
|
b"message",
|
|
"raw",
|
|
Some("pkcs1v15"),
|
|
Some(hash),
|
|
)
|
|
.unwrap();
|
|
let sig = result.bytes.unwrap();
|
|
if hash == "sha512" {
|
|
key.to_public_key()
|
|
.verify(
|
|
rsa::Pkcs1v15Sign::new::<Sha512>(),
|
|
&Sha512::digest(b"message"),
|
|
&sig,
|
|
)
|
|
.unwrap();
|
|
} else {
|
|
key.to_public_key()
|
|
.verify(
|
|
rsa::Pkcs1v15Sign::new::<Sha256>(),
|
|
&Sha256::digest(b"message"),
|
|
&sig,
|
|
)
|
|
.unwrap();
|
|
}
|
|
let wire = STANDARD
|
|
.decode(
|
|
sign(
|
|
value,
|
|
pass,
|
|
alg,
|
|
b"message",
|
|
"ssh",
|
|
Some("pkcs1v15"),
|
|
Some(hash),
|
|
)
|
|
.unwrap()
|
|
.signature,
|
|
)
|
|
.unwrap();
|
|
let len = u32::from_be_bytes(wire[..4].try_into().unwrap()) as usize;
|
|
assert_eq!(
|
|
&wire[4..4 + len],
|
|
if hash == "sha512" {
|
|
b"rsa-sha2-512"
|
|
} else {
|
|
b"rsa-sha2-256"
|
|
}
|
|
);
|
|
assert_eq!(&wire[8 + len..], sig);
|
|
}
|
|
for output in ["pem", "jwk", "ssh", "base64", "hex"] {
|
|
assert!(public_key(value, pass, "rs256", output).is_ok());
|
|
}
|
|
}
|
|
assert!(sign(&pem, None, "rs256", b"", "raw", None, None).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_and_mismatched_keys() {
|
|
for value in [
|
|
"not a key",
|
|
"{broken",
|
|
"{\"kty\":\"unknown\"}",
|
|
"-----BEGIN UNKNOWN PRIVATE KEY-----\nAAAA\n-----END UNKNOWN PRIVATE KEY-----",
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\ninvalid",
|
|
"AAAA",
|
|
] {
|
|
assert!(sign(value, None, "ed25519", b"", "raw", None, None).is_err());
|
|
}
|
|
let bad=serde_json::json!({"kty":"OKP","crv":"Ed25519","d":URL_SAFE_NO_PAD.encode([1;32]),"x":URL_SAFE_NO_PAD.encode([2;32])}).to_string();
|
|
assert!(sign(&bad, None, "ed25519", b"", "raw", None, None).is_err());
|
|
let oct = serde_json::json!({"kty":"oct","k":URL_SAFE_NO_PAD.encode([11;20])}).to_string();
|
|
assert!(sign(&oct, None, "hmac-sha384", b"Hi There", "raw", None, None).is_ok());
|
|
assert!(sign(&oct, None, "ed25519", b"", "raw", None, None).is_err());
|
|
assert!(sign(&oct, None, "hmac-sha384", b"", "ssh", None, None).is_err());
|
|
assert!(sign(&oct, None, "hmac-sha384", b"", "unknown", None, None).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn rfc8292() {
|
|
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
|
|
let public=URL_SAFE_NO_PAD.decode("BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs").unwrap();
|
|
let signature=URL_SAFE_NO_PAD.decode("i3CYb7t4xfxCDquptFOepC9GAu_HLGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA").unwrap();
|
|
let input = concat!(
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.",
|
|
"eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzpwdXNoQGV4YW1wbGUuY29tIn0"
|
|
);
|
|
VerifyingKey::from_sec1_bytes(&public)
|
|
.unwrap()
|
|
.verify(
|
|
input.as_bytes(),
|
|
&Signature::from_slice(&signature).unwrap(),
|
|
)
|
|
.unwrap();
|
|
}
|