external.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package ca
  2. import (
  3. "bytes"
  4. cryptorand "crypto/rand"
  5. "crypto/tls"
  6. "crypto/x509"
  7. "encoding/hex"
  8. "encoding/json"
  9. "encoding/pem"
  10. "io/ioutil"
  11. "net/http"
  12. "sync"
  13. "time"
  14. "github.com/Sirupsen/logrus"
  15. "github.com/cloudflare/cfssl/api"
  16. "github.com/cloudflare/cfssl/config"
  17. "github.com/cloudflare/cfssl/csr"
  18. "github.com/cloudflare/cfssl/signer"
  19. "github.com/pkg/errors"
  20. "golang.org/x/net/context"
  21. "golang.org/x/net/context/ctxhttp"
  22. )
  23. // ErrNoExternalCAURLs is an error used it indicate that an ExternalCA is
  24. // configured with no URLs to which it can proxy certificate signing requests.
  25. var ErrNoExternalCAURLs = errors.New("no external CA URLs")
  26. // ExternalCA is able to make certificate signing requests to one of a list
  27. // remote CFSSL API endpoints.
  28. type ExternalCA struct {
  29. ExternalRequestTimeout time.Duration
  30. mu sync.Mutex
  31. rootCA *RootCA
  32. urls []string
  33. client *http.Client
  34. }
  35. // NewExternalCA creates a new ExternalCA which uses the given tlsConfig to
  36. // authenticate to any of the given URLS of CFSSL API endpoints.
  37. func NewExternalCA(rootCA *RootCA, tlsConfig *tls.Config, urls ...string) *ExternalCA {
  38. return &ExternalCA{
  39. ExternalRequestTimeout: 5 * time.Second,
  40. rootCA: rootCA,
  41. urls: urls,
  42. client: &http.Client{
  43. Transport: &http.Transport{
  44. TLSClientConfig: tlsConfig,
  45. },
  46. },
  47. }
  48. }
  49. // Copy returns a copy of the external CA that can be updated independently
  50. func (eca *ExternalCA) Copy() *ExternalCA {
  51. eca.mu.Lock()
  52. defer eca.mu.Unlock()
  53. return &ExternalCA{
  54. ExternalRequestTimeout: eca.ExternalRequestTimeout,
  55. rootCA: eca.rootCA,
  56. urls: eca.urls,
  57. client: eca.client,
  58. }
  59. }
  60. // UpdateTLSConfig updates the HTTP Client for this ExternalCA by creating
  61. // a new client which uses the given tlsConfig.
  62. func (eca *ExternalCA) UpdateTLSConfig(tlsConfig *tls.Config) {
  63. eca.mu.Lock()
  64. defer eca.mu.Unlock()
  65. eca.client = &http.Client{
  66. Transport: &http.Transport{
  67. TLSClientConfig: tlsConfig,
  68. },
  69. }
  70. }
  71. // UpdateURLs updates the list of CSR API endpoints by setting it to the given
  72. // urls.
  73. func (eca *ExternalCA) UpdateURLs(urls ...string) {
  74. eca.mu.Lock()
  75. defer eca.mu.Unlock()
  76. eca.urls = urls
  77. }
  78. // Sign signs a new certificate by proxying the given certificate signing
  79. // request to an external CFSSL API server.
  80. func (eca *ExternalCA) Sign(ctx context.Context, req signer.SignRequest) (cert []byte, err error) {
  81. // Get the current HTTP client and list of URLs in a small critical
  82. // section. We will use these to make certificate signing requests.
  83. eca.mu.Lock()
  84. urls := eca.urls
  85. client := eca.client
  86. eca.mu.Unlock()
  87. if len(urls) == 0 {
  88. return nil, ErrNoExternalCAURLs
  89. }
  90. csrJSON, err := json.Marshal(req)
  91. if err != nil {
  92. return nil, errors.Wrap(err, "unable to JSON-encode CFSSL signing request")
  93. }
  94. // Try each configured proxy URL. Return after the first success. If
  95. // all fail then the last error will be returned.
  96. for _, url := range urls {
  97. requestCtx, cancel := context.WithTimeout(ctx, eca.ExternalRequestTimeout)
  98. cert, err = makeExternalSignRequest(requestCtx, client, url, csrJSON)
  99. cancel()
  100. if err == nil {
  101. return append(cert, eca.rootCA.Intermediates...), err
  102. }
  103. logrus.Debugf("unable to proxy certificate signing request to %s: %s", url, err)
  104. }
  105. return nil, err
  106. }
  107. // CrossSignRootCA takes a RootCA object, generates a CA CSR, sends a signing request with the CA CSR to the external
  108. // CFSSL API server in order to obtain a cross-signed root
  109. func (eca *ExternalCA) CrossSignRootCA(ctx context.Context, rca RootCA) ([]byte, error) {
  110. // ExtractCertificateRequest generates a new key request, and we want to continue to use the old
  111. // key. However, ExtractCertificateRequest will also convert the pkix.Name to csr.Name, which we
  112. // need in order to generate a signing request
  113. rcaSigner, err := rca.Signer()
  114. if err != nil {
  115. return nil, err
  116. }
  117. rootCert := rcaSigner.parsedCert
  118. cfCSRObj := csr.ExtractCertificateRequest(rootCert)
  119. der, err := x509.CreateCertificateRequest(cryptorand.Reader, &x509.CertificateRequest{
  120. RawSubjectPublicKeyInfo: rootCert.RawSubjectPublicKeyInfo,
  121. RawSubject: rootCert.RawSubject,
  122. PublicKeyAlgorithm: rootCert.PublicKeyAlgorithm,
  123. Subject: rootCert.Subject,
  124. Extensions: rootCert.Extensions,
  125. DNSNames: rootCert.DNSNames,
  126. EmailAddresses: rootCert.EmailAddresses,
  127. IPAddresses: rootCert.IPAddresses,
  128. }, rcaSigner.cryptoSigner)
  129. if err != nil {
  130. return nil, err
  131. }
  132. req := signer.SignRequest{
  133. Request: string(pem.EncodeToMemory(&pem.Block{
  134. Type: "CERTIFICATE REQUEST",
  135. Bytes: der,
  136. })),
  137. Subject: &signer.Subject{
  138. CN: rootCert.Subject.CommonName,
  139. Names: cfCSRObj.Names,
  140. },
  141. }
  142. // cfssl actually ignores non subject alt name extensions in the CSR, so we have to add the CA extension in the signing
  143. // request as well
  144. for _, ext := range rootCert.Extensions {
  145. if ext.Id.Equal(BasicConstraintsOID) {
  146. req.Extensions = append(req.Extensions, signer.Extension{
  147. ID: config.OID(ext.Id),
  148. Critical: ext.Critical,
  149. Value: hex.EncodeToString(ext.Value),
  150. })
  151. }
  152. }
  153. return eca.Sign(ctx, req)
  154. }
  155. func makeExternalSignRequest(ctx context.Context, client *http.Client, url string, csrJSON []byte) (cert []byte, err error) {
  156. resp, err := ctxhttp.Post(ctx, client, url, "application/json", bytes.NewReader(csrJSON))
  157. if err != nil {
  158. return nil, recoverableErr{err: errors.Wrap(err, "unable to perform certificate signing request")}
  159. }
  160. defer resp.Body.Close()
  161. body, err := ioutil.ReadAll(resp.Body)
  162. if err != nil {
  163. return nil, recoverableErr{err: errors.Wrap(err, "unable to read CSR response body")}
  164. }
  165. if resp.StatusCode != http.StatusOK {
  166. return nil, recoverableErr{err: errors.Errorf("unexpected status code in CSR response: %d - %s", resp.StatusCode, string(body))}
  167. }
  168. var apiResponse api.Response
  169. if err := json.Unmarshal(body, &apiResponse); err != nil {
  170. logrus.Debugf("unable to JSON-parse CFSSL API response body: %s", string(body))
  171. return nil, recoverableErr{err: errors.Wrap(err, "unable to parse JSON response")}
  172. }
  173. if !apiResponse.Success || apiResponse.Result == nil {
  174. if len(apiResponse.Errors) > 0 {
  175. return nil, errors.Errorf("response errors: %v", apiResponse.Errors)
  176. }
  177. return nil, errors.New("certificate signing request failed")
  178. }
  179. result, ok := apiResponse.Result.(map[string]interface{})
  180. if !ok {
  181. return nil, errors.Errorf("invalid result type: %T", apiResponse.Result)
  182. }
  183. certPEM, ok := result["certificate"].(string)
  184. if !ok {
  185. return nil, errors.Errorf("invalid result certificate field type: %T", result["certificate"])
  186. }
  187. return []byte(certPEM), nil
  188. }