jws.go raw

   1  package secure
   2  
   3  import (
   4  	"crypto"
   5  	"crypto/ecdsa"
   6  	"crypto/elliptic"
   7  	"crypto/rsa"
   8  	"encoding/base64"
   9  	"fmt"
  10  
  11  	"github.com/go-acme/lego/v4/acme/api/internal/nonces"
  12  	jose "github.com/go-jose/go-jose/v4"
  13  )
  14  
  15  // JWS Represents a JWS.
  16  type JWS struct {
  17  	privKey crypto.PrivateKey
  18  	kid     string // Key identifier
  19  	nonces  *nonces.Manager
  20  }
  21  
  22  // NewJWS Create a new JWS.
  23  func NewJWS(privateKey crypto.PrivateKey, kid string, nonceManager *nonces.Manager) *JWS {
  24  	return &JWS{
  25  		privKey: privateKey,
  26  		nonces:  nonceManager,
  27  		kid:     kid,
  28  	}
  29  }
  30  
  31  // SetKid Sets a key identifier.
  32  func (j *JWS) SetKid(kid string) {
  33  	j.kid = kid
  34  }
  35  
  36  // SignContent Signs a content with the JWS.
  37  func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
  38  	var alg jose.SignatureAlgorithm
  39  
  40  	switch k := j.privKey.(type) {
  41  	case *rsa.PrivateKey:
  42  		alg = jose.RS256
  43  	case *ecdsa.PrivateKey:
  44  		if k.Curve == elliptic.P256() {
  45  			alg = jose.ES256
  46  		} else if k.Curve == elliptic.P384() {
  47  			alg = jose.ES384
  48  		}
  49  	}
  50  
  51  	signKey := jose.SigningKey{
  52  		Algorithm: alg,
  53  		Key:       jose.JSONWebKey{Key: j.privKey, KeyID: j.kid},
  54  	}
  55  
  56  	options := jose.SignerOptions{
  57  		NonceSource: j.nonces,
  58  		ExtraHeaders: map[jose.HeaderKey]any{
  59  			"url": url,
  60  		},
  61  	}
  62  
  63  	if j.kid == "" {
  64  		options.EmbedJWK = true
  65  	}
  66  
  67  	signer, err := jose.NewSigner(signKey, &options)
  68  	if err != nil {
  69  		return nil, fmt.Errorf("failed to create jose signer: %w", err)
  70  	}
  71  
  72  	signed, err := signer.Sign(content)
  73  	if err != nil {
  74  		return nil, fmt.Errorf("failed to sign content: %w", err)
  75  	}
  76  
  77  	return signed, nil
  78  }
  79  
  80  // SignEABContent Signs an external account binding content with the JWS.
  81  func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
  82  	jwk := jose.JSONWebKey{Key: j.privKey}
  83  
  84  	jwkJSON, err := jwk.Public().MarshalJSON()
  85  	if err != nil {
  86  		return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err)
  87  	}
  88  
  89  	signer, err := jose.NewSigner(
  90  		jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
  91  		&jose.SignerOptions{
  92  			EmbedJWK: false,
  93  			ExtraHeaders: map[jose.HeaderKey]any{
  94  				"kid": kid,
  95  				"url": url,
  96  			},
  97  		},
  98  	)
  99  	if err != nil {
 100  		return nil, fmt.Errorf("failed to create External Account Binding jose signer: %w", err)
 101  	}
 102  
 103  	signed, err := signer.Sign(jwkJSON)
 104  	if err != nil {
 105  		return nil, fmt.Errorf("failed to External Account Binding sign content: %w", err)
 106  	}
 107  
 108  	return signed, nil
 109  }
 110  
 111  // GetKeyAuthorization Gets the key authorization for a token.
 112  func (j *JWS) GetKeyAuthorization(token string) (string, error) {
 113  	var publicKey crypto.PublicKey
 114  
 115  	switch k := j.privKey.(type) {
 116  	case *ecdsa.PrivateKey:
 117  		publicKey = k.Public()
 118  	case *rsa.PrivateKey:
 119  		publicKey = k.Public()
 120  	}
 121  
 122  	// Generate the Key Authorization for the challenge
 123  	jwk := &jose.JSONWebKey{Key: publicKey}
 124  
 125  	thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
 126  	if err != nil {
 127  		return "", err
 128  	}
 129  
 130  	// unpad the base64URL
 131  	keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
 132  
 133  	return token + "." + keyThumb, nil
 134  }
 135