Skip to content

Commit 11cf9ec

Browse files
authored
[KeyManager] Add protobuf definitions for standardizing error handling (#721)
This commit introduces the foundational infrastructure for standardizing error handling across the Go-Rust FFI boundary in the `KeyManager` component. By moving to Protobuf-defined error codes, we ensure that both Go and Rust have a shared, type-safe understanding of failure states, replacing more brittle or inconsistent error-passing mechanisms. Key Changes - Standardized Error Definitions: Introduced `status.proto`, which defines a comprehensive Error enum covering common failure scenarios such as `INTERNAL_ERROR`, `INVALID_ARGUMENT`, `CRYPTO_ERROR`, and more. - Go Integration: - Added a `FFIStatus` struct that implements the Go error interface. - Provided a `ToStatus()` helper on the generated Protobuf Error type to simplify converting FFI return codes into idiomatic Go errors. - Rust Infrastructure: - Integrated the new Status `proto` into the Rust build pipeline. - Implemented `std::status::Status` and `fmt::Display` for the generated Rust Error type. - Added `ffi_call` and `ffi_call_i32` utility functions. these helpers wrap FFI operations in catch_unwind blocks to prevent panics from crossing the FFI boundary, translating them into standardized `ERROR_INTERNAL` codes instead. - Tooling: Updated the `gen_keymanager.sh` script to ensure consistent Go code generation for the new error definitions.
1 parent 53c0c9d commit 11cf9ec

File tree

9 files changed

+318
-4
lines changed

9 files changed

+318
-4
lines changed

keymanager/km_common/build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ fn main() -> Result<()> {
88

99
let mut config = prost_build::Config::new();
1010

11-
config.compile_protos(&["proto/algorithms.proto"], &["proto/"])?;
11+
config.compile_protos(
12+
&["proto/algorithms.proto", "proto/status.proto"],
13+
&["proto/"],
14+
)?;
1215

1316
Ok(())
1417
}

keymanager/km_common/proto/algorithms.pb.go

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Package keymanager provides common cryptographic utilities and FFI wrappers.
2+
package keymanager
3+
4+
import (
5+
"fmt"
6+
)
7+
8+
// FFIStatus represents a status returned from the Rust FFI.
9+
type FFIStatus struct {
10+
Code Status
11+
}
12+
13+
func (e *FFIStatus) Error() string {
14+
return fmt.Sprintf("FFI status: %s", e.Code.String())
15+
}
16+
17+
// Is allows users to check errors.Is(err, Status_CODE.ToStatus()) or errors.Is(err, &FFIStatus{Code: Status_CODE}).
18+
func (e *FFIStatus) Is(target error) bool {
19+
if t, ok := target.(*FFIStatus); ok {
20+
return e.Code == t.Code
21+
}
22+
return false
23+
}
24+
25+
// Status returns the string representation of the Status.
26+
func (e Status) Status() string {
27+
return e.String()
28+
}
29+
30+
// ToStatus converts a Status to a Go error, or returns nil if it is Success.
31+
func (e Status) ToStatus() error {
32+
if e == Status_STATUS_SUCCESS {
33+
return nil
34+
}
35+
return &FFIStatus{Code: e}
36+
}

keymanager/km_common/proto/status.pb.go

Lines changed: 175 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
syntax = "proto3";
2+
3+
package keymanager;
4+
5+
option go_package = "github.com/google/go-tpm-tools/keymanager/km_common/proto;keymanager";
6+
7+
enum Status {
8+
STATUS_UNSPECIFIED = 0;
9+
STATUS_SUCCESS = 1;
10+
STATUS_INTERNAL_ERROR = 2;
11+
STATUS_INVALID_ARGUMENT = 3;
12+
STATUS_NOT_FOUND = 4;
13+
STATUS_ALREADY_EXISTS = 5;
14+
STATUS_PERMISSION_DENIED = 6;
15+
STATUS_UNAUTHENTICATED = 7;
16+
STATUS_UNSUPPORTED_ALGORITHM = 8;
17+
STATUS_INVALID_KEY = 9;
18+
STATUS_CRYPTO_ERROR = 10;
19+
STATUS_DECRYPTION_FAILURE = 11;
20+
STATUS_ENCRYPTION_FAILURE = 12;
21+
STATUS_DECAPSULATION_FAILURE = 13;
22+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package keymanager
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestFFIStatus(t *testing.T) {
9+
err := Status_STATUS_INTERNAL_ERROR.ToStatus()
10+
if err == nil {
11+
t.Fatalf("ToStatus() = nil, want error")
12+
}
13+
if !errors.Is(err, Status_STATUS_INTERNAL_ERROR.ToStatus()) {
14+
t.Errorf("errors.Is(err, Status_STATUS_INTERNAL_ERROR.ToStatus()) = false, want true")
15+
}
16+
if errors.Is(err, Status_STATUS_INVALID_ARGUMENT.ToStatus()) {
17+
t.Errorf("errors.Is(err, Status_STATUS_INVALID_ARGUMENT.ToStatus()) = true, want false")
18+
}
19+
20+
// Test with FFIStatus pointer
21+
if !errors.Is(err, &FFIStatus{Code: Status_STATUS_INTERNAL_ERROR}) {
22+
t.Errorf("errors.Is(err, &FFIStatus{Code: Status_STATUS_INTERNAL_ERROR}) = false, want true")
23+
}
24+
if errors.Is(err, &FFIStatus{Code: Status_STATUS_INVALID_ARGUMENT}) {
25+
t.Errorf("errors.Is(err, &FFIStatus{Code: Status_STATUS_INVALID_ARGUMENT}) = true, want false")
26+
}
27+
28+
// Test other error type
29+
if errors.Is(err, errors.New("other error")) {
30+
t.Errorf("errors.Is(err, errors.New(\"other error\")) = true, want false")
31+
}
32+
}
33+
34+
func TestFFIStatusSuccess(t *testing.T) {
35+
if err := Status_STATUS_SUCCESS.ToStatus(); err != nil {
36+
t.Errorf("Status_STATUS_SUCCESS.ToStatus() = %v, want nil", err)
37+
}
38+
}
39+
40+
func TestStatusMethod(t *testing.T) {
41+
val := Status_STATUS_INTERNAL_ERROR
42+
if val.Status() != "STATUS_INTERNAL_ERROR" {
43+
t.Errorf("Status_STATUS_INTERNAL_ERROR.Status() = %q, want \"STATUS_INTERNAL_ERROR\"", val.Status())
44+
}
45+
}

keymanager/km_common/src/crypto/x25519.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::algorithms::{AeadAlgorithm, HpkeAlgorithm, KdfAlgorithm, KemAlgorithm};
2+
use crate::crypto::secret_box::SecretBox;
23
#[cfg(any(test, feature = "test-utils"))]
34
use crate::crypto::PrivateKey;
4-
use crate::crypto::secret_box::SecretBox;
55
use crate::crypto::{Error, PrivateKeyOps, PublicKeyOps};
66
use bssl_crypto::{hkdf, hpke, x25519};
77

keymanager/km_common/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
1-
pub mod algorithms {
1+
pub mod keymanager {
22
include!(concat!(env!("OUT_DIR"), "/keymanager.rs"));
33
}
4+
pub use keymanager as algorithms;
5+
pub use keymanager as proto;
6+
7+
pub use proto::Status;
8+
9+
impl std::error::Error for Status {}
10+
impl std::fmt::Display for Status {
11+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12+
write!(f, "{:?}", self)
13+
}
14+
}
15+
16+
/// Helper function to safely execute an FFI closure, catch panics, and return a standardized Status.
17+
pub fn ffi_call<F>(f: F) -> Status
18+
where
19+
F: FnOnce() -> Result<(), Status>,
20+
{
21+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
22+
.unwrap_or(Err(Status::InternalError))
23+
.err()
24+
.unwrap_or(Status::Success)
25+
}
26+
27+
/// Helper function for FFI calls returning i32 (positive for count/success, negative for Status).
28+
pub fn ffi_call_i32<F>(f: F) -> i32
29+
where
30+
F: FnOnce() -> Result<i32, Status>,
31+
{
32+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
33+
.unwrap_or(Err(Status::InternalError))
34+
.unwrap_or_else(|e| -(e as i32))
35+
}
436

537
pub mod crypto;
638
pub mod key_types;

proto/gen_keymanager.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ protoc -I. --go_out=. --go_opt=paths=source_relative keymanager/km_common/proto/
77
protoc -I. --go_out=. --go_opt=paths=source_relative keymanager/km_common/proto/key_claims.proto
88
# Generate payload.pb.go
99
protoc -I. --go_out=. --go_opt=paths=source_relative keymanager/km_common/proto/payload.proto
10+
# Generate status.pb.go
11+
protoc -I. --go_out=. --go_opt=paths=source_relative keymanager/km_common/proto/status.proto

0 commit comments

Comments
 (0)