Implement minisign identity proofs

This commit is contained in:
silverpill 2022-11-10 10:13:03 +00:00
parent 9d86d58274
commit 14a123ad7e
6 changed files with 222 additions and 26 deletions

43
Cargo.lock generated
View file

@ -389,6 +389,15 @@ dependencies = [
"wyz",
]
[[package]]
name = "blake2"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
dependencies = [
"digest 0.10.3",
]
[[package]]
name = "blake2b_simd"
version = "0.5.11"
@ -690,9 +699,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
version = "3.2.1"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
@ -798,6 +807,29 @@ dependencies = [
"signature",
]
[[package]]
name = "ed25519"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand 0.7.3",
"serde",
"sha2 0.9.5",
"zeroize",
]
[[package]]
name = "either"
version = "1.8.0"
@ -1687,6 +1719,7 @@ dependencies = [
"ammonia",
"anyhow",
"base64",
"blake2",
"bs58",
"chrono",
"clap",
@ -1694,6 +1727,8 @@ dependencies = [
"deadpool",
"deadpool-postgres",
"dotenv",
"ed25519",
"ed25519-dalek",
"env_logger",
"hex",
"log",
@ -3774,9 +3809,9 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.3.0"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
dependencies = [
"zeroize_derive",
]

View file

@ -39,6 +39,10 @@ hex = { version = "0.4.3", features = ["serde"] }
# Used for logging
log = { version = "0.4.14", features = ["serde"] }
env_logger = { version = "0.9.0", default-features = false }
# Used to verify minisign signatures
ed25519-dalek = "1.0.1"
ed25519 = "=1.3.0"
blake2 = "0.10.4"
# Used to guess media type of a file
mime_guess = "2.0.3"
mime-sniffer = "0.1.2"

View file

@ -158,6 +158,7 @@ paths:
type: string
enum:
- ethereum
- minisign
- name: signer
in: query
description: Information about the signer.

136
src/identity/minisign.rs Normal file
View file

@ -0,0 +1,136 @@
/// https://jedisct1.github.io/minisign/
use blake2::{Blake2b512, Digest};
use ed25519_dalek::{
PublicKey,
Signature,
SignatureError,
Verifier,
};
use super::did_key::{DidKey, MulticodecError};
pub const IDENTITY_PROOF_MINISIGN: &str = "MinisignSignatureDemo0";
const MINISIGN_SIGNATURE_CODE: [u8; 2] = *b"Ed";
const MINISIGN_SIGNATURE_HASHED_CODE: [u8; 2] = *b"ED";
#[derive(thiserror::Error, Debug)]
pub enum ParseError {
#[error("invalid encoding")]
InvalidEncoding(#[from] base64::DecodeError),
#[error("invalid key length")]
InvalidKeyLength,
#[error("invalid signature length")]
InvalidSignatureLength,
#[error("invalid signature type")]
InvalidSignatureType,
}
// Public key format:
// base64(<signature_algorithm> || <key_id> || <public_key>)
fn parse_minisign_public_key(key_b64: &str)
-> Result<[u8; 32], ParseError>
{
let key_bin = base64::decode(key_b64)?;
if key_bin.len() != 42 {
return Err(ParseError::InvalidKeyLength);
};
let mut signature_algorithm = [0; 2];
let mut _key_id = [0; 8];
let mut key = [0; 32];
signature_algorithm.copy_from_slice(&key_bin[0..2]);
_key_id.copy_from_slice(&key_bin[2..10]);
key.copy_from_slice(&key_bin[10..42]);
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_CODE {
return Err(ParseError::InvalidSignatureType);
};
Ok(key)
}
pub fn minisign_key_to_did(key_b64: &str) -> Result<DidKey, ParseError> {
let key = parse_minisign_public_key(key_b64)?;
let did_key = DidKey::from_ed25519_key(key);
Ok(did_key)
}
// Signature format:
// base64(<signature_algorithm> || <key_id> || <signature>)
fn parse_minisign_signature(signature_b64: &str)
-> Result<[u8; 64], ParseError>
{
let signature_bin = base64::decode(signature_b64)?;
if signature_bin.len() != 74 {
return Err(ParseError::InvalidSignatureLength);
};
let mut signature_algorithm = [0; 2];
let mut _key_id = [0; 8];
let mut signature = [0; 64];
signature_algorithm.copy_from_slice(&signature_bin[0..2]);
_key_id.copy_from_slice(&signature_bin[2..10]);
signature.copy_from_slice(&signature_bin[10..74]);
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_HASHED_CODE {
return Err(ParseError::InvalidSignatureType);
};
Ok(signature)
}
fn verify_signature(
message: &str,
signer: [u8; 32],
signature: [u8; 64],
) -> Result<(), SignatureError> {
let signature = Signature::from_bytes(&signature)?;
let public_key = PublicKey::from_bytes(&signer)?;
let mut hasher = Blake2b512::new();
hasher.update(message);
let hash = hasher.finalize();
public_key.verify(&hash, &signature)?;
Ok(())
}
#[derive(thiserror::Error, Debug)]
pub enum VerificationError {
#[error(transparent)]
InvalidKey(#[from] MulticodecError),
#[error(transparent)]
ParseError(#[from] ParseError),
#[error(transparent)]
SignatureError(#[from] SignatureError),
}
pub fn verify_minisign_identity_proof(
signer: &DidKey,
message: &str,
signature: &str,
) -> Result<(), VerificationError> {
let ed25519_key = signer.try_ed25519_key()?;
let ed25519_signature = parse_minisign_signature(signature)?;
let message = format!("{}\n", message);
verify_signature(&message, ed25519_key, ed25519_signature)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_minisign_identity_proof() {
let minisign_key =
"RWSA58rRENpGFYwAjRjbdST7VHFoIuH9JBHfO2u6i5JgANPIoQhABAF/";
let message = "test";
let minisign_signature =
"RUSA58rRENpGFVKxdZGMG1WdIJ+dlyP83qOqw6GP0H/Li6Brug2A3mFKLtleIRLi6IIG0smzOlX5CEsisNnc897OUHIOSNLsQQs=";
let signer = minisign_key_to_did(minisign_key).unwrap();
verify_minisign_identity_proof(&signer, message, minisign_signature).unwrap();
}
}

View file

@ -2,3 +2,4 @@ pub mod claims;
pub mod did;
pub mod did_key;
pub mod did_pkh;
pub mod minisign;

View file

@ -28,6 +28,11 @@ use crate::identity::{
claims::create_identity_claim,
did::Did,
did_pkh::DidPkh,
minisign::{
minisign_key_to_did,
verify_minisign_identity_proof,
IDENTITY_PROOF_MINISIGN,
},
};
use crate::json_signatures::{
canonicalization::canonicalize_object,
@ -305,6 +310,11 @@ async fn get_identity_claim(
);
Did::Pkh(did_pkh)
},
"minisign" => {
let did_key = minisign_key_to_did(&query_params.signer)
.map_err(|_| ValidationError("invalid key"))?;
Did::Key(did_key)
},
_ => return Err(ValidationError("unknown proof type").into()),
};
let actor_id = current_user.profile.actor_id(&config.instance_url());
@ -341,32 +351,41 @@ async fn create_identity_proof(
.map_err(|_| ValidationError("invalid claim"))?;
// Verify proof
let did_pkh = match did {
Did::Key(_) => return Err(ValidationError("unsupported DID type").into()),
Did::Pkh(ref did_pkh) => did_pkh,
let proof_type = match did {
Did::Key(ref did_key) => {
verify_minisign_identity_proof(
did_key,
&message,
&proof_data.signature,
).map_err(|_| ValidationError("invalid signature"))?;
IDENTITY_PROOF_MINISIGN
},
Did::Pkh(ref did_pkh) => {
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
// DID must point to Ethereum Mainnet because it is a valid
// identifier on any Ethereum chain
return Err(ValidationError("unsupported chain ID").into());
};
let maybe_public_address =
current_user.public_wallet_address(&Currency::Ethereum);
if let Some(address) = maybe_public_address {
// Do not allow to add more than one address proof
if did_pkh.address != address {
return Err(ValidationError("DID doesn't match current identity").into());
};
};
verify_eip191_identity_proof(
did_pkh,
&message,
&proof_data.signature,
)?;
ETHEREUM_EIP191_PROOF
},
};
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
// DID must point to Ethereum Mainnet because it is a valid
// identifier on any Ethereum chain
return Err(ValidationError("unsupported chain ID").into());
};
let maybe_public_address =
current_user.public_wallet_address(&Currency::Ethereum);
if let Some(address) = maybe_public_address {
// Do not allow to add more than one address proof
if did_pkh.address != address {
return Err(ValidationError("DID doesn't match current identity").into());
};
};
verify_eip191_identity_proof(
did_pkh,
&message,
&proof_data.signature,
)?;
let proof = IdentityProof {
issuer: did,
proof_type: ETHEREUM_EIP191_PROOF.to_string(),
proof_type: proof_type.to_string(),
value: proof_data.signature.clone(),
};
let mut profile_data = ProfileUpdateData::from(&current_user.profile);