open-vault/builtin/logical/pki/acme_jws.go
Alexander Scheel 3ed31ff262
Add acme account storage (#19953)
* Enable creation of accounts

 - Refactors many methods to take an acmeContext, which holds the
   storageContext on it.
 - Updates the core ACME Handlers to use *acmeContext, to avoid
   copying structs.
 - Makes JWK exported so the JSON parser can find it.

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

* Finish ACME account creation

 - This ensures a Kid is created when one doesn't exist
 - Expands the parsed handler capabilities, to format the response and
   set required headers.

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

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2023-04-03 16:08:25 -04:00

143 lines
3.7 KiB
Go

package pki
import (
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
jose "gopkg.in/square/go-jose.v2"
)
var AllowedOuterJWSTypes = map[string]interface{}{
"RS256": true,
"RS384": true,
"RS512": true,
"PS256": true,
"PS384": true,
"PS512": true,
"ES256": true,
"ES384": true,
"ES512": true,
"EdDSA2": true,
}
// 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:"-"`
Existing bool `json:"-"`
}
func (c *jwsCtx) UnmarshalJSON(a *acmeState, ac *acmeContext, 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: %w", ErrMalformed)
}
if c.Kid == "" && len(c.Jwk) == 0 {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > 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': %w", ErrMalformed)
}
if _, present := AllowedOuterJWSTypes[c.Algo]; !present {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > The JWS Protected Header MUST include the following fields:
// >
// > - "alg" (Algorithm)
// >
// > * This field MUST NOT contain "none" or a Message
// > Authentication Code (MAC) algorithm (e.g. one in which the
// > algorithm registry description mentions MAC/HMAC).
return fmt.Errorf("invalid header: unexpected value for 'algo': %w", ErrMalformed)
}
if c.Kid != "" {
// Load KID from storage first.
c.Jwk, err = a.LoadJWK(ac, c.Kid)
if err != nil {
return err
}
c.Existing = true
}
if err = c.Key.UnmarshalJSON(c.Jwk); err != nil {
return err
}
if !c.Key.Valid() {
return fmt.Errorf("received invalid jwk: %w", ErrMalformed)
}
if c.Kid == "" {
// Create a key ID
kid, err := c.Key.Thumbprint(crypto.SHA256)
if err != nil {
return fmt.Errorf("failed creating thumbprint: %w", err)
}
c.Kid = base64.URLEncoding.EncodeToString(kid)
c.Existing = false
}
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) {
// See RFC 8555 Section 6.2. Request Authentication:
//
// > The JWS Unencoded Payload Option [RFC7797] MUST NOT be used
//
// This is validated by go-jose.
sig, err := jose.ParseSigned(signature)
if err != nil {
return nil, fmt.Errorf("error parsing signature: %s: %w", err, ErrMalformed)
}
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: %w", ErrMalformed)
}
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: %w", ErrMalformed)
}
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': %s: %w", err, ErrMalformed)
}
return m, nil
}