Skip to content

Commit

Permalink
jwe: Support overriding the algorithm when supplying a JWK
Browse files Browse the repository at this point in the history
Now, passing a JWK (via EncryptWithJwe / JSONWebKey.MarshalJSON) will
allow for ECDSA keys and for customizing the algorithm used with a
particular key.

Previously, the code made it impossible to supply a JWK-encoded ECDSA
public key in the encryption config, as all keys passed as JSONWebKey-s
were treated as RSA_OAEP keys, since utils.ParsePublicKey delegates to
parseJWKPublicKey which returns the JWK itself; and hence the switch in
the JWE keywrap failed to detect those as an ecdsa public key.
A simpler patch here would have been to change parseJWKPublicKey to return
the key contained inside the JWK directly, however, as pointed out by
stefanberger, this would have broken backwards compatibility of the public
API. Plus, using the algorithm encoded in the JWK allows us to more easily
extend the JWE encoder to new algorithms.

Risks: JWK-s containing RSA keys but with .Algorithm not set to "" (the
default value) or string(jose.RSA_OLAP) will end up erroring or producing
different encryptions than before. However, such keys would have failed to
decrypt the contents regardless, so it should be fine to consider this a
correction rather than breakage of old behavior. (Hyrum's law
notwithstanding)

Signed-off-by: Bojidar Marinov <bojidar.marinov.bg@gmail.com>
  • Loading branch information
bojidar-bg authored and stefanberger committed Dec 28, 2023
1 parent c430e43 commit 4b2101a
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 3 deletions.
17 changes: 16 additions & 1 deletion keywrap/jwe/keywrapper_jwe.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,24 @@ func addPubKeys(joseRecipients *[]jose.Recipient, pubKeys [][]byte) error {
}

alg := jose.RSA_OAEP
switch key.(type) {
switch key := key.(type) {
case *ecdsa.PublicKey:
alg = jose.ECDH_ES_A256KW
case *jose.JSONWebKey:
if key.Algorithm != "" {
alg = jose.KeyAlgorithm(key.Algorithm)
switch alg {
/* accepted algorithms */
case jose.RSA_OAEP:
case jose.RSA_OAEP_256:
case jose.ECDH_ES_A128KW:
case jose.ECDH_ES_A192KW:
case jose.ECDH_ES_A256KW:
/* all others are rejected */
default:
return fmt.Errorf("%s is an unsupported JWE key algorithm", alg)
}
}
}

*joseRecipients = append(*joseRecipients, jose.Recipient{
Expand Down
36 changes: 36 additions & 0 deletions keywrap/jwe/keywrapper_jwe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ func createValidJweCcs() ([]*config.CryptoConfig, error) {
return nil, err
}

ecKey, err := utils.CreateECDSAKey(elliptic.P521())
if err != nil {
return nil, err
}

jweEcPrivKeyJwk, err := jose.JSONWebKey{Key: ecKey, Algorithm: string(jose.ECDH_ES_A256KW)}.MarshalJSON()
if err != nil {
return nil, err
}

jweEcPubKeyJwk, err := jose.JSONWebKey{Key: &ecKey.PublicKey, Algorithm: string(jose.ECDH_ES_A256KW)}.MarshalJSON()
if err != nil {
return nil, err
}

validJweCcs := []*config.CryptoConfig{
// Key 1
{
Expand Down Expand Up @@ -226,6 +241,27 @@ func createValidJweCcs() ([]*config.CryptoConfig, error) {
},
},
},
// EC Key (JWK format)
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jweEcPubKeyJwk},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jweEcPrivKeyJwk},
"privkeys-passwords": {oneEmpty},
},
},
},

DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jweEcPrivKeyJwk},
"privkeys-passwords": {oneEmpty},
},
},
},
}
return validJweCcs, nil
}
Expand Down
13 changes: 11 additions & 2 deletions utils/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ func CreateRSAKey(bits int) (*rsa.PrivateKey, error) {
return key, nil
}

// CreateECDSAKey creates an elliptic curve key for the given curve
func CreateECDSAKey(curve elliptic.Curve) (*ecdsa.PrivateKey, error) {
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, fmt.Errorf("ecdsa.GenerateKey failed: %w", err)
}
return key, nil
}

// CreateRSATestKey creates an RSA key of the given size and returns
// the public and private key in PEM or DER format
func CreateRSATestKey(bits int, password []byte, pemencode bool) ([]byte, []byte, error) {
Expand Down Expand Up @@ -85,9 +94,9 @@ func CreateRSATestKey(bits int, password []byte, pemencode bool) ([]byte, []byte
// CreateECDSATestKey creates and elliptic curve key for the given curve and returns
// the public and private key in DER format
func CreateECDSATestKey(curve elliptic.Curve) ([]byte, []byte, error) {
key, err := ecdsa.GenerateKey(curve, rand.Reader)
key, err := CreateECDSAKey(curve)
if err != nil {
return nil, nil, fmt.Errorf("ecdsa.GenerateKey failed: %w", err)
return nil, nil, err
}

pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
Expand Down

0 comments on commit 4b2101a

Please sign in to comment.