Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions go.work.sum

This file was deleted.

48 changes: 48 additions & 0 deletions sdk/auth_config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package sdk

import (
"context"
"encoding/json"
"fmt"
"github.com/opentdf/opentdf-v2-poc/internal/crypto"
"io"
"net/http"
"net/url"
"strings"
)

type AuthConfig struct {
Expand Down Expand Up @@ -30,3 +36,45 @@ func NewAuthConfig() (*AuthConfig, error) {

return &AuthConfig{signingPublicKey: publicKey, signingPrivateKey: privateKey}, nil
}

func NewOIDCAuthConfig(ctx context.Context, host, realm, clientId, clientSecret, subjectToken string) (*AuthConfig, error) {
authConfig, err := NewAuthConfig()
if err != nil {
return nil, err
}

authConfig.authToken, err = authConfig.fetchOIDCAccessToken(ctx, host, realm, clientId, clientSecret, subjectToken)
if err != nil {
return nil, fmt.Errorf("Failed to fetch acces token:%w", err)
}
return authConfig, nil
}
func (a *AuthConfig) fetchOIDCAccessToken(ctx context.Context, host, realm, clientId, clientSecret, subjectToken string) (string, error) {
data := url.Values{"grant_type": {"urn:ietf:params:oauth:grant-type:token-exchange"}, "client_id": {clientId}, "client_secret": {clientSecret}, "subject_token": {subjectToken}, "requested_token_type": {"urn:ietf:params:oauth:token-type:access_token"}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you run go fmt on this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did... i wasnt sure if i shouldve done it manually, as it slightly annoyed me, but i left it as is.


body := strings.NewReader(data.Encode())
kcURL := fmt.Sprintf("%s/auth/realms/%s/protocol/openid-connect/token", host, realm)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, kcURL, body)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

certB64 := crypto.Base64Encode([]byte(a.signingPublicKey))
req.Header.Set("X-VirtruPubKey", string(certB64))

client := &http.Client{}
resp, err := client.Do(req)
type keycloakResponsePayload struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
}
keyResp := keycloakResponsePayload{}
respBody, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(respBody, &keyResp)
if err != nil {
return "", err
}
return "Bearer " + keyResp.AccessToken, nil
}
47 changes: 47 additions & 0 deletions sdk/auth_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package sdk

import (
"context"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)

func TestNewOIDCAuthConfig(t *testing.T) {
expectedAccessToken := "Bearer fail"
clientId := "idk"
clientSecret := "secret password"
subjectToken := "token"
realm := "tdf"
urlVals := url.Values{"grant_type": {"urn:ietf:params:oauth:grant-type:token-exchange"}, "client_id": {clientId}, "client_secret": {clientSecret}, "subject_token": {subjectToken}, "requested_token_type": {"urn:ietf:params:oauth:token-type:access_token"}}
expectedBody := urlVals.Encode()

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
body, err := io.ReadAll(req.Body)
if "" == req.Header.Get("X-VirtruPubKey") || err != nil || string(body) != expectedBody {
w.WriteHeader(400)
return
}

_, _ = w.Write([]byte("{\"access_token\": \"fail\", \"token_type\": \"ok\"}"))
w.WriteHeader(200)
}),
)
defer s.Close()
u, _ := url.Parse(s.URL)
host, port, _ := net.SplitHostPort(u.Host)

authConfig, err := NewOIDCAuthConfig(context.TODO(), "http://"+host+":"+port, realm, clientId, clientSecret, subjectToken)

if err != nil {
t.Fatalf("authconfig failed: %v", err)
}

if authConfig.authToken != expectedAccessToken {
t.Fatalf("Auth token expected %s recived %s", expectedAccessToken, authConfig.authToken)
}
}
1 change: 1 addition & 0 deletions sdk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
)

require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions sdk/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231115204500-e097f827e652.2 h1:iEPA5SBtdLJNwQis/SrcCuDWJh5E1V0mVO4Ih7/mRbg=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231115204500-e097f827e652.2/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
Expand Down
9 changes: 7 additions & 2 deletions sdk/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type KeyAccess struct {
Protocol string `json:"protocol"`
WrappedKey string `json:"wrappedKey"`
PolicyBinding string `json:"policyBinding"`
EncryptedMetadata string `json:"encryptedMetadata"`
EncryptedMetadata string `json:"encryptedMetadata,omitempty"`
}

