Skip to content

Commit

Permalink
keymanager: Add e2e tests for ephemeral keys
Browse files Browse the repository at this point in the history
  • Loading branch information
peternose committed Aug 16, 2022
1 parent 78a0cef commit af18566
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .buildkite/code.pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ steps:
- export CFLAGS_x86_64_fortanix_unknown_sgx="-isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening"
- export CC_x86_64_fortanix_unknown_sgx=clang-11
# Only run runtime scenarios as others do not use SGX.
- .buildkite/scripts/test_e2e.sh --scenario e2e/runtime/runtime-encryption --scenario e2e/runtime/trust-root
- .buildkite/scripts/test_e2e.sh --scenario e2e/runtime/runtime-encryption --scenario e2e/runtime/trust-root --scenario e2e/runtime/keymanager-key-generation
artifact_paths:
- coverage-merged-e2e-*.txt
- /tmp/e2e/**/*.log
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

236 changes: 236 additions & 0 deletions go/oasis-test-runner/scenario/e2e/runtime/keymanager_key_generation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package runtime

import (
"context"
"fmt"

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario"
)

// KeymanagerKeyGeneration is the keymanager key generation scenario.
//
// It uses encryption and decryption transactions provided by the
// simple key/value runtime to test whether the key manager client
// can retrieve private and public ephemeral keys from the key manager
// and if the latter generates those according to the specifications.
var KeymanagerKeyGeneration scenario.Scenario = newKmKeyGenerationImpl()

type kmKeyGenerationImpl struct {
runtimeImpl
}

func newKmKeyGenerationImpl() scenario.Scenario {
return &kmKeyGenerationImpl{
runtimeImpl: *newRuntimeImpl("keymanager-key-generation", BasicKVEncTestClient),
}
}

func (sc *kmKeyGenerationImpl) Fixture() (*oasis.NetworkFixture, error) {
return sc.runtimeImpl.Fixture()
}

func (sc *kmKeyGenerationImpl) Clone() scenario.Scenario {
return &kmKeyGenerationImpl{
runtimeImpl: *sc.runtimeImpl.Clone().(*runtimeImpl),
}
}

func (sc *kmKeyGenerationImpl) Run(childEnv *env.Env) error {
ctx := context.Background()
if err := sc.runtimeImpl.startNetworkAndTestClient(ctx, childEnv); err != nil {
return err
}

// Wait for the client to exit.
if err := sc.waitTestClientOnly(); err != nil {
return err
}

// Initialize the nonce DRBG.
rng, err := drbgFromSeed(
[]byte("oasis-core/oasis-test-runner/e2e/runtime/keymanager-key-generation"),
[]byte("keymanager-key-generation"),
)
if err != nil {
return err
}

// Data needed for encryption and decryption.
keyPairID := "key pair id"
plaintext := "The quick brown fox jumps over the lazy dog"

epoch, err := sc.Net.Controller().Beacon.GetEpoch(ctx, consensus.HeightLatest)
if err != nil {
return fmt.Errorf("failed to get epoch at the latest height: %w", err)
}

// Encrypt plaintext using ephemeral public key for the current epoch.
// Successful encryption indicates that the key manager generated
// an ephemeral public key.
sc.Logger.Info("encrypting plaintext")
ciphertext, err := sc.submitKeyValueRuntimeEncryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch,
keyPairID,
plaintext,
)
if err != nil {
return fmt.Errorf("failed to encrypt plaintext: %w", err)
}

// Decrypt ciphertext using ephemeral private key for the current epoch.
// Successful decryption indicates that the key manager generates
// matching public and private ephemeral keys.
sc.Logger.Info("decrypting ciphertext")
decrypted, err := sc.submitKeyValueRuntimeDecryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch,
keyPairID,
ciphertext,
)
if err != nil {
return fmt.Errorf("failed to decrypt ciphertext: %w", err)
}
if decrypted != plaintext {
return fmt.Errorf("decrypted ciphertext does match the plaintext (got: '%s', expected: '%s')", decrypted, plaintext)
}

// Decrypt ciphertext using ephemeral private key for the previous epoch.
// As ephemeral keys are derived from the epoch, the decryption should
// fail.
sc.Logger.Info("decrypting ciphertext with wrong epoch")
decrypted, err = sc.submitKeyValueRuntimeDecryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch-1,
keyPairID,
ciphertext,
)
if err == nil && decrypted == plaintext {
return fmt.Errorf("decryption with wrong epoch should fail or produce garbage")
}

