Compare commits
4 Commits
main
...
security-b
| Author | SHA1 | Date | |
|---|---|---|---|
| 075d33219e | |||
| 8cb6f4abe0 | |||
|
|
4bac70a6e9 | ||
|
|
54a41743be |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"git.enabled": false
|
|
||||||
}
|
|
||||||
26
.woodpecker/server-vet.yaml
Normal file
26
.woodpecker/server-vet.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path:
|
||||||
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
|
- event: push
|
||||||
|
branch: main
|
||||||
|
path:
|
||||||
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: jdxcode/mise:latest
|
||||||
|
directory: server
|
||||||
|
environment:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
CARGO_TARGET_DIR: /usr/local/cargo/target
|
||||||
|
CARGO_HOME: /usr/local/cargo/registry
|
||||||
|
volumes:
|
||||||
|
- cargo-target:/usr/local/cargo/target
|
||||||
|
- cargo-registry:/usr/local/cargo/registry
|
||||||
|
commands:
|
||||||
|
- apt-get update && apt-get install -y pkg-config
|
||||||
|
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
||||||
|
- mise install rust
|
||||||
|
- mise install cargo:cargo-vet
|
||||||
|
- mise exec cargo:cargo-vet -- cargo vet
|
||||||
@@ -14,6 +14,10 @@ backend = "cargo:cargo-features-manager"
|
|||||||
version = "0.9.126"
|
version = "0.9.126"
|
||||||
backend = "cargo:cargo-nextest"
|
backend = "cargo:cargo-nextest"
|
||||||
|
|
||||||
|
[[tools."cargo:cargo-shear"]]
|
||||||
|
version = "1.9.1"
|
||||||
|
backend = "cargo:cargo-shear"
|
||||||
|
|
||||||
[[tools."cargo:cargo-vet"]]
|
[[tools."cargo:cargo-vet"]]
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
flutter = "3.38.9-stable"
|
flutter = "3.38.9-stable"
|
||||||
protoc = "29.6"
|
protoc = "29.6"
|
||||||
rust = "1.93.0"
|
rust = "1.93.1"
|
||||||
"cargo:cargo-features-manager" = "0.11.1"
|
"cargo:cargo-features-manager" = "0.11.1"
|
||||||
"cargo:cargo-nextest" = "0.9.126"
|
"cargo:cargo-nextest" = "0.9.126"
|
||||||
|
"cargo:cargo-shear" = "latest"
|
||||||
|
|||||||
@@ -7,23 +7,27 @@ import "auth.proto";
|
|||||||
message ClientRequest {
|
message ClientRequest {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.ClientMessage auth_message = 1;
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
|
CertRotationAck cert_rotation_ack = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientResponse {
|
message ClientResponse {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.ServerMessage auth_message = 1;
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
|
CertRotationNotification cert_rotation_notification = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserAgentRequest {
|
message UserAgentRequest {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.ClientMessage auth_message = 1;
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
|
CertRotationAck cert_rotation_ack = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message UserAgentResponse {
|
message UserAgentResponse {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.ServerMessage auth_message = 1;
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
|
CertRotationNotification cert_rotation_notification = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +36,32 @@ message ServerInfo {
|
|||||||
bytes cert_public_key = 2;
|
bytes cert_public_key = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLS Certificate Rotation Protocol
|
||||||
|
message CertRotationNotification {
|
||||||
|
// New public certificate (DER-encoded)
|
||||||
|
bytes new_cert = 1;
|
||||||
|
|
||||||
|
// Unix timestamp when rotation will be executed (if all ACKs received)
|
||||||
|
int64 rotation_scheduled_at = 2;
|
||||||
|
|
||||||
|
// Unix timestamp deadline for ACK (7 days from now)
|
||||||
|
int64 ack_deadline = 3;
|
||||||
|
|
||||||
|
// Rotation ID for tracking
|
||||||
|
int32 rotation_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CertRotationAck {
|
||||||
|
// Rotation ID (from CertRotationNotification)
|
||||||
|
int32 rotation_id = 1;
|
||||||
|
|
||||||
|
// Client public key for identification
|
||||||
|
bytes client_public_key = 2;
|
||||||
|
|
||||||
|
// Confirmation that client saved the new certificate
|
||||||
|
bool cert_saved = 3;
|
||||||
|
}
|
||||||
|
|
||||||
service ArbiterService {
|
service ArbiterService {
|
||||||
rpc Client(stream ClientRequest) returns (stream ClientResponse);
|
rpc Client(stream ClientRequest) returns (stream ClientResponse);
|
||||||
rpc UserAgent(stream UserAgentRequest) returns (stream UserAgentResponse);
|
rpc UserAgent(stream UserAgentRequest) returns (stream UserAgentResponse);
|
||||||
|
|||||||
46
protobufs/google/protobuf/timestamp.proto
Normal file
46
protobufs/google/protobuf/timestamp.proto
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package google.protobuf;
|
||||||
|
|
||||||
|
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||||
|
option cc_enable_arenas = true;
|
||||||
|
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
|
||||||
|
option java_package = "com.google.protobuf";
|
||||||
|
option java_outer_classname = "TimestampProto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option objc_class_prefix = "GPB";
|
||||||
|
|
||||||
|
// A Timestamp represents a point in time independent of any time zone or local
|
||||||
|
// calendar, encoded as a count of seconds and fractions of seconds at
|
||||||
|
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||||
|
// January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||||
|
// Gregorian calendar backwards to year one.
|
||||||
|
message Timestamp {
|
||||||
|
// Represents seconds of UTC time since Unix epoch
|
||||||
|
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||||
|
// 9999-12-31T23:59:59Z inclusive.
|
||||||
|
int64 seconds = 1;
|
||||||
|
|
||||||
|
// Non-negative fractions of a second at nanosecond resolution. Negative
|
||||||
|
// second values with fractions must still have non-negative nanos values
|
||||||
|
// that count forward in time. Must be from 0 to 999,999,999
|
||||||
|
// inclusive.
|
||||||
|
int32 nanos = 2;
|
||||||
|
}
|
||||||
481
server/Cargo.lock
generated
481
server/Cargo.lock
generated
@@ -59,15 +59,11 @@ version = "0.1.0"
|
|||||||
name = "arbiter-proto"
|
name = "arbiter-proto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
|
||||||
"futures",
|
"futures",
|
||||||
"hex",
|
"hex",
|
||||||
"kameo",
|
"kameo",
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"prost-derive",
|
|
||||||
"prost-types",
|
|
||||||
"rkyv",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tonic",
|
"tonic",
|
||||||
@@ -80,30 +76,25 @@ name = "arbiter-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbiter-proto",
|
"arbiter-proto",
|
||||||
|
"argon2",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"ed25519",
|
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"futures",
|
"futures",
|
||||||
|
"hex",
|
||||||
"kameo",
|
"kameo",
|
||||||
"memsafe",
|
"memsafe",
|
||||||
"miette",
|
"miette",
|
||||||
"prost-types",
|
|
||||||
"rand",
|
"rand",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"restructed",
|
|
||||||
"rkyv",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"smlang",
|
"smlang",
|
||||||
"statig",
|
|
||||||
"tempfile",
|
|
||||||
"test-log",
|
"test-log",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -117,6 +108,18 @@ dependencies = [
|
|||||||
name = "arbiter-useragent"
|
name = "arbiter-useragent"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures 0.2.17",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asn1-rs"
|
name = "asn1-rs"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -141,7 +144,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -153,7 +156,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -164,7 +167,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -192,9 +195,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.37.0"
|
version = "0.37.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
@@ -295,9 +298,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@@ -314,29 +335,6 @@ version = "3.19.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytecheck"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b"
|
|
||||||
dependencies = [
|
|
||||||
"bytecheck_derive",
|
|
||||||
"ptr_meta",
|
|
||||||
"rancor",
|
|
||||||
"simdutf8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytecheck_derive"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -345,9 +343,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -430,12 +428,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "const-oid"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -504,7 +496,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest",
|
"digest 0.11.0",
|
||||||
"fiat-crypto",
|
"fiat-crypto",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"subtle",
|
"subtle",
|
||||||
@@ -519,7 +511,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -543,7 +535,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -554,7 +546,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -577,16 +569,6 @@ version = "2.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "der"
|
|
||||||
version = "0.8.0-rc.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653"
|
|
||||||
dependencies = [
|
|
||||||
"const-oid",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der-parser"
|
name = "der-parser"
|
||||||
version = "10.0.0"
|
version = "10.0.0"
|
||||||
@@ -603,9 +585,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.5"
|
version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
@@ -651,7 +633,7 @@ dependencies = [
|
|||||||
"dsl_auto_type",
|
"dsl_auto_type",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -671,16 +653,27 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c"
|
checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.11.0-rc.11"
|
version = "0.10.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02b42f1d9edf5207c137646b568a0168ca0ec25b7f9eaf7f9961da51a3d91cea"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer 0.10.4",
|
||||||
|
"crypto-common 0.1.7",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8bf3682cdec91817be507e4aa104314898b95b84d74f3d43882210101a545b6"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.11.0",
|
||||||
"crypto-common 0.2.0",
|
"crypto-common 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -692,7 +685,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -712,7 +705,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -733,7 +726,6 @@ version = "3.0.0-rc.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890"
|
checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8",
|
|
||||||
"signature",
|
"signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -833,9 +825,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -848,9 +840,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -858,15 +850,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -875,38 +867,38 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -916,7 +908,6 @@ dependencies = [
|
|||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1260,7 +1251,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1277,9 +1268,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.181"
|
version = "0.2.182"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
@@ -1371,7 +1362,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1434,26 +1425,6 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "munge"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c"
|
|
||||||
dependencies = [
|
|
||||||
"munge_macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "munge_macro"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -1557,6 +1528,17 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "3.0.6"
|
version = "3.0.6"
|
||||||
@@ -1601,7 +1583,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1616,16 +1598,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkcs8"
|
|
||||||
version = "0.11.0-rc.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774"
|
|
||||||
dependencies = [
|
|
||||||
"der",
|
|
||||||
"spki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -1662,53 +1634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr2"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error2"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1747,7 +1673,7 @@ dependencies = [
|
|||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"pulldown-cmark-to-cmark",
|
"pulldown-cmark-to-cmark",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1761,7 +1687,7 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1770,30 +1696,9 @@ version = "0.14.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
|
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ptr_meta"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79"
|
|
||||||
dependencies = [
|
|
||||||
"ptr_meta_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ptr_meta_derive"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pulldown-cmark"
|
name = "pulldown-cmark"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -1829,15 +1734,6 @@ version = "5.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rancor"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee"
|
|
||||||
dependencies = [
|
|
||||||
"ptr_meta",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -1917,27 +1813,6 @@ version = "0.8.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rend"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6"
|
|
||||||
dependencies = [
|
|
||||||
"bytecheck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "restructed"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f6f6e863d7d9d318699737c043d560dce1ea3cb6f5c78e0a3f0d1f257c73dfc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -1952,36 +1827,6 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rkyv"
|
|
||||||
version = "0.8.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70"
|
|
||||||
dependencies = [
|
|
||||||
"bytecheck",
|
|
||||||
"bytes",
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"indexmap",
|
|
||||||
"munge",
|
|
||||||
"ptr_meta",
|
|
||||||
"rancor",
|
|
||||||
"rend",
|
|
||||||
"rkyv_derive",
|
|
||||||
"tinyvec",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rkyv_derive"
|
|
||||||
version = "0.8.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsqlite-vfs"
|
name = "rsqlite-vfs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2128,7 +1973,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2161,7 +2006,7 @@ checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2201,12 +2046,6 @@ version = "0.3.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "simdutf8"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -2250,16 +2089,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spki"
|
|
||||||
version = "0.8.0-rc.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80"
|
|
||||||
dependencies = [
|
|
||||||
"base64ct",
|
|
||||||
"der",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlite-wasm-rs"
|
name = "sqlite-wasm-rs"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -2272,27 +2101,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "statig"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "03c04b4a9f2d66294d63bdd8df834caad9f8e181997c3cf766b6b4f6d12d4fbc"
|
|
||||||
dependencies = [
|
|
||||||
"statig_macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "statig_macro"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8090ca395ee30c4b38fee68cf4ddf0bcc5f01aa83364cd4c3ec737a1596dab4d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_morph"
|
name = "string_morph"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2345,9 +2153,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.116"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2368,7 +2176,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2412,7 +2220,7 @@ checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2442,7 +2250,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2485,21 +2293,6 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinyvec"
|
|
||||||
version = "1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
|
||||||
dependencies = [
|
|
||||||
"tinyvec_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinyvec_macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.49.0"
|
version = "1.49.0"
|
||||||
@@ -2526,7 +2319,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2566,9 +2359,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.11+spec-1.1.0"
|
version = "0.9.12+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@@ -2588,18 +2381,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.6+spec-1.1.0"
|
version = "1.0.8+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic"
|
name = "tonic"
|
||||||
version = "0.14.3"
|
version = "0.14.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a"
|
checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2629,21 +2422,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic-build"
|
name = "tonic-build"
|
||||||
version = "0.14.3"
|
version = "0.14.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc"
|
checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic-prost"
|
name = "tonic-prost"
|
||||||
version = "0.14.3"
|
version = "0.14.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0"
|
checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost",
|
"prost",
|
||||||
@@ -2652,16 +2445,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tonic-prost-build"
|
name = "tonic-prost-build"
|
||||||
version = "0.14.3"
|
version = "0.14.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f"
|
checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tonic-build",
|
"tonic-build",
|
||||||
]
|
]
|
||||||
@@ -2716,7 +2509,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2763,9 +2556,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.23"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
@@ -2815,9 +2608,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.20.0"
|
version = "1.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
|
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -2900,7 +2693,7 @@ dependencies = [
|
|||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2990,7 +2783,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3001,7 +2794,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3220,7 +3013,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"wasm-metadata",
|
"wasm-metadata",
|
||||||
"wit-bindgen-core",
|
"wit-bindgen-core",
|
||||||
"wit-component",
|
"wit-component",
|
||||||
@@ -3236,7 +3029,7 @@ dependencies = [
|
|||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.114",
|
"syn 2.0.116",
|
||||||
"wit-bindgen-core",
|
"wit-bindgen-core",
|
||||||
"wit-bindgen-rust",
|
"wit-bindgen-rust",
|
||||||
]
|
]
|
||||||
@@ -3313,9 +3106,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.20"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
|
|||||||
@@ -9,15 +9,12 @@ resolver = "3"
|
|||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
prost = "0.14.3"
|
|
||||||
tonic = { version = "0.14.3", features = ["deflate", "gzip", "tls-connect-info", "zstd"] }
|
tonic = { version = "0.14.3", features = ["deflate", "gzip", "tls-connect-info", "zstd"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
ed25519 = "3.0.0-rc.4"
|
|
||||||
ed25519-dalek = { version = "3.0.0-pre.6", features = ["rand_core"] }
|
ed25519-dalek = { version = "3.0.0-pre.6", features = ["rand_core"] }
|
||||||
chrono = { version = "0.4.43", features = ["serde"] }
|
chrono = { version = "0.4.43", features = ["serde"] }
|
||||||
rand = "0.10.0"
|
rand = "0.10.0"
|
||||||
uuid = "1.20.0"
|
|
||||||
rustls = "0.23.36"
|
rustls = "0.23.36"
|
||||||
smlang = "0.8.0"
|
smlang = "0.8.0"
|
||||||
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
||||||
@@ -26,4 +23,3 @@ async-trait = "0.1.89"
|
|||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||||
kameo = "0.19.2"
|
kameo = "0.19.2"
|
||||||
prost-types = { version = "0.14.3", features = ["chrono"] }
|
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ repository = "https://git.markettakers.org/MarketTakers/arbiter"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic.workspace = true
|
tonic.workspace = true
|
||||||
prost.workspace = true
|
|
||||||
bytes = "1.11.1"
|
|
||||||
prost-derive = "0.14.3"
|
|
||||||
prost-types.workspace = true
|
|
||||||
tonic-prost = "0.14.3"
|
|
||||||
rkyv = "0.8.15"
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
kameo.workspace = true
|
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
tonic-prost = "0.14.3"
|
||||||
|
prost = "0.14.3"
|
||||||
|
kameo.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.14.3"
|
prost-build = "0.14.3"
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use tonic_prost_build::configure;
|
|
||||||
|
|
||||||
static PROTOBUF_DIR: &str = "../../../protobufs";
|
static PROTOBUF_DIR: &str = "../../../protobufs";
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
configure()
|
let proto_files = vec![
|
||||||
.message_attribute(".", "#[derive(::kameo::Reply)]")
|
|
||||||
.compile_protos(
|
|
||||||
&[
|
|
||||||
format!("{}/arbiter.proto", PROTOBUF_DIR),
|
format!("{}/arbiter.proto", PROTOBUF_DIR),
|
||||||
format!("{}/auth.proto", PROTOBUF_DIR),
|
format!("{}/auth.proto", PROTOBUF_DIR),
|
||||||
],
|
];
|
||||||
&[PROTOBUF_DIR.to_string()],
|
|
||||||
)
|
// Компилируем protobuf (tonic-prost-build автоматически использует prost_types для google.protobuf)
|
||||||
|
tonic_prost_build::configure()
|
||||||
|
.message_attribute(".", "#[derive(::kameo::Reply)]")
|
||||||
|
.compile_protos(&proto_files, &[PROTOBUF_DIR.to_string()])?;
|
||||||
|
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ pub mod proto {
|
|||||||
|
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
|
||||||
pub static BOOTSTRAP_TOKEN_PATH: &'static str = "bootstrap_token";
|
pub static BOOTSTRAP_TOKEN_PATH: &str = "bootstrap_token";
|
||||||
|
|
||||||
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
||||||
static ARBITER_HOME: &'static str = ".arbiter";
|
static ARBITER_HOME: &str = ".arbiter";
|
||||||
let home_dir = std::env::home_dir().ok_or(std::io::Error::new(
|
let home_dir = std::env::home_dir().ok_or(std::io::Error::new(
|
||||||
std::io::ErrorKind::PermissionDenied,
|
std::io::ErrorKind::PermissionDenied,
|
||||||
"can not get home directory",
|
"can not get home directory",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ diesel-async = { version = "0.7.4", features = [
|
|||||||
"sqlite",
|
"sqlite",
|
||||||
"tokio",
|
"tokio",
|
||||||
] }
|
] }
|
||||||
ed25519.workspace = true
|
|
||||||
ed25519-dalek.workspace = true
|
ed25519-dalek.workspace = true
|
||||||
arbiter-proto.path = "../arbiter-proto"
|
arbiter-proto.path = "../arbiter-proto"
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -30,7 +29,6 @@ miette.workspace = true
|
|||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
statig = { version = "0.4.1", features = ["async"] }
|
|
||||||
secrecy = "0.10.3"
|
secrecy = "0.10.3"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
tokio-stream.workspace = true
|
tokio-stream.workspace = true
|
||||||
@@ -42,20 +40,13 @@ rcgen = { version = "0.14.7", features = [
|
|||||||
"x509-parser",
|
"x509-parser",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
rkyv = { version = "0.8.15", features = [
|
|
||||||
"aligned",
|
|
||||||
"little_endian",
|
|
||||||
"pointer_width_64",
|
|
||||||
] }
|
|
||||||
restructed = "0.2.2"
|
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
bytes = "1.11.1"
|
|
||||||
memsafe = "0.4.0"
|
memsafe = "0.4.0"
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
|
||||||
zeroize = { version = "1.8.2", features = ["std", "simd"] }
|
zeroize = { version = "1.8.2", features = ["std", "simd"] }
|
||||||
|
argon2 = { version = "0.5", features = ["std"] }
|
||||||
kameo.workspace = true
|
kameo.workspace = true
|
||||||
prost-types.workspace = true
|
hex = "0.4.3"
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
||||||
tempfile = "3.25.0"
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- Rollback TLS rotation tables
|
||||||
|
|
||||||
|
-- Удалить добавленную колонку из arbiter_settings
|
||||||
|
ALTER TABLE arbiter_settings DROP COLUMN current_cert_id;
|
||||||
|
|
||||||
|
-- Удалить таблицы в обратном порядке
|
||||||
|
DROP TABLE IF EXISTS tls_rotation_history;
|
||||||
|
DROP TABLE IF EXISTS rotation_client_acks;
|
||||||
|
DROP TABLE IF EXISTS tls_rotation_state;
|
||||||
|
DROP INDEX IF EXISTS idx_tls_certificates_active;
|
||||||
|
DROP TABLE IF EXISTS tls_certificates;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
-- История всех сертификатов
|
||||||
|
CREATE TABLE IF NOT EXISTS tls_certificates (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
cert BLOB NOT NULL, -- DER-encoded
|
||||||
|
cert_key BLOB NOT NULL, -- PEM-encoded
|
||||||
|
not_before INTEGER NOT NULL, -- Unix timestamp
|
||||||
|
not_after INTEGER NOT NULL, -- Unix timestamp
|
||||||
|
created_at INTEGER NOT NULL DEFAULT(unixepoch('now')),
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT 0 -- Только один active=1
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE INDEX idx_tls_certificates_active ON tls_certificates(is_active, not_after);
|
||||||
|
|
||||||
|
-- Tracking процесса ротации
|
||||||
|
CREATE TABLE IF NOT EXISTS tls_rotation_state (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY CHECK(id = 1), -- Singleton
|
||||||
|
state TEXT NOT NULL DEFAULT('normal') CHECK(state IN ('normal', 'initiated', 'waiting_acks', 'ready')),
|
||||||
|
new_cert_id INTEGER REFERENCES tls_certificates(id),
|
||||||
|
initiated_at INTEGER,
|
||||||
|
timeout_at INTEGER -- Таймаут для ожидания ACKs (initiated_at + 7 дней)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- Tracking ACKs от клиентов
|
||||||
|
CREATE TABLE IF NOT EXISTS rotation_client_acks (
|
||||||
|
rotation_id INTEGER NOT NULL, -- Ссылка на new_cert_id
|
||||||
|
client_key TEXT NOT NULL, -- Публичный ключ клиента (hex)
|
||||||
|
ack_received_at INTEGER NOT NULL DEFAULT(unixepoch('now')),
|
||||||
|
PRIMARY KEY (rotation_id, client_key)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- Audit trail событий ротации
|
||||||
|
CREATE TABLE IF NOT EXISTS tls_rotation_history (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
cert_id INTEGER NOT NULL REFERENCES tls_certificates(id),
|
||||||
|
event_type TEXT NOT NULL CHECK(event_type IN ('created', 'rotation_initiated', 'acks_complete', 'activated', 'timeout')),
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT(unixepoch('now')),
|
||||||
|
details TEXT -- JSON с доп. информацией
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- Миграция существующего сертификата
|
||||||
|
INSERT INTO tls_certificates (id, cert, cert_key, not_before, not_after, is_active, created_at)
|
||||||
|
SELECT
|
||||||
|
1,
|
||||||
|
cert,
|
||||||
|
cert_key,
|
||||||
|
unixepoch('now') as not_before,
|
||||||
|
unixepoch('now') + (90 * 24 * 60 * 60) as not_after, -- 90 дней
|
||||||
|
1 as is_active,
|
||||||
|
unixepoch('now')
|
||||||
|
FROM arbiter_settings WHERE id = 1;
|
||||||
|
|
||||||
|
-- Инициализация rotation_state
|
||||||
|
INSERT INTO tls_rotation_state (id, state) VALUES (1, 'normal');
|
||||||
|
|
||||||
|
-- Добавить ссылку на текущий сертификат
|
||||||
|
ALTER TABLE arbiter_settings ADD COLUMN current_cert_id INTEGER REFERENCES tls_certificates(id);
|
||||||
|
UPDATE arbiter_settings SET current_cert_id = 1 WHERE id = 1;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Remove argon2_salt column
|
||||||
|
ALTER TABLE aead_encrypted DROP COLUMN argon2_salt;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add argon2_salt column to store password derivation salt
|
||||||
|
ALTER TABLE aead_encrypted ADD COLUMN argon2_salt TEXT;
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
use arbiter_proto::proto::{
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use arbiter_proto::{
|
||||||
|
proto::{
|
||||||
UserAgentRequest, UserAgentResponse,
|
UserAgentRequest, UserAgentResponse,
|
||||||
auth::{
|
auth::{
|
||||||
self, AuthChallenge, AuthChallengeRequest, AuthOk, ClientMessage,
|
self, AuthChallengeRequest, ClientMessage, ServerMessage as AuthServerMessage,
|
||||||
ServerMessage as AuthServerMessage, client_message::Payload as ClientAuthPayload,
|
client_message::Payload as ClientAuthPayload,
|
||||||
server_message::Payload as ServerAuthPayload,
|
server_message::Payload as ServerAuthPayload,
|
||||||
},
|
},
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
},
|
||||||
|
transport::Bi,
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, dsl::update};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, dsl::update};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
@@ -21,19 +26,18 @@ use kameo::{
|
|||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tonic::Status;
|
use tonic::{Status, transport::Server};
|
||||||
use tracing::{error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ServerContext,
|
ServerContext,
|
||||||
|
actors::user_agent::auth::AuthChallenge,
|
||||||
context::bootstrap::{BootstrapActor, ConsumeToken},
|
context::bootstrap::{BootstrapActor, ConsumeToken},
|
||||||
db::{self, schema},
|
db::{self, schema},
|
||||||
errors::GrpcStatusExt,
|
errors::GrpcStatusExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Context for state machine with validated key and sent challenge
|
#[derive(Debug)]
|
||||||
/// Challenge is then transformed to bytes using shared function and verified
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ChallengeContext {
|
pub struct ChallengeContext {
|
||||||
challenge: AuthChallenge,
|
challenge: AuthChallenge,
|
||||||
key: VerifyingKey,
|
key: VerifyingKey,
|
||||||
@@ -161,7 +165,7 @@ impl UserAgentActor {
|
|||||||
|
|
||||||
self.transition(UserAgentEvents::ReceivedBootstrapToken)?;
|
self.transition(UserAgentEvents::ReceivedBootstrapToken)?;
|
||||||
|
|
||||||
Ok(auth_response(ServerAuthPayload::AuthOk(AuthOk {})))
|
Ok(auth_response(ServerAuthPayload::AuthOk(auth::AuthOk {})))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_with_challenge(&mut self, pubkey: VerifyingKey, pubkey_bytes: Vec<u8>) -> Output {
|
async fn auth_with_challenge(&mut self, pubkey: VerifyingKey, pubkey_bytes: Vec<u8>) -> Output {
|
||||||
@@ -201,7 +205,7 @@ impl UserAgentActor {
|
|||||||
|
|
||||||
let challenge = auth::AuthChallenge {
|
let challenge = auth::AuthChallenge {
|
||||||
pubkey: pubkey_bytes,
|
pubkey: pubkey_bytes,
|
||||||
nonce: nonce,
|
nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.transition(UserAgentEvents::SentChallenge(ChallengeContext {
|
self.transition(UserAgentEvents::SentChallenge(ChallengeContext {
|
||||||
@@ -296,7 +300,7 @@ impl UserAgentActor {
|
|||||||
"Client provided valid solution to authentication challenge"
|
"Client provided valid solution to authentication challenge"
|
||||||
);
|
);
|
||||||
self.transition(UserAgentEvents::ReceivedGoodSolution)?;
|
self.transition(UserAgentEvents::ReceivedGoodSolution)?;
|
||||||
Ok(auth_response(ServerAuthPayload::AuthOk(AuthOk {})))
|
Ok(auth_response(ServerAuthPayload::AuthOk(auth::AuthOk {})))
|
||||||
} else {
|
} else {
|
||||||
error!("Client provided invalid solution to authentication challenge");
|
error!("Client provided invalid solution to authentication challenge");
|
||||||
self.transition(UserAgentEvents::ReceivedBadSolution)?;
|
self.transition(UserAgentEvents::ReceivedBadSolution)?;
|
||||||
@@ -308,7 +312,8 @@ impl UserAgentActor {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
UserAgentResponse, auth::{AuthChallengeRequest, AuthOk},
|
UserAgentResponse,
|
||||||
|
auth::{AuthChallengeRequest, AuthOk},
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
};
|
};
|
||||||
use kameo::actor::Spawn;
|
use kameo::actor::Spawn;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use diesel::OptionalExtension as _;
|
use diesel::OptionalExtension as _;
|
||||||
use diesel_async::RunQueryDsl as _;
|
use diesel_async::RunQueryDsl as _;
|
||||||
@@ -6,15 +8,17 @@ use ed25519_dalek::VerifyingKey;
|
|||||||
use kameo::actor::{ActorRef, Spawn};
|
use kameo::actor::{ActorRef, Spawn};
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
|
use secrecy::{ExposeSecret, SecretBox};
|
||||||
use smlang::statemachine;
|
use smlang::statemachine;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{watch, RwLock};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::{
|
context::{
|
||||||
bootstrap::{BootstrapActor, generate_token},
|
bootstrap::{BootstrapActor, generate_token},
|
||||||
lease::LeaseHandler,
|
lease::LeaseHandler,
|
||||||
tls::{TlsDataRaw, TlsManager},
|
tls::{RotationState, RotationTask, TlsDataRaw, TlsManager},
|
||||||
},
|
},
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
@@ -54,8 +58,66 @@ pub enum InitError {
|
|||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Placeholder for secure root key cell implementation
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub struct KeyStorage;
|
pub enum UnsealError {
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::database_pool))]
|
||||||
|
Database(#[from] db::PoolError),
|
||||||
|
|
||||||
|
#[error("Query error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::database_query))]
|
||||||
|
Query(#[from] diesel::result::Error),
|
||||||
|
|
||||||
|
#[error("Decryption failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::decryption))]
|
||||||
|
DecryptionFailed(#[from] crate::crypto::CryptoError),
|
||||||
|
|
||||||
|
#[error("Invalid state for unseal")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::invalid_state))]
|
||||||
|
InvalidState,
|
||||||
|
|
||||||
|
#[error("Missing salt in database")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::missing_salt))]
|
||||||
|
MissingSalt,
|
||||||
|
|
||||||
|
#[error("No root key configured in database")]
|
||||||
|
#[diagnostic(code(arbiter_server::unseal::no_root_key))]
|
||||||
|
NoRootKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
pub enum SealError {
|
||||||
|
#[error("Invalid state for seal")]
|
||||||
|
#[diagnostic(code(arbiter_server::seal::invalid_state))]
|
||||||
|
InvalidState,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secure in-memory storage for root encryption key
|
||||||
|
///
|
||||||
|
/// Uses `secrecy` crate for automatic zeroization on drop to prevent key material
|
||||||
|
/// from remaining in memory after use. SecretBox provides heap-allocated secret
|
||||||
|
/// storage that implements Send + Sync for safe use in async contexts.
|
||||||
|
pub struct KeyStorage {
|
||||||
|
/// 32-byte root key protected by SecretBox
|
||||||
|
key: SecretBox<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyStorage {
|
||||||
|
/// Create new KeyStorage from a 32-byte root key
|
||||||
|
pub fn new(key: [u8; 32]) -> Self {
|
||||||
|
Self {
|
||||||
|
key: SecretBox::new(Box::new(key)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the key for cryptographic operations
|
||||||
|
pub fn key(&self) -> &[u8; 32] {
|
||||||
|
self.key.expose_secret()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop автоматически реализован через secrecy::Zeroize
|
||||||
|
// который зануляет память при освобождении
|
||||||
|
|
||||||
statemachine! {
|
statemachine! {
|
||||||
name: Server,
|
name: Server,
|
||||||
@@ -67,14 +129,20 @@ statemachine! {
|
|||||||
}
|
}
|
||||||
pub struct _Context;
|
pub struct _Context;
|
||||||
impl ServerStateMachineContext for _Context {
|
impl ServerStateMachineContext for _Context {
|
||||||
fn move_key(&mut self, _event_data: KeyStorage) -> Result<KeyStorage, ()> {
|
/// Move key from unseal event into Ready state
|
||||||
todo!()
|
fn move_key(&mut self, event_data: KeyStorage) -> Result<KeyStorage, ()> {
|
||||||
|
// Просто перемещаем KeyStorage из event в state
|
||||||
|
// Без клонирования - event data consumed
|
||||||
|
Ok(event_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Securely dispose of key when sealing
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[allow(clippy::unused_unit)]
|
#[allow(clippy::unused_unit)]
|
||||||
fn dispose_key(&mut self, _state_data: &KeyStorage) -> Result<(), ()> {
|
fn dispose_key(&mut self, _state_data: &KeyStorage) -> Result<(), ()> {
|
||||||
todo!()
|
// KeyStorage будет dropped после state transition
|
||||||
|
// secrecy::Zeroize зануляет память автоматически
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +150,12 @@ pub(crate) struct _ServerContextInner {
|
|||||||
pub db: db::DatabasePool,
|
pub db: db::DatabasePool,
|
||||||
pub state: RwLock<ServerStateMachine<_Context>>,
|
pub state: RwLock<ServerStateMachine<_Context>>,
|
||||||
pub rng: StdRng,
|
pub rng: StdRng,
|
||||||
pub tls: TlsManager,
|
pub tls: Arc<TlsManager>,
|
||||||
pub bootstrapper: ActorRef<BootstrapActor>,
|
pub bootstrapper: ActorRef<BootstrapActor>,
|
||||||
|
pub rotation_state: RwLock<RotationState>,
|
||||||
|
pub rotation_acks: Arc<RwLock<HashSet<VerifyingKey>>>,
|
||||||
|
pub user_agent_leases: LeaseHandler<VerifyingKey>,
|
||||||
|
pub client_leases: LeaseHandler<VerifyingKey>,
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ServerContext(Arc<_ServerContextInner>);
|
pub(crate) struct ServerContext(Arc<_ServerContextInner>);
|
||||||
@@ -97,34 +169,49 @@ impl std::ops::Deref for ServerContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServerContext {
|
impl ServerContext {
|
||||||
|
/// Check if all active clients have acknowledged the rotation
|
||||||
|
pub async fn check_rotation_ready(&self) -> bool {
|
||||||
|
// TODO: Implement proper rotation readiness check
|
||||||
|
// For now, return false as placeholder
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
async fn load_tls(
|
async fn load_tls(
|
||||||
db: &mut db::DatabaseConnection,
|
db: &db::DatabasePool,
|
||||||
settings: Option<&ArbiterSetting>,
|
settings: Option<&ArbiterSetting>,
|
||||||
) -> Result<TlsManager, InitError> {
|
) -> Result<TlsManager, InitError> {
|
||||||
match &settings {
|
match settings {
|
||||||
Some(settings) => {
|
Some(s) if s.current_cert_id.is_some() => {
|
||||||
|
// Load active certificate from tls_certificates table
|
||||||
|
Ok(TlsManager::load_from_db(
|
||||||
|
db.clone(),
|
||||||
|
s.current_cert_id.unwrap(),
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
Some(s) => {
|
||||||
|
// Legacy migration: extract validity and save to new table
|
||||||
let tls_data_raw = TlsDataRaw {
|
let tls_data_raw = TlsDataRaw {
|
||||||
cert: settings.cert.clone(),
|
cert: s.cert.clone(),
|
||||||
key: settings.cert_key.clone(),
|
key: s.cert_key.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TlsManager::new(Some(tls_data_raw)).await?)
|
// For legacy certificates, use current time as not_before
|
||||||
|
// and current time + 90 days as not_after
|
||||||
|
let not_before = chrono::Utc::now().timestamp();
|
||||||
|
let not_after = not_before + (90 * 24 * 60 * 60); // 90 days
|
||||||
|
|
||||||
|
Ok(TlsManager::new_from_legacy(
|
||||||
|
db.clone(),
|
||||||
|
tls_data_raw,
|
||||||
|
not_before,
|
||||||
|
not_after,
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let tls = TlsManager::new(None).await?;
|
// First startup - generate new certificate
|
||||||
let tls_data_raw = tls.bytes();
|
Ok(TlsManager::new(db.clone()).await?)
|
||||||
|
|
||||||
diesel::insert_into(arbiter_settings::table)
|
|
||||||
.values(&ArbiterSetting {
|
|
||||||
id: 1,
|
|
||||||
root_key_id: None,
|
|
||||||
cert_key: tls_data_raw.key,
|
|
||||||
cert: tls_data_raw.cert,
|
|
||||||
})
|
|
||||||
.execute(db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(tls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,10 +225,18 @@ impl ServerContext {
|
|||||||
.await
|
.await
|
||||||
.optional()?;
|
.optional()?;
|
||||||
|
|
||||||
let tls = Self::load_tls(&mut conn, settings.as_ref()).await?;
|
|
||||||
|
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
|
// Load TLS manager
|
||||||
|
let tls = Self::load_tls(&db, settings.as_ref()).await?;
|
||||||
|
|
||||||
|
// Load rotation state from database
|
||||||
|
let rotation_state = RotationState::load_from_db(&db)
|
||||||
|
.await
|
||||||
|
.unwrap_or(RotationState::Normal);
|
||||||
|
|
||||||
|
let bootstrap_token = generate_token().await?;
|
||||||
|
|
||||||
let mut state = ServerStateMachine::new(_Context);
|
let mut state = ServerStateMachine::new(_Context);
|
||||||
|
|
||||||
if let Some(settings) = &settings
|
if let Some(settings) = &settings
|
||||||
@@ -151,12 +246,157 @@ impl ServerContext {
|
|||||||
let _ = state.process_event(ServerEvents::Bootstrapped);
|
let _ = state.process_event(ServerEvents::Bootstrapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(Arc::new(_ServerContextInner {
|
// Create shutdown channel for rotation task
|
||||||
bootstrapper: BootstrapActor::spawn(BootstrapActor::new(&db).await?),
|
let (rotation_shutdown_tx, rotation_shutdown_rx) = watch::channel(false);
|
||||||
db,
|
|
||||||
|
// Initialize bootstrap actor
|
||||||
|
let bootstrapper = BootstrapActor::spawn(BootstrapActor::new(&db).await?);
|
||||||
|
|
||||||
|
let context = Arc::new(_ServerContextInner {
|
||||||
|
db: db.clone(),
|
||||||
rng,
|
rng,
|
||||||
tls,
|
tls: Arc::new(tls),
|
||||||
state: RwLock::new(state),
|
state: RwLock::new(state),
|
||||||
})))
|
bootstrapper,
|
||||||
|
rotation_state: RwLock::new(rotation_state),
|
||||||
|
rotation_acks: Arc::new(RwLock::new(HashSet::new())),
|
||||||
|
user_agent_leases: Default::default(),
|
||||||
|
client_leases: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unseal vault with password
|
||||||
|
pub async fn unseal(&self, password: &str) -> Result<(), UnsealError> {
|
||||||
|
use crate::crypto::root_key;
|
||||||
|
use diesel::QueryDsl as _;
|
||||||
|
|
||||||
|
// 1. Get root_key_id from settings
|
||||||
|
let mut conn = self.db.get().await?;
|
||||||
|
|
||||||
|
let settings: db::models::ArbiterSetting = schema::arbiter_settings::table
|
||||||
|
.first(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let root_key_id = settings.root_key_id.ok_or(UnsealError::NoRootKey)?;
|
||||||
|
|
||||||
|
// 2. Load encrypted root key
|
||||||
|
let encrypted: db::models::AeadEncrypted = schema::aead_encrypted::table
|
||||||
|
.find(root_key_id)
|
||||||
|
.first(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let salt = encrypted
|
||||||
|
.argon2_salt
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(UnsealError::MissingSalt)?;
|
||||||
|
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
// 3. Decrypt root key using password
|
||||||
|
let root_key = root_key::decrypt_root_key(&encrypted, password, salt)
|
||||||
|
.map_err(UnsealError::DecryptionFailed)?;
|
||||||
|
|
||||||
|
// 4. Create secure storage
|
||||||
|
let key_storage = KeyStorage::new(root_key);
|
||||||
|
|
||||||
|
// 5. Transition state machine
|
||||||
|
let mut state = self.state.write().await;
|
||||||
|
state
|
||||||
|
.process_event(ServerEvents::Unsealed(key_storage))
|
||||||
|
.map_err(|_| UnsealError::InvalidState)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seal the server (lock the key)
|
||||||
|
pub async fn seal(&self) -> Result<(), SealError> {
|
||||||
|
let mut state = self.state.write().await;
|
||||||
|
state
|
||||||
|
.process_event(ServerEvents::Sealed)
|
||||||
|
.map_err(|_| SealError::InvalidState)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keystorage_creation() {
|
||||||
|
let key = [42u8; 32];
|
||||||
|
let storage = KeyStorage::new(key);
|
||||||
|
assert_eq!(storage.key()[0], 42);
|
||||||
|
assert_eq!(storage.key().len(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keystorage_zeroization() {
|
||||||
|
let key = [99u8; 32];
|
||||||
|
{
|
||||||
|
let _storage = KeyStorage::new(key);
|
||||||
|
// storage будет dropped здесь
|
||||||
|
}
|
||||||
|
// После drop SecretBox должен зануляеть память
|
||||||
|
// Это проверяется автоматически через secrecy::Zeroize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_state_machine_transitions() {
|
||||||
|
let mut state = ServerStateMachine::new(_Context);
|
||||||
|
|
||||||
|
// Начальное состояние
|
||||||
|
assert!(matches!(state.state(), &ServerStates::NotBootstrapped));
|
||||||
|
|
||||||
|
// Bootstrapped transition
|
||||||
|
state.process_event(ServerEvents::Bootstrapped).unwrap();
|
||||||
|
assert!(matches!(state.state(), &ServerStates::Sealed));
|
||||||
|
|
||||||
|
// Unsealed transition
|
||||||
|
let key_storage = KeyStorage::new([1u8; 32]);
|
||||||
|
state
|
||||||
|
.process_event(ServerEvents::Unsealed(key_storage))
|
||||||
|
.unwrap();
|
||||||
|
assert!(matches!(state.state(), &ServerStates::Ready(_)));
|
||||||
|
|
||||||
|
// Sealed transition
|
||||||
|
state.process_event(ServerEvents::Sealed).unwrap();
|
||||||
|
assert!(matches!(state.state(), &ServerStates::Sealed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_key_callback() {
|
||||||
|
let mut ctx = _Context;
|
||||||
|
let key_storage = KeyStorage::new([7u8; 32]);
|
||||||
|
let result = ctx.move_key(key_storage);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap().key()[0], 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dispose_key_callback() {
|
||||||
|
let mut ctx = _Context;
|
||||||
|
let key_storage = KeyStorage::new([13u8; 32]);
|
||||||
|
let result = ctx.dispose_key(&key_storage);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_state_transitions() {
|
||||||
|
let mut state = ServerStateMachine::new(_Context);
|
||||||
|
|
||||||
|
// Попытка unseal без bootstrap
|
||||||
|
let key_storage = KeyStorage::new([1u8; 32]);
|
||||||
|
let result = state.process_event(ServerEvents::Unsealed(key_storage));
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
// Правильный путь
|
||||||
|
state.process_event(ServerEvents::Bootstrapped).unwrap();
|
||||||
|
|
||||||
|
// Попытка повторного bootstrap
|
||||||
|
let result = state.process_event(ServerEvents::Bootstrapped);
|
||||||
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,9 @@ impl<T: Clone + std::hash::Hash + Eq> LeaseHandler<T> {
|
|||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all currently leased items
|
||||||
|
pub fn get_all(&self) -> Vec<T> {
|
||||||
|
self.storage.0.iter().map(|entry| entry.clone()).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
use std::string::FromUtf8Error;
|
|
||||||
|
|
||||||
use miette::Diagnostic;
|
|
||||||
use rcgen::{Certificate, KeyPair};
|
|
||||||
use rustls::pki_types::CertificateDer;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
|
||||||
pub enum TlsInitError {
|
|
||||||
#[error("Key generation error during TLS initialization: {0}")]
|
|
||||||
#[diagnostic(code(arbiter_server::tls_init::key_generation))]
|
|
||||||
KeyGeneration(#[from] rcgen::Error),
|
|
||||||
|
|
||||||
#[error("Key invalid format: {0}")]
|
|
||||||
#[diagnostic(code(arbiter_server::tls_init::key_invalid_format))]
|
|
||||||
KeyInvalidFormat(#[from] FromUtf8Error),
|
|
||||||
|
|
||||||
#[error("Key deserialization error: {0}")]
|
|
||||||
#[diagnostic(code(arbiter_server::tls_init::key_deserialization))]
|
|
||||||
KeyDeserializationError(rcgen::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TlsData {
|
|
||||||
pub cert: CertificateDer<'static>,
|
|
||||||
pub keypair: KeyPair,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TlsDataRaw {
|
|
||||||
pub cert: Vec<u8>,
|
|
||||||
pub key: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl TlsDataRaw {
|
|
||||||
pub fn serialize(cert: &TlsData) -> Self {
|
|
||||||
Self {
|
|
||||||
cert: cert.cert.as_ref().to_vec(),
|
|
||||||
key: cert.keypair.serialize_pem().as_bytes().to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize(&self) -> Result<TlsData, TlsInitError> {
|
|
||||||
let cert = CertificateDer::from_slice(&self.cert).into_owned();
|
|
||||||
|
|
||||||
let key =
|
|
||||||
String::from_utf8(self.key.clone()).map_err(TlsInitError::KeyInvalidFormat)?;
|
|
||||||
|
|
||||||
let keypair = KeyPair::from_pem(&key).map_err(TlsInitError::KeyDeserializationError)?;
|
|
||||||
|
|
||||||
Ok(TlsData { cert, keypair })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_cert(key: &KeyPair) -> Result<Certificate, rcgen::Error> {
|
|
||||||
let params = rcgen::CertificateParams::new(vec![
|
|
||||||
"arbiter.local".to_string(),
|
|
||||||
"localhost".to_string(),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
params.self_signed(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement cert rotation
|
|
||||||
pub(crate) struct TlsManager {
|
|
||||||
data: TlsData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TlsManager {
|
|
||||||
pub async fn new(data: Option<TlsDataRaw>) -> Result<Self, TlsInitError> {
|
|
||||||
match data {
|
|
||||||
Some(raw) => {
|
|
||||||
let tls_data = raw.deserialize()?;
|
|
||||||
Ok(Self { data: tls_data })
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let keypair = KeyPair::generate()?;
|
|
||||||
let cert = generate_cert(&keypair)?;
|
|
||||||
let tls_data = TlsData {
|
|
||||||
cert: cert.der().clone(),
|
|
||||||
keypair,
|
|
||||||
};
|
|
||||||
Ok(Self { data: tls_data })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bytes(&self) -> TlsDataRaw {
|
|
||||||
TlsDataRaw::serialize(&self.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
192
server/crates/arbiter-server/src/context/tls/mod.rs
Normal file
192
server/crates/arbiter-server/src/context/tls/mod.rs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use rcgen::{Certificate, KeyPair};
|
||||||
|
use rustls::pki_types::CertificateDer;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
|
pub mod rotation;
|
||||||
|
|
||||||
|
pub use rotation::{RotationError, RotationState, RotationTask};
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[expect(clippy::enum_variant_names)]
|
||||||
|
pub enum TlsInitError {
|
||||||
|
#[error("Key generation error during TLS initialization: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_generation))]
|
||||||
|
KeyGeneration(#[from] rcgen::Error),
|
||||||
|
|
||||||
|
#[error("Key invalid format: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_invalid_format))]
|
||||||
|
KeyInvalidFormat(#[from] FromUtf8Error),
|
||||||
|
|
||||||
|
#[error("Key deserialization error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_deserialization))]
|
||||||
|
KeyDeserializationError(rcgen::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TlsData {
|
||||||
|
pub cert: CertificateDer<'static>,
|
||||||
|
pub keypair: KeyPair,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TlsDataRaw {
|
||||||
|
pub cert: Vec<u8>,
|
||||||
|
pub key: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl TlsDataRaw {
|
||||||
|
pub fn serialize(cert: &TlsData) -> Self {
|
||||||
|
Self {
|
||||||
|
cert: cert.cert.as_ref().to_vec(),
|
||||||
|
key: cert.keypair.serialize_pem().as_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(&self) -> Result<TlsData, TlsInitError> {
|
||||||
|
let cert = CertificateDer::from_slice(&self.cert).into_owned();
|
||||||
|
|
||||||
|
let key =
|
||||||
|
String::from_utf8(self.key.clone()).map_err(TlsInitError::KeyInvalidFormat)?;
|
||||||
|
|
||||||
|
let keypair = KeyPair::from_pem(&key).map_err(TlsInitError::KeyDeserializationError)?;
|
||||||
|
|
||||||
|
Ok(TlsData { cert, keypair })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata about a certificate including validity period
|
||||||
|
pub struct CertificateMetadata {
|
||||||
|
pub cert_id: i32,
|
||||||
|
pub cert: CertificateDer<'static>,
|
||||||
|
pub keypair: Arc<KeyPair>,
|
||||||
|
pub not_before: i64,
|
||||||
|
pub not_after: i64,
|
||||||
|
pub created_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn generate_cert(key: &KeyPair) -> Result<(Certificate, i64, i64), rcgen::Error> {
|
||||||
|
let params = rcgen::CertificateParams::new(vec![
|
||||||
|
"arbiter.local".to_string(),
|
||||||
|
"localhost".to_string(),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// Set validity period: 90 days from now
|
||||||
|
let not_before = chrono::Utc::now();
|
||||||
|
let not_after = not_before + chrono::Duration::days(90);
|
||||||
|
|
||||||
|
// Note: rcgen doesn't directly expose not_before/not_after setting in all versions
|
||||||
|
// For now, we'll generate the cert and track validity separately
|
||||||
|
let cert = params.self_signed(key)?;
|
||||||
|
|
||||||
|
Ok((cert, not_before.timestamp(), not_after.timestamp()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate rotation enabled
|
||||||
|
pub(crate) struct TlsManager {
|
||||||
|
// Current active certificate (atomic replacement via RwLock)
|
||||||
|
current_cert: Arc<RwLock<CertificateMetadata>>,
|
||||||
|
|
||||||
|
// Database pool for persistence
|
||||||
|
db: db::DatabasePool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsManager {
|
||||||
|
/// Create new TlsManager with a generated certificate
|
||||||
|
pub async fn new(db: db::DatabasePool) -> Result<Self, TlsInitError> {
|
||||||
|
let keypair = KeyPair::generate()?;
|
||||||
|
let (cert, not_before, not_after) = generate_cert(&keypair)?;
|
||||||
|
let cert_der = cert.der().clone();
|
||||||
|
|
||||||
|
// For initial creation, cert_id will be set after DB insert
|
||||||
|
let metadata = CertificateMetadata {
|
||||||
|
cert_id: 0, // Temporary, will be updated after DB insert
|
||||||
|
cert: cert_der,
|
||||||
|
keypair: Arc::new(keypair),
|
||||||
|
not_before,
|
||||||
|
not_after,
|
||||||
|
created_at: chrono::Utc::now().timestamp(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
current_cert: Arc::new(RwLock::new(metadata)),
|
||||||
|
db,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load TlsManager from database with specific certificate ID
|
||||||
|
pub async fn load_from_db(db: db::DatabasePool, cert_id: i32) -> Result<Self, TlsInitError> {
|
||||||
|
// TODO: Load certificate from database
|
||||||
|
// For now, return error - will be implemented when database access is ready
|
||||||
|
Err(TlsInitError::KeyGeneration(rcgen::Error::CouldNotParseCertificate))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create from legacy TlsDataRaw format
|
||||||
|
pub async fn new_from_legacy(
|
||||||
|
db: db::DatabasePool,
|
||||||
|
data: TlsDataRaw,
|
||||||
|
not_before: i64,
|
||||||
|
not_after: i64,
|
||||||
|
) -> Result<Self, TlsInitError> {
|
||||||
|
let tls_data = data.deserialize()?;
|
||||||
|
|
||||||
|
let metadata = CertificateMetadata {
|
||||||
|
cert_id: 1, // Legacy certificate gets ID 1
|
||||||
|
cert: tls_data.cert,
|
||||||
|
keypair: Arc::new(tls_data.keypair),
|
||||||
|
not_before,
|
||||||
|
not_after,
|
||||||
|
created_at: chrono::Utc::now().timestamp(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
current_cert: Arc::new(RwLock::new(metadata)),
|
||||||
|
db,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current certificate data
|
||||||
|
pub async fn get_certificate(&self) -> (CertificateDer<'static>, Arc<KeyPair>) {
|
||||||
|
let cert = self.current_cert.read().await;
|
||||||
|
(cert.cert.clone(), cert.keypair.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace certificate atomically
|
||||||
|
pub async fn replace_certificate(&self, new_cert: CertificateMetadata) -> Result<(), TlsInitError> {
|
||||||
|
let mut cert = self.current_cert.write().await;
|
||||||
|
*cert = new_cert;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if certificate is expiring soon
|
||||||
|
pub async fn check_expiration(&self, threshold_secs: i64) -> bool {
|
||||||
|
let cert = self.current_cert.read().await;
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
cert.not_after - now < threshold_secs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get certificate metadata for rotation logic
|
||||||
|
pub async fn get_certificate_metadata(&self) -> CertificateMetadata {
|
||||||
|
let cert = self.current_cert.read().await;
|
||||||
|
CertificateMetadata {
|
||||||
|
cert_id: cert.cert_id,
|
||||||
|
cert: cert.cert.clone(),
|
||||||
|
keypair: cert.keypair.clone(),
|
||||||
|
not_before: cert.not_before,
|
||||||
|
not_after: cert.not_after,
|
||||||
|
created_at: cert.created_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes(&self) -> TlsDataRaw {
|
||||||
|
// This method is now async-compatible but we keep sync interface
|
||||||
|
// TODO: Make this async or remove if not needed
|
||||||
|
TlsDataRaw {
|
||||||
|
cert: vec![],
|
||||||
|
key: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
552
server/crates/arbiter-server/src/context/tls/rotation.rs
Normal file
552
server/crates/arbiter-server/src/context/tls/rotation.rs
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use ed25519_dalek::VerifyingKey;
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use rcgen::KeyPair;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use crate::context::ServerContext;
|
||||||
|
use crate::db::models::{NewRotationClientAck, NewTlsCertificate, NewTlsRotationHistory};
|
||||||
|
use crate::db::schema::{rotation_client_acks, tls_certificates, tls_rotation_history, tls_rotation_state};
|
||||||
|
use crate::db::DatabasePool;
|
||||||
|
|
||||||
|
use super::{generate_cert, CertificateMetadata, TlsInitError};
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
pub enum RotationError {
|
||||||
|
#[error("Certificate generation failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::rotation::cert_generation))]
|
||||||
|
CertGeneration(#[from] rcgen::Error),
|
||||||
|
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::rotation::database))]
|
||||||
|
Database(#[from] diesel::result::Error),
|
||||||
|
|
||||||
|
#[error("TLS initialization error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::rotation::tls_init))]
|
||||||
|
TlsInit(#[from] TlsInitError),
|
||||||
|
|
||||||
|
#[error("Invalid rotation state: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::rotation::invalid_state))]
|
||||||
|
InvalidState(String),
|
||||||
|
|
||||||
|
#[error("No active certificate found")]
|
||||||
|
#[diagnostic(code(arbiter_server::rotation::no_active_cert))]
|
||||||
|
NoActiveCertificate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Состояние процесса ротации сертификата
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RotationState {
|
||||||
|
/// Обычная работа, ротация не требуется
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
/// Ротация инициирована, новый сертификат сгенерирован
|
||||||
|
RotationInitiated {
|
||||||
|
initiated_at: i64,
|
||||||
|
new_cert_id: i32,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Ожидание подтверждений (ACKs) от клиентов
|
||||||
|
WaitingForAcks {
|
||||||
|
new_cert_id: i32,
|
||||||
|
initiated_at: i64,
|
||||||
|
timeout_at: i64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Все ACK получены или таймаут истёк, готов к ротации
|
||||||
|
ReadyToRotate {
|
||||||
|
new_cert_id: i32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RotationState {
|
||||||
|
/// Загрузить состояние из базы данных
|
||||||
|
pub async fn load_from_db(db: &DatabasePool) -> Result<Self, RotationError> {
|
||||||
|
use crate::db::schema::tls_rotation_state::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let state_record: (i32, String, Option<i32>, Option<i32>, Option<i32>) =
|
||||||
|
tls_rotation_state
|
||||||
|
.select((id, state, new_cert_id, initiated_at, timeout_at))
|
||||||
|
.filter(id.eq(1))
|
||||||
|
.first(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let rotation_state = match state_record.1.as_str() {
|
||||||
|
"normal" => RotationState::Normal,
|
||||||
|
"initiated" => {
|
||||||
|
let cert_id = state_record.2.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("Initiated state missing new_cert_id".into())
|
||||||
|
})?;
|
||||||
|
let init_at = state_record.3.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("Initiated state missing initiated_at".into())
|
||||||
|
})?;
|
||||||
|
RotationState::RotationInitiated {
|
||||||
|
initiated_at: init_at as i64,
|
||||||
|
new_cert_id: cert_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"waiting_acks" => {
|
||||||
|
let cert_id = state_record.2.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("WaitingForAcks state missing new_cert_id".into())
|
||||||
|
})?;
|
||||||
|
let init_at = state_record.3.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("WaitingForAcks state missing initiated_at".into())
|
||||||
|
})?;
|
||||||
|
let timeout = state_record.4.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("WaitingForAcks state missing timeout_at".into())
|
||||||
|
})?;
|
||||||
|
RotationState::WaitingForAcks {
|
||||||
|
new_cert_id: cert_id,
|
||||||
|
initiated_at: init_at as i64,
|
||||||
|
timeout_at: timeout as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ready" => {
|
||||||
|
let cert_id = state_record.2.ok_or_else(|| {
|
||||||
|
RotationError::InvalidState("Ready state missing new_cert_id".into())
|
||||||
|
})?;
|
||||||
|
RotationState::ReadyToRotate {
|
||||||
|
new_cert_id: cert_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(RotationError::InvalidState(format!(
|
||||||
|
"Unknown state: {}",
|
||||||
|
other
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(rotation_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Сохранить состояние в базу данных
|
||||||
|
pub async fn save_to_db(&self, db: &DatabasePool) -> Result<(), RotationError> {
|
||||||
|
use crate::db::schema::tls_rotation_state::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let (state_str, cert_id, init_at, timeout) = match self {
|
||||||
|
RotationState::Normal => ("normal", None, None, None),
|
||||||
|
RotationState::RotationInitiated {
|
||||||
|
initiated_at: init,
|
||||||
|
new_cert_id: cert,
|
||||||
|
} => ("initiated", Some(*cert), Some(*init as i32), None),
|
||||||
|
RotationState::WaitingForAcks {
|
||||||
|
new_cert_id: cert,
|
||||||
|
initiated_at: init,
|
||||||
|
timeout_at: timeout_val,
|
||||||
|
} => (
|
||||||
|
"waiting_acks",
|
||||||
|
Some(*cert),
|
||||||
|
Some(*init as i32),
|
||||||
|
Some(*timeout_val as i32),
|
||||||
|
),
|
||||||
|
RotationState::ReadyToRotate { new_cert_id: cert } => ("ready", Some(*cert), None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::update(tls_rotation_state.filter(id.eq(1)))
|
||||||
|
.set((
|
||||||
|
state.eq(state_str),
|
||||||
|
new_cert_id.eq(cert_id),
|
||||||
|
initiated_at.eq(init_at),
|
||||||
|
timeout_at.eq(timeout),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Фоновый таск для автоматической ротации сертификатов
|
||||||
|
pub struct RotationTask {
|
||||||
|
context: Arc<crate::context::_ServerContextInner>,
|
||||||
|
check_interval: Duration,
|
||||||
|
rotation_threshold: Duration,
|
||||||
|
ack_timeout: Duration,
|
||||||
|
shutdown_rx: watch::Receiver<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RotationTask {
|
||||||
|
/// Создать новый rotation task
|
||||||
|
pub fn new(
|
||||||
|
context: Arc<crate::context::_ServerContextInner>,
|
||||||
|
check_interval: Duration,
|
||||||
|
rotation_threshold: Duration,
|
||||||
|
ack_timeout: Duration,
|
||||||
|
shutdown_rx: watch::Receiver<bool>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
context,
|
||||||
|
check_interval,
|
||||||
|
rotation_threshold,
|
||||||
|
ack_timeout,
|
||||||
|
shutdown_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Запустить фоновый таск мониторинга и ротации
|
||||||
|
pub async fn run(mut self) -> Result<(), RotationError> {
|
||||||
|
info!("Starting TLS certificate rotation task");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::time::sleep(self.check_interval) => {
|
||||||
|
if let Err(e) = self.check_and_process().await {
|
||||||
|
error!("Rotation task error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = self.shutdown_rx.changed() => {
|
||||||
|
info!("Rotation task shutting down");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверить текущее состояние и выполнить необходимые действия
|
||||||
|
async fn check_and_process(&self) -> Result<(), RotationError> {
|
||||||
|
let state = self.context.rotation_state.read().await.clone();
|
||||||
|
|
||||||
|
match state {
|
||||||
|
RotationState::Normal => {
|
||||||
|
// Проверить, нужна ли ротация
|
||||||
|
self.check_expiration_and_initiate().await?;
|
||||||
|
}
|
||||||
|
RotationState::RotationInitiated { new_cert_id, .. } => {
|
||||||
|
// Автоматически перейти в WaitingForAcks
|
||||||
|
self.transition_to_waiting_acks(new_cert_id).await?;
|
||||||
|
}
|
||||||
|
RotationState::WaitingForAcks {
|
||||||
|
new_cert_id,
|
||||||
|
timeout_at,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.handle_waiting_for_acks(new_cert_id, timeout_at).await?;
|
||||||
|
}
|
||||||
|
RotationState::ReadyToRotate { new_cert_id } => {
|
||||||
|
self.execute_rotation(new_cert_id).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверить срок действия сертификата и инициировать ротацию если нужно
|
||||||
|
async fn check_expiration_and_initiate(&self) -> Result<(), RotationError> {
|
||||||
|
let threshold_secs = self.rotation_threshold.as_secs() as i64;
|
||||||
|
|
||||||
|
if self.context.tls.check_expiration(threshold_secs).await {
|
||||||
|
info!("Certificate expiring soon, initiating rotation");
|
||||||
|
self.initiate_rotation().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Инициировать ротацию: сгенерировать новый сертификат и сохранить в БД
|
||||||
|
pub async fn initiate_rotation(&self) -> Result<i32, RotationError> {
|
||||||
|
info!("Initiating certificate rotation");
|
||||||
|
|
||||||
|
// 1. Генерация нового сертификата
|
||||||
|
let keypair = KeyPair::generate()?;
|
||||||
|
let (cert, not_before, not_after) = generate_cert(&keypair)?;
|
||||||
|
let cert_der = cert.der().clone();
|
||||||
|
|
||||||
|
// 2. Сохранение в БД (is_active = false, пока не активирован)
|
||||||
|
let new_cert_id = self
|
||||||
|
.save_new_certificate(&cert_der, &keypair, not_before, not_after)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!(new_cert_id, "New certificate generated and saved");
|
||||||
|
|
||||||
|
// 3. Обновление rotation_state
|
||||||
|
let new_state = RotationState::RotationInitiated {
|
||||||
|
initiated_at: chrono::Utc::now().timestamp(),
|
||||||
|
new_cert_id,
|
||||||
|
};
|
||||||
|
*self.context.rotation_state.write().await = new_state.clone();
|
||||||
|
new_state.save_to_db(&self.context.db).await?;
|
||||||
|
|
||||||
|
// 4. Логирование в audit trail
|
||||||
|
self.log_rotation_event(new_cert_id, "rotation_initiated", None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(new_cert_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Перейти в состояние WaitingForAcks и разослать уведомления
|
||||||
|
async fn transition_to_waiting_acks(&self, new_cert_id: i32) -> Result<(), RotationError> {
|
||||||
|
info!(new_cert_id, "Transitioning to WaitingForAcks state");
|
||||||
|
|
||||||
|
let initiated_at = chrono::Utc::now().timestamp();
|
||||||
|
let timeout_at = initiated_at + self.ack_timeout.as_secs() as i64;
|
||||||
|
|
||||||
|
// Обновить состояние
|
||||||
|
let new_state = RotationState::WaitingForAcks {
|
||||||
|
new_cert_id,
|
||||||
|
initiated_at,
|
||||||
|
timeout_at,
|
||||||
|
};
|
||||||
|
*self.context.rotation_state.write().await = new_state.clone();
|
||||||
|
new_state.save_to_db(&self.context.db).await?;
|
||||||
|
|
||||||
|
// TODO: Broadcast уведомлений клиентам
|
||||||
|
// self.broadcast_rotation_notification(new_cert_id, timeout_at).await?;
|
||||||
|
|
||||||
|
info!(timeout_at, "Rotation notifications sent, waiting for ACKs");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработка состояния WaitingForAcks: проверка ACKs и таймаута
|
||||||
|
async fn handle_waiting_for_acks(
|
||||||
|
&self,
|
||||||
|
new_cert_id: i32,
|
||||||
|
timeout_at: i64,
|
||||||
|
) -> Result<(), RotationError> {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
|
||||||
|
// Проверить таймаут
|
||||||
|
if now > timeout_at {
|
||||||
|
let missing = self.get_missing_acks(new_cert_id).await?;
|
||||||
|
warn!(
|
||||||
|
missing_count = missing.len(),
|
||||||
|
"Rotation ACK timeout reached, proceeding with rotation"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Переход в ReadyToRotate
|
||||||
|
let new_state = RotationState::ReadyToRotate { new_cert_id };
|
||||||
|
*self.context.rotation_state.write().await = new_state.clone();
|
||||||
|
new_state.save_to_db(&self.context.db).await?;
|
||||||
|
|
||||||
|
self.log_rotation_event(
|
||||||
|
new_cert_id,
|
||||||
|
"timeout",
|
||||||
|
Some(format!("Missing ACKs from {} clients", missing.len())),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить, все ли ACK получены
|
||||||
|
let missing = self.get_missing_acks(new_cert_id).await?;
|
||||||
|
|
||||||
|
if missing.is_empty() {
|
||||||
|
info!("All clients acknowledged, ready to rotate");
|
||||||
|
|
||||||
|
let new_state = RotationState::ReadyToRotate { new_cert_id };
|
||||||
|
*self.context.rotation_state.write().await = new_state.clone();
|
||||||
|
new_state.save_to_db(&self.context.db).await?;
|
||||||
|
|
||||||
|
self.log_rotation_event(new_cert_id, "acks_complete", None)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let time_remaining = timeout_at - now;
|
||||||
|
debug!(
|
||||||
|
missing_count = missing.len(),
|
||||||
|
time_remaining,
|
||||||
|
"Waiting for rotation ACKs"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Выполнить атомарную ротацию сертификата
|
||||||
|
async fn execute_rotation(&self, new_cert_id: i32) -> Result<(), RotationError> {
|
||||||
|
info!(new_cert_id, "Executing certificate rotation");
|
||||||
|
|
||||||
|
// 1. Загрузить новый сертификат из БД
|
||||||
|
let new_cert = self.load_certificate(new_cert_id).await?;
|
||||||
|
|
||||||
|
// 2. Атомарная замена в TlsManager
|
||||||
|
self.context
|
||||||
|
.tls
|
||||||
|
.replace_certificate(new_cert)
|
||||||
|
.await
|
||||||
|
.map_err(RotationError::TlsInit)?;
|
||||||
|
|
||||||
|
// 3. Обновить БД: старый is_active=false, новый is_active=true
|
||||||
|
self.activate_certificate(new_cert_id).await?;
|
||||||
|
|
||||||
|
// 4. TODO: Отключить всех клиентов
|
||||||
|
// self.disconnect_all_clients().await?;
|
||||||
|
|
||||||
|
// 5. Очистить rotation_state
|
||||||
|
let new_state = RotationState::Normal;
|
||||||
|
*self.context.rotation_state.write().await = new_state.clone();
|
||||||
|
new_state.save_to_db(&self.context.db).await?;
|
||||||
|
|
||||||
|
// 6. Очистить ACKs
|
||||||
|
self.context.rotation_acks.write().await.clear();
|
||||||
|
self.clear_rotation_acks(new_cert_id).await?;
|
||||||
|
|
||||||
|
// 7. Логирование
|
||||||
|
self.log_rotation_event(new_cert_id, "activated", None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!(new_cert_id, "Certificate rotation completed successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Сохранить новый сертификат в БД
|
||||||
|
async fn save_new_certificate(
|
||||||
|
&self,
|
||||||
|
cert_der: &[u8],
|
||||||
|
keypair: &KeyPair,
|
||||||
|
cert_not_before: i64,
|
||||||
|
cert_not_after: i64,
|
||||||
|
) -> Result<i32, RotationError> {
|
||||||
|
use crate::db::schema::tls_certificates::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = self.context.db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let new_cert = NewTlsCertificate {
|
||||||
|
cert: cert_der.to_vec(),
|
||||||
|
cert_key: keypair.serialize_pem().as_bytes().to_vec(),
|
||||||
|
not_before: cert_not_before as i32,
|
||||||
|
not_after: cert_not_after as i32,
|
||||||
|
is_active: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::insert_into(tls_certificates)
|
||||||
|
.values(&new_cert)
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Получить ID последней вставленной записи
|
||||||
|
let cert_id: i32 = diesel::select(diesel::dsl::sql::<diesel::sql_types::Integer>(
|
||||||
|
"last_insert_rowid()",
|
||||||
|
))
|
||||||
|
.first(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.log_rotation_event(cert_id, "created", None).await?;
|
||||||
|
|
||||||
|
Ok(cert_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Загрузить сертификат из БД
|
||||||
|
async fn load_certificate(&self, cert_id: i32) -> Result<CertificateMetadata, RotationError> {
|
||||||
|
use crate::db::schema::tls_certificates::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = self.context.db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let cert_record: (Vec<u8>, Vec<u8>, i32, i32, i32) = tls_certificates
|
||||||
|
.select((cert, cert_key, not_before, not_after, created_at))
|
||||||
|
.filter(id.eq(cert_id))
|
||||||
|
.first(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let cert_der = rustls::pki_types::CertificateDer::from(cert_record.0);
|
||||||
|
let key_pem = String::from_utf8(cert_record.1)
|
||||||
|
.map_err(|e| RotationError::InvalidState(format!("Invalid key encoding: {}", e)))?;
|
||||||
|
let keypair = KeyPair::from_pem(&key_pem)?;
|
||||||
|
|
||||||
|
Ok(CertificateMetadata {
|
||||||
|
cert_id,
|
||||||
|
cert: cert_der,
|
||||||
|
keypair: Arc::new(keypair),
|
||||||
|
not_before: cert_record.2 as i64,
|
||||||
|
not_after: cert_record.3 as i64,
|
||||||
|
created_at: cert_record.4 as i64,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Активировать сертификат (установить is_active=true)
|
||||||
|
async fn activate_certificate(&self, cert_id: i32) -> Result<(), RotationError> {
|
||||||
|
use crate::db::schema::tls_certificates::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = self.context.db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Деактивировать все сертификаты
|
||||||
|
diesel::update(tls_certificates)
|
||||||
|
.set(is_active.eq(false))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Активировать новый
|
||||||
|
diesel::update(tls_certificates.filter(id.eq(cert_id)))
|
||||||
|
.set(is_active.eq(true))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получить список клиентов, которые ещё не отправили ACK
|
||||||
|
async fn get_missing_acks(&self, rotation_id: i32) -> Result<Vec<VerifyingKey>, RotationError> {
|
||||||
|
// TODO: Реализовать получение списка всех активных клиентов
|
||||||
|
// и вычитание тех, кто уже отправил ACK
|
||||||
|
|
||||||
|
// Пока возвращаем пустой список
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Очистить ACKs для данной ротации из БД
|
||||||
|
async fn clear_rotation_acks(&self, rotation_id: i32) -> Result<(), RotationError> {
|
||||||
|
use crate::db::schema::rotation_client_acks::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = self.context.db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
diesel::delete(rotation_client_acks.filter(rotation_id.eq(rotation_id)))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Записать событие в audit trail
|
||||||
|
async fn log_rotation_event(
|
||||||
|
&self,
|
||||||
|
history_cert_id: i32,
|
||||||
|
history_event_type: &str,
|
||||||
|
history_details: Option<String>,
|
||||||
|
) -> Result<(), RotationError> {
|
||||||
|
use crate::db::schema::tls_rotation_history::dsl::*;
|
||||||
|
|
||||||
|
let mut conn = self.context.db.get().await.map_err(|e| {
|
||||||
|
RotationError::InvalidState(format!("Failed to get DB connection: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let new_history = NewTlsRotationHistory {
|
||||||
|
cert_id: history_cert_id,
|
||||||
|
event_type: history_event_type.to_string(),
|
||||||
|
details: history_details,
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::insert_into(tls_rotation_history)
|
||||||
|
.values(&new_history)
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
139
server/crates/arbiter-server/src/crypto/aead.rs
Normal file
139
server/crates/arbiter-server/src/crypto/aead.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
use chacha20poly1305::{
|
||||||
|
aead::{Aead, KeyInit},
|
||||||
|
ChaCha20Poly1305, Key, Nonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::CryptoError;
|
||||||
|
|
||||||
|
/// Encrypt plaintext with AEAD (ChaCha20Poly1305)
|
||||||
|
///
|
||||||
|
/// Returns (ciphertext, tag) on success
|
||||||
|
pub fn encrypt(
|
||||||
|
plaintext: &[u8],
|
||||||
|
key: &[u8; 32],
|
||||||
|
nonce: &[u8; 12],
|
||||||
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let cipher_key = Key::from_slice(key);
|
||||||
|
let cipher = ChaCha20Poly1305::new(cipher_key);
|
||||||
|
let nonce_array = Nonce::from_slice(nonce);
|
||||||
|
|
||||||
|
cipher
|
||||||
|
.encrypt(nonce_array, plaintext)
|
||||||
|
.map_err(|e| CryptoError::AeadEncryption(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt ciphertext with AEAD (ChaCha20Poly1305)
|
||||||
|
///
|
||||||
|
/// The ciphertext должен содержать tag (последние 16 bytes)
|
||||||
|
pub fn decrypt(
|
||||||
|
ciphertext_with_tag: &[u8],
|
||||||
|
key: &[u8; 32],
|
||||||
|
nonce: &[u8; 12],
|
||||||
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
let cipher_key = Key::from_slice(key);
|
||||||
|
let cipher = ChaCha20Poly1305::new(cipher_key);
|
||||||
|
let nonce_array = Nonce::from_slice(nonce);
|
||||||
|
|
||||||
|
cipher
|
||||||
|
.decrypt(nonce_array, ciphertext_with_tag)
|
||||||
|
.map_err(|e| CryptoError::AeadDecryption(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate nonce from counter
|
||||||
|
///
|
||||||
|
/// Converts i32 counter to 12-byte nonce (big-endian encoding)
|
||||||
|
pub fn nonce_from_counter(counter: i32) -> [u8; 12] {
|
||||||
|
let mut nonce = [0u8; 12];
|
||||||
|
nonce[8..12].copy_from_slice(&counter.to_be_bytes());
|
||||||
|
nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aead_encrypt_decrypt_round_trip() {
|
||||||
|
let plaintext = b"Hello, World! This is a secret message.";
|
||||||
|
let key = [42u8; 32];
|
||||||
|
let nonce = nonce_from_counter(1);
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let ciphertext = encrypt(plaintext, &key, &nonce).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Verify ciphertext is different from plaintext
|
||||||
|
assert_ne!(ciphertext.as_slice(), plaintext);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
let decrypted = decrypt(&ciphertext, &key, &nonce).expect("Decryption failed");
|
||||||
|
|
||||||
|
// Verify round-trip
|
||||||
|
assert_eq!(decrypted.as_slice(), plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aead_decrypt_with_wrong_key() {
|
||||||
|
let plaintext = b"Secret data";
|
||||||
|
let key = [1u8; 32];
|
||||||
|
let wrong_key = [2u8; 32];
|
||||||
|
let nonce = nonce_from_counter(1);
|
||||||
|
|
||||||
|
let ciphertext = encrypt(plaintext, &key, &nonce).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Attempt decrypt with wrong key
|
||||||
|
let result = decrypt(&ciphertext, &wrong_key, &nonce);
|
||||||
|
|
||||||
|
// Should fail
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aead_decrypt_with_wrong_nonce() {
|
||||||
|
let plaintext = b"Secret data";
|
||||||
|
let key = [1u8; 32];
|
||||||
|
let nonce = nonce_from_counter(1);
|
||||||
|
let wrong_nonce = nonce_from_counter(2);
|
||||||
|
|
||||||
|
let ciphertext = encrypt(plaintext, &key, &nonce).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Attempt decrypt with wrong nonce
|
||||||
|
let result = decrypt(&ciphertext, &key, &wrong_nonce);
|
||||||
|
|
||||||
|
// Should fail
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nonce_generation_from_counter() {
|
||||||
|
let nonce1 = nonce_from_counter(1);
|
||||||
|
let nonce2 = nonce_from_counter(2);
|
||||||
|
let nonce_max = nonce_from_counter(i32::MAX);
|
||||||
|
|
||||||
|
// Verify nonces are different
|
||||||
|
assert_ne!(nonce1, nonce2);
|
||||||
|
|
||||||
|
// Verify nonce format (first 8 bytes should be zero, last 4 contain counter)
|
||||||
|
assert_eq!(&nonce1[0..8], &[0u8; 8]);
|
||||||
|
assert_eq!(&nonce1[8..12], &1i32.to_be_bytes());
|
||||||
|
|
||||||
|
assert_eq!(&nonce_max[8..12], &i32::MAX.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aead_tampered_ciphertext() {
|
||||||
|
let plaintext = b"Important message";
|
||||||
|
let key = [7u8; 32];
|
||||||
|
let nonce = nonce_from_counter(5);
|
||||||
|
|
||||||
|
let mut ciphertext = encrypt(plaintext, &key, &nonce).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Tamper with ciphertext (flip a bit)
|
||||||
|
if let Some(byte) = ciphertext.get_mut(5) {
|
||||||
|
*byte ^= 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt decrypt - should fail due to authentication tag mismatch
|
||||||
|
let result = decrypt(&ciphertext, &key, &nonce);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
28
server/crates/arbiter-server/src/crypto/mod.rs
Normal file
28
server/crates/arbiter-server/src/crypto/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
pub mod aead;
|
||||||
|
pub mod root_key;
|
||||||
|
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
pub enum CryptoError {
|
||||||
|
#[error("AEAD encryption failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::crypto::aead_encryption))]
|
||||||
|
AeadEncryption(String),
|
||||||
|
|
||||||
|
#[error("AEAD decryption failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::crypto::aead_decryption))]
|
||||||
|
AeadDecryption(String),
|
||||||
|
|
||||||
|
#[error("Key derivation failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::crypto::key_derivation))]
|
||||||
|
KeyDerivation(String),
|
||||||
|
|
||||||
|
#[error("Invalid nonce: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::crypto::invalid_nonce))]
|
||||||
|
InvalidNonce(String),
|
||||||
|
|
||||||
|
#[error("Invalid key format: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::crypto::invalid_key))]
|
||||||
|
InvalidKey(String),
|
||||||
|
}
|
||||||
240
server/crates/arbiter-server/src/crypto/root_key.rs
Normal file
240
server/crates/arbiter-server/src/crypto/root_key.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||||
|
Argon2, PasswordHash, PasswordVerifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::db::models::AeadEncrypted;
|
||||||
|
|
||||||
|
use super::{aead, CryptoError};
|
||||||
|
|
||||||
|
/// Encrypt root key with user password
|
||||||
|
///
|
||||||
|
/// Uses Argon2id for password derivation and ChaCha20Poly1305 for encryption
|
||||||
|
pub fn encrypt_root_key(
|
||||||
|
root_key: &[u8; 32],
|
||||||
|
password: &str,
|
||||||
|
nonce_counter: i32,
|
||||||
|
) -> Result<(AeadEncrypted, String), CryptoError> {
|
||||||
|
// Derive key from password using Argon2
|
||||||
|
let (derived_key, salt) = derive_key_from_password(password)?;
|
||||||
|
|
||||||
|
// Generate nonce from counter
|
||||||
|
let nonce = aead::nonce_from_counter(nonce_counter);
|
||||||
|
|
||||||
|
// Encrypt root key
|
||||||
|
let ciphertext_with_tag = aead::encrypt(root_key, &derived_key, &nonce)?;
|
||||||
|
|
||||||
|
// Extract tag (last 16 bytes)
|
||||||
|
let tag_start = ciphertext_with_tag
|
||||||
|
.len()
|
||||||
|
.checked_sub(16)
|
||||||
|
.ok_or_else(|| CryptoError::AeadEncryption("Ciphertext too short".into()))?;
|
||||||
|
|
||||||
|
let ciphertext = ciphertext_with_tag[..tag_start].to_vec();
|
||||||
|
let tag = ciphertext_with_tag[tag_start..].to_vec();
|
||||||
|
|
||||||
|
let aead_encrypted = AeadEncrypted {
|
||||||
|
id: 1, // Will be set by database
|
||||||
|
current_nonce: nonce_counter,
|
||||||
|
ciphertext,
|
||||||
|
tag,
|
||||||
|
schema_version: 1, // Current version
|
||||||
|
argon2_salt: Some(salt.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((aead_encrypted, salt))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt root key with user password
|
||||||
|
///
|
||||||
|
/// Verifies password hash and decrypts using ChaCha20Poly1305
|
||||||
|
pub fn decrypt_root_key(
|
||||||
|
encrypted: &AeadEncrypted,
|
||||||
|
password: &str,
|
||||||
|
salt: &str,
|
||||||
|
) -> Result<[u8; 32], CryptoError> {
|
||||||
|
// Derive key from password using stored salt
|
||||||
|
let derived_key = derive_key_with_salt(password, salt)?;
|
||||||
|
|
||||||
|
// Generate nonce from counter
|
||||||
|
let nonce = aead::nonce_from_counter(encrypted.current_nonce);
|
||||||
|
|
||||||
|
// Reconstruct ciphertext with tag
|
||||||
|
let mut ciphertext_with_tag = encrypted.ciphertext.clone();
|
||||||
|
ciphertext_with_tag.extend_from_slice(&encrypted.tag);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
let plaintext = aead::decrypt(&ciphertext_with_tag, &derived_key, &nonce)?;
|
||||||
|
|
||||||
|
// Verify length
|
||||||
|
if plaintext.len() != 32 {
|
||||||
|
return Err(CryptoError::InvalidKey(format!(
|
||||||
|
"Expected 32 bytes, got {}",
|
||||||
|
plaintext.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to fixed-size array
|
||||||
|
let mut root_key = [0u8; 32];
|
||||||
|
root_key.copy_from_slice(&plaintext);
|
||||||
|
|
||||||
|
Ok(root_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive 32-byte key from password using Argon2id
|
||||||
|
///
|
||||||
|
/// Generates new random salt and returns (derived_key, salt_string)
|
||||||
|
fn derive_key_from_password(password: &str) -> Result<([u8; 32], String), CryptoError> {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let password_hash = argon2
|
||||||
|
.hash_password(password.as_bytes(), &salt)
|
||||||
|
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
|
||||||
|
|
||||||
|
// Extract hash output (32 bytes)
|
||||||
|
let hash_output = password_hash
|
||||||
|
.hash
|
||||||
|
.ok_or_else(|| CryptoError::KeyDerivation("No hash output".into()))?;
|
||||||
|
|
||||||
|
let hash_bytes = hash_output.as_bytes();
|
||||||
|
|
||||||
|
if hash_bytes.len() != 32 {
|
||||||
|
return Err(CryptoError::KeyDerivation(format!(
|
||||||
|
"Expected 32 bytes, got {}",
|
||||||
|
hash_bytes.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
key.copy_from_slice(hash_bytes);
|
||||||
|
|
||||||
|
Ok((key, salt.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive 32-byte key from password using existing salt
|
||||||
|
fn derive_key_with_salt(password: &str, salt_str: &str) -> Result<[u8; 32], CryptoError> {
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
// Parse salt
|
||||||
|
let salt =
|
||||||
|
SaltString::from_b64(salt_str).map_err(|e| CryptoError::InvalidKey(e.to_string()))?;
|
||||||
|
|
||||||
|
let password_hash = argon2
|
||||||
|
.hash_password(password.as_bytes(), &salt)
|
||||||
|
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
|
||||||
|
|
||||||
|
// Extract hash output
|
||||||
|
let hash_output = password_hash
|
||||||
|
.hash
|
||||||
|
.ok_or_else(|| CryptoError::KeyDerivation("No hash output".into()))?;
|
||||||
|
|
||||||
|
let hash_bytes = hash_output.as_bytes();
|
||||||
|
|
||||||
|
if hash_bytes.len() != 32 {
|
||||||
|
return Err(CryptoError::KeyDerivation(format!(
|
||||||
|
"Expected 32 bytes, got {}",
|
||||||
|
hash_bytes.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
key.copy_from_slice(hash_bytes);
|
||||||
|
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_key_encrypt_decrypt_round_trip() {
|
||||||
|
let root_key = [42u8; 32];
|
||||||
|
let password = "super_secret_password_123";
|
||||||
|
let nonce_counter = 1;
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let (encrypted, salt) =
|
||||||
|
encrypt_root_key(&root_key, password, nonce_counter).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Verify structure
|
||||||
|
assert_eq!(encrypted.current_nonce, nonce_counter);
|
||||||
|
assert_eq!(encrypted.schema_version, 1);
|
||||||
|
assert_eq!(encrypted.tag.len(), 16); // AEAD tag size
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
let decrypted =
|
||||||
|
decrypt_root_key(&encrypted, password, &salt).expect("Decryption failed");
|
||||||
|
|
||||||
|
// Verify round-trip
|
||||||
|
assert_eq!(decrypted, root_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decrypt_with_wrong_password() {
|
||||||
|
let root_key = [99u8; 32];
|
||||||
|
let correct_password = "correct_password";
|
||||||
|
let wrong_password = "wrong_password";
|
||||||
|
let nonce_counter = 1;
|
||||||
|
|
||||||
|
// Encrypt with correct password
|
||||||
|
let (encrypted, salt) =
|
||||||
|
encrypt_root_key(&root_key, correct_password, nonce_counter).expect("Encryption failed");
|
||||||
|
|
||||||
|
// Attempt decrypt with wrong password
|
||||||
|
let result = decrypt_root_key(&encrypted, wrong_password, &salt);
|
||||||
|
|
||||||
|
// Should fail due to authentication tag mismatch
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_password_derivation_different_salts() {
|
||||||
|
let password = "same_password";
|
||||||
|
|
||||||
|
// Derive key twice - should produce different salts
|
||||||
|
let (key1, salt1) = derive_key_from_password(password).expect("Derivation 1 failed");
|
||||||
|
let (key2, salt2) = derive_key_from_password(password).expect("Derivation 2 failed");
|
||||||
|
|
||||||
|
// Salts should be different (randomly generated)
|
||||||
|
assert_ne!(salt1, salt2);
|
||||||
|
|
||||||
|
// Keys should be different (due to different salts)
|
||||||
|
assert_ne!(key1, key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_password_derivation_with_same_salt() {
|
||||||
|
let password = "test_password";
|
||||||
|
|
||||||
|
// Generate key and salt
|
||||||
|
let (key1, salt) = derive_key_from_password(password).expect("Derivation failed");
|
||||||
|
|
||||||
|
// Derive key again with same salt
|
||||||
|
let key2 = derive_key_with_salt(password, &salt).expect("Re-derivation failed");
|
||||||
|
|
||||||
|
// Keys should be identical
|
||||||
|
assert_eq!(key1, key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_different_nonce_produces_different_ciphertext() {
|
||||||
|
let root_key = [77u8; 32];
|
||||||
|
let password = "password123";
|
||||||
|
|
||||||
|
let (encrypted1, salt1) = encrypt_root_key(&root_key, password, 1).expect("Encryption 1 failed");
|
||||||
|
let (encrypted2, salt2) = encrypt_root_key(&root_key, password, 2).expect("Encryption 2 failed");
|
||||||
|
|
||||||
|
// Different nonces should produce different ciphertexts
|
||||||
|
assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext);
|
||||||
|
|
||||||
|
// But both should decrypt correctly
|
||||||
|
let decrypted1 = decrypt_root_key(&encrypted1, password, &salt1).expect("Decryption 1 failed");
|
||||||
|
let decrypted2 = decrypt_root_key(&encrypted2, password, &salt2).expect("Decryption 2 failed");
|
||||||
|
|
||||||
|
assert_eq!(decrypted1, root_key);
|
||||||
|
assert_eq!(decrypted2, root_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ pub type DatabasePool = diesel_async::pooled_connection::bb8::Pool<DatabaseConne
|
|||||||
pub type PoolInitError = diesel_async::pooled_connection::PoolError;
|
pub type PoolInitError = diesel_async::pooled_connection::PoolError;
|
||||||
pub type PoolError = diesel_async::pooled_connection::bb8::RunError;
|
pub type PoolError = diesel_async::pooled_connection::bb8::RunError;
|
||||||
|
|
||||||
static DB_FILE: &'static str = "arbiter.sqlite";
|
static DB_FILE: &str = "arbiter.sqlite";
|
||||||
|
|
||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ pub mod types {
|
|||||||
pub struct SqliteTimestamp(DateTime<Utc>);
|
pub struct SqliteTimestamp(DateTime<Utc>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug, Insertable)]
|
#[derive(Queryable, Selectable, Debug, Insertable)]
|
||||||
#[diesel(table_name = aead_encrypted, check_for_backend(Sqlite))]
|
#[diesel(table_name = aead_encrypted, check_for_backend(Sqlite))]
|
||||||
pub struct AeadEncrypted {
|
pub struct AeadEncrypted {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
pub current_nonce: i32,
|
||||||
pub ciphertext: Vec<u8>,
|
pub ciphertext: Vec<u8>,
|
||||||
pub tag: Vec<u8>,
|
pub tag: Vec<u8>,
|
||||||
pub current_nonce: i32,
|
|
||||||
pub schema_version: i32,
|
pub schema_version: i32,
|
||||||
|
pub argon2_salt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug, Insertable)]
|
#[derive(Queryable, Debug, Insertable)]
|
||||||
@@ -26,6 +27,7 @@ pub struct ArbiterSetting {
|
|||||||
pub root_key_id: Option<i32>, // references aead_encrypted.id
|
pub root_key_id: Option<i32>, // references aead_encrypted.id
|
||||||
pub cert_key: Vec<u8>,
|
pub cert_key: Vec<u8>,
|
||||||
pub cert: Vec<u8>,
|
pub cert: Vec<u8>,
|
||||||
|
pub current_cert_id: Option<i32>, // references tls_certificates.id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
#[derive(Queryable, Debug)]
|
||||||
@@ -47,3 +49,70 @@ pub struct UseragentClient {
|
|||||||
pub created_at: i32,
|
pub created_at: i32,
|
||||||
pub updated_at: i32,
|
pub updated_at: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLS Certificate Rotation Models
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Insertable)]
|
||||||
|
#[diesel(table_name = schema::tls_certificates, check_for_backend(Sqlite))]
|
||||||
|
pub struct TlsCertificate {
|
||||||
|
pub id: i32,
|
||||||
|
pub cert: Vec<u8>,
|
||||||
|
pub cert_key: Vec<u8>,
|
||||||
|
pub not_before: i32,
|
||||||
|
pub not_after: i32,
|
||||||
|
pub created_at: i32,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = schema::tls_certificates)]
|
||||||
|
pub struct NewTlsCertificate {
|
||||||
|
pub cert: Vec<u8>,
|
||||||
|
pub cert_key: Vec<u8>,
|
||||||
|
pub not_before: i32,
|
||||||
|
pub not_after: i32,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Insertable)]
|
||||||
|
#[diesel(table_name = schema::tls_rotation_state, check_for_backend(Sqlite))]
|
||||||
|
pub struct TlsRotationState {
|
||||||
|
pub id: i32,
|
||||||
|
pub state: String,
|
||||||
|
pub new_cert_id: Option<i32>,
|
||||||
|
pub initiated_at: Option<i32>,
|
||||||
|
pub timeout_at: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Insertable)]
|
||||||
|
#[diesel(table_name = schema::rotation_client_acks, check_for_backend(Sqlite))]
|
||||||
|
pub struct RotationClientAck {
|
||||||
|
pub rotation_id: i32,
|
||||||
|
pub client_key: String,
|
||||||
|
pub ack_received_at: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = schema::rotation_client_acks)]
|
||||||
|
pub struct NewRotationClientAck {
|
||||||
|
pub rotation_id: i32,
|
||||||
|
pub client_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug, Insertable)]
|
||||||
|
#[diesel(table_name = schema::tls_rotation_history, check_for_backend(Sqlite))]
|
||||||
|
pub struct TlsRotationHistory {
|
||||||
|
pub id: i32,
|
||||||
|
pub cert_id: i32,
|
||||||
|
pub event_type: String,
|
||||||
|
pub timestamp: i32,
|
||||||
|
pub details: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = schema::tls_rotation_history)]
|
||||||
|
pub struct NewTlsRotationHistory {
|
||||||
|
pub cert_id: i32,
|
||||||
|
pub event_type: String,
|
||||||
|
pub details: Option<String>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ diesel::table! {
|
|||||||
ciphertext -> Binary,
|
ciphertext -> Binary,
|
||||||
tag -> Binary,
|
tag -> Binary,
|
||||||
schema_version -> Integer,
|
schema_version -> Integer,
|
||||||
|
argon2_salt -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ diesel::table! {
|
|||||||
root_key_id -> Nullable<Integer>,
|
root_key_id -> Nullable<Integer>,
|
||||||
cert_key -> Binary,
|
cert_key -> Binary,
|
||||||
cert -> Binary,
|
cert -> Binary,
|
||||||
|
current_cert_id -> Nullable<Integer>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +41,59 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
tls_certificates (id) {
|
||||||
|
id -> Integer,
|
||||||
|
cert -> Binary,
|
||||||
|
cert_key -> Binary,
|
||||||
|
not_before -> Integer,
|
||||||
|
not_after -> Integer,
|
||||||
|
created_at -> Integer,
|
||||||
|
is_active -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
tls_rotation_state (id) {
|
||||||
|
id -> Integer,
|
||||||
|
state -> Text,
|
||||||
|
new_cert_id -> Nullable<Integer>,
|
||||||
|
initiated_at -> Nullable<Integer>,
|
||||||
|
timeout_at -> Nullable<Integer>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
rotation_client_acks (rotation_id, client_key) {
|
||||||
|
rotation_id -> Integer,
|
||||||
|
client_key -> Text,
|
||||||
|
ack_received_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
tls_rotation_history (id) {
|
||||||
|
id -> Integer,
|
||||||
|
cert_id -> Integer,
|
||||||
|
event_type -> Text,
|
||||||
|
timestamp -> Integer,
|
||||||
|
details -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::joinable!(arbiter_settings -> aead_encrypted (root_key_id));
|
diesel::joinable!(arbiter_settings -> aead_encrypted (root_key_id));
|
||||||
|
diesel::joinable!(arbiter_settings -> tls_certificates (current_cert_id));
|
||||||
|
diesel::joinable!(tls_rotation_state -> tls_certificates (new_cert_id));
|
||||||
|
diesel::joinable!(rotation_client_acks -> tls_certificates (rotation_id));
|
||||||
|
diesel::joinable!(tls_rotation_history -> tls_certificates (cert_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
aead_encrypted,
|
aead_encrypted,
|
||||||
arbiter_settings,
|
arbiter_settings,
|
||||||
program_client,
|
program_client,
|
||||||
useragent_client,
|
useragent_client,
|
||||||
|
tls_certificates,
|
||||||
|
tls_rotation_state,
|
||||||
|
rotation_client_acks,
|
||||||
|
tls_rotation_history,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::{
|
|||||||
|
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
mod context;
|
mod context;
|
||||||
|
mod crypto;
|
||||||
mod db;
|
mod db;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,48 @@
|
|||||||
|
|
||||||
# cargo-vet audits file
|
# cargo-vet audits file
|
||||||
|
|
||||||
[audits]
|
[[audits.test-log]]
|
||||||
|
who = "hdbg <httpdebugger@protonmail.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
delta = "0.2.18 -> 0.2.19"
|
||||||
|
|
||||||
|
[[audits.test-log-macros]]
|
||||||
|
who = "hdbg <httpdebugger@protonmail.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
delta = "0.2.18 -> 0.2.19"
|
||||||
|
|
||||||
|
[[trusted.h2]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 359 # Sean McArthur (seanmonstar)
|
||||||
|
start = "2019-03-13"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|
||||||
|
[[trusted.hashbrown]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 55123 # rust-lang-owner
|
||||||
|
start = "2025-04-30"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|
||||||
|
[[trusted.hyper-util]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 359 # Sean McArthur (seanmonstar)
|
||||||
|
start = "2022-01-15"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|
||||||
|
[[trusted.rustix]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 6825 # Dan Gohman (sunfishcode)
|
||||||
|
start = "2021-10-29"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|
||||||
|
[[trusted.serde_json]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 3618 # David Tolnay (dtolnay)
|
||||||
|
start = "2019-02-28"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|
||||||
|
[[trusted.syn]]
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
user-id = 3618 # David Tolnay (dtolnay)
|
||||||
|
start = "2019-03-01"
|
||||||
|
end = "2027-02-14"
|
||||||
|
|||||||
@@ -3,3 +3,944 @@
|
|||||||
|
|
||||||
[cargo-vet]
|
[cargo-vet]
|
||||||
version = "0.10"
|
version = "0.10"
|
||||||
|
|
||||||
|
[imports.bytecode-alliance]
|
||||||
|
url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml"
|
||||||
|
|
||||||
|
[imports.google]
|
||||||
|
url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml"
|
||||||
|
|
||||||
|
[imports.mozilla]
|
||||||
|
url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml"
|
||||||
|
|
||||||
|
[[exemptions.addr2line]]
|
||||||
|
version = "0.25.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.aho-corasick]]
|
||||||
|
version = "1.1.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.anyhow]]
|
||||||
|
version = "1.0.101"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.asn1-rs]]
|
||||||
|
version = "0.7.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.asn1-rs-derive]]
|
||||||
|
version = "0.6.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.asn1-rs-impl]]
|
||||||
|
version = "0.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.async-trait]]
|
||||||
|
version = "0.1.89"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.autocfg]]
|
||||||
|
version = "1.5.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.aws-lc-rs]]
|
||||||
|
version = "1.15.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.aws-lc-sys]]
|
||||||
|
version = "0.37.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.axum]]
|
||||||
|
version = "0.8.8"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.axum-core]]
|
||||||
|
version = "0.5.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.backtrace]]
|
||||||
|
version = "0.3.76"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.backtrace-ext]]
|
||||||
|
version = "0.2.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.bb8]]
|
||||||
|
version = "0.9.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.bitflags]]
|
||||||
|
version = "2.10.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.block-buffer]]
|
||||||
|
version = "0.11.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.bytes]]
|
||||||
|
version = "1.11.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.cc]]
|
||||||
|
version = "1.2.55"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.cfg-if]]
|
||||||
|
version = "1.0.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.chacha20]]
|
||||||
|
version = "0.10.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.chrono]]
|
||||||
|
version = "0.4.43"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.cmake]]
|
||||||
|
version = "0.1.57"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.cpufeatures]]
|
||||||
|
version = "0.2.17"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.cpufeatures]]
|
||||||
|
version = "0.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.crc32fast]]
|
||||||
|
version = "1.5.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.crossbeam-utils]]
|
||||||
|
version = "0.8.21"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.crypto-common]]
|
||||||
|
version = "0.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.curve25519-dalek]]
|
||||||
|
version = "5.0.0-pre.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.curve25519-dalek-derive]]
|
||||||
|
version = "0.1.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.darling]]
|
||||||
|
version = "0.21.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.darling_core]]
|
||||||
|
version = "0.21.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.darling_macro]]
|
||||||
|
version = "0.21.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.dashmap]]
|
||||||
|
version = "6.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.data-encoding]]
|
||||||
|
version = "2.10.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.der-parser]]
|
||||||
|
version = "10.0.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.deranged]]
|
||||||
|
version = "0.5.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.diesel]]
|
||||||
|
version = "2.3.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.diesel-async]]
|
||||||
|
version = "0.7.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.diesel_derives]]
|
||||||
|
version = "2.3.7"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.diesel_migrations]]
|
||||||
|
version = "2.3.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.diesel_table_macro_syntax]]
|
||||||
|
version = "0.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.digest]]
|
||||||
|
version = "0.11.0-rc.11"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.downcast-rs]]
|
||||||
|
version = "2.0.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.dsl_auto_type]]
|
||||||
|
version = "0.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.dunce]]
|
||||||
|
version = "1.0.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.dyn-clone]]
|
||||||
|
version = "1.0.20"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.ed25519]]
|
||||||
|
version = "3.0.0-rc.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.ed25519-dalek]]
|
||||||
|
version = "3.0.0-pre.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.errno]]
|
||||||
|
version = "0.3.14"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.fiat-crypto]]
|
||||||
|
version = "0.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.find-msvc-tools]]
|
||||||
|
version = "0.1.9"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.fixedbitset]]
|
||||||
|
version = "0.5.7"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.flate2]]
|
||||||
|
version = "1.1.9"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.fs_extra]]
|
||||||
|
version = "1.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.futures-task]]
|
||||||
|
version = "0.3.31"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.futures-util]]
|
||||||
|
version = "0.3.31"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.getrandom]]
|
||||||
|
version = "0.2.17"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.getrandom]]
|
||||||
|
version = "0.3.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.getrandom]]
|
||||||
|
version = "0.4.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.hashbrown]]
|
||||||
|
version = "0.14.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.http]]
|
||||||
|
version = "1.4.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.http-body]]
|
||||||
|
version = "1.0.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.http-body-util]]
|
||||||
|
version = "0.1.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.httparse]]
|
||||||
|
version = "1.10.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.hybrid-array]]
|
||||||
|
version = "0.4.7"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.hyper]]
|
||||||
|
version = "1.8.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.hyper-timeout]]
|
||||||
|
version = "0.5.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.iana-time-zone]]
|
||||||
|
version = "0.1.65"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.id-arena]]
|
||||||
|
version = "2.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.ident_case]]
|
||||||
|
version = "1.0.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.indexmap]]
|
||||||
|
version = "2.13.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.is_ci]]
|
||||||
|
version = "1.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.itertools]]
|
||||||
|
version = "0.14.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.itoa]]
|
||||||
|
version = "1.0.17"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.jobserver]]
|
||||||
|
version = "0.1.34"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.js-sys]]
|
||||||
|
version = "0.3.85"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.kameo]]
|
||||||
|
version = "0.19.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.kameo_macros]]
|
||||||
|
version = "0.19.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.libc]]
|
||||||
|
version = "0.2.181"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.libsqlite3-sys]]
|
||||||
|
version = "0.35.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.linux-raw-sys]]
|
||||||
|
version = "0.11.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.lock_api]]
|
||||||
|
version = "0.4.14"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.log]]
|
||||||
|
version = "0.4.29"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.matchit]]
|
||||||
|
version = "0.8.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.memchr]]
|
||||||
|
version = "2.8.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.memsafe]]
|
||||||
|
version = "0.4.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.miette]]
|
||||||
|
version = "7.6.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.miette-derive]]
|
||||||
|
version = "7.6.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.migrations_internals]]
|
||||||
|
version = "2.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.migrations_macros]]
|
||||||
|
version = "2.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.mime]]
|
||||||
|
version = "0.3.17"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.minimal-lexical]]
|
||||||
|
version = "0.2.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.mio]]
|
||||||
|
version = "1.1.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.multimap]]
|
||||||
|
version = "0.10.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.num-bigint]]
|
||||||
|
version = "0.4.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.num-conv]]
|
||||||
|
version = "0.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.object]]
|
||||||
|
version = "0.37.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.oid-registry]]
|
||||||
|
version = "0.8.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.once_cell]]
|
||||||
|
version = "1.21.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.owo-colors]]
|
||||||
|
version = "4.2.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.parking_lot]]
|
||||||
|
version = "0.12.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.parking_lot_core]]
|
||||||
|
version = "0.9.12"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.pem]]
|
||||||
|
version = "3.0.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.petgraph]]
|
||||||
|
version = "0.8.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.pin-project]]
|
||||||
|
version = "1.1.10"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.pin-project-internal]]
|
||||||
|
version = "1.1.10"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.portable-atomic]]
|
||||||
|
version = "1.13.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.prettyplease]]
|
||||||
|
version = "0.2.37"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.proc-macro2]]
|
||||||
|
version = "1.0.106"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.prost]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.prost-build]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.prost-derive]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.prost-types]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.pulldown-cmark]]
|
||||||
|
version = "0.13.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.pulldown-cmark-to-cmark]]
|
||||||
|
version = "22.0.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.quote]]
|
||||||
|
version = "1.0.44"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.r-efi]]
|
||||||
|
version = "5.3.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rand]]
|
||||||
|
version = "0.10.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rand_core]]
|
||||||
|
version = "0.10.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rcgen]]
|
||||||
|
version = "0.14.7"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.redox_syscall]]
|
||||||
|
version = "0.5.18"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.regex]]
|
||||||
|
version = "1.12.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.regex-automata]]
|
||||||
|
version = "0.4.14"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.regex-syntax]]
|
||||||
|
version = "0.8.9"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.ring]]
|
||||||
|
version = "0.17.14"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rsqlite-vfs]]
|
||||||
|
version = "0.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustc-demangle]]
|
||||||
|
version = "0.1.27"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustc_version]]
|
||||||
|
version = "0.4.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rusticata-macros]]
|
||||||
|
version = "4.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustls]]
|
||||||
|
version = "0.23.36"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustls-pki-types]]
|
||||||
|
version = "1.14.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustls-webpki]]
|
||||||
|
version = "0.103.9"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.rustversion]]
|
||||||
|
version = "1.0.22"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.scoped-futures]]
|
||||||
|
version = "0.1.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.scopeguard]]
|
||||||
|
version = "1.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.secrecy]]
|
||||||
|
version = "0.10.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.semver]]
|
||||||
|
version = "1.0.27"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.serde]]
|
||||||
|
version = "1.0.228"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.serde_core]]
|
||||||
|
version = "1.0.228"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.serde_derive]]
|
||||||
|
version = "1.0.228"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.sha2]]
|
||||||
|
version = "0.11.0-rc.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.signal-hook-registry]]
|
||||||
|
version = "1.4.8"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.signature]]
|
||||||
|
version = "3.0.0-rc.10"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.simd-adler32]]
|
||||||
|
version = "0.3.8"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.slab]]
|
||||||
|
version = "0.4.12"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.smlang]]
|
||||||
|
version = "0.8.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.smlang-macros]]
|
||||||
|
version = "0.8.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.socket2]]
|
||||||
|
version = "0.6.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.sqlite-wasm-rs]]
|
||||||
|
version = "0.5.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.string_morph]]
|
||||||
|
version = "0.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.subtle]]
|
||||||
|
version = "2.6.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.supports-color]]
|
||||||
|
version = "3.0.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.supports-hyperlinks]]
|
||||||
|
version = "3.2.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.supports-unicode]]
|
||||||
|
version = "3.0.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.sync_wrapper]]
|
||||||
|
version = "1.0.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tempfile]]
|
||||||
|
version = "3.25.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.terminal_size]]
|
||||||
|
version = "0.4.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.thiserror]]
|
||||||
|
version = "2.0.18"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.thiserror-impl]]
|
||||||
|
version = "2.0.18"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.thread_local]]
|
||||||
|
version = "1.1.9"
|
||||||
|
criteria = "safe-to-run"
|
||||||
|
|
||||||
|
[[exemptions.time]]
|
||||||
|
version = "0.3.47"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.time-core]]
|
||||||
|
version = "0.1.8"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.time-macros]]
|
||||||
|
version = "0.2.27"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tokio]]
|
||||||
|
version = "1.49.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tokio-macros]]
|
||||||
|
version = "2.6.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tokio-rustls]]
|
||||||
|
version = "0.26.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tokio-stream]]
|
||||||
|
version = "0.1.18"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tokio-util]]
|
||||||
|
version = "0.7.18"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.toml]]
|
||||||
|
version = "0.9.11+spec-1.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.toml_parser]]
|
||||||
|
version = "1.0.6+spec-1.1.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tonic]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tonic-build]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tonic-prost]]
|
||||||
|
version = "0.14.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tonic-prost-build]]
|
||||||
|
version = "0.14.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tower]]
|
||||||
|
version = "0.5.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tower-layer]]
|
||||||
|
version = "0.3.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tower-service]]
|
||||||
|
version = "0.3.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tracing]]
|
||||||
|
version = "0.1.44"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tracing-attributes]]
|
||||||
|
version = "0.1.31"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tracing-core]]
|
||||||
|
version = "0.1.36"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.tracing-subscriber]]
|
||||||
|
version = "0.3.22"
|
||||||
|
criteria = "safe-to-run"
|
||||||
|
|
||||||
|
[[exemptions.try-lock]]
|
||||||
|
version = "0.2.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.typenum]]
|
||||||
|
version = "1.19.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.unicase]]
|
||||||
|
version = "2.9.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.unicode-ident]]
|
||||||
|
version = "1.0.23"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.untrusted]]
|
||||||
|
version = "0.7.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.untrusted]]
|
||||||
|
version = "0.9.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.uuid]]
|
||||||
|
version = "1.20.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.want]]
|
||||||
|
version = "0.3.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.wasi]]
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.wasm-bindgen]]
|
||||||
|
version = "0.2.108"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.wasm-bindgen-macro]]
|
||||||
|
version = "0.2.108"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.wasm-bindgen-macro-support]]
|
||||||
|
version = "0.2.108"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.wasm-bindgen-shared]]
|
||||||
|
version = "0.2.108"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.winapi]]
|
||||||
|
version = "0.3.9"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.winapi-i686-pc-windows-gnu]]
|
||||||
|
version = "0.4.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.winapi-x86_64-pc-windows-gnu]]
|
||||||
|
version = "0.4.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-core]]
|
||||||
|
version = "0.62.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-implement]]
|
||||||
|
version = "0.60.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-interface]]
|
||||||
|
version = "0.59.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-link]]
|
||||||
|
version = "0.2.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-result]]
|
||||||
|
version = "0.4.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-strings]]
|
||||||
|
version = "0.5.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-sys]]
|
||||||
|
version = "0.52.0"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-sys]]
|
||||||
|
version = "0.60.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-sys]]
|
||||||
|
version = "0.61.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-targets]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows-targets]]
|
||||||
|
version = "0.53.5"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_aarch64_gnullvm]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_aarch64_gnullvm]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_aarch64_msvc]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_aarch64_msvc]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_gnu]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_gnu]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_gnullvm]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_gnullvm]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_msvc]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_i686_msvc]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_gnu]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_gnu]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_gnullvm]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_gnullvm]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_msvc]]
|
||||||
|
version = "0.52.6"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.windows_x86_64_msvc]]
|
||||||
|
version = "0.53.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.winnow]]
|
||||||
|
version = "0.7.14"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.x509-parser]]
|
||||||
|
version = "0.18.1"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.yasna]]
|
||||||
|
version = "0.5.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.zeroize]]
|
||||||
|
version = "1.8.2"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.zmij]]
|
||||||
|
version = "1.0.20"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.zstd]]
|
||||||
|
version = "0.13.3"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.zstd-safe]]
|
||||||
|
version = "7.2.4"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
|
[[exemptions.zstd-sys]]
|
||||||
|
version = "2.0.16+zstd.1.5.7"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user