Skip to content

Commit b695e66

Browse files
authored
Merge branch 'main' into feature/ecc-wrappper
2 parents f2814e3 + 9ca042d commit b695e66

File tree

9 files changed

+378
-172
lines changed

9 files changed

+378
-172
lines changed

.github/workflows/nightly-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
password: ${{ steps.gcp-auth.outputs.access_token }}
4040

4141
- id: docker_meta
42-
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
42+
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
4343
with:
4444
images: ${{ secrets.DOCKER_REPO }}
4545
tags: |

.github/workflows/release-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
password: ${{ steps.gcp-auth.outputs.access_token }}
4646

4747
- id: docker_meta
48-
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
48+
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
4949
with:
5050
images: ${{ secrets.DOCKER_REPO }}
5151
tags: |

.github/workflows/traffic.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ jobs:
1313
matrix:
1414
repo-values:
1515
- {repo: platform, event: ""}
16-
- {repo: otdfctl, event: backend-}
17-
- {repo: spec, event: frontend-}
16+
- {repo: otdfctl, event: otdfctl-}
17+
- {repo: spec, event: spec-}
1818
- {repo: tests, event: tests-}
19-
- {repo: client-web, event: clientweb-}
20-
- {repo: client-cpp, event: cpp-sdk-}
19+
- {repo: web-sdk, event: web-sdk-}
2120
- {repo: java-sdk, event: java-sdk-}
2221
- {repo: charts, event: charts-}
2322
- {repo: nifi, event: nifi-}

docs/grpc/index.html

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protocol/go/kas/kas.pb.go

Lines changed: 197 additions & 153 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/kas_client.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type KASClient struct {
2727
accessTokenSource auth.AccessTokenSource
2828
dialOptions []grpc.DialOption
2929
sessionKey ocrypto.KeyPair
30+
31+
// Set this to enable legacy, non-batch rewrap requests
32+
supportSingleRewrapEndpoint bool
3033
}
3134

