open-vault/builtin/logical/pki/acme/jws.go
Alexander Scheel cb8be1d8be
Add initial ACME server library to PKI (#19778)
* Add ACME package to provide a nonce service

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add JWS parsing helper

Using go-jose v2, we start building a JWS parsing helper, ensuring that
fields are properly validated w.r.t. the ACME spec's intersection with
JWS.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add error context information

Start adding the ability to wrap errors returned by Vault to
ACME-specific errors.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Make ACMEState exported

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-03-28 17:29:54 +00:00

97 lines
2.4 KiB
Go

package acme
import (
"encoding/json"
"fmt"
jose "gopkg.in/square/go-jose.v2"
)
// This wraps a JWS message structure.
type JWSCtx struct {
Algo string `json:"alg"`
Kid string `json:"kid"`
jwk json.RawMessage `json:"jwk"`
Nonce string `json:"nonce"`
Url string `json:"url"`
key jose.JSONWebKey `json:"-"`
}
func (c *JWSCtx) UnmarshalJSON(a *ACMEState, jws []byte) error {
var err error
if err = json.Unmarshal(jws, c); err != nil {
return err
}
if c.Kid != "" && len(c.jwk) > 0 {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > The "jwk" and "kid" fields are mutually exclusive. Servers MUST
// > reject requests that contain both.
return fmt.Errorf("invalid header: got both account 'kid' and 'jwk' in the same message; expected only one")
}
if c.Kid == "" && len(c.jwk) == 0 {
// See RFC 8555 Section 6.2 Request Authorization:
//
// > Either "jwk" (JSON Web Key) or "kid" (Key ID) as specified
// > below
return fmt.Errorf("invalid header: got neither required fields of 'kid' nor 'jwk'")
}
if c.Kid != "" {
// Load KID from storage first.
c.jwk, err = a.LoadJWK(c.Kid)
if err != nil {
return err
}
}
if err = c.key.UnmarshalJSON(c.jwk); err != nil {
return err
}
if !c.key.Valid() {
return fmt.Errorf("received invalid jwk")
}
return nil
}
func hasValues(h jose.Header) bool {
return h.KeyID != "" || h.JSONWebKey != nil || h.Algorithm != "" || h.Nonce != "" || len(h.ExtraHeaders) > 0
}
func (c *JWSCtx) VerifyJWS(signature string) (map[string]interface{}, error) {
sig, err := jose.ParseSigned(signature)
if err != nil {
return nil, fmt.Errorf("error parsing signature: %w", err)
}
if len(sig.Signatures) > 1 {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > The JWS MUST NOT have multiple signatures
return nil, fmt.Errorf("request had multiple signatures")
}
if hasValues(sig.Signatures[0].Unprotected) {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > The JWS Unprotected Header [RFC7515] MUST NOT be used
return nil, fmt.Errorf("request had unprotected headers")
}
payload, err := sig.Verify(c.key)
if err != nil {
return nil, err
}
var m map[string]interface{}
if err := json.Unmarshal(payload, &m); err != nil {
return nil, fmt.Errorf("failed to json unmarshal 'payload': %w", err)
}
return m, nil
}