Skip to content

Commit

Permalink
Merge branch 'main' into feature/ecc-wrappper
Browse files Browse the repository at this point in the history
  • Loading branch information
sujankota authored Feb 22, 2025
2 parents f2814e3 + 9ca042d commit b695e66
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 172 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nightly-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
password: ${{ steps.gcp-auth.outputs.access_token }}

- id: docker_meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
with:
images: ${{ secrets.DOCKER_REPO }}
tags: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
password: ${{ steps.gcp-auth.outputs.access_token }}

- id: docker_meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
with:
images: ${{ secrets.DOCKER_REPO }}
tags: |
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/traffic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ jobs:
matrix:
repo-values:
- {repo: platform, event: ""}
- {repo: otdfctl, event: backend-}
- {repo: spec, event: frontend-}
- {repo: otdfctl, event: otdfctl-}
- {repo: spec, event: spec-}
- {repo: tests, event: tests-}
- {repo: client-web, event: clientweb-}
- {repo: client-cpp, event: cpp-sdk-}
- {repo: web-sdk, event: web-sdk-}
- {repo: java-sdk, event: java-sdk-}
- {repo: charts, event: charts-}
- {repo: nifi, event: nifi-}
Expand Down
52 changes: 52 additions & 0 deletions docs/grpc/index.html

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

350 changes: 197 additions & 153 deletions protocol/go/kas/kas.pb.go

Large diffs are not rendered by default.

47 changes: 44 additions & 3 deletions sdk/kas_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type KASClient struct {
accessTokenSource auth.AccessTokenSource
dialOptions []grpc.DialOption
sessionKey ocrypto.KeyPair

// Set this to enable legacy, non-batch rewrap requests
supportSingleRewrapEndpoint bool
}