3235
type kaoResult struct {
@@ -42,9 +45,10 @@ type decryptor interface {
4245

4346
func newKASClient(dialOptions []grpc.DialOption, accessTokenSource auth.AccessTokenSource, sessionKey ocrypto.KeyPair) *KASClient {
4447
return &KASClient{
45-
accessTokenSource: accessTokenSource,
46-
dialOptions: dialOptions,
47-
sessionKey: sessionKey,
48+
accessTokenSource: accessTokenSource,
49+
dialOptions: dialOptions,
50+
sessionKey: sessionKey,
51+
supportSingleRewrapEndpoint: true,
4852
}
4953
}
5054

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

79+
upgradeRewrapResponseV1(response, requests)
80+
7581
return response, nil
7682
}
7783

84+
// convert v1 responses to v2
85+
func upgradeRewrapResponseV1(response *kas.RewrapResponse, requests []*kas.UnsignedRewrapRequest_WithPolicyRequest) {
86+
if len(response.GetResponses()) > 0 {
87+
return
88+
}
89+
if len(response.GetEntityWrappedKey()) == 0 { //nolint:staticcheck // SA1019: use of deprecated method required for compatibility
90+
return
91+
}
92+
if len(requests) == 0 {
93+
return
94+
}
95+
response.Responses = []*kas.PolicyRewrapResult{
96+
{
97+
PolicyId: requests[0].GetPolicy().GetId(),
98+
Results: []*kas.KeyAccessRewrapResult{
99+
{
100+
KeyAccessObjectId: requests[0].GetKeyAccessObjects()[0].GetKeyAccessObjectId(),
101+
Status: statusPermit,
102+
Result: &kas.KeyAccessRewrapResult_KasWrappedKey{
103+
KasWrappedKey: response.GetEntityWrappedKey(), //nolint:staticcheck // SA1019: use of deprecated method
104+
},
105+
},
106+
},
107+
},
108+
}
109+
}
110+
78111
func (k *KASClient) nanoUnwrap(ctx context.Context, requests ...*kas.UnsignedRewrapRequest_WithPolicyRequest) (map[string][]kaoResult, error) {
79112
keypair, err := ocrypto.NewECKeyPair(ocrypto.ECCModeSecp256r1)
80113
if err != nil {
@@ -251,10 +284,18 @@ func getGRPCAddress(kasURL string) (string, error) {
251284
}
252285

253286
func (k *KASClient) getRewrapRequest(reqs []*kas.UnsignedRewrapRequest_WithPolicyRequest, pubKey string) (*kas.RewrapRequest, error) {
287+
if len(reqs) == 0 {
288+
return nil, errors.New("no requests provided")
289+
}
254290
requestBody := &kas.UnsignedRewrapRequest{
255291
ClientPublicKey: pubKey,
256292
Requests: reqs,
257293
}
294+
if len(reqs) == 1 && len(reqs[0].GetKeyAccessObjects()) == 1 && k.supportSingleRewrapEndpoint {
295+
requestBody.KeyAccess = reqs[0].GetKeyAccessObjects()[0].GetKeyAccessObject() //nolint:staticcheck // SA1019: use of deprecated method
296+
requestBody.Policy = reqs[0].GetPolicy().GetBody() //nolint:staticcheck // SA1019: use of deprecated method
297+
requestBody.Algorithm = reqs[0].GetAlgorithm() //nolint:staticcheck // SA1019: use of deprecated method
298+
}
258299

259300
requestBodyJSON, err := protojson.Marshal(requestBody)
260301
if err != nil {

sdk/kas_client_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import (
1414
"github.com/opentdf/platform/sdk/auth"
1515
"github.com/stretchr/testify/assert"
1616
"github.com/stretchr/testify/require"
17+
"github.com/stretchr/testify/suite"
1718
"google.golang.org/grpc"
1819
"google.golang.org/protobuf/encoding/protojson"
1920
)
2021

2122
type FakeAccessTokenSource struct {
2223
dpopKey jwk.Key
2324
asymDecryption ocrypto.AsymDecryption
25+
asymEncryption ocrypto.AsymEncryption
2426
accessToken string
2527
}
2628

@@ -36,6 +38,8 @@ func getTokenSource(t *testing.T) FakeAccessTokenSource {
3638
dpopKey, _ := ocrypto.NewRSAKeyPair(2048)
3739
dpopPEM, _ := dpopKey.PrivateKeyInPemFormat()
3840
decryption, _ := ocrypto.NewAsymDecryption(dpopPEM)
41+
dpopPEMPublic, _ := dpopKey.PublicKeyInPemFormat()
42+
encryption, _ := ocrypto.NewAsymEncryption(dpopPEMPublic)
3943
dpopJWK, err := jwk.ParseKey([]byte(dpopPEM), jwk.WithPEM(true))
4044
if err != nil {
4145
t.Fatalf("error creating JWK: %v", err)
@@ -48,6 +52,7 @@ func getTokenSource(t *testing.T) FakeAccessTokenSource {
4852
return FakeAccessTokenSource{
4953
dpopKey: dpopJWK,
5054
asymDecryption: decryption,
55+
asymEncryption: encryption,
5156
accessToken: "thisistheaccesstoken",
5257
}
5358
}
@@ -161,3 +166,51 @@ func Test_StoreKASKeys(t *testing.T) {
161166
assert.Nil(t, k2)
162167
require.ErrorContains(t, err, "error making request")
163168
}
169+
170+
type TestUpgradeRewrapRequestV1Suite struct {
171+
suite.Suite
172+
}
173+
174+
func (suite *TestUpgradeRewrapRequestV1Suite) TestUpgradeRewrapRequestV1_Happy() {
175+
response := &kaspb.RewrapResponse{
176+
EntityWrappedKey: []byte("wrappedKey"),
177+
}
178+
requests := []*kaspb.UnsignedRewrapRequest_WithPolicyRequest{
179+
{
180+
KeyAccessObjects: []*kaspb.UnsignedRewrapRequest_WithKeyAccessObject{
181+
{
182+
KeyAccessObjectId: "kaoID",
183+
},
184+
},
185+
Policy: &kaspb.UnsignedRewrapRequest_WithPolicy{
186+
Id: "policyID",
187+
},
188+
},
189+
}
190+
191+
upgradeRewrapResponseV1(response, requests)
192+
193+
suite.Require().Len(response.GetResponses(), 1)
194+
policyResult := response.GetResponses()[0]
195+
suite.Equal("policyID", policyResult.GetPolicyId())
196+
197+
suite.Require().Len(policyResult.GetResults(), 1)
198+
kaoResult := policyResult.GetResults()[0]
199+
200+
suite.Equal("kaoID", kaoResult.GetKeyAccessObjectId())
201+
suite.NotNil(kaoResult.GetKasWrappedKey())
202+
suite.Empty(kaoResult.GetError())
203+
}
204+
205+
func (suite *TestUpgradeRewrapRequestV1Suite) TestUpgradeRewrapRequestV1_Empty() {
206+
response := &kaspb.RewrapResponse{}
207+
requests := []*kaspb.UnsignedRewrapRequest_WithPolicyRequest{}
208+
209+
upgradeRewrapResponseV1(response, requests)
210+
211+
suite.EqualExportedValues(&kaspb.RewrapResponse{}, response)
212+
}
213+
214+
func TestUpgradeRewrapRequestV1(t *testing.T) {
215+
suite.Run(t, new(TestUpgradeRewrapRequestV1Suite))
216+
}

service/kas/access/rewrap.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,20 @@ func extractSRTBody(ctx context.Context, headers http.Header, in *kaspb.RewrapRe
222222
}
223223

224224
var requestBody kaspb.UnsignedRewrapRequest
225-
err = protojson.Unmarshal([]byte(rbString), &requestBody)
225+
err = protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal([]byte(rbString), &requestBody)
226226
// if there are no requests then it could be a v1 request
227-
if err != nil || len(requestBody.GetRequests()) == 0 {
227+
if err != nil {
228+
logger.WarnContext(ctx, "invalid SRT", "err.v2", err, "srt", rbString)
229+
return nil, false, err400("invalid request body")
230+
}
231+
if len(requestBody.GetRequests()) == 0 {
232+
logger.DebugContext(ctx, "legacy v1 SRT")
228233
var errv1 error
229-
requestBody, errv1 = extractAndConvertV1SRTBody([]byte(rbString))
230-
if errv1 != nil {
231-
logger.WarnContext(ctx, "invalid SRT", "err.v1", errv1, "err.v2", err)
234+
235+
if requestBody, errv1 = extractAndConvertV1SRTBody([]byte(rbString)); errv1 != nil {
236+
logger.WarnContext(ctx, "invalid SRT", "err.v1", errv1, "srt", rbString, "rewrap.body", requestBody.String())
232237
return nil, false, err400("invalid request body")
233238
}
234-
logger.DebugContext(ctx, "legacy v1 SRT", "err.v2", err)
235239
isV1 = true
236240
}
237241
logger.DebugContext(ctx, "extracted request body", slog.String("rewrap.body", requestBody.String()), slog.Any("rewrap.srt", rbString))
@@ -289,9 +293,15 @@ func verifyPolicyBinding(ctx context.Context, policy []byte, kao *kaspb.Unsigned
289293
func extractPolicyBinding(policyBinding interface{}) (string, error) {
290294
switch v := policyBinding.(type) {
291295
case string:
296+
if v == "" {
297+
return "", fmt.Errorf("empty policy binding")
298+
}
292299
return v, nil
293300
case map[string]interface{}:
294301
if hash, ok := v["hash"].(string); ok {
302+
if hash == "" {
303+
return "", fmt.Errorf("empty policy binding hash field")
304+
}
295305
return hash, nil
296306
}
297307
return "", fmt.Errorf("invalid policy binding object, missing 'hash' field")

service/kas/kas.proto

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ message LegacyPublicKeyRequest {
3333

3434
message PolicyBinding {
3535
string algorithm = 1 [json_name = "alg"];
36-
string hash = 2;
36+
string hash = 2;
3737
}
3838

3939
message KeyAccess {
@@ -45,7 +45,7 @@ message KeyAccess {
4545
string kid = 6;
4646
string split_id = 7 [json_name = "sid"];
4747
bytes wrapped_key = 8;
48-
// header is only used for NanoTDFs
48+
// header is only used for NanoTDFs
4949
bytes header = 9;
5050

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

7171
string client_public_key = 1;
7272
repeated WithPolicyRequest requests = 2;
73+
74+
// Used for legacy non-bulk requests
75+
KeyAccess key_access = 3 [deprecated = true];
76+
// Used for legacy non-bulk requests
77+
string policy = 4 [deprecated = true];
78+
// Used for legacy non-bulk requests
79+
string algorithm = 5 [deprecated = true];
7380
}
7481
message PublicKeyRequest {
7582
string algorithm = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "algorithm type rsa:<keysize> or ec:<curvename>"}];
@@ -109,8 +116,8 @@ message RewrapResponse {
109116
bytes entity_wrapped_key = 2 [deprecated = true];
110117
string session_public_key = 3;
111118
string schema_version = 4 [deprecated = true];
112-
// New Rewrap API changes
113-
repeated PolicyRewrapResult responses = 5;
119+
// New Rewrap API changes
120+
repeated PolicyRewrapResult responses = 5;
114121
}
115122

116123
// Get app info from the root path

0 commit comments

Comments
 (0)