jws.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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 key 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. // noNonce indicates that the nonce should be omitted from the protected header.
  31. // See jwsEncodeJSON for details.
  32. const noNonce = ""
  33. // jsonWebSignature can be easily serialized into a JWS following
  34. // https://tools.ietf.org/html/rfc7515#section-3.2.
  35. type jsonWebSignature struct {
  36. Protected string `json:"protected"`
  37. Payload string `json:"payload"`
  38. Sig string `json:"signature"`
  39. }
  40. // jwsEncodeJSON signs claimset using provided key and a nonce.
  41. // The result is serialized in JSON format containing either kid or jwk
  42. // fields based on the provided KeyID value.
  43. //
  44. // The claimset is marshalled using json.Marshal unless it is a string.
  45. // In which case it is inserted directly into the message.
  46. //
  47. // If kid is non-empty, its quoted value is inserted in the protected header
  48. // as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
  49. // as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
  50. //
  51. // If nonce is non-empty, its quoted value is inserted in the protected header.
  52. //
  53. // See https://tools.ietf.org/html/rfc7515#section-7.
  54. func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
  55. if key == nil {
  56. return nil, errors.New("nil key")
  57. }
  58. alg, sha := jwsHasher(key.Public())
  59. if alg == "" || !sha.Available() {
  60. return nil, ErrUnsupportedKey
  61. }
  62. headers := struct {
  63. Alg string `json:"alg"`
  64. KID string `json:"kid,omitempty"`
  65. JWK json.RawMessage `json:"jwk,omitempty"`
  66. Nonce string `json:"nonce,omitempty"`
  67. URL string `json:"url"`
  68. }{
  69. Alg: alg,
  70. Nonce: nonce,
  71. URL: url,
  72. }
  73. switch kid {
  74. case noKeyID:
  75. jwk, err := jwkEncode(key.Public())
  76. if err != nil {
  77. return nil, err
  78. }
  79. headers.JWK = json.RawMessage(jwk)
  80. default:
  81. headers.KID = string(kid)
  82. }
  83. phJSON, err := json.Marshal(headers)
  84. if err != nil {
  85. return nil, err
  86. }
  87. phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
  88. var payload string
  89. if val, ok := claimset.(string); ok {
  90. payload = val
  91. } else {
  92. cs, err := json.Marshal(claimset)
  93. if err != nil {
  94. return nil, err
  95. }
  96. payload = base64.RawURLEncoding.EncodeToString(cs)
  97. }
  98. hash := sha.New()
  99. hash.Write([]byte(phead + "." + payload))
  100. sig, err := jwsSign(key, sha, hash.Sum(nil))
  101. if err != nil {
  102. return nil, err
  103. }
  104. enc := jsonWebSignature{
  105. Protected: phead,
  106. Payload: payload,
  107. Sig: base64.RawURLEncoding.EncodeToString(sig),
  108. }
  109. return json.Marshal(&enc)
  110. }
  111. // jwsWithMAC creates and signs a JWS using the given key and the HS256
  112. // algorithm. kid and url are included in the protected header. rawPayload
  113. // should not be base64-URL-encoded.
  114. func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
  115. if len(key) == 0 {
  116. return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
  117. }
  118. header := struct {
  119. Algorithm string `json:"alg"`
  120. KID string `json:"kid"`
  121. URL string `json:"url,omitempty"`
  122. }{
  123. // Only HMAC-SHA256 is supported.
  124. Algorithm: "HS256",
  125. KID: kid,
  126. URL: url,
  127. }
  128. rawProtected, err := json.Marshal(header)
  129. if err != nil {
  130. return nil, err
  131. }
  132. protected := base64.RawURLEncoding.EncodeToString(rawProtected)
  133. payload := base64.RawURLEncoding.EncodeToString(rawPayload)
  134. h := hmac.New(sha256.New, key)
  135. if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
  136. return nil, err
  137. }
  138. mac := h.Sum(nil)
  139. return &jsonWebSignature{
  140. Protected: protected,
  141. Payload: payload,
  142. Sig: base64.RawURLEncoding.EncodeToString(mac),
  143. }, nil
  144. }
  145. // jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
  146. // The result is also suitable for creating a JWK thumbprint.
  147. // https://tools.ietf.org/html/rfc7517
  148. func jwkEncode(pub crypto.PublicKey) (string, error) {
  149. switch pub := pub.(type) {
  150. case *rsa.PublicKey:
  151. // https://tools.ietf.org/html/rfc7518#section-6.3.1
  152. n := pub.N
  153. e := big.NewInt(int64(pub.E))
  154. // Field order is important.
  155. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  156. return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
  157. base64.RawURLEncoding.EncodeToString(e.Bytes()),
  158. base64.RawURLEncoding.EncodeToString(n.Bytes()),
  159. ), nil
  160. case *ecdsa.PublicKey:
  161. // https://tools.ietf.org/html/rfc7518#section-6.2.1
  162. p := pub.Curve.Params()
  163. n := p.BitSize / 8
  164. if p.BitSize%8 != 0 {
  165. n++
  166. }
  167. x := pub.X.Bytes()
  168. if n > len(x) {
  169. x = append(make([]byte, n-len(x)), x...)
  170. }
  171. y := pub.Y.Bytes()
  172. if n > len(y) {
  173. y = append(make([]byte, n-len(y)), y...)
  174. }
  175. // Field order is important.
  176. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  177. return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
  178. p.Name,
  179. base64.RawURLEncoding.EncodeToString(x),
  180. base64.RawURLEncoding.EncodeToString(y),
  181. ), nil
  182. }
  183. return "", ErrUnsupportedKey
  184. }
  185. // jwsSign signs the digest using the given key.
  186. // The hash is unused for ECDSA keys.
  187. func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
  188. switch pub := key.Public().(type) {
  189. case *rsa.PublicKey:
  190. return key.Sign(rand.Reader, digest, hash)
  191. case *ecdsa.PublicKey:
  192. sigASN1, err := key.Sign(rand.Reader, digest, hash)
  193. if err != nil {
  194. return nil, err
  195. }
  196. var rs struct{ R, S *big.Int }
  197. if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
  198. return nil, err
  199. }
  200. rb, sb := rs.R.Bytes(), rs.S.Bytes()
  201. size := pub.Params().BitSize / 8
  202. if size%8 > 0 {
  203. size++
  204. }
  205. sig := make([]byte, size*2)
  206. copy(sig[size-len(rb):], rb)
  207. copy(sig[size*2-len(sb):], sb)
  208. return sig, nil
  209. }
  210. return nil, ErrUnsupportedKey
  211. }
  212. // jwsHasher indicates suitable JWS algorithm name and a hash function
  213. // to use for signing a digest with the provided key.
  214. // It returns ("", 0) if the key is not supported.
  215. func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
  216. switch pub := pub.(type) {
  217. case *rsa.PublicKey:
  218. return "RS256", crypto.SHA256
  219. case *ecdsa.PublicKey:
  220. switch pub.Params().Name {
  221. case "P-256":
  222. return "ES256", crypto.SHA256
  223. case "P-384":
  224. return "ES384", crypto.SHA384
  225. case "P-521":
  226. return "ES512", crypto.SHA512
  227. }
  228. }
  229. return "", 0
  230. }
  231. // JWKThumbprint creates a JWK thumbprint out of pub
  232. // as specified in https://tools.ietf.org/html/rfc7638.
  233. func JWKThumbprint(pub crypto.PublicKey) (string, error) {
  234. jwk, err := jwkEncode(pub)
  235. if err != nil {
  236. return "", err
  237. }
  238. b := sha256.Sum256([]byte(jwk))
  239. return base64.RawURLEncoding.EncodeToString(b[:]), nil
  240. }