-
Notifications
You must be signed in to change notification settings - Fork 33
feat(core): Deprecate ECKeyPair in favor of asym_* #3293
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
Draft
dmihalcik-virtru
wants to merge
9
commits into
main
Choose a base branch
from
DSPX-1089-deprecate-eckp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d04c19d
feat(core): Deprecate ECKeyPair in favor of asym_*
dmihalcik-virtru 08055b2
fix(core): Remove DeriveSharedKey from PrivateKeyDecryptor interface
dmihalcik-virtru b3360ad
fixup gocritic
dmihalcik-virtru 1a800a2
chore! remove exported key derivation
dmihalcik-virtru edd8f56
fixup linter errors
dmihalcik-virtru 0a1cba2
fixup test repair
dmihalcik-virtru 2b0a459
fixup coderabbit.ai suggestions
dmihalcik-virtru c7b0b7f
fix(core): Address PR review findings for ECKeyPair deprecation
dmihalcik-virtru c8e7433
fixup missing godoc
dmihalcik-virtru 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 |
|---|---|---|
|
|
@@ -54,4 +54,4 @@ traces/ | |
| .cache/* | ||
|
|
||
| # Claude AI files | ||
| .claude/ | ||
| .claude/ | ||
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 |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import ( | |
| "crypto/ecdh" | ||
| "crypto/ecdsa" | ||
| "crypto/elliptic" | ||
| "crypto/rand" | ||
| "crypto/rsa" | ||
| "crypto/sha256" | ||
| "crypto/x509" | ||
|
|
@@ -25,7 +26,42 @@ type AsymDecryption struct { | |
|
|
||
| type PrivateKeyDecryptor interface { | ||
| // Decrypt decrypts ciphertext with private key. | ||
| // For EC keys, use DecryptWithEphemeralKey on the concrete ECDecryptor type instead. | ||
| Decrypt(data []byte) ([]byte, error) | ||
|
|
||
| // PrivateKeyInPemFormat returns the private key in PEM format. | ||
| PrivateKeyInPemFormat() (string, error) | ||
|
|
||
| // Public returns the corresponding public-key encryptor. | ||
| Public() (PublicKeyEncryptor, error) | ||
|
|
||
| // KeyType returns the key type, e.g. RSA or EC. | ||
| KeyType() KeyType | ||
| } | ||
|
|
||
| // NewPrivateKeyDecryptor generates a new private key of the requested type, | ||
| // enclosing it in a PrivateKeyDecryptor interface. | ||
| func NewPrivateKeyDecryptor(kt KeyType) (PrivateKeyDecryptor, error) { | ||
| switch { | ||
| case IsRSAKeyType(kt): | ||
| bits, err := RSAKeyTypeToBits(kt) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| keyPair, err := NewRSAKeyPair(bits) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return keyPair, nil | ||
| case IsECKeyType(kt): | ||
| mode, err := ECKeyTypeToMode(kt) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return NewECPrivateKey(mode) | ||
| default: | ||
| return nil, fmt.Errorf("unsupported key type: %v", kt) | ||
| } | ||
| } | ||
|
|
||
| // FromPrivatePEM creates and returns a new AsymDecryption. | ||
|
|
@@ -109,6 +145,33 @@ func (asymDecryption AsymDecryption) Decrypt(data []byte) ([]byte, error) { | |
| return bytes, nil | ||
| } | ||
|
|
||
| func (asymDecryption AsymDecryption) PrivateKeyInPemFormat() (string, error) { | ||
| return privateKeyInPemFormat(asymDecryption.PrivateKey) | ||
| } | ||
|
|
||
| func (asymDecryption AsymDecryption) Public() (PublicKeyEncryptor, error) { | ||
| if asymDecryption.PrivateKey == nil { | ||
| return nil, errors.New("failed to generate public key encryptor, private key is empty") | ||
| } | ||
|
|
||
| return &AsymEncryption{PublicKey: &asymDecryption.PrivateKey.PublicKey}, nil | ||
| } | ||
|
|
||
| func (asymDecryption AsymDecryption) KeyType() KeyType { | ||
| if asymDecryption.PrivateKey == nil { | ||
| return KeyType("rsa:[unknown]") | ||
| } | ||
|
|
||
| switch asymDecryption.PrivateKey.Size() { | ||
| case RSA2048Size / 8: //nolint:mnd // standard key size in bytes | ||
| return RSA2048Key | ||
| case RSA4096Size / 8: //nolint:mnd // large key size in bytes | ||
| return RSA4096Key | ||
| default: | ||
| return KeyType(fmt.Sprintf("rsa:%d", asymDecryption.PrivateKey.Size()*8)) //nolint:mnd // convert to bits | ||
| } | ||
| } | ||
|
|
||
| type ECDecryptor struct { | ||
| sk *ecdh.PrivateKey | ||
| salt []byte | ||
|
|
@@ -124,39 +187,55 @@ func NewECDecryptor(sk *ecdh.PrivateKey) (ECDecryptor, error) { | |
| return ECDecryptor{sk, salt, nil}, nil | ||
| } | ||
|
|
||
| func NewECPrivateKey(mode ECCMode) (ECDecryptor, error) { | ||
| curve, err := curveFromECCMode(mode) | ||
| if err != nil { | ||
| return ECDecryptor{}, err | ||
| } | ||
|
|
||
| sk, err := curve.GenerateKey(rand.Reader) | ||
| if err != nil { | ||
| return ECDecryptor{}, fmt.Errorf("ecdh.GenerateKey failed: %w", err) | ||
| } | ||
|
|
||
| return NewECDecryptor(sk) | ||
| } | ||
|
|
||
| func NewSaltedECDecryptor(sk *ecdh.PrivateKey, salt, info []byte) (ECDecryptor, error) { | ||
| if sk == nil { | ||
| return ECDecryptor{}, errors.New("private key must not be nil") | ||
| } | ||
| return ECDecryptor{sk, salt, info}, nil | ||
| } | ||
|
|
||
| func (e ECDecryptor) Decrypt(_ []byte) ([]byte, error) { | ||
| // TK How to get the ephmeral key into here? | ||
| return nil, errors.New("ecdh standard decrypt unimplemented") | ||
| return nil, errors.New("EC keys require DecryptWithEphemeralKey; standard Decrypt is not supported for ECDH") | ||
| } | ||
|
|
||
| func (e ECDecryptor) DecryptWithEphemeralKey(data, ephemeral []byte) ([]byte, error) { | ||
| var ek *ecdh.PublicKey | ||
| func (e ECDecryptor) PrivateKeyInPemFormat() (string, error) { | ||
| return privateKeyInPemFormat(e.sk) | ||
| } | ||
|
|
||
| if pubFromDSN, err := x509.ParsePKIXPublicKey(ephemeral); err == nil { | ||
| switch pubFromDSN := pubFromDSN.(type) { | ||
| case *ecdsa.PublicKey: | ||
| ek, err = ConvertToECDHPublicKey(pubFromDSN) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("ecdh conversion failure: %w", err) | ||
| } | ||
| case *ecdh.PublicKey: | ||
| ek = pubFromDSN | ||
| default: | ||
| return nil, fmt.Errorf("unsupported public key of type: %T", pubFromDSN) | ||
| } | ||
| } else { | ||
| ekDSA, err := UncompressECPubKey(convCurve(e.sk.Curve()), ephemeral) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| ek, err = ekDSA.ECDH() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("ecdh failure: %w", err) | ||
| } | ||
| func (e ECDecryptor) Public() (PublicKeyEncryptor, error) { | ||
| if e.sk == nil { | ||
| return nil, errors.New("failed to generate public key encryptor, private key is empty") | ||
| } | ||
|
|
||
| return newECIES(e.sk.PublicKey(), e.salt, e.info) | ||
| } | ||
|
|
||
| func (e ECDecryptor) KeyType() KeyType { | ||
| if e.sk == nil { | ||
| return KeyType("ec:[unknown]") | ||
| } | ||
|
Comment on lines
+228
to
+230
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| return keyTypeFromECDHCurve(e.sk.Curve()) | ||
| } | ||
|
|
||
| func (e ECDecryptor) DecryptWithEphemeralKey(data, ephemeral []byte) ([]byte, error) { | ||
| ek, err := e.parseEphemeralPublicKey(ephemeral) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse ephemeral public key: %w", err) | ||
| } | ||
|
|
||
| ikm, err := e.sk.ECDH(ek) | ||
|
|
@@ -171,7 +250,7 @@ func (e ECDecryptor) DecryptWithEphemeralKey(data, ephemeral []byte) ([]byte, er | |
| return nil, fmt.Errorf("hkdf failure: %w", err) | ||
| } | ||
|
|
||
| // Encrypt data with derived key using aes-gcm | ||
| // Decrypt data with derived key using AES-GCM | ||
| block, err := aes.NewCipher(derivedKey) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("aes.NewCipher failure: %w", err) | ||
|
|
@@ -196,15 +275,61 @@ func (e ECDecryptor) DecryptWithEphemeralKey(data, ephemeral []byte) ([]byte, er | |
| return plaintext, nil | ||
| } | ||
|
|
||
| func convCurve(c ecdh.Curve) elliptic.Curve { | ||
| func (e ECDecryptor) deriveSharedKey(publicKeyInPem string) ([]byte, error) { | ||
| if e.sk == nil { | ||
| return nil, errors.New("failed to derive shared key, private key is empty") | ||
| } | ||
|
|
||
| pub, err := getPublicPart(publicKeyInPem) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| ecdhPublicKey, err := ConvertToECDHPublicKey(pub) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unsupported public key type: %w", err) | ||
| } | ||
|
|
||
| sharedKey, err := e.sk.ECDH(ecdhPublicKey) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("there was a problem deriving a shared ECDH key: %w", err) | ||
| } | ||
|
|
||
| return sharedKey, nil | ||
| } | ||
|
|
||
| // parseEphemeralPublicKey parses an ephemeral public key from DER (PKIX) or compressed EC point bytes. | ||
| func (e ECDecryptor) parseEphemeralPublicKey(ephemeral []byte) (*ecdh.PublicKey, error) { | ||
| if pub, derErr := x509.ParsePKIXPublicKey(ephemeral); derErr == nil { | ||
| switch pub := pub.(type) { | ||
| case *ecdsa.PublicKey: | ||
| return ConvertToECDHPublicKey(pub) | ||
| case *ecdh.PublicKey: | ||
| return pub, nil | ||
| default: | ||
| return nil, fmt.Errorf("unsupported public key of type: %T", pub) | ||
| } | ||
| } | ||
| curve, err := convCurve(e.sk.Curve()) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| ekDSA, err := UncompressECPubKey(curve, ephemeral) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("ephemeral key is neither valid DER (PKIX) nor a compressed EC point: %w", err) | ||
| } | ||
| return ekDSA.ECDH() | ||
| } | ||
|
|
||
| func convCurve(c ecdh.Curve) (elliptic.Curve, error) { | ||
| switch c { | ||
| case ecdh.P256(): | ||
| return elliptic.P256() | ||
| return elliptic.P256(), nil | ||
| case ecdh.P384(): | ||
| return elliptic.P384() | ||
| return elliptic.P384(), nil | ||
| case ecdh.P521(): | ||
| return elliptic.P521() | ||
| return elliptic.P521(), nil | ||
| default: | ||
| return nil | ||
| return nil, fmt.Errorf("unsupported ECDH curve: %v", c) | ||
| } | ||
| } | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check for
asymDecryption.PrivateKey == nilis redundant here becausePrivateKeyis a field of the struct and the method is called on an instance. If the instance is initialized, this field should be checked at the point of creation or usage, not inside every getter method. This adds unnecessary complexity.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that would make sense.. unfortunately
FromRSAand the corresponding SDKWithSession...RSAoptions don't returnerrorso it would be hard to wire that in at the moment