type Method struct {
Expand Down Expand Up @@ -69,5 +69,10 @@ type policyObject struct {
Body struct {
DataAttributes []attributeObject `json:"dataAttributes"`
Dissem []string `json:"dissem"`
}
} `json:"body"`
}

type EncryptedMetadata struct {
Cipher string `json:"ciphertext"`
Iv string `json:"iv"`
}
100 changes: 63 additions & 37 deletions sdk/split_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ type tdfKeyAccess struct {
metaData string
}

type RequestBody struct {
KeyAccess `json:"keyAccess"`
ClientPublicKey string `json:"clientPublicKey"`
Policy string `json:"policy"`
}

var (
errInvalidKasInfo = errors.New("split-key: kas information is missing")
errKasPubKeyMissing = errors.New("split-key: kas public key is missing")
Expand Down Expand Up @@ -117,21 +123,15 @@ func newSplitKeyFromManifest(authConfig AuthConfig, manifest Manifest) (splitKey
sKey := splitKey{}

for _, keyAccessObj := range manifest.EncryptionInformation.KeyAccessObjs {
keyAccessAsMap, err := structToMap(keyAccessObj)
if err != nil {
return splitKey{}, fmt.Errorf("fail to convert key access object to map:%w", err)
}

keyAccessAsMap[kPolicy] = manifest.EncryptionInformation.Policy
key, err := sKey.rewrap(authConfig, keyAccessAsMap)
requestBody := RequestBody{keyAccessObj, "", manifest.EncryptionInformation.Policy}
key, err := sKey.rewrap(authConfig, &requestBody)
if err != nil {
return splitKey{}, fmt.Errorf(" splitKey.rewrap failed:%w", err)
}

for keyByteIndex, keyByte := range key {
sKey.key[keyByteIndex] ^= keyByte
}

keyAccess := tdfKeyAccess{}
keyAccess.kasURL = keyAccessObj.KasURL
keyAccess.wrappedKey = [32]byte(key)
Expand All @@ -146,8 +146,15 @@ func newSplitKeyFromManifest(authConfig AuthConfig, manifest Manifest) (splitKey
if err != nil {
return splitKey{}, fmt.Errorf("crypto.Base64Decode failed:%w", err)
}
metadata := EncryptedMetadata{}
err = json.Unmarshal(decodedMetaData, &metadata)
if err != nil {
return splitKey{}, fmt.Errorf("json.Unmarshal failed:%w", err)

metaData, err := gcm.Decrypt(decodedMetaData)
}
encodedCipherText := metadata.Cipher
cipherText, _ := crypto.Base64Decode([]byte(encodedCipherText))
metaData, err := gcm.Decrypt(cipherText)
if err != nil {
return splitKey{}, fmt.Errorf("crypto.AesGcm.encrypt failed:%w", err)
}
Expand Down Expand Up @@ -218,15 +225,23 @@ func (splitKey splitKey) getManifest() (*Manifest, error) {
return nil, fmt.Errorf("crypto.AesGcm.encrypt failed:%w", err)
}

keyAccess.EncryptedMetadata = string(crypto.Base64Encode(encryptedMetaData))
iv := encryptedMetaData[:crypto.GcmStandardNonceSize]
metadata := EncryptedMetadata{Cipher: string(crypto.Base64Encode(encryptedMetaData)), Iv: string(crypto.Base64Encode(iv))}

metadataJson, err := json.Marshal(metadata)
if err != nil {
return nil, fmt.Errorf(" json.Marshal failed:%w", err)

}

keyAccess.EncryptedMetadata = string(crypto.Base64Encode(metadataJson))
}

manifest.EncryptionInformation.KeyAccessObjs = append(manifest.EncryptionInformation.KeyAccessObjs, keyAccess)
}

manifest.EncryptionInformation.Policy = string(base64PolicyObject)
manifest.EncryptionInformation.Method.Algorithm = kGCMCipherAlgorithm

return &manifest, nil
}

Expand Down Expand Up @@ -307,34 +322,16 @@ func (splitKey splitKey) createPolicyObject() (policyObject, error) {
attributeObj := attributeObject{}
attributeObj.Attribute = attribute
policyObj.Body.DataAttributes = append(policyObj.Body.DataAttributes, attributeObj)
policyObj.Body.Dissem = make([]string, 0)
}

return policyObj, nil
}

