-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP support deprecated configuration
- Loading branch information
1 parent
9a8fd1d
commit 6f2bb64
Showing
5 changed files
with
269 additions
and
9 deletions.
There are no files selected for viewing
This file contains 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,143 @@ | ||
package security | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
) | ||
|
||
// Copied from service/kas/access to avoid dep loop. To be removed. | ||
type CurrentKeyFor struct { | ||
Algorithm string `mapstructure:"alg"` | ||
KID string `mapstructure:"kid"` | ||
Private string `mapstructure:"private"` | ||
Certificate string `mapstructure:"cert"` | ||
Active bool `mapstructure:"active"` | ||
Legacy bool `mapstructure:"legacy"` | ||
} | ||
|
||
// locate finds the index of the key in the Keyring slice. | ||
func (k *KASConfigDupe) locate(kid string) (int, bool) { | ||
for i, key := range k.Keyring { | ||
if key.KID == kid { | ||
return i, true | ||
} | ||
} | ||
return -1, false | ||
} | ||
|
||
// For entries in keyring that appear with the same value for their KID field, | ||
// consolidate them into a single entry. If one of the copies has 'Legacy' set, let the consolidated entry have 'Legacy' set. | ||
// If one of the entries does not have `Legacy` set, set the value of `Active`. | ||
func (k *KASConfigDupe) consolidate() { | ||
seen := make(map[string]int) | ||
for i, key := range k.Keyring { | ||
if j, ok := seen[key.KID]; ok { | ||
if key.Legacy { | ||
k.Keyring[j].Legacy = true | ||
} else { | ||
k.Keyring[j].Active = key.Active | ||
} | ||
k.Keyring = append(k.Keyring[:i], k.Keyring[i+1:]...) | ||
i-- | ||
} else { | ||
seen[key.KID] = i | ||
} | ||
} | ||
} | ||
|
||
// Deprecated | ||
type KeyPairInfo struct { | ||
// Valid algorithm. May be able to be derived from Private but it is better to just say it. | ||
Algorithm string `mapstructure:"alg" json:"alg"` | ||
// Key identifier. Should be short | ||
KID string `mapstructure:"kid" json:"kid"` | ||
// Implementation specific locator for private key; | ||
// for 'standard' crypto service this is the path to a PEM file | ||
Private string `mapstructure:"private" json:"private"` | ||
// Optional locator for the corresponding certificate. | ||
// If not found, only public key (derivable from Private) is available. | ||
Certificate string `mapstructure:"cert" json:"cert"` | ||
// Optional enumeration of intended usages of keypair | ||
Usage string `mapstructure:"usage" json:"usage"` | ||
// Optional long form description of key pair including purpose and life cycle information | ||
Purpose string `mapstructure:"purpose" json:"purpose"` | ||
} | ||
|
||
// Deprecated | ||
type StandardKeyInfo struct { | ||
PrivateKeyPath string `mapstructure:"private_key_path" json:"private_key_path"` | ||
PublicKeyPath string `mapstructure:"public_key_path" json:"public_key_path"` | ||
} | ||
|
||
// Deprecated | ||
type CryptoConfig2024 struct { | ||
Keys []KeyPairInfo `mapstructure:"keys" json:"keys"` | ||
// Deprecated | ||
RSAKeys map[string]StandardKeyInfo `mapstructure:"rsa,omitempty" json:"rsa,omitempty"` | ||
// Deprecated | ||
ECKeys map[string]StandardKeyInfo `mapstructure:"ec,omitempty" json:"ec,omitempty"` | ||
} | ||
|
||
type KASConfigDupe struct { | ||
// Which keys are currently the default. | ||
Keyring []CurrentKeyFor `mapstructure:"keyring" json:"keyring"` | ||
// Deprecated | ||
ECCertID string `mapstructure:"eccertid" json:"eccertid"` | ||
// Deprecated | ||
RSACertID string `mapstructure:"rsacertid" json:"rsacertid"` | ||
} | ||
|
||
func (c CryptoConfig2024) MarshalTo(within map[string]any) error { | ||
var kasCfg KASConfigDupe | ||
if err := mapstructure.Decode(within, &kasCfg); err != nil { | ||
return fmt.Errorf("invalid kas cfg [%v] %w", within, err) | ||
} | ||
kasCfg.consolidate() | ||
for kid, stdKeyInfo := range c.RSAKeys { | ||
if i, ok := kasCfg.locate(kid); ok { | ||
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath | ||
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath | ||
continue | ||
} | ||
k := CurrentKeyFor{ | ||
Algorithm: "rsa:2048", | ||
KID: kid, | ||
Private: stdKeyInfo.PrivateKeyPath, | ||
Certificate: stdKeyInfo.PublicKeyPath, | ||
Active: true, | ||
Legacy: true, | ||
} | ||
kasCfg.Keyring = append(kasCfg.Keyring, k) | ||
} | ||
for kid, stdKeyInfo := range c.ECKeys { | ||
if i, ok := kasCfg.locate(kid); ok { | ||
kasCfg.Keyring[i].Private = stdKeyInfo.PrivateKeyPath | ||
kasCfg.Keyring[i].Certificate = stdKeyInfo.PublicKeyPath | ||
continue | ||
} | ||
k := CurrentKeyFor{ | ||
Algorithm: "ec:secp256r1", | ||
KID: kid, | ||
Private: stdKeyInfo.PrivateKeyPath, | ||
Certificate: stdKeyInfo.PublicKeyPath, | ||
Active: true, | ||
Legacy: true, | ||
} | ||
kasCfg.Keyring = append(kasCfg.Keyring, k) | ||
} | ||
for _, k := range c.Keys { | ||
if i, ok := kasCfg.locate(k.KID); ok { | ||
kasCfg.Keyring[i].Private = k.Private | ||
kasCfg.Keyring[i].Certificate = k.Certificate | ||
continue | ||
} | ||
kasCfg.Keyring = append(kasCfg.Keyring, CurrentKeyFor{ | ||
Algorithm: k.Algorithm, | ||
KID: k.KID, | ||
Private: k.Private, | ||
Certificate: k.Certificate, | ||
}) | ||
} | ||
return nil | ||
} |
This file contains 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,107 @@ | ||
package security | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMarshalTo(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
config CryptoConfig2024 | ||
input map[string]any | ||
expected KASConfigDupe | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "older input (pre-2024, no legacy)", | ||
config: CryptoConfig2024{ | ||
RSAKeys: map[string]StandardKeyInfo{ | ||
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"}, | ||
}, | ||
ECKeys: map[string]StandardKeyInfo{ | ||
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"}, | ||
}, | ||
}, | ||
input: map[string]any{ | ||
"eccertid": "ec1", | ||
"rsacertid": "rsa1", | ||
}, | ||
expected: KASConfigDupe{ | ||
Keyring: []CurrentKeyFor{ | ||
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true}, | ||
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "older input (pre-2024, supports legacy)", | ||
config: CryptoConfig2024{ | ||
RSAKeys: map[string]StandardKeyInfo{ | ||
"rsa1": {PrivateKeyPath: "rsa1_private.pem", PublicKeyPath: "rsa1_public.pem"}, | ||
}, | ||
ECKeys: map[string]StandardKeyInfo{ | ||
"ec1": {PrivateKeyPath: "ec1_private.pem", PublicKeyPath: "ec1_public.pem"}, | ||
}, | ||
}, | ||
input: map[string]any{}, | ||
expected: KASConfigDupe{ | ||
Keyring: []CurrentKeyFor{ | ||
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true}, | ||
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "older input (2024)", | ||
config: CryptoConfig2024{ | ||
Keys: []KeyPairInfo{ | ||
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem"}, | ||
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem"}, | ||
}, | ||
}, | ||
input: map[string]any{ | ||
"keyring": []map[string]any{ | ||
{"alg": "rsa:2048", "kid": "rsa1", "private": "rsa1_private.pem", "cert": "rsa1_public.pem", "active": true, "legacy": true}, | ||
{"alg": "ec:secp256r1", "kid": "ec1", "private": "ec1_private.pem", "cert": "ec1_public.pem", "active": true, "legacy": true}, | ||
}, | ||
}, | ||
expected: KASConfigDupe{ | ||
Keyring: []CurrentKeyFor{ | ||
{Algorithm: "rsa:2048", KID: "rsa1", Private: "rsa1_private.pem", Certificate: "rsa1_public.pem", Active: true, Legacy: true}, | ||
{Algorithm: "ec:secp256r1", KID: "ec1", Private: "ec1_private.pem", Certificate: "ec1_public.pem", Active: true, Legacy: true}, | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Invalid input", | ||
config: CryptoConfig2024{ | ||
RSAKeys: map[string]StandardKeyInfo{}, | ||
ECKeys: map[string]StandardKeyInfo{}, | ||
Keys: []KeyPairInfo{}, | ||
}, | ||
input: map[string]any{}, | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := tt.config.MarshalTo(tt.input) | ||
if tt.wantErr { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
var result KASConfigDupe | ||
err = mapstructure.Decode(tt.input, &result) | ||
assert.NoError(t, err) | ||
assert.Equal(t, tt.expected, result) | ||
} | ||
}) | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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