jws.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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/rand"
  9. "crypto/rsa"
  10. "crypto/sha256"
  11. _ "crypto/sha512" // need for EC keys
  12. "encoding/base64"
  13. "encoding/json"
  14. "fmt"
  15. "math/big"
  16. )
  17. // keyID is the account identity provided by a CA during registration.
  18. type keyID string
  19. // noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
  20. // See jwsEncodeJSON for details.
  21. const noKeyID = keyID("")
  22. // noPayload indicates jwsEncodeJSON will encode zero-length octet string
  23. // in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
  24. // authenticated GET requests via POSTing with an empty payload.
  25. // See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
  26. const noPayload = ""
  27. // jwsEncodeJSON signs claimset using provided key and a nonce.
  28. // The result is serialized in JSON format containing either kid or jwk
  29. // fields based on the provided keyID value.
  30. //
  31. // If kid is non-empty, its quoted value is inserted in the protected head
  32. // as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
  33. // as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
  34. //
  35. // See https://tools.ietf.org/html/rfc7515#section-7.
  36. func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
  37. alg, sha := jwsHasher(key.Public())
  38. if alg == "" || !sha.Available() {
  39. return nil, ErrUnsupportedKey
  40. }
  41. var phead string
  42. switch kid {
  43. case noKeyID:
  44. jwk, err := jwkEncode(key.Public())
  45. if err != nil {
  46. return nil, err
  47. }
  48. phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
  49. default:
  50. phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
  51. }
  52. phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
  53. var payload string
  54. if claimset != noPayload {
  55. cs, err := json.Marshal(claimset)
  56. if err != nil {
  57. return nil, err
  58. }
  59. payload = base64.RawURLEncoding.EncodeToString(cs)
  60. }
  61. hash := sha.New()
  62. hash.Write([]byte(phead + "." + payload))
  63. sig, err := jwsSign(key, sha, hash.Sum(nil))
  64. if err != nil {
  65. return nil, err
  66. }
  67. enc := struct {
  68. Protected string `json:"protected"`
  69. Payload string `json:"payload"`
  70. Sig string `json:"signature"`
  71. }{
  72. Protected: phead,
  73. Payload: payload,
  74. Sig: base64.RawURLEncoding.EncodeToString(sig),
  75. }
  76. return json.Marshal(&enc)
  77. }
  78. // jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
  79. // The result is also suitable for creating a JWK thumbprint.
  80. // https://tools.ietf.org/html/rfc7517
  81. func jwkEncode(pub crypto.PublicKey) (string, error) {
  82. switch pub := pub.(type) {
  83. case *rsa.PublicKey:
  84. // https://tools.ietf.org/html/rfc7518#section-6.3.1
  85. n := pub.N
  86. e := big.NewInt(int64(pub.E))
  87. // Field order is important.
  88. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  89. return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
  90. base64.RawURLEncoding.EncodeToString(e.Bytes()),
  91. base64.RawURLEncoding.EncodeToString(n.Bytes()),
  92. ), nil
  93. case *ecdsa.PublicKey:
  94. // https://tools.ietf.org/html/rfc7518#section-6.2.1
  95. p := pub.Curve.Params()
  96. n := p.BitSize / 8
  97. if p.BitSize%8 != 0 {
  98. n++
  99. }
  100. x := pub.X.Bytes()
  101. if n > len(x) {
  102. x = append(make([]byte, n-len(x)), x...)
  103. }
  104. y := pub.Y.Bytes()
  105. if n > len(y) {
  106. y = append(make([]byte, n-len(y)), y...)
  107. }
  108. // Field order is important.
  109. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
  110. return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
  111. p.Name,
  112. base64.RawURLEncoding.EncodeToString(x),
  113. base64.RawURLEncoding.EncodeToString(y),
  114. ), nil
  115. }
  116. return "", ErrUnsupportedKey
  117. }
  118. // jwsSign signs the digest using the given key.
  119. // The hash is unused for ECDSA keys.
  120. //
  121. // Note: non-stdlib crypto.Signer implementations are expected to return
  122. // the signature in the format as specified in RFC7518.
  123. // See https://tools.ietf.org/html/rfc7518 for more details.
  124. func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
  125. if key, ok := key.(*ecdsa.PrivateKey); ok {
  126. // The key.Sign method of ecdsa returns ASN1-encoded signature.
  127. // So, we use the package Sign function instead
  128. // to get R and S values directly and format the result accordingly.
  129. r, s, err := ecdsa.Sign(rand.Reader, key, digest)
  130. if err != nil {
  131. return nil, err
  132. }
  133. rb, sb := r.Bytes(), s.Bytes()
  134. size := key.Params().BitSize / 8
  135. if size%8 > 0 {
  136. size++
  137. }
  138. sig := make([]byte, size*2)
  139. copy(sig[size-len(rb):], rb)
  140. copy(sig[size*2-len(sb):], sb)
  141. return sig, nil
  142. }
  143. return key.Sign(rand.Reader, digest, hash)
  144. }
  145. // jwsHasher indicates suitable JWS algorithm name and a hash function
  146. // to use for signing a digest with the provided key.
  147. // It returns ("", 0) if the key is not supported.
  148. func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
  149. switch pub := pub.(type) {
  150. case *rsa.PublicKey:
  151. return "RS256", crypto.SHA256
  152. case *ecdsa.PublicKey:
  153. switch pub.Params().Name {
  154. case "P-256":
  155. return "ES256", crypto.SHA256
  156. case "P-384":
  157. return "ES384", crypto.SHA384
  158. case "P-521":
  159. return "ES512", crypto.SHA512
  160. }
  161. }
  162. return "", 0
  163. }
  164. // JWKThumbprint creates a JWK thumbprint out of pub
  165. // as specified in https://tools.ietf.org/html/rfc7638.
  166. func JWKThumbprint(pub crypto.PublicKey) (string, error) {
  167. jwk, err := jwkEncode(pub)
  168. if err != nil {
  169. return "", err
  170. }
  171. b := sha256.Sum256([]byte(jwk))
  172. return base64.RawURLEncoding.EncodeToString(b[:]), nil
  173. }