use ed25519_dalek::{Signature, VerifyingKey};
use mkit_core::repo_identity::Namespace;
use super::webauthn::{WebAuthnBinding, verify_webauthn};
use super::{GrantError, OwnerScheme, VerifierConfig};
use crate::eth::{self, Address, EthError};
const ED25519_BLOB_LEN: usize = 64;
const EIP191_BLOB_LEN: usize = 65;
pub fn verify_owner_signature(
cfg: &VerifierConfig,
scheme: OwnerScheme,
statement: &[u8],
blob: &[u8],
namespace: &Namespace,
) -> Result<(), GrantError> {
verify_owner(cfg, scheme, statement, blob, namespace).map(|_| ())
}
pub(crate) fn verify_owner(
cfg: &VerifierConfig,
scheme: OwnerScheme,
statement: &[u8],
blob: &[u8],
namespace: &Namespace,
) -> Result<Option<WebAuthnBinding>, GrantError> {
if !cfg.schemes().contains(scheme) {
return Err(GrantError::SchemeNotAdvertised);
}
match (scheme, namespace) {
(OwnerScheme::Ed25519, Namespace::Ed25519(key)) => {
verify_ed25519(statement, blob, key).map(|()| None)
}
(OwnerScheme::Secp256k1Eip191, Namespace::Address(address)) => {
verify_eip191(statement, blob, address).map(|()| None)
}
(OwnerScheme::WebAuthnP256, Namespace::Address(address)) => {
verify_webauthn(cfg.relying_parties(), statement, blob, address).map(Some)
}
_ => Err(GrantError::SchemeNamespaceMismatch),
}
}
fn verify_ed25519(statement: &[u8], blob: &[u8], key: &[u8; 32]) -> Result<(), GrantError> {
let signature: &[u8; ED25519_BLOB_LEN] =
blob.try_into().map_err(|_| GrantError::SignatureLength)?;
let key = VerifyingKey::from_bytes(key).map_err(|_| GrantError::BadSignature)?;
let digest = mkit_core::hash::hash(statement);
key.verify_strict(&digest, &Signature::from_bytes(signature))
.map_err(|_| GrantError::BadSignature)
}
fn verify_eip191(statement: &[u8], blob: &[u8], namespace: &Address) -> Result<(), GrantError> {
let signature: &[u8; EIP191_BLOB_LEN] =
blob.try_into().map_err(|_| GrantError::SignatureLength)?;
let address = eth::eip191_recover_address(statement, signature).map_err(|e| match e {
EthError::RecoveryIdInvalid => GrantError::SignatureRecoveryId,
EthError::ScalarOutOfRange => GrantError::SignatureScalar,
EthError::HighS => GrantError::HighS,
_ => GrantError::BadSignature,
})?;
if address != *namespace {
return Err(GrantError::OwnerMismatch);
}
Ok(())
}
#[cfg(test)]
mod tests {
use ed25519_dalek::{Signer, SigningKey};
use super::super::AcceptedSchemes;
use super::*;
const STATEMENT: &[u8] = b"mkit-write-epoch:v1\nany bytes: the scheme signs what it is given";
fn cfg(schemes: &[OwnerScheme]) -> VerifierConfig {
VerifierConfig::new(
"https://git.example.com",
AcceptedSchemes::of(schemes),
vec![],
)
.unwrap()
}
fn owner() -> (SigningKey, Namespace) {
let key = SigningKey::from_bytes(&[9; 32]);
let ns = Namespace::Ed25519(key.verifying_key().to_bytes());
(key, ns)
}
fn sign(key: &SigningKey, statement: &[u8]) -> Vec<u8> {
key.sign(&mkit_core::hash::hash(statement))
.to_bytes()
.to_vec()
}
#[test]
fn ed25519_owner_signature_verifies() {
let (key, ns) = owner();
let cfg = cfg(&[OwnerScheme::Ed25519]);
let sig = sign(&key, STATEMENT);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Ed25519, STATEMENT, &sig, &ns),
Ok(())
);
let raw = key.sign(STATEMENT).to_bytes();
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Ed25519, STATEMENT, &raw, &ns),
Err(GrantError::BadSignature)
);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Ed25519, b"other", &sig, &ns),
Err(GrantError::BadSignature)
);
let other = SigningKey::from_bytes(&[10; 32]);
assert_eq!(
verify_owner_signature(
&cfg,
OwnerScheme::Ed25519,
STATEMENT,
&sign(&other, STATEMENT),
&ns
),
Err(GrantError::BadSignature)
);
}
#[test]
fn ed25519_owner_signature_rejects_blob_lengths() {
let (key, ns) = owner();
let cfg = cfg(&[OwnerScheme::Ed25519]);
let sig = sign(&key, STATEMENT);
for len in [0, 63, 65, 128] {
let mut blob = sig.clone();
blob.resize(len, 0);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Ed25519, STATEMENT, &blob, &ns),
Err(GrantError::SignatureLength),
"{len}"
);
}
}
#[test]
fn ed25519_owner_signature_is_strict() {
let (key, ns) = owner();
let cfg = cfg(&[OwnerScheme::Ed25519]);
let sig = sign(&key, STATEMENT);
let l: [u8; 32] = [
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9,
0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10,
];
let mut high = sig.clone();
let mut carry = 0u16;
for i in 0..32 {
let sum = u16::from(high[32 + i]) + u16::from(l[i]) + carry;
high[32 + i] = sum.to_le_bytes()[0];
carry = sum >> 8;
}
assert_eq!(carry, 0);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Ed25519, STATEMENT, &high, &ns),
Err(GrantError::BadSignature)
);
let mut identity = [0u8; 32];
identity[0] = 1;
let mut order2 = [0xffu8; 32];
order2[0] = 0xec;
order2[31] = 0x7f;
let mut forged = identity.to_vec();
forged.extend([0; 32]);
for weak in [identity, order2, [0; 32]] {
assert_eq!(
verify_owner_signature(
&cfg,
OwnerScheme::Ed25519,
STATEMENT,
&forged,
&Namespace::Ed25519(weak)
),
Err(GrantError::BadSignature)
);
}
let mut not_a_point = [0u8; 32];
not_a_point[0] = 2;
assert_eq!(
verify_owner_signature(
&cfg,
OwnerScheme::Ed25519,
STATEMENT,
&sig,
&Namespace::Ed25519(not_a_point)
),
Err(GrantError::BadSignature)
);
}
#[test]
fn owner_scheme_must_be_advertised_and_match_the_namespace_form() {
let (key, ns) = owner();
let sig = sign(&key, STATEMENT);
let addr = Namespace::Address([7; 20]);
assert_eq!(
verify_owner_signature(
&cfg(&[OwnerScheme::Secp256k1Eip191]),
OwnerScheme::Ed25519,
STATEMENT,
&sig,
&ns
),
Err(GrantError::SchemeNotAdvertised)
);
let all = cfg(&[OwnerScheme::Ed25519, OwnerScheme::Secp256k1Eip191]);
assert_eq!(
verify_owner_signature(&all, OwnerScheme::Ed25519, STATEMENT, &sig, &addr),
Err(GrantError::SchemeNamespaceMismatch)
);
assert_eq!(
verify_owner_signature(&all, OwnerScheme::Secp256k1Eip191, STATEMENT, &[0; 65], &ns),
Err(GrantError::SchemeNamespaceMismatch)
);
}
fn eip191_sign(key: &k256::ecdsa::SigningKey, statement: &[u8]) -> [u8; 65] {
let (sig, recid) = key.sign_prehash_recoverable(ð::eip191_hash(statement));
let mut out = [0u8; 65];
out[..64].copy_from_slice(&sig.to_bytes());
out[64] = 27 + recid.to_byte();
out
}
fn eip191_owner(seed: u8) -> (k256::ecdsa::SigningKey, Namespace) {
let key = k256::ecdsa::SigningKey::from_slice(&[seed; 32]).unwrap();
let point = key.verifying_key().to_sec1_point(false);
let xy: [u8; 64] = point.as_bytes()[1..].try_into().unwrap();
(
key,
Namespace::Address(eth::address_secp256k1(&xy).unwrap()),
)
}
#[test]
fn secp256k1_eip191_owner_signature_verifies() {
let (key, ns) = eip191_owner(0x11);
let cfg = cfg(&[OwnerScheme::Secp256k1Eip191]);
let sig = eip191_sign(&key, STATEMENT);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Secp256k1Eip191, STATEMENT, &sig, &ns),
Ok(())
);
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Secp256k1Eip191, b"other", &sig, &ns),
Err(GrantError::OwnerMismatch)
);
let (other, _) = eip191_owner(0x12);
assert_eq!(
verify_owner_signature(
&cfg,
OwnerScheme::Secp256k1Eip191,
STATEMENT,
&eip191_sign(&other, STATEMENT),
&ns
),
Err(GrantError::OwnerMismatch)
);
let mut flipped = sig;
flipped[64] = if sig[64] == 27 { 28 } else { 27 };
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Secp256k1Eip191, STATEMENT, &flipped, &ns),
Err(GrantError::OwnerMismatch)
);
}
#[test]
fn secp256k1_eip191_owner_signature_rejects() {
let (key, ns) = eip191_owner(0x11);
let cfg = cfg(&[OwnerScheme::Secp256k1Eip191, OwnerScheme::Ed25519]);
let sig = eip191_sign(&key, STATEMENT);
let run = |blob: &[u8]| {
verify_owner_signature(&cfg, OwnerScheme::Secp256k1Eip191, STATEMENT, blob, &ns)
};
assert_eq!(run(&sig[..64]), Err(GrantError::SignatureLength));
assert_eq!(
run(&[&sig[..], &[0]].concat()),
Err(GrantError::SignatureLength)
);
for v in [0u8, 1, 26, 29, 255] {
let mut bad = sig;
bad[64] = v;
assert_eq!(run(&bad), Err(GrantError::SignatureRecoveryId), "v = {v}");
}
let mut zero_r = sig;
zero_r[..32].fill(0);
assert_eq!(run(&zero_r), Err(GrantError::SignatureScalar));
let high = eth::normalize_eip191_signature(sig).unwrap();
assert_eq!(high, sig);
let n = hex::decode("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
.unwrap();
let mut twin = sig;
let mut borrow = 0i16;
for i in (0..32).rev() {
let d = i16::from(n[i]) - i16::from(sig[32 + i]) - borrow;
borrow = i16::from(d < 0);
twin[32 + i] = (d + 256 * borrow).to_le_bytes()[0];
}
twin[64] = if sig[64] == 27 { 28 } else { 27 };
assert_eq!(run(&twin), Err(GrantError::HighS));
assert_eq!(eth::normalize_eip191_signature(twin), Ok(sig));
let (_, ed_ns) = owner();
assert_eq!(
verify_owner_signature(&cfg, OwnerScheme::Secp256k1Eip191, STATEMENT, &sig, &ed_ns),
Err(GrantError::SchemeNamespaceMismatch)
);
}
}