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