jws.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2015 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package acme
  5. import (
  6. "crypto"
  7. "crypto/ecdsa"
  8. "crypto/hmac"
  9. "crypto/rand"
  10. "crypto/rsa"
  11. "crypto/sha256"
  12. _ "crypto/sha512" // need for EC keys
  13. "encoding/asn1"
  14. "encoding/base64"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "math/big"
  19. )
  20. // keyID is the account identity provided by a CA during registration.
  21. type keyID string
  22. // noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
  23. // See jwsEncodeJSON for details.
  24. const noKeyID = keyID("")
  25. // noPayload indicates jwsEncodeJSON will encode zero-length octet string
  26. // in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
  27. // authenticated GET requests via POSTing with an empty payload.
  28. // See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
  29. const noPayload = ""
  30. // jsonWebSignature can be easily serialized into a JWS following
  31. // https://tools.ietf.org/html/rfc7515#section-3.2.
  32. type jsonWebSignature struct {
  33. Protected string `json:"protected"`
  34. Payload string `json:"payload"`
  35. Sig string `json:"signature"`
  36. }
  37. // jwsEncodeJSON signs claimset using provided key and a nonce.
  38. // The result is serialized in JSON format containing either kid or jwk
  39. // fields based on the provided keyID value.
  40. //
  41. // If kid is non-empty, its quoted value is inserted in the protected head
  42. // as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
  43. // as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
  44. //
  45. // See https://tools.ietf.org/html/rfc7515#section-7.
  46. func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
  47. alg, sha := jwsHasher(key.Public())
  48. if alg == "" || !sha.Available() {
  49. return nil, ErrUnsupportedKey
  50. }
  51. var phead string
  52. switch kid {
  53. case noKeyID:
  54. jwk, err := jwkEncode(key.Public())
  55. if err != nil {
  56. return nil, err
  57. }
  58. phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
  59. default:
  60. phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
  61. }
  62. phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
  63. var payload string
  64. if claimset != noPayload {
  65. cs, err := json.Marshal(claimset)
  66. if err != nil {
  67. return nil, err
  68. }
  69. payload = base64.RawURLEncoding.EncodeToString(cs)
  70. }
  71. hash := sha.New()
  72. hash.Write([]byte(phead + "." + payload))
  73. sig, err := jwsSign(key, sha, hash.Sum(nil))
  74. if err != nil {
  75. return nil, err
  76. }
  77. enc := jsonWebSignature{
  78. Protected: phead,
  79. Payload: payload,
  80. Sig: base64.RawURLEncoding.EncodeToString(sig),
  81. }
  82. return json.Marshal(&enc)
  83. }
  84. // jwsWithMAC creates and signs a JWS using the given key and the HS256
  85. // algorithm. kid and url are included in the protected header. rawPayload
  86. // should not be base64-URL-encoded.
  87. func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
  88. if len(key) == 0 {
  89. return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
  90. }
  91. header := struct {
  92. Algorithm string `json:"alg"`
  93. KID string `json:"kid"`
  94. URL string `json:"url,omitempty"`
  95. }{
  96. // Only HMAC-SHA256 is supported.
  97. Algorithm: "HS256",
  98. KID: kid,
  99. URL: url,
  100. }
  101. rawProtected, err := json.Marshal(header)
  102. if err != nil {
  103. return nil, err
  104. }
  105. protected := base64.RawURLEncoding.EncodeToString(rawProtected)
  106. payload := base64.RawURLEncoding.EncodeToString(rawPayload)
  107. h := hmac.New(sha256.New, key)
  108. if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
  109. return nil, err
  110. }
  111. mac := h.Sum(nil)
  112. return &jsonWebSignature{
  113. Protected: protected,
  114. Payload: payload,
  115. Sig: base64.RawURLEncoding.EncodeToString(mac),
  116. }, nil
  117. }
  118. // jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
  119. // The result is also suitable for creating a JWK thumbprint.
  120. // https://tools.ietf.org/html/rfc7517
  121. func jwkEncode(pub crypto.PublicKey) (string, error) {
  122. switch pub := pub.(type) {
  123. case *rsa.PublicKey:
  124. // https://tools.ietf.org/html/rfc7518#section-6.3.1
  125. n := pub.N
  126. e := big.NewInt(int64(pub.E))
  127. // Field order is important.
  128. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  129. return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
  130. base64.RawURLEncoding.EncodeToString(e.Bytes()),
  131. base64.RawURLEncoding.EncodeToString(n.Bytes()),
  132. ), nil
  133. case *ecdsa.PublicKey:
  134. // https://tools.ietf.org/html/rfc7518#section-6.2.1
  135. p := pub.Curve.Params()
  136. n := p.BitSize / 8
  137. if p.BitSize%8 != 0 {
  138. n++
  139. }
  140. x := pub.X.Bytes()
  141. if n > len(x) {
  142. x = append(make([]byte, n-len(x)), x...)
  143. }
  144. y := pub.Y.Bytes()
  145. if n > len(y) {
  146. y = append(make([]byte, n-len(y)), y...)
  147. }
  148. // Field order is important.
  149. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  150. return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
  151. p.Name,
  152. base64.RawURLEncoding.EncodeToString(x),
  153. base64.RawURLEncoding.EncodeToString(y),
  154. ), nil
  155. }
  156. return "", ErrUnsupportedKey
  157. }
  158. // jwsSign signs the digest using the given key.
  159. // The hash is unused for ECDSA keys.
  160. func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
  161. switch pub := key.Public().(type) {
  162. case *rsa.PublicKey:
  163. return key.Sign(rand.Reader, digest, hash)
  164. case *ecdsa.PublicKey:
  165. sigASN1, err := key.Sign(rand.Reader, digest, hash)
  166. if err != nil {
  167. return nil, err
  168. }
  169. var rs struct{ R, S *big.Int }
  170. if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
  171. return nil, err
  172. }
  173. rb, sb := rs.R.Bytes(), rs.S.Bytes()
  174. size := pub.Params().BitSize / 8
  175. if size%8 > 0 {
  176. size++
  177. }
  178. sig := make([]byte, size*2)
  179. copy(sig[size-len(rb):], rb)
  180. copy(sig[size*2-len(sb):], sb)
  181. return sig, nil
  182. }
  183. return nil, ErrUnsupportedKey
  184. }
  185. // jwsHasher indicates suitable JWS algorithm name and a hash function
  186. // to use for signing a digest with the provided key.
  187. // It returns ("", 0) if the key is not supported.
  188. func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
  189. switch pub := pub.(type) {
  190. case *rsa.PublicKey:
  191. return "RS256", crypto.SHA256
  192. case *ecdsa.PublicKey:
  193. switch pub.Params().Name {
  194. case "P-256":
  195. return "ES256", crypto.SHA256
  196. case "P-384":
  197. return "ES384", crypto.SHA384
  198. case "P-521":
  199. return "ES512", crypto.SHA512
  200. }
  201. }
  202. return "", 0
  203. }
  204. // JWKThumbprint creates a JWK thumbprint out of pub
  205. // as specified in https://tools.ietf.org/html/rfc7638.
  206. func JWKThumbprint(pub crypto.PublicKey) (string, error) {
  207. jwk, err := jwkEncode(pub)
  208. if err != nil {
  209. return "", err
  210. }
  211. b := sha256.Sum256([]byte(jwk))
  212. return base64.RawURLEncoding.EncodeToString(b[:]), nil
  213. }