-
Notifications
You must be signed in to change notification settings - Fork 24
feat(policy): Add platform key indexer. #2189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
6413d34
feat(policy): Add platform key indexer.
c-r33d a115efb
Merge branch 'main' into feat/DSPX-1062-key-indexer
c-r33d ced0307
Merge branch 'main' into feat/DSPX-1062-key-indexer
c-r33d ed2a294
refactor.
c-r33d f715b84
move to pkg dir and fix linting.
c-r33d 4b2e545
Merge branch 'feat/DSPX-1062-key-indexer' of github.com:opentdf/platf…
c-r33d 6fbd291
rename pkg.
c-r33d ca1b8e7
move platform key indexer and return better errors.
c-r33d 4fdd9ba
better comment.
c-r33d d55ad35
refactor.
c-r33d d1eedf3
Merge branch 'main' into feat/DSPX-1062-key-indexer
c-r33d eb1d7b7
refactor.
c-r33d File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| package trust | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/rsa" | ||
| "crypto/x509" | ||
| "encoding/base64" | ||
| "encoding/json" | ||
| "encoding/pem" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/lestrrat-go/jwx/v2/jwk" | ||
| "github.com/opentdf/platform/protocol/go/policy" | ||
| "github.com/opentdf/platform/protocol/go/policy/kasregistry" | ||
| "github.com/opentdf/platform/sdk" | ||
| "github.com/opentdf/platform/service/logger" | ||
| "github.com/opentdf/platform/service/trust" | ||
| ) | ||
|
|
||
| var ErrNoActiveKeyForAlgorithm = errors.New("no active key found for specified algorithm") | ||
|
|
||
| // Used for reaching out to platform to get keys | ||
| type PlatformKeyIndexer struct { | ||
| // KeyIndex is the key index used to manage keys | ||
| trust.KeyIndex | ||
c-r33d marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // SDK is the SDK instance used to interact with the platform | ||
| sdk *sdk.SDK | ||
| // KasURI | ||
| kasURI string | ||
| // Logger is the logger instance used for logging | ||
| log *logger.Logger | ||
| } | ||
|
|
||
| // platformKeyAdapter is an adapter for KeyDetails, where keys come from the platform | ||
| type KasKeyAdapter struct { | ||
| key *policy.KasKey | ||
| log *logger.Logger | ||
| } | ||
|
|
||
| func NewPlatformKeyIndexer(sdk *sdk.SDK, kasURI string, l *logger.Logger) *PlatformKeyIndexer { | ||
| return &PlatformKeyIndexer{ | ||
| sdk: sdk, | ||
| kasURI: kasURI, | ||
| log: l, | ||
| } | ||
| } | ||
|
|
||
| func convertAlgToEnum(alg string) (policy.Algorithm, error) { | ||
| switch alg { | ||
| case "rsa:2048": | ||
| return policy.Algorithm_ALGORITHM_RSA_2048, nil | ||
| case "rsa:4096": | ||
| return policy.Algorithm_ALGORITHM_RSA_4096, nil | ||
| case "ec:secp256r1": | ||
| return policy.Algorithm_ALGORITHM_EC_P256, nil | ||
| case "ec:secp384r1": | ||
| return policy.Algorithm_ALGORITHM_EC_P384, nil | ||
| case "ec:secp521r1": | ||
| return policy.Algorithm_ALGORITHM_EC_P521, nil | ||
| default: | ||
| return policy.Algorithm_ALGORITHM_UNSPECIFIED, errors.New("unsupported algorithm") | ||
c-r33d marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| func (p *PlatformKeyIndexer) FindKeyByAlgorithm(ctx context.Context, algorithm string, _ bool) (trust.KeyDetails, error) { | ||
| alg, err := convertAlgToEnum(algorithm) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| req := &kasregistry.ListKeysRequest{ | ||
| KeyAlgorithm: alg, | ||
| KasFilter: &kasregistry.ListKeysRequest_KasUri{ | ||
| KasUri: p.kasURI, | ||
| }, | ||
| } | ||
| resp, err := p.sdk.KeyAccessServerRegistry.ListKeys(ctx, req) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Find active key. | ||
| var activeKey *policy.KasKey | ||
| for _, key := range resp.GetKasKeys() { | ||
| if key.GetKey().GetKeyStatus() == policy.KeyStatus_KEY_STATUS_ACTIVE { | ||
| activeKey = key | ||
| break | ||
| } | ||
| } | ||
| if activeKey == nil { | ||
| return nil, ErrNoActiveKeyForAlgorithm | ||
| } | ||
|
|
||
| return &KasKeyAdapter{ | ||
| key: activeKey, | ||
| log: p.log, | ||
| }, nil | ||
| } | ||
|
|
||
| func (p *PlatformKeyIndexer) FindKeyByID(ctx context.Context, id trust.KeyIdentifier) (trust.KeyDetails, error) { | ||
| req := &kasregistry.GetKeyRequest{ | ||
| Identifier: &kasregistry.GetKeyRequest_Key{ | ||
| Key: &kasregistry.KasKeyIdentifier{ | ||
| Identifier: &kasregistry.KasKeyIdentifier_Uri{ | ||
| Uri: p.kasURI, | ||
| }, | ||
| Kid: string(id), | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| resp, err := p.sdk.KeyAccessServerRegistry.GetKey(ctx, req) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &KasKeyAdapter{ | ||
| key: resp.GetKasKey(), | ||
| log: p.log, | ||
| }, nil | ||
| } | ||
|
|
||
| func (p *PlatformKeyIndexer) ListKeys(ctx context.Context) ([]trust.KeyDetails, error) { | ||
| req := &kasregistry.ListKeysRequest{ | ||
| KasFilter: &kasregistry.ListKeysRequest_KasUri{ | ||
| KasUri: p.kasURI, | ||
| }, | ||
| } | ||
| resp, err := p.sdk.KeyAccessServerRegistry.ListKeys(ctx, req) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| keys := make([]trust.KeyDetails, len(resp.GetKasKeys())) | ||
| for i, key := range resp.GetKasKeys() { | ||
| keys[i] = &KasKeyAdapter{ | ||
| key: key, | ||
| log: p.log, | ||
| } | ||
| } | ||
|
|
||
| return keys, nil | ||
| } | ||
|
|
||
| func (p *KasKeyAdapter) ID() trust.KeyIdentifier { | ||
| return trust.KeyIdentifier(p.key.GetKey().GetKeyId()) | ||
| } | ||
|
|
||
| // Might need to convert this to a standard format | ||
| func (p *KasKeyAdapter) Algorithm() string { | ||
| return p.key.GetKey().GetKeyAlgorithm().String() | ||
| } | ||
|
|
||
| func (p *KasKeyAdapter) IsLegacy() bool { | ||
| return false | ||
| } | ||
|
|
||
| // This will point to the correct "manager" | ||
| func (p *KasKeyAdapter) System() string { | ||
| var mode string | ||
| if p.key.GetKey().GetProviderConfig() != nil { | ||
| mode = p.key.GetKey().GetProviderConfig().GetName() | ||
| } | ||
| return mode | ||
| } | ||
|
|
||
| func pemToPublicKey(publicPEM string) (*rsa.PublicKey, error) { | ||
| // Decode the PEM data | ||
| block, _ := pem.Decode([]byte(publicPEM)) | ||
| if block == nil || block.Type != "PUBLIC KEY" { | ||
| return nil, errors.New("failed to decode PEM block or incorrect PEM type") | ||
| } | ||
|
|
||
| // Parse the public key | ||
| pub, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse public key: %w", err) | ||
| } | ||
|
|
||
| // Assert type and return | ||
| rsaPub, ok := pub.(*rsa.PublicKey) | ||
| if !ok { | ||
| return nil, errors.New("not an RSA public key") | ||
| } | ||
|
|
||
| return rsaPub, nil | ||
| } | ||
|
|
||
| // Repurpose of the StandardCrypto function | ||
| func rsaPublicKeyAsJSON(_ context.Context, publicPEM string) (string, error) { | ||
| pubKey, err := pemToPublicKey(publicPEM) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| rsaPublicKeyJwk, err := jwk.FromRaw(pubKey) | ||
| if err != nil { | ||
| return "", fmt.Errorf("jwk.FromRaw: %w", err) | ||
| } | ||
|
|
||
| // Convert the public key to JSON format | ||
| pubKeyJSON, err := json.Marshal(rsaPublicKeyJwk) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return string(pubKeyJSON), nil | ||
| } | ||
|
|
||
| // Repurpose of the StandardCrypto function | ||
| func convertPEMToJWK(_ string) (string, error) { | ||
| return "", errors.New("convertPEMToJWK function is not implemented") | ||
| } | ||
|
|
||
| func (p *KasKeyAdapter) ExportPublicKey(ctx context.Context, format trust.KeyType) (string, error) { | ||
| publicKeyCtx := p.key.GetKey().GetPublicKeyCtx() | ||
|
|
||
| // Decode the base64-encoded public key | ||
| decodedPubKey, err := base64.StdEncoding.DecodeString(publicKeyCtx.GetPem()) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| switch format { | ||
| case trust.KeyTypeJWK: | ||
| // For JWK format (currently only supported for RSA) | ||
| if p.key.GetKey().GetKeyAlgorithm() == policy.Algorithm_ALGORITHM_RSA_2048 || | ||
| p.key.GetKey().GetKeyAlgorithm() == policy.Algorithm_ALGORITHM_RSA_4096 { | ||
| return rsaPublicKeyAsJSON(ctx, string(decodedPubKey)) | ||
| } | ||
| // For EC keys, we return the public key in PEM format | ||
| jwkKey, err := convertPEMToJWK(string(decodedPubKey)) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return jwkKey, nil | ||
| case trust.KeyTypePKCS8: | ||
| return string(decodedPubKey), nil | ||
| default: | ||
| return "", errors.New("unsupported key type") | ||
| } | ||
| } | ||
|
|
||
| func (p *KasKeyAdapter) ExportCertificate(_ context.Context) (string, error) { | ||
| return "", errors.New("not implemented") | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package trust | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/lestrrat-go/jwx/v2/jwk" | ||
| "github.com/opentdf/platform/lib/ocrypto" | ||
| "github.com/opentdf/platform/protocol/go/policy" | ||
| "github.com/opentdf/platform/service/trust" | ||
| "github.com/stretchr/testify/suite" | ||
| ) | ||
|
|
||
| type PlatformKeyIndexTestSuite struct { | ||
| suite.Suite | ||
| rsaKey trust.KeyDetails | ||
| } | ||
|
|
||
| func (s *PlatformKeyIndexTestSuite) SetupTest() { | ||
| s.rsaKey = &KasKeyAdapter{ | ||
| key: &policy.KasKey{ | ||
| KasId: "test-kas-id", | ||
| Key: &policy.AsymmetricKey{ | ||
| Id: "test-id", | ||
| KeyId: "test-key-id", | ||
| KeyAlgorithm: policy.Algorithm_ALGORITHM_RSA_2048, | ||
| KeyStatus: policy.KeyStatus_KEY_STATUS_ACTIVE, | ||
| KeyMode: policy.KeyMode_KEY_MODE_LOCAL, | ||
| PublicKeyCtx: &policy.KasPublicKeyCtx{ | ||
| Pem: "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF3SEw0TkVrOFpDa0JzNjZXQVpWagpIS3NseDRseWdmaXN3aW42RUx5OU9OczZLVDRYa1crRGxsdExtck14bHZkbzVRaDg1UmFZS01mWUdDTWtPM0dGCkFsK0JOeWFOM1kwa0N1QjNPU2ErTzdyMURhNVZteVVuaEJNbFBrYnVPY1Y0cjlLMUhOSGd3eDl2UFp3RjRpQW8KQStEY1VBcWFEeHlvYjV6enNGZ0hUNjJHLzdLdEtiZ2hYT1dCanRUYUl1ZHpsK2FaSjFPemY0U1RkOXhST2QrMQordVo2VG1ocmFEUm9zdDUrTTZUN0toL2lGWk40TTFUY2hwWXU1TDhKR2tVaG9YaEdZcHUrMGczSzlqYlh6RVh5CnpJU3VXN2d6SGRWYUxvcnBkQlNkRHpOWkNvTFVoL0U1T3d5TFZFQkNKaDZJVUtvdWJ5WHVucnIxQnJmK2tLbEsKeHdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", | ||
| }, | ||
| ProviderConfig: &policy.KeyProviderConfig{ | ||
| Id: "test-provider-id", | ||
| Name: "openbao", | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| func (s *PlatformKeyIndexTestSuite) TearDownTest() {} | ||
|
|
||
| func (s *PlatformKeyIndexTestSuite) TestKeyDetails() { | ||
| s.Equal("test-key-id", string(s.rsaKey.ID())) | ||
| s.Equal("ALGORITHM_RSA_2048", s.rsaKey.Algorithm()) | ||
| s.False(s.rsaKey.IsLegacy()) | ||
| s.Equal("openbao", s.rsaKey.System()) | ||
| } | ||
|
|
||
| func (s *PlatformKeyIndexTestSuite) TestKeyExportPublicKey_JWKFormat() { | ||
| // Export JWK format | ||
| jwkString, err := s.rsaKey.ExportPublicKey(context.Background(), trust.KeyTypeJWK) | ||
| s.Require().NoError(err) | ||
| s.Require().NotEmpty(jwkString) | ||
|
|
||
| rsaKey, err := jwk.ParseKey([]byte(jwkString)) | ||
| s.Require().NoError(err) | ||
| s.Require().NotNil(rsaKey) | ||
| } | ||
|
|
||
| func (s *PlatformKeyIndexTestSuite) TestKeyExportPublicKey_PKCSFormat() { | ||
| // Export JWK format | ||
| pem, err := s.rsaKey.ExportPublicKey(context.Background(), trust.KeyTypePKCS8) | ||
| s.Require().NoError(err) | ||
| s.Require().NotEmpty(pem) | ||
|
|
||
| keyAdapter, ok := s.rsaKey.(*KasKeyAdapter) | ||
| s.Require().True(ok) | ||
| pubCtx := keyAdapter.key.GetKey().GetPublicKeyCtx() | ||
| s.Require().NotEmpty(pubCtx) | ||
| base64Pem := ocrypto.Base64Encode([]byte(pem)) | ||
| s.Equal(pubCtx.GetPem(), string(base64Pem)) | ||
| } | ||
|
|
||
| func TestNewPlatformKeyIndexTestSuite(t *testing.T) { | ||
| suite.Run(t, new(PlatformKeyIndexTestSuite)) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.