type kaoResult struct {
Expand All @@ -42,9 +45,10 @@ type decryptor interface {

func newKASClient(dialOptions []grpc.DialOption, accessTokenSource auth.AccessTokenSource, sessionKey ocrypto.KeyPair) *KASClient {
return &KASClient{
accessTokenSource: accessTokenSource,
dialOptions: dialOptions,
sessionKey: sessionKey,
accessTokenSource: accessTokenSource,
dialOptions: dialOptions,
sessionKey: sessionKey,
supportSingleRewrapEndpoint: true,
}
}

Expand Down Expand Up @@ -72,9 +76,38 @@ func (k *KASClient) makeRewrapRequest(ctx context.Context, requests []*kas.Unsig
return nil, fmt.Errorf("error making rewrap request: %w", err)
}

upgradeRewrapResponseV1(response, requests)

return response, nil
}

// convert v1 responses to v2
func upgradeRewrapResponseV1(response *kas.RewrapResponse, requests []*kas.UnsignedRewrapRequest_WithPolicyRequest) {
if len(response.GetResponses()) > 0 {
return
}
if len(response.GetEntityWrappedKey()) == 0 { //nolint:staticcheck // SA1019: use of deprecated method required for compatibility
return
}
if len(requests) == 0 {
return
}
response.Responses = []*kas.PolicyRewrapResult{
{
PolicyId: requests[0].GetPolicy().GetId(),
Results: []*kas.KeyAccessRewrapResult{
{
KeyAccessObjectId: requests[0].GetKeyAccessObjects()[0].GetKeyAccessObjectId(),
Status: statusPermit,
Result: &kas.KeyAccessRewrapResult_KasWrappedKey{
KasWrappedKey: response.GetEntityWrappedKey(), //nolint:staticcheck // SA1019: use of deprecated method
},
},
},
},
}
}

func (k *KASClient) nanoUnwrap(ctx context.Context, requests ...*kas.UnsignedRewrapRequest_WithPolicyRequest) (map[string][]kaoResult, error) {
keypair, err := ocrypto.NewECKeyPair(ocrypto.ECCModeSecp256r1)
if err != nil {
Expand Down Expand Up @@ -251,10 +284,18 @@ func getGRPCAddress(kasURL string) (string, error) {
}

func (k *KASClient) getRewrapRequest(reqs []*kas.UnsignedRewrapRequest_WithPolicyRequest, pubKey string) (*kas.RewrapRequest, error) {
if len(reqs) == 0 {
return nil, errors.New("no requests provided")
}
requestBody := &kas.UnsignedRewrapRequest{
ClientPublicKey: pubKey,
Requests: reqs,
}
if len(reqs) == 1 && len(reqs[0].GetKeyAccessObjects()) == 1 && k.supportSingleRewrapEndpoint {
requestBody.KeyAccess = reqs[0].GetKeyAccessObjects()[0].GetKeyAccessObject() //nolint:staticcheck // SA1019: use of deprecated method
requestBody.Policy = reqs[0].GetPolicy().GetBody() //nolint:staticcheck // SA1019: use of deprecated method
requestBody.Algorithm = reqs[0].GetAlgorithm() //nolint:staticcheck // SA1019: use of deprecated method
}

requestBodyJSON, err := protojson.Marshal(requestBody)
if err != nil {
Expand Down
53 changes: 53 additions & 0 deletions sdk/kas_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import (
"github.com/opentdf/platform/sdk/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)

type FakeAccessTokenSource struct {
dpopKey jwk.Key
asymDecryption ocrypto.AsymDecryption
asymEncryption ocrypto.AsymEncryption
accessToken string
}

Expand All @@ -36,6 +38,8 @@ func getTokenSource(t *testing.T) FakeAccessTokenSource {
dpopKey, _ := ocrypto.NewRSAKeyPair(2048)
dpopPEM, _ := dpopKey.PrivateKeyInPemFormat()
decryption, _ := ocrypto.NewAsymDecryption(dpopPEM)
dpopPEMPublic, _ := dpopKey.PublicKeyInPemFormat()
encryption, _ := ocrypto.NewAsymEncryption(dpopPEMPublic)
dpopJWK, err := jwk.ParseKey([]byte(dpopPEM), jwk.WithPEM(true))
if err != nil {
t.Fatalf("error creating JWK: %v", err)
Expand All @@ -48,6 +52,7 @@ func getTokenSource(t *testing.T) FakeAccessTokenSource {
return FakeAccessTokenSource{
dpopKey: dpopJWK,
asymDecryption: decryption,
asymEncryption: encryption,
accessToken: "thisistheaccesstoken",
}
}
Expand Down Expand Up @@ -161,3 +166,51 @@ func Test_StoreKASKeys(t *testing.T) {
assert.Nil(t, k2)
require.ErrorContains(t, err, "error making request")
}

type TestUpgradeRewrapRequestV1Suite struct {
suite.Suite
}

func (suite *TestUpgradeRewrapRequestV1Suite) TestUpgradeRewrapRequestV1_Happy() {
response := &kaspb.RewrapResponse{
EntityWrappedKey: []byte("wrappedKey"),
}
requests := []*kaspb.UnsignedRewrapRequest_WithPolicyRequest{
{
KeyAccessObjects: []*kaspb.UnsignedRewrapRequest_WithKeyAccessObject{
{
KeyAccessObjectId: "kaoID",
},
},
Policy: &kaspb.UnsignedRewrapRequest_WithPolicy{
Id: "policyID",
},
},
}

upgradeRewrapResponseV1(response, requests)

suite.Require().Len(response.GetResponses(), 1)
policyResult := response.GetResponses()[0]
suite.Equal("policyID", policyResult.GetPolicyId())

suite.Require().Len(policyResult.GetResults(), 1)
kaoResult := policyResult.GetResults()[0]

suite.Equal("kaoID", kaoResult.GetKeyAccessObjectId())
suite.NotNil(kaoResult.GetKasWrappedKey())
suite.Empty(kaoResult.GetError())
}

func (suite *TestUpgradeRewrapRequestV1Suite) TestUpgradeRewrapRequestV1_Empty() {
response := &kaspb.RewrapResponse{}
requests := []*kaspb.UnsignedRewrapRequest_WithPolicyRequest{}

upgradeRewrapResponseV1(response, requests)

suite.EqualExportedValues(&kaspb.RewrapResponse{}, response)
}

func TestUpgradeRewrapRequestV1(t *testing.T) {
suite.Run(t, new(TestUpgradeRewrapRequestV1Suite))
}
22 changes: 16 additions & 6 deletions service/kas/access/rewrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,20 @@ func extractSRTBody(ctx context.Context, headers http.Header, in *kaspb.RewrapRe
}

var requestBody kaspb.UnsignedRewrapRequest
err = protojson.Unmarshal([]byte(rbString), &requestBody)
err = protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal([]byte(rbString), &requestBody)
// if there are no requests then it could be a v1 request
if err != nil || len(requestBody.GetRequests()) == 0 {
if err != nil {
logger.WarnContext(ctx, "invalid SRT", "err.v2", err, "srt", rbString)
return nil, false, err400("invalid request body")
}
if len(requestBody.GetRequests()) == 0 {
logger.DebugContext(ctx, "legacy v1 SRT")
var errv1 error
requestBody, errv1 = extractAndConvertV1SRTBody([]byte(rbString))
if errv1 != nil {
logger.WarnContext(ctx, "invalid SRT", "err.v1", errv1, "err.v2", err)

if requestBody, errv1 = extractAndConvertV1SRTBody([]byte(rbString)); errv1 != nil {
logger.WarnContext(ctx, "invalid SRT", "err.v1", errv1, "srt", rbString, "rewrap.body", requestBody.String())
return nil, false, err400("invalid request body")
}
logger.DebugContext(ctx, "legacy v1 SRT", "err.v2", err)
isV1 = true
}
logger.DebugContext(ctx, "extracted request body", slog.String("rewrap.body", requestBody.String()), slog.Any("rewrap.srt", rbString))
Expand Down Expand Up @@ -289,9 +293,15 @@ func verifyPolicyBinding(ctx context.Context, policy []byte, kao *kaspb.Unsigned
func extractPolicyBinding(policyBinding interface{}) (string, error) {
switch v := policyBinding.(type) {
case string:
if v == "" {
return "", fmt.Errorf("empty policy binding")
}
return v, nil
case map[string]interface{}:
if hash, ok := v["hash"].(string); ok {
if hash == "" {
return "", fmt.Errorf("empty policy binding hash field")
}
return hash, nil
}
return "", fmt.Errorf("invalid policy binding object, missing 'hash' field")
Expand Down
15 changes: 11 additions & 4 deletions service/kas/kas.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ message LegacyPublicKeyRequest {

message PolicyBinding {
string algorithm = 1 [json_name = "alg"];
string hash = 2;
string hash = 2;
}

message KeyAccess {
Expand All @@ -45,7 +45,7 @@ message KeyAccess {
string kid = 6;
string split_id = 7 [json_name = "sid"];
bytes wrapped_key = 8;
// header is only used for NanoTDFs
// header is only used for NanoTDFs
bytes header = 9;

// For wrapping with an ECDH derived key, when type=ec-wrapped
Expand All @@ -70,6 +70,13 @@ message UnsignedRewrapRequest {

string client_public_key = 1;
repeated WithPolicyRequest requests = 2;

// Used for legacy non-bulk requests
KeyAccess key_access = 3 [deprecated = true];
// Used for legacy non-bulk requests
string policy = 4 [deprecated = true];
// Used for legacy non-bulk requests
string algorithm = 5 [deprecated = true];
}
message PublicKeyRequest {
string algorithm = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "algorithm type rsa:<keysize> or ec:<curvename>"}];
Expand Down Expand Up @@ -109,8 +116,8 @@ message RewrapResponse {
bytes entity_wrapped_key = 2 [deprecated = true];
string session_public_key = 3;
string schema_version = 4 [deprecated = true];
// New Rewrap API changes
repeated PolicyRewrapResult responses = 5;
// New Rewrap API changes
repeated PolicyRewrapResult responses = 5;
}

// Get app info from the root path
Expand Down

0 comments on commit b695e66

Please sign in to comment.