Implement minisign identity proofs
This commit is contained in:
parent
9d86d58274
commit
14a123ad7e
6 changed files with 222 additions and 26 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
136
src/identity/minisign.rs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -2,3 +2,4 @@ pub mod claims;
|
|||
pub mod did;
|
||||
pub mod did_key;
|
||||
pub mod did_pkh;
|
||||
pub mod minisign;
|
||||
|
|
|
@ -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(¤t_user.profile);
|
||||
|
|
Loading…
Reference in a new issue