func (splitKey splitKey) rewrap(authConfig AuthConfig, requestBody map[string]interface{}) ([]byte, error) {
kasURL, ok := requestBody[kKasURL]
if !ok {
return nil, fmt.Errorf("kas url is missing in key access object")
}

clientKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize)
if err != nil {
return nil, fmt.Errorf("crypto.NewRSAKeyPair failed: %w", err)
}
func (splitKey splitKey) handleKasRequest(kasPath string, body *RequestBody, authConfig AuthConfig) (*http.Response, error) {
kasURL := body.KasURL

clientPubKey, err := clientKeyPair.PublicKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err)
}

clientPrivateKey, err := clientKeyPair.PrivateKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("crypto.PrivateKeyInPemFormat failed: %w", err)
}

requestBody[kClientPublicKey] = clientPubKey
requestBodyData, err := json.Marshal(requestBody)
requestBodyData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("json.Marshal failed: %w", err)
}
Expand Down Expand Up @@ -365,12 +362,11 @@ func (splitKey splitKey) rewrap(authConfig AuthConfig, requestBody map[string]in
return nil, fmt.Errorf("json.Marshal failed: %w", err)
}

kasRewrapURL, err := url.JoinPath(fmt.Sprintf("%v", kasURL), kRewrapV2)
kasRequestURL, err := url.JoinPath(fmt.Sprintf("%v", kasURL), kasPath)
if err != nil {
return nil, fmt.Errorf("url.JoinPath failed: %w", err)
}

request, err := http.NewRequestWithContext(context.Background(), http.MethodPost, kasRewrapURL,
request, err := http.NewRequestWithContext(context.Background(), http.MethodPost, kasRequestURL,
bytes.NewBuffer(signedTokenRequestBody))
if err != nil {
return nil, fmt.Errorf("http.NewRequestWithContext failed: %w", err)
Expand All @@ -386,8 +382,38 @@ func (splitKey splitKey) rewrap(authConfig AuthConfig, requestBody map[string]in
client := &http.Client{}

response, err := client.Do(request)
if err != nil {
slog.Error("failed http request")
return nil, err
}

return response, nil
}
func (splitKey splitKey) rewrap(authConfig AuthConfig, requestBody *RequestBody) ([]byte, error) {

clientKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize)
if err != nil {
return nil, fmt.Errorf("crypto.NewRSAKeyPair failed: %w", err)
}

clientPubKey, err := clientKeyPair.PublicKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err)
}
requestBody.ClientPublicKey = clientPubKey

clientPrivateKey, err := clientKeyPair.PrivateKeyInPemFormat()
if err != nil {
return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err)
}

response, err := splitKey.handleKasRequest(kRewrapV2, requestBody, authConfig)
if err != nil {
slog.Error("failed http request")
return nil, err
}
if response.StatusCode != kHTTPOk {
return nil, fmt.Errorf("%s failed status code:%d", kasRewrapURL, response.StatusCode)
return nil, fmt.Errorf("http request failed status code:%d", response.StatusCode)
}

defer func(Body io.ReadCloser) {
Expand Down
8 changes: 4 additions & 4 deletions sdk/split_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,13 @@ func TestNewSplitKeyFromManifest(t *testing.T) {
} else {
t.Fatalf("unknown claims type, cannot proceed")
}

err = json.Unmarshal([]byte(rewrapRequest), &data)
reqBody := RequestBody{}
err = json.Unmarshal([]byte(rewrapRequest), &reqBody)
if err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}

wrappedKey, err := crypto.Base64Decode([]byte(data["wrappedKey"]))
wrappedKey, err := crypto.Base64Decode([]byte(reqBody.WrappedKey))
if err != nil {
t.Fatalf("crypto.Base64Decode failed: %v", err)
}
Expand All @@ -207,7 +207,7 @@ func TestNewSplitKeyFromManifest(t *testing.T) {
t.Fatalf("crypto.Decrypt failed: %v", err)
}

asymEncrypt, err := crypto.NewAsymEncryption(data[kClientPublicKey])
asymEncrypt, err := crypto.NewAsymEncryption(reqBody.ClientPublicKey)
if err != nil {
t.Fatalf("crypto.NewAsymEncryption failed: %v", err)
}
Expand Down
4 changes: 4 additions & 0 deletions sdk/tdf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ func NewTDFConfig() (*TDFConfig, error) {
}, nil
}

func NewKasInfo(url string) KASInfo {
return KASInfo{url: url}
}

// AddKasInformation Add all the kas urls and their corresponding public keys
// that is required to create and read the tdf.
func (tdfConfig *TDFConfig) AddKasInformation(kasInfoList []KASInfo) error {
Expand Down
Loading