// Decrypt ciphertext using ephemeral private key derived from wrong
// key pair id. As ephemeral keys are derived from the id, the decryption
// should fail.
sc.Logger.Info("decrypting ciphertext with wrong key pair id")
decrypted, err = sc.submitKeyValueRuntimeDecryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch,
"wrong key pair id",
ciphertext,
)
if err == nil && decrypted == plaintext {
return fmt.Errorf("decryption with wrong key pair id should fail or produce garbage")
}

// Change epoch and test what happens if epoch is invalid,
// i.e. too old or somewhere in the future.
epoch = epoch + 10

// Encrypt plaintext using epoch that is in the future.
// As public ephemeral keys are not allowed to be derived
// for future epoch neither for epoch that are too far
// in the past, the encryption should fail.
sc.Logger.Info("encrypting plaintext with invalid epoch")
_, err = sc.submitKeyValueRuntimeEncryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch,
keyPairID,
plaintext,
)
if err == nil {
return fmt.Errorf("encryption with invalid epoch should fail")
}

// Decrypt ciphertext using epoch that is in the future.
// The same rule holds for private ephemeral keys,
// so the encryption should fail.
sc.Logger.Info("decrypting ciphertext with invalid epoch")
_, err = sc.submitKeyValueRuntimeDecryptTx(
ctx,
runtimeID,
rng.Uint64(),
epoch,
keyPairID,
ciphertext,
)
if err == nil {
return fmt.Errorf("decryption with invalid epoch should fail")
}

return nil
}

func (sc *kmKeyGenerationImpl) submitKeyValueRuntimeEncryptTx(
ctx context.Context,
id common.Namespace,
nonce uint64,
epoch beacon.EpochTime,
keyPairID string,
plaintext string,
) (string, error) {
rawRsp, err := sc.submitRuntimeTx(ctx, runtimeID, nonce, "encrypt", struct {
Epoch uint64 `json:"epoch"`
KeyPairID string `json:"key_pair_id"`
Plaintext string `json:"plaintext"`
}{
Epoch: uint64(epoch),
KeyPairID: keyPairID,
Plaintext: plaintext,
})
if err != nil {
return "", fmt.Errorf("failed to submit encrypt tx to runtime: %w", err)
}

var rsp string
if err = cbor.Unmarshal(rawRsp, &rsp); err != nil {
return "", fmt.Errorf("failed to unmarshal response from runtime: %w", err)
}

return rsp, nil
}

func (sc *kmKeyGenerationImpl) submitKeyValueRuntimeDecryptTx(
ctx context.Context,
id common.Namespace,
nonce uint64,
epoch beacon.EpochTime,
keyPairID string,
ciphertext string,
) (string, error) {
rawRsp, err := sc.submitRuntimeTx(ctx, runtimeID, nonce, "decrypt", struct {
Epoch uint64 `json:"epoch"`
KeyPairID string `json:"key_pair_id"`
Ciphertext string `json:"ciphertext"`
}{
Epoch: uint64(epoch),
KeyPairID: keyPairID,
Ciphertext: ciphertext,
})
if err != nil {
return "", fmt.Errorf("failed to submit decrypt tx to runtime: %w", err)
}

var rsp string
if err = cbor.Unmarshal(rawRsp, &rsp); err != nil {
return "", fmt.Errorf("failed to unmarshal response from runtime: %w", err)
}

return rsp, nil
}
2 changes: 2 additions & 0 deletions go/oasis-test-runner/scenario/e2e/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,8 @@ func RegisterScenarios() error {
StorageEarlyStateSync,
// Sentry test.
Sentry,
// Keymanager key generation test.
KeymanagerKeyGeneration,
// Keymanager restart test.
KeymanagerRestart,
// Keymanager replicate test.
Expand Down
3 changes: 3 additions & 0 deletions tests/runtimes/simple-keyvalue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ anyhow = "1.0"
thiserror = "1.0"
io-context = "0.2.0"
byteorder = "1.4.3"
rand = "0.7.3"
base64 = "0.13.0"
x25519-dalek = "1.1.0"
tokio = { version = "1", features = ["rt"] }

[build-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions tests/runtimes/simple-keyvalue/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ impl Dispatcher {
"enc_insert" => Self::dispatch_call(ctx, tx.args, Methods::enc_insert),
"enc_get" => Self::dispatch_call(ctx, tx.args, Methods::enc_get),
"enc_remove" => Self::dispatch_call(ctx, tx.args, Methods::enc_remove),
"encrypt" => Self::dispatch_call(ctx, tx.args, Methods::encrypt),
"decrypt" => Self::dispatch_call(ctx, tx.args, Methods::decrypt),
_ => Err("method not found".to_string()),
}
}
Expand Down
Loading

0 comments on commit af18566

Please sign in to comment.