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",
|
"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]]
|
[[package]]
|
||||||
name = "blake2b_simd"
|
name = "blake2b_simd"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
|
@ -690,9 +699,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "curve25519-dalek"
|
name = "curve25519-dalek"
|
||||||
version = "3.2.1"
|
version = "3.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
|
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
|
@ -798,6 +807,29 @@ dependencies = [
|
||||||
"signature",
|
"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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -1687,6 +1719,7 @@ dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
"blake2",
|
||||||
"bs58",
|
"bs58",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -1694,6 +1727,8 @@ dependencies = [
|
||||||
"deadpool",
|
"deadpool",
|
||||||
"deadpool-postgres",
|
"deadpool-postgres",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
"ed25519",
|
||||||
|
"ed25519-dalek",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3774,9 +3809,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.3.0"
|
version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zeroize_derive",
|
"zeroize_derive",
|
||||||
]
|
]
|
||||||
|
|
|
@ -39,6 +39,10 @@ hex = { version = "0.4.3", features = ["serde"] }
|
||||||
# Used for logging
|
# Used for logging
|
||||||
log = { version = "0.4.14", features = ["serde"] }
|
log = { version = "0.4.14", features = ["serde"] }
|
||||||
env_logger = { version = "0.9.0", default-features = false }
|
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
|
# Used to guess media type of a file
|
||||||
mime_guess = "2.0.3"
|
mime_guess = "2.0.3"
|
||||||
mime-sniffer = "0.1.2"
|
mime-sniffer = "0.1.2"
|
||||||
|
|
|
@ -158,6 +158,7 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- ethereum
|
- ethereum
|
||||||
|
- minisign
|
||||||
- name: signer
|
- name: signer
|
||||||
in: query
|
in: query
|
||||||
description: Information about the signer.
|
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;
|
||||||
pub mod did_key;
|
pub mod did_key;
|
||||||
pub mod did_pkh;
|
pub mod did_pkh;
|
||||||
|
pub mod minisign;
|
||||||
|
|
|
@ -28,6 +28,11 @@ use crate::identity::{
|
||||||
claims::create_identity_claim,
|
claims::create_identity_claim,
|
||||||
did::Did,
|
did::Did,
|
||||||
did_pkh::DidPkh,
|
did_pkh::DidPkh,
|
||||||
|
minisign::{
|
||||||
|
minisign_key_to_did,
|
||||||
|
verify_minisign_identity_proof,
|
||||||
|
IDENTITY_PROOF_MINISIGN,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use crate::json_signatures::{
|
use crate::json_signatures::{
|
||||||
canonicalization::canonicalize_object,
|
canonicalization::canonicalize_object,
|
||||||
|
@ -305,6 +310,11 @@ async fn get_identity_claim(
|
||||||
);
|
);
|
||||||
Did::Pkh(did_pkh)
|
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()),
|
_ => return Err(ValidationError("unknown proof type").into()),
|
||||||
};
|
};
|
||||||
let actor_id = current_user.profile.actor_id(&config.instance_url());
|
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"))?;
|
.map_err(|_| ValidationError("invalid claim"))?;
|
||||||
|
|
||||||
// Verify proof
|
// Verify proof
|
||||||
let did_pkh = match did {
|
let proof_type = match did {
|
||||||
Did::Key(_) => return Err(ValidationError("unsupported DID type").into()),
|
Did::Key(ref did_key) => {
|
||||||
Did::Pkh(ref did_pkh) => did_pkh,
|
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 {
|
let proof = IdentityProof {
|
||||||
issuer: did,
|
issuer: did,
|
||||||
proof_type: ETHEREUM_EIP191_PROOF.to_string(),
|
proof_type: proof_type.to_string(),
|
||||||
value: proof_data.signature.clone(),
|
value: proof_data.signature.clone(),
|
||||||
};
|
};
|
||||||
let mut profile_data = ProfileUpdateData::from(¤t_user.profile);
|
let mut profile_data = ProfileUpdateData::from(¤t_user.profile);
|
||||||
|
|
Loading…
Reference in a new issue