Require signed SSH metadata provenance
This commit is contained in:
parent
c5548e4df9
commit
6e04e786c2
8 changed files with 414 additions and 45 deletions
|
|
@ -32,6 +32,7 @@ geth-ssh-identity = { path = "../geth-ssh-identity" }
|
|||
geth-ssh-proxy = { path = "../geth-ssh-proxy" }
|
||||
geth-store = { path = "../geth-store" }
|
||||
geth-types = { path = "../geth-types" }
|
||||
hex.workspace = true
|
||||
iroh.workspace = true
|
||||
swarm-discovery.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,13 @@ use geth_pubsub::PubsubMessage;
|
|||
use geth_resource::ResourceDescriptor;
|
||||
use geth_secrets::{BearerAccess, BearerChallenge, BearerProof, ResourceMasterSecret};
|
||||
use geth_ssh_identity::{
|
||||
SSH_CERT_ISSUANCE_NAMESPACE, SSH_CERT_REQUEST_NAMESPACE, SSH_REVOCATION_LIST_NAMESPACE,
|
||||
SshCertApproval, SshCertKind, SshCertRequest, SshCertRequestStatus, SshCertificateRecord,
|
||||
SshRevocationEntry, SshRevocationExportFormat, SshRevocationKind, build_ssh_cert_sign_command,
|
||||
cert_request_id, certificate_id, openssh_krl_spec, parse_openssh_krl_spec, revocation_id,
|
||||
ssh_public_key_fingerprint, write_openssh_krl,
|
||||
SshRecordProvenance, SshRevocationEntry, SshRevocationExportFormat, SshRevocationKind,
|
||||
build_ssh_cert_sign_command, cert_request_id, certificate_id, openssh_krl_spec,
|
||||
parse_openssh_krl_spec, revocation_id, ssh_cert_request_signing_payload,
|
||||
ssh_certificate_signing_payload, ssh_public_key_fingerprint, ssh_revocation_signing_payload,
|
||||
write_openssh_krl,
|
||||
};
|
||||
use geth_ssh_proxy::SshProxyConnection;
|
||||
use geth_store::{
|
||||
|
|
@ -3304,7 +3307,7 @@ async fn handle_iroh_control_connection(
|
|||
.list_ssh_certificates_since(since_ms)?
|
||||
.into_iter()
|
||||
.map(ssh_certificate_from_stored)
|
||||
.collect(),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
)
|
||||
} else {
|
||||
(Vec::new(), Vec::new())
|
||||
|
|
@ -5098,7 +5101,7 @@ pub fn handle_request(
|
|||
.map_err(|_| NodeError::InvalidSshCertKind(cert_kind.clone()))?;
|
||||
let public_key = std::fs::read_to_string(&public_key_path)?;
|
||||
let created_at = UnixMillis(geth_store::now_ms());
|
||||
let request = SshCertRequest {
|
||||
let mut request = SshCertRequest {
|
||||
id: cert_request_id(
|
||||
&NodeId::new(node.node_id.clone()),
|
||||
&public_key,
|
||||
|
|
@ -5115,7 +5118,9 @@ pub fn handle_request(
|
|||
reason,
|
||||
status: SshCertRequestStatus::Pending,
|
||||
created_at,
|
||||
provenance: None,
|
||||
};
|
||||
sign_ssh_cert_request(node, &mut request)?;
|
||||
store.insert_ssh_cert_request(&stored_from_ssh_cert_request(&request))?;
|
||||
Ok(ControlResponse::SshCertRequested { request })
|
||||
}
|
||||
|
|
@ -5156,7 +5161,8 @@ pub fn handle_request(
|
|||
.ok_or_else(|| NodeError::SshCertRequestNotFound(request_id.clone()))?;
|
||||
let mut request = ssh_cert_request_from_stored(stored)?;
|
||||
request.status = SshCertRequestStatus::Approved;
|
||||
store.update_ssh_cert_request_status(request.id.as_str(), request.status.as_str())?;
|
||||
sign_ssh_cert_request(node, &mut request)?;
|
||||
store.insert_ssh_cert_request(&stored_from_ssh_cert_request(&request))?;
|
||||
let public_key_path = out.clone().unwrap_or_else(|| {
|
||||
node.paths
|
||||
.home()
|
||||
|
|
@ -5193,18 +5199,19 @@ pub fn handle_request(
|
|||
.into());
|
||||
}
|
||||
let certificate = std::fs::read_to_string(&expected_certificate_path)?;
|
||||
let record = SshCertificateRecord {
|
||||
let mut record = SshCertificateRecord {
|
||||
id: certificate_id(&certificate),
|
||||
request_id: request.id.clone(),
|
||||
certificate_fingerprint: ssh_public_key_fingerprint(&certificate),
|
||||
certificate,
|
||||
imported_at: UnixMillis(geth_store::now_ms()),
|
||||
provenance: None,
|
||||
};
|
||||
sign_ssh_certificate(node, &mut record)?;
|
||||
store.insert_ssh_certificate(&stored_from_ssh_certificate(&record))?;
|
||||
store.update_ssh_cert_request_status(
|
||||
request.id.as_str(),
|
||||
SshCertRequestStatus::Signed.as_str(),
|
||||
)?;
|
||||
request.status = SshCertRequestStatus::Signed;
|
||||
sign_ssh_cert_request(node, &mut request)?;
|
||||
store.insert_ssh_cert_request(&stored_from_ssh_cert_request(&request))?;
|
||||
signed = true;
|
||||
certificate_id_value = Some(record.id);
|
||||
note = "request approved, signed with ssh-keygen, and imported into local certificate metadata".to_owned();
|
||||
|
|
@ -5237,18 +5244,22 @@ pub fn handle_request(
|
|||
"ssh_cert.import",
|
||||
)?;
|
||||
let certificate = std::fs::read_to_string(&cert_path)?;
|
||||
let record = SshCertificateRecord {
|
||||
let mut record = SshCertificateRecord {
|
||||
id: certificate_id(&certificate),
|
||||
request_id: SshCertRequestId::new(request_id.clone()),
|
||||
certificate_fingerprint: ssh_public_key_fingerprint(&certificate),
|
||||
certificate,
|
||||
imported_at: UnixMillis(geth_store::now_ms()),
|
||||
provenance: None,
|
||||
};
|
||||
sign_ssh_certificate(node, &mut record)?;
|
||||
store.insert_ssh_certificate(&stored_from_ssh_certificate(&record))?;
|
||||
store.update_ssh_cert_request_status(
|
||||
&request_id,
|
||||
SshCertRequestStatus::Signed.as_str(),
|
||||
)?;
|
||||
if let Some(stored) = store.get_ssh_cert_request(&request_id)? {
|
||||
let mut request = ssh_cert_request_from_stored(stored)?;
|
||||
request.status = SshCertRequestStatus::Signed;
|
||||
sign_ssh_cert_request(node, &mut request)?;
|
||||
store.insert_ssh_cert_request(&stored_from_ssh_cert_request(&request))?;
|
||||
}
|
||||
Ok(ControlResponse::SshCertImported {
|
||||
certificate: record,
|
||||
})
|
||||
|
|
@ -5271,7 +5282,7 @@ pub fn handle_request(
|
|||
.list_ssh_certificates()?
|
||||
.into_iter()
|
||||
.map(ssh_certificate_from_stored)
|
||||
.collect(),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
ControlRequest::SshRevocationAdd {
|
||||
|
|
@ -5291,14 +5302,16 @@ pub fn handle_request(
|
|||
.parse::<SshRevocationKind>()
|
||||
.map_err(|_| NodeError::InvalidSshRevocationKind(kind.clone()))?;
|
||||
let created_at = UnixMillis(geth_store::now_ms());
|
||||
let revocation = SshRevocationEntry {
|
||||
let mut revocation = SshRevocationEntry {
|
||||
id: revocation_id(&kind, &target, created_at),
|
||||
kind,
|
||||
target,
|
||||
reason,
|
||||
created_at,
|
||||
published: true,
|
||||
provenance: None,
|
||||
};
|
||||
sign_ssh_revocation(node, &mut revocation)?;
|
||||
store.insert_ssh_revocation(&stored_from_ssh_revocation(&revocation))?;
|
||||
Ok(ControlResponse::SshRevocationAdded { revocation })
|
||||
}
|
||||
|
|
@ -5390,7 +5403,7 @@ pub fn handle_request(
|
|||
)?;
|
||||
let body = std::fs::read_to_string(&path)?;
|
||||
let created_at = UnixMillis(geth_store::now_ms());
|
||||
let revocations = match format.as_str() {
|
||||
let mut revocations = match format.as_str() {
|
||||
"jsonl" => body
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
|
|
@ -5408,6 +5421,7 @@ pub fn handle_request(
|
|||
reason: Some(format!("imported from {}", path.display())),
|
||||
created_at,
|
||||
published: true,
|
||||
provenance: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
|
|
@ -5424,6 +5438,9 @@ pub fn handle_request(
|
|||
));
|
||||
}
|
||||
};
|
||||
for revocation in &mut revocations {
|
||||
sign_ssh_revocation(node, revocation)?;
|
||||
}
|
||||
for revocation in &revocations {
|
||||
store.insert_ssh_revocation(&stored_from_ssh_revocation(revocation))?;
|
||||
}
|
||||
|
|
@ -6585,6 +6602,140 @@ fn expected_openssh_cert_path(public_key_path: &Path) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn sign_ssh_cert_request(node: &LocalNode, request: &mut SshCertRequest) -> Result<(), NodeError> {
|
||||
let key = AgentKey::load(&node.paths.agent_key())?;
|
||||
let signature = key.sign_canonical(
|
||||
SSH_CERT_REQUEST_NAMESPACE,
|
||||
&ssh_cert_request_signing_payload(request),
|
||||
)?;
|
||||
request.provenance = Some(SshRecordProvenance {
|
||||
namespace: SSH_CERT_REQUEST_NAMESPACE.to_owned(),
|
||||
signer_node: NodeId::new(node.node_id.clone()),
|
||||
signer_agent: node.agent_id.clone(),
|
||||
signer_public_key: key.public_key_hex(),
|
||||
signature_hex: hex::encode(signature),
|
||||
signed_at: UnixMillis(geth_store::now_ms()),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_ssh_certificate(
|
||||
node: &LocalNode,
|
||||
certificate: &mut SshCertificateRecord,
|
||||
) -> Result<(), NodeError> {
|
||||
let key = AgentKey::load(&node.paths.agent_key())?;
|
||||
let signature = key.sign_canonical(
|
||||
SSH_CERT_ISSUANCE_NAMESPACE,
|
||||
&ssh_certificate_signing_payload(certificate),
|
||||
)?;
|
||||
certificate.provenance = Some(SshRecordProvenance {
|
||||
namespace: SSH_CERT_ISSUANCE_NAMESPACE.to_owned(),
|
||||
signer_node: NodeId::new(node.node_id.clone()),
|
||||
signer_agent: node.agent_id.clone(),
|
||||
signer_public_key: key.public_key_hex(),
|
||||
signature_hex: hex::encode(signature),
|
||||
signed_at: UnixMillis(geth_store::now_ms()),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_ssh_revocation(
|
||||
node: &LocalNode,
|
||||
revocation: &mut SshRevocationEntry,
|
||||
) -> Result<(), NodeError> {
|
||||
let key = AgentKey::load(&node.paths.agent_key())?;
|
||||
let signature = key.sign_canonical(
|
||||
SSH_REVOCATION_LIST_NAMESPACE,
|
||||
&ssh_revocation_signing_payload(revocation),
|
||||
)?;
|
||||
revocation.provenance = Some(SshRecordProvenance {
|
||||
namespace: SSH_REVOCATION_LIST_NAMESPACE.to_owned(),
|
||||
signer_node: NodeId::new(node.node_id.clone()),
|
||||
signer_agent: node.agent_id.clone(),
|
||||
signer_public_key: key.public_key_hex(),
|
||||
signature_hex: hex::encode(signature),
|
||||
signed_at: UnixMillis(geth_store::now_ms()),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_ssh_cert_request_provenance(request: &SshCertRequest) -> Result<(), NodeError> {
|
||||
let provenance = request.provenance.as_ref().ok_or_else(|| {
|
||||
NodeError::IrohPeer(format!(
|
||||
"SSH cert request {} is missing signed provenance",
|
||||
request.id
|
||||
))
|
||||
})?;
|
||||
if provenance.namespace != SSH_CERT_REQUEST_NAMESPACE {
|
||||
return Err(NodeError::IrohPeer(format!(
|
||||
"SSH cert request {} uses invalid provenance namespace {}",
|
||||
request.id, provenance.namespace
|
||||
)));
|
||||
}
|
||||
verify_record_provenance(
|
||||
&provenance.signer_public_key,
|
||||
SSH_CERT_REQUEST_NAMESPACE,
|
||||
&ssh_cert_request_signing_payload(request),
|
||||
&provenance.signature_hex,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_ssh_certificate_provenance(certificate: &SshCertificateRecord) -> Result<(), NodeError> {
|
||||
let provenance = certificate.provenance.as_ref().ok_or_else(|| {
|
||||
NodeError::IrohPeer(format!(
|
||||
"SSH certificate {} is missing signed provenance",
|
||||
certificate.id
|
||||
))
|
||||
})?;
|
||||
if provenance.namespace != SSH_CERT_ISSUANCE_NAMESPACE {
|
||||
return Err(NodeError::IrohPeer(format!(
|
||||
"SSH certificate {} uses invalid provenance namespace {}",
|
||||
certificate.id, provenance.namespace
|
||||
)));
|
||||
}
|
||||
verify_record_provenance(
|
||||
&provenance.signer_public_key,
|
||||
SSH_CERT_ISSUANCE_NAMESPACE,
|
||||
&ssh_certificate_signing_payload(certificate),
|
||||
&provenance.signature_hex,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_ssh_revocation_provenance(revocation: &SshRevocationEntry) -> Result<(), NodeError> {
|
||||
let provenance = revocation.provenance.as_ref().ok_or_else(|| {
|
||||
NodeError::IrohPeer(format!(
|
||||
"SSH revocation {} is missing signed provenance",
|
||||
revocation.id
|
||||
))
|
||||
})?;
|
||||
if provenance.namespace != SSH_REVOCATION_LIST_NAMESPACE {
|
||||
return Err(NodeError::IrohPeer(format!(
|
||||
"SSH revocation {} uses invalid provenance namespace {}",
|
||||
revocation.id, provenance.namespace
|
||||
)));
|
||||
}
|
||||
verify_record_provenance(
|
||||
&provenance.signer_public_key,
|
||||
SSH_REVOCATION_LIST_NAMESPACE,
|
||||
&ssh_revocation_signing_payload(revocation),
|
||||
&provenance.signature_hex,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_record_provenance<T: serde::Serialize + ?Sized>(
|
||||
public_key_hex: &str,
|
||||
namespace: &str,
|
||||
payload: &T,
|
||||
signature_hex: &str,
|
||||
) -> Result<(), NodeError> {
|
||||
let public_key =
|
||||
hex::decode(public_key_hex).map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
let signature =
|
||||
hex::decode(signature_hex).map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
geth_crypto::verify_canonical(&public_key, namespace, payload, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stored_from_ssh_cert_request(request: &SshCertRequest) -> StoredSshCertRequest {
|
||||
StoredSshCertRequest {
|
||||
request_id: request.id.to_string(),
|
||||
|
|
@ -6598,6 +6749,12 @@ fn stored_from_ssh_cert_request(request: &SshCertRequest) -> StoredSshCertReques
|
|||
reason: request.reason.clone(),
|
||||
status: request.status.to_string(),
|
||||
created_at_ms: request.created_at.0,
|
||||
provenance_json: request
|
||||
.provenance
|
||||
.as_ref()
|
||||
.map(serde_json::to_string)
|
||||
.transpose()
|
||||
.expect("SSH provenance serializes"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6615,6 +6772,7 @@ fn insert_ssh_cert_request_if_not_conflicting(
|
|||
}
|
||||
return Ok(false);
|
||||
}
|
||||
verify_ssh_cert_request_provenance(request)?;
|
||||
store.insert_ssh_cert_request(&stored)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
@ -6640,6 +6798,10 @@ fn ssh_cert_request_from_stored(stored: StoredSshCertRequest) -> Result<SshCertR
|
|||
reason: stored.reason,
|
||||
status,
|
||||
created_at: UnixMillis(stored.created_at_ms),
|
||||
provenance: stored
|
||||
.provenance_json
|
||||
.map(|json| serde_json::from_str(&json))
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -6650,6 +6812,12 @@ fn stored_from_ssh_certificate(certificate: &SshCertificateRecord) -> StoredSshC
|
|||
certificate: certificate.certificate.clone(),
|
||||
certificate_fingerprint: certificate.certificate_fingerprint.clone(),
|
||||
imported_at_ms: certificate.imported_at.0,
|
||||
provenance_json: certificate
|
||||
.provenance
|
||||
.as_ref()
|
||||
.map(serde_json::to_string)
|
||||
.transpose()
|
||||
.expect("SSH provenance serializes"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6671,18 +6839,25 @@ fn insert_ssh_certificate_if_not_conflicting(
|
|||
}
|
||||
return Ok(false);
|
||||
}
|
||||
verify_ssh_certificate_provenance(certificate)?;
|
||||
store.insert_ssh_certificate(&stored)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn ssh_certificate_from_stored(stored: StoredSshCertificate) -> SshCertificateRecord {
|
||||
SshCertificateRecord {
|
||||
fn ssh_certificate_from_stored(
|
||||
stored: StoredSshCertificate,
|
||||
) -> Result<SshCertificateRecord, NodeError> {
|
||||
Ok(SshCertificateRecord {
|
||||
id: SshCertId::new(stored.cert_id),
|
||||
request_id: SshCertRequestId::new(stored.request_id),
|
||||
certificate: stored.certificate,
|
||||
certificate_fingerprint: stored.certificate_fingerprint,
|
||||
imported_at: UnixMillis(stored.imported_at_ms),
|
||||
}
|
||||
provenance: stored
|
||||
.provenance_json
|
||||
.map(|json| serde_json::from_str(&json))
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn stored_from_ssh_revocation(revocation: &SshRevocationEntry) -> StoredSshRevocation {
|
||||
|
|
@ -6693,6 +6868,12 @@ fn stored_from_ssh_revocation(revocation: &SshRevocationEntry) -> StoredSshRevoc
|
|||
reason: revocation.reason.clone(),
|
||||
created_at_ms: revocation.created_at.0,
|
||||
published: revocation.published,
|
||||
provenance_json: revocation
|
||||
.provenance
|
||||
.as_ref()
|
||||
.map(serde_json::to_string)
|
||||
.transpose()
|
||||
.expect("SSH provenance serializes"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6714,6 +6895,7 @@ fn insert_ssh_revocation_if_not_conflicting(
|
|||
}
|
||||
return Ok(false);
|
||||
}
|
||||
verify_ssh_revocation_provenance(revocation)?;
|
||||
store.insert_ssh_revocation(&stored)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
@ -6732,6 +6914,10 @@ fn ssh_revocation_from_stored(
|
|||
reason: stored.reason,
|
||||
created_at: UnixMillis(stored.created_at_ms),
|
||||
published: stored.published,
|
||||
provenance: stored
|
||||
.provenance_json
|
||||
.map(|json| serde_json::from_str(&json))
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -6826,7 +7012,7 @@ mod tests {
|
|||
#[test]
|
||||
fn ssh_sync_import_rejects_conflicting_records() {
|
||||
let store = Store::open_memory().expect("open");
|
||||
let request = SshCertRequest {
|
||||
let mut request = SshCertRequest {
|
||||
id: SshCertRequestId::new("ssh-cert-request:1"),
|
||||
requester_node: NodeId::new("node:right"),
|
||||
public_key: "ssh-ed25519 AAAA".to_owned(),
|
||||
|
|
@ -6838,7 +7024,9 @@ mod tests {
|
|||
reason: Some("original".to_owned()),
|
||||
status: SshCertRequestStatus::Pending,
|
||||
created_at: UnixMillis(1),
|
||||
provenance: None,
|
||||
};
|
||||
add_test_ssh_cert_request_provenance(&mut request);
|
||||
assert!(
|
||||
insert_ssh_cert_request_if_not_conflicting(&store, &request).expect("insert request")
|
||||
);
|
||||
|
|
@ -6848,6 +7036,10 @@ mod tests {
|
|||
!insert_ssh_cert_request_if_not_conflicting(&store, &conflicting_request)
|
||||
.expect("reject conflicting request")
|
||||
);
|
||||
let mut unsigned_request = request.clone();
|
||||
unsigned_request.id = SshCertRequestId::new("ssh-cert-request:unsigned");
|
||||
unsigned_request.provenance = None;
|
||||
assert!(insert_ssh_cert_request_if_not_conflicting(&store, &unsigned_request).is_err());
|
||||
assert_eq!(
|
||||
store
|
||||
.get_ssh_cert_request(request.id.as_str())
|
||||
|
|
@ -6858,14 +7050,16 @@ mod tests {
|
|||
Some("original")
|
||||
);
|
||||
|
||||
let revocation = SshRevocationEntry {
|
||||
let mut revocation = SshRevocationEntry {
|
||||
id: geth_types::SshRevocationId::new("ssh-revocation:1"),
|
||||
kind: SshRevocationKind::KeyId,
|
||||
target: "old-key".to_owned(),
|
||||
reason: Some("original".to_owned()),
|
||||
created_at: UnixMillis(2),
|
||||
published: true,
|
||||
provenance: None,
|
||||
};
|
||||
add_test_ssh_revocation_provenance(&mut revocation);
|
||||
assert!(
|
||||
insert_ssh_revocation_if_not_conflicting(&store, &revocation)
|
||||
.expect("insert revocation")
|
||||
|
|
@ -6876,6 +7070,10 @@ mod tests {
|
|||
!insert_ssh_revocation_if_not_conflicting(&store, &conflicting_revocation)
|
||||
.expect("reject conflicting revocation")
|
||||
);
|
||||
let mut unsigned_revocation = revocation.clone();
|
||||
unsigned_revocation.id = geth_types::SshRevocationId::new("ssh-revocation:unsigned");
|
||||
unsigned_revocation.provenance = None;
|
||||
assert!(insert_ssh_revocation_if_not_conflicting(&store, &unsigned_revocation).is_err());
|
||||
assert_eq!(
|
||||
store
|
||||
.list_ssh_revocations()
|
||||
|
|
@ -6887,6 +7085,42 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn add_test_ssh_cert_request_provenance(request: &mut SshCertRequest) {
|
||||
let key = AgentKey::generate();
|
||||
let signature = key
|
||||
.sign_canonical(
|
||||
SSH_CERT_REQUEST_NAMESPACE,
|
||||
&ssh_cert_request_signing_payload(request),
|
||||
)
|
||||
.expect("sign request");
|
||||
request.provenance = Some(SshRecordProvenance {
|
||||
namespace: SSH_CERT_REQUEST_NAMESPACE.to_owned(),
|
||||
signer_node: NodeId::new("node:right"),
|
||||
signer_agent: key.agent_id().to_string(),
|
||||
signer_public_key: key.public_key_hex(),
|
||||
signature_hex: hex::encode(signature),
|
||||
signed_at: UnixMillis(1),
|
||||
});
|
||||
}
|
||||
|
||||
fn add_test_ssh_revocation_provenance(revocation: &mut SshRevocationEntry) {
|
||||
let key = AgentKey::generate();
|
||||
let signature = key
|
||||
.sign_canonical(
|
||||
SSH_REVOCATION_LIST_NAMESPACE,
|
||||
&ssh_revocation_signing_payload(revocation),
|
||||
)
|
||||
.expect("sign revocation");
|
||||
revocation.provenance = Some(SshRecordProvenance {
|
||||
namespace: SSH_REVOCATION_LIST_NAMESPACE.to_owned(),
|
||||
signer_node: NodeId::new("node:right"),
|
||||
signer_agent: key.agent_id().to_string(),
|
||||
signer_public_key: key.public_key_hex(),
|
||||
signature_hex: hex::encode(signature),
|
||||
signed_at: UnixMillis(2),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_watermarks_include_only_authorized_streams() {
|
||||
let store = Store::open_memory().expect("open");
|
||||
|
|
|
|||
Loading…
Reference in a new issue