feat: WIP new multisig threshold scheme

with a tree like structure allowing nested m of n schemes.

Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
Harald Hoyer 2024-08-06 09:37:30 +02:00
parent 8ce8f5bccb
commit e2b64d5519
Signed by: harald
GPG key ID: F519A1143B3FBE32
19 changed files with 1138 additions and 173 deletions

View file

@ -21,3 +21,6 @@ tracing.workspace = true
tracing-actix-web.workspace = true
tracing-log.workspace = true
tracing-subscriber.workspace = true
[dev-dependencies]
test-log.workspace = true

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Matter Labs
// Copyright (c) 2023-2024 Matter Labs
//! post commands
@ -14,7 +14,7 @@ use teepot::json::http::{
VaultCommandRequest, VaultCommandResponse, VaultCommands, VaultCommandsResponse,
};
use teepot::json::secrets::{AdminConfig, AdminState};
use teepot::server::{signatures::VerifySig, HttpResponseError, Status};
use teepot::server::{HttpResponseError, Status};
use tracing::instrument;
/// Post command
@ -52,7 +52,9 @@ pub async fn post_command(
.await?
.context("empty admin config")
.status(StatusCode::BAD_GATEWAY)?;
admin_config.check_sigs(&item.signatures, item.commands.as_bytes())?;
admin_config
.policy
.check_sigs(&item.signatures, item.commands.as_bytes())?;
let mut hasher = Sha256::new();
hasher.update(item.commands.as_bytes());

View file

@ -116,6 +116,7 @@ async fn main() -> Result<()> {
mod tests {
use serde_json::json;
use teepot::json::http::{VaultCommand, VaultCommands};
use test_log::test;
const TEST_DATA: &str = include_str!("../../../crates/teepot/tests/data/test.json");

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Matter Labs
// Copyright (c) 2023-2024 Matter Labs
//! post signing request
@ -12,7 +12,6 @@ use std::sync::Arc;
use teepot::client::vault::VaultConnection;
use teepot::json::http::{SignRequest, SignRequestData, SignResponse};
use teepot::json::secrets::{AdminConfig, AdminState, SGXSigningKey};
use teepot::server::signatures::VerifySig as _;
use teepot::server::{HttpResponseError, Status};
use teepot::sgx::sign::PrivateKey as _;
use teepot::sgx::sign::{Author, Signature};
@ -76,7 +75,9 @@ pub async fn post_sign(
.await?
.context("empty admin config")
.status(StatusCode::BAD_GATEWAY)?;
admin_config.check_sigs(&item.signatures, item.sign_request_data.as_bytes())?;
admin_config
.policy
.check_sigs(&item.signatures, item.sign_request_data.as_bytes())?;
let mut hasher = Sha256::new();
hasher.update(item.sign_request_data.as_bytes());

View file

@ -2,14 +2,11 @@
// Copyright (c) 2023-2024 Matter Labs
use crate::{get_vault_status, UnsealServerState, Worker};
use actix_web::error::ErrorBadRequest;
use actix_web::{web, HttpResponse};
use anyhow::{anyhow, Context, Result};
use awc::http::StatusCode;
use serde_json::json;
use teepot::client::TeeConnection;
use teepot::json::http::{Init, InitResponse, VaultInitRequest};
use teepot::json::secrets::AdminConfig;
use teepot::server::{HttpResponseError, Status};
use tracing::{debug, error, info, instrument, trace};
@ -22,8 +19,7 @@ pub async fn post_init(
pgp_keys,
secret_shares,
secret_threshold,
admin_pgp_keys,
admin_threshold,
admin_config,
admin_tee_mrenclave,
} = init.into_inner();
let conn = TeeConnection::new(&worker.vault_attestation);
@ -36,17 +32,10 @@ pub async fn post_init(
secret_threshold,
};
if admin_threshold < 1 {
return Ok(HttpResponse::from_error(ErrorBadRequest(
json!({"error": "admin_threshold must be at least 1"}),
)));
}
if admin_threshold > admin_pgp_keys.len() {
return Ok(HttpResponse::from_error(ErrorBadRequest(
json!({"error": "admin_threshold must be less than or equal to the number of admin_pgp_keys"}),
)));
}
admin_config
.validate()
.context("Invalid admin config")
.status(StatusCode::BAD_REQUEST)?;
loop {
let current_state = worker.state.read().unwrap().clone();
@ -123,10 +112,7 @@ pub async fn post_init(
*/
*worker.state.write().unwrap() = UnsealServerState::VaultInitialized {
admin_config: AdminConfig {
admin_pgp_keys,
admin_threshold,
},
admin_config,
admin_tee_mrenclave,
root_token,
};

View file

@ -9,7 +9,6 @@ repository.workspace = true
[dependencies]
actix-web.workspace = true
anyhow.workspace = true
base64.workspace = true
clap.workspace = true
serde_json.workspace = true
teepot.workspace = true

View file

@ -2,13 +2,13 @@
// Copyright (c) 2023-2024 Matter Labs
use anyhow::{anyhow, bail, Context, Result};
use base64::{engine::general_purpose, Engine as _};
use clap::{Args, Parser, Subcommand};
use serde_json::Value;
use std::fs::File;
use std::io::Read;
use teepot::client::{AttestationArgs, TeeConnection};
use teepot::json::http::{Init, InitResponse, Unseal};
use teepot::json::secrets::AdminConfig;
use tracing::{error, info, trace, warn};
use tracing_log::LogTracer;
use tracing_subscriber::Registry;
@ -16,12 +16,9 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
#[derive(Args, Debug)]
pub struct InitArgs {
/// admin threshold
#[arg(long)]
admin_threshold: usize,
/// PGP keys to sign commands for the admin tee
#[arg(short, long)]
admin_pgp_key_file: Vec<String>,
admin_config_json: String,
/// admin TEE mrenclave
#[arg(long)]
admin_tee_mrenclave: String,
@ -78,17 +75,12 @@ async fn init(args: Arguments) -> Result<()> {
unreachable!()
};
if init_args.admin_threshold == 0 {
bail!("admin threshold must be greater than 0");
}
if init_args.unseal_threshold == 0 {
bail!("unseal threshold must be greater than 0");
}
if init_args.admin_threshold > init_args.admin_pgp_key_file.len() {
bail!("admin threshold must be less than or equal to the number of admin pgp keys");
}
let admin_config: AdminConfig = serde_json::from_str(&init_args.admin_config_json)
.context("failed to parse admin config")?;
if init_args.unseal_threshold > init_args.unseal_pgp_key_file.len() {
bail!("unseal threshold must be less than or equal to the number of unseal pgp keys");
@ -105,30 +97,11 @@ async fn init(args: Arguments) -> Result<()> {
pgp_keys.push(key);
}
let mut admin_pgp_keys = Vec::new();
for filename in init_args.admin_pgp_key_file {
let mut file =
File::open(&filename).context(format!("Failed to open pgp key file {}", &filename))?;
// read all lines from file and concatenate them
let mut key = String::new();
file.read_to_string(&mut key)
.context(format!("Failed to read pgp key file {}", &filename))?;
key.retain(|c| !c.is_ascii_whitespace());
let bytes = general_purpose::STANDARD.decode(key).context(format!(
"Failed to base64 decode pgp key file {}",
&filename
))?;
admin_pgp_keys.push(bytes.into_boxed_slice());
}
let init = Init {
secret_shares: pgp_keys.len() as _,
secret_threshold: init_args.unseal_threshold,
admin_threshold: init_args.admin_threshold,
admin_tee_mrenclave: init_args.admin_tee_mrenclave,
admin_pgp_keys: admin_pgp_keys.into_boxed_slice(),
admin_config,
pgp_keys,
};