Skip to content

Commit

Permalink
Add support for EdDSA JWT signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
sundbry committed Oct 17, 2024
1 parent f9ec59f commit 8bd975c
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ verification as long as any JWTs signed with them are valid.
The plugin supports a subset of the asymmetric encryption algorithms outlined in the JWT
specification.

* EdDSA
* ES256
* ES384
* ES512
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hashicorp/vault/api v1.10.0
github.com/hashicorp/vault/sdk v0.10.2
github.com/mariuszs/friendlyid-go v0.0.0-20200911181514-555cced97798
golang.org/x/crypto v0.12.0
gopkg.in/square/go-jose.v2 v2.6.0
)

Expand Down Expand Up @@ -65,7 +66,6 @@ require (
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/stretchr/testify v1.8.3 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions plugin/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ func (b *backend) getPolicy(ctx context.Context, stg logical.Storage, config *Co
default:
err = errutil.InternalError{Err: "unsupported RSA key size"}
}
case jose.EdDSA:
polReq.KeyType = keysutil.KeyType_ED25519
case jose.ES256:
polReq.KeyType = keysutil.KeyType_ECDSA_P256
case jose.ES384:
Expand Down
4 changes: 3 additions & 1 deletion plugin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var DefaultAllowedClaims = []string{"sub", "aud"}
var ReservedClaims = []string{"iss", "exp", "nbf", "iat", "jti"}
var ReservedHeaders = []string{"kid", "alg", "enc", "zip", "crit"}

var AllowedSignatureAlgorithmNames = []string{string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.RS256), string(jose.RS384), string(jose.RS512)}
var AllowedSignatureAlgorithmNames = []string{string(jose.EdDSA), string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.RS256), string(jose.RS384), string(jose.RS512)}
var AllowedRSAKeyBits = []int{2048, 3072, 4096}

// Config holds all configuration for the backend.
Expand Down Expand Up @@ -186,6 +186,8 @@ func (b *backend) saveConfig(ctx context.Context, stg logical.Storage, config *C
default:
err = errutil.InternalError{Err: "unsupported RSA key size"}
}
case jose.EdDSA:
policy.Type = keysutil.KeyType_ED25519
case jose.ES256:
policy.Type = keysutil.KeyType_ECDSA_P256
case jose.ES384:
Expand Down
18 changes: 16 additions & 2 deletions plugin/path_jwks.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ package jwtsecrets
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/keysutil"
"github.com/hashicorp/vault/sdk/logical"
"golang.org/x/crypto/ed25519"
"gopkg.in/square/go-jose.v2"
"strconv"
)
Expand Down Expand Up @@ -84,6 +87,7 @@ func (b *backend) getPublicKeys(ctx context.Context, stg logical.Storage, mount
Keys: make([]jose.JSONWebKey, keyCount),
}

logger := b.Logger()
keyIdx := 0
for version := policy.MinDecryptionVersion; version <= policy.LatestVersion; version++ {

Expand All @@ -92,16 +96,26 @@ func (b *backend) getPublicKeys(ctx context.Context, stg logical.Storage, mount
continue
}

if key.FormattedPublicKey != "" {
if policy.Type == keysutil.KeyType_ED25519 {
keyBytes, err := base64.StdEncoding.DecodeString(key.FormattedPublicKey)
if err != nil {
logger.Error("Failed to decode ED25519 public key", "public_key", key.FormattedPublicKey, "error", err)
continue
}
jwkSet.Keys[keyIdx].Key = ed25519.PublicKey(keyBytes)
} else if key.FormattedPublicKey != "" {
block, _ := pem.Decode([]byte(key.FormattedPublicKey))
if block == nil {
logger.Error("Failed to decode PEM key", "public_key", key.FormattedPublicKey)
continue
}

jwkSet.Keys[keyIdx].Key, err = x509.ParsePKIXPublicKey(block.Bytes)
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
logger.Error("Failed to parse PKIX public key", "public_key", key.FormattedPublicKey, "error", err)
continue
}
jwkSet.Keys[keyIdx].Key = publicKey
} else if key.RSAKey != nil {
jwkSet.Keys[keyIdx].Key = &key.RSAKey.PublicKey
}
Expand Down
17 changes: 12 additions & 5 deletions plugin/policy_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ func (ps *PolicySigner) sign(input []byte) ([]byte, error) {
hashType = keysutil.HashTypeSHA2512
hash = crypto.SHA512
sigAlg = "pkcs1v15"
case jose.EdDSA:
hashType = keysutil.HashTypeNone
sigAlg = ""
case jose.ES256:
hashType = keysutil.HashTypeSHA2256
hash = crypto.SHA256
Expand All @@ -120,11 +123,15 @@ func (ps *PolicySigner) sign(input []byte) ([]byte, error) {

keyVersion := ps.Policy.LatestVersion

hasher := hash.New()

// According to documentation, Write() on hash never fails
_, _ = hasher.Write(input)
hashedInput := hasher.Sum(nil)
var hashedInput []byte
if hashType == keysutil.HashTypeNone {
hashedInput = input
} else {
hasher := hash.New()
// According to documentation, Write() on hash never fails
_, _ = hasher.Write(input)
hashedInput = hasher.Sum(nil)
}

result, err := ps.Policy.Sign(keyVersion, nil, hashedInput, hashType, sigAlg, keysutil.MarshalingTypeJWS)
if err != nil {
Expand Down

0 comments on commit 8bd975c

Please sign in to comment.