service.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. package originca
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto"
  6. "crypto/ecdsa"
  7. "crypto/elliptic"
  8. "crypto/rand"
  9. "crypto/rsa"
  10. "crypto/tls"
  11. "crypto/x509"
  12. "crypto/x509/pkix"
  13. "encoding/json"
  14. "encoding/pem"
  15. "errors"
  16. "io"
  17. "io/fs"
  18. "net"
  19. "net/http"
  20. "slices"
  21. "strconv"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/sagernet/sing-box/adapter"
  26. "github.com/sagernet/sing-box/adapter/certificate"
  27. C "github.com/sagernet/sing-box/constant"
  28. "github.com/sagernet/sing-box/log"
  29. "github.com/sagernet/sing-box/option"
  30. "github.com/sagernet/sing/common"
  31. E "github.com/sagernet/sing/common/exceptions"
  32. "github.com/sagernet/sing/common/ntp"
  33. "github.com/sagernet/sing/service"
  34. "github.com/caddyserver/certmagic"
  35. )
  36. const (
  37. cloudflareOriginCAEndpoint = "https://api.cloudflare.com/client/v4/certificates"
  38. defaultRequestedValidity = option.CloudflareOriginCARequestValidity5475
  39. // min of 30 days and certmagic's 1/3 lifetime ratio (maintain.go)
  40. defaultRenewBefore = 30 * 24 * time.Hour
  41. // from certmagic retry backoff range (async.go)
  42. minimumRenewRetryDelay = time.Minute
  43. maximumRenewRetryDelay = time.Hour
  44. storageLockPrefix = "cloudflare-origin-ca"
  45. )
  46. func RegisterCertificateProvider(registry *certificate.Registry) {
  47. certificate.Register[option.CloudflareOriginCACertificateProviderOptions](registry, C.TypeCloudflareOriginCA, NewCertificateProvider)
  48. }
  49. var _ adapter.CertificateProviderService = (*Service)(nil)
  50. type Service struct {
  51. certificate.Adapter
  52. logger log.ContextLogger
  53. ctx context.Context
  54. cancel context.CancelFunc
  55. done chan struct{}
  56. timeFunc func() time.Time
  57. httpClient *http.Client
  58. storage certmagic.Storage
  59. storageIssuerKey string
  60. storageNamesKey string
  61. storageLockKey string
  62. apiToken string
  63. originCAKey string
  64. domain []string
  65. requestType option.CloudflareOriginCARequestType
  66. requestedValidity option.CloudflareOriginCARequestValidity
  67. access sync.RWMutex
  68. currentCertificate *tls.Certificate
  69. currentLeaf *x509.Certificate
  70. }
  71. func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag string, options option.CloudflareOriginCACertificateProviderOptions) (adapter.CertificateProviderService, error) {
  72. domain, err := normalizeHostnames(options.Domain)
  73. if err != nil {
  74. return nil, err
  75. }
  76. if len(domain) == 0 {
  77. return nil, E.New("missing domain")
  78. }
  79. apiToken := strings.TrimSpace(options.APIToken)
  80. originCAKey := strings.TrimSpace(options.OriginCAKey)
  81. switch {
  82. case apiToken == "" && originCAKey == "":
  83. return nil, E.New("api_token or origin_ca_key is required")
  84. case apiToken != "" && originCAKey != "":
  85. return nil, E.New("api_token and origin_ca_key are mutually exclusive")
  86. }
  87. requestType := options.RequestType
  88. if requestType == "" {
  89. requestType = option.CloudflareOriginCARequestTypeOriginRSA
  90. }
  91. requestedValidity := options.RequestedValidity
  92. if requestedValidity == 0 {
  93. requestedValidity = defaultRequestedValidity
  94. }
  95. ctx, cancel := context.WithCancel(ctx)
  96. httpClient, err := originCAHTTPClient(ctx, logger, options)
  97. if err != nil {
  98. cancel()
  99. return nil, err
  100. }
  101. var storage certmagic.Storage
  102. if options.DataDirectory != "" {
  103. storage = &certmagic.FileStorage{Path: options.DataDirectory}
  104. } else {
  105. storage = certmagic.Default.Storage
  106. }
  107. timeFunc := ntp.TimeFuncFromContext(ctx)
  108. if timeFunc == nil {
  109. timeFunc = time.Now
  110. }
  111. storageIssuerKey := C.TypeCloudflareOriginCA + "-" + string(requestType)
  112. storageNamesKey := (&certmagic.CertificateResource{SANs: slices.Clone(domain)}).NamesKey()
  113. storageLockKey := strings.Join([]string{
  114. storageLockPrefix,
  115. certmagic.StorageKeys.Safe(storageIssuerKey),
  116. certmagic.StorageKeys.Safe(storageNamesKey),
  117. }, "/")
  118. return &Service{
  119. Adapter: certificate.NewAdapter(C.TypeCloudflareOriginCA, tag),
  120. logger: logger,
  121. ctx: ctx,
  122. cancel: cancel,
  123. timeFunc: timeFunc,
  124. httpClient: httpClient,
  125. storage: storage,
  126. storageIssuerKey: storageIssuerKey,
  127. storageNamesKey: storageNamesKey,
  128. storageLockKey: storageLockKey,
  129. apiToken: apiToken,
  130. originCAKey: originCAKey,
  131. domain: domain,
  132. requestType: requestType,
  133. requestedValidity: requestedValidity,
  134. }, nil
  135. }
  136. func originCAHTTPClient(ctx context.Context, logger log.ContextLogger, options option.CloudflareOriginCACertificateProviderOptions) (*http.Client, error) {
  137. httpClientOptions := common.PtrValueOrDefault(options.HTTPClient)
  138. httpClientManager := service.FromContext[adapter.HTTPClientManager](ctx)
  139. transport, err := httpClientManager.ResolveTransport(ctx, logger, httpClientOptions)
  140. if err != nil {
  141. return nil, E.Cause(err, "create Cloudflare Origin CA http client")
  142. }
  143. return &http.Client{Transport: transport}, nil
  144. }
  145. func (s *Service) Start(stage adapter.StartStage) error {
  146. if stage != adapter.StartStateStart {
  147. return nil
  148. }
  149. cachedCertificate, cachedLeaf, err := s.loadCachedCertificate()
  150. if err != nil {
  151. s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate"))
  152. } else if cachedCertificate != nil {
  153. s.setCurrentCertificate(cachedCertificate, cachedLeaf)
  154. }
  155. if cachedCertificate == nil {
  156. err = s.issueAndStoreCertificate()
  157. if err != nil {
  158. return err
  159. }
  160. } else if s.shouldRenew(cachedLeaf, s.timeFunc()) {
  161. err = s.issueAndStoreCertificate()
  162. if err != nil {
  163. s.logger.Warn(E.Cause(err, "renew cached Cloudflare Origin CA certificate"))
  164. }
  165. }
  166. s.done = make(chan struct{})
  167. go s.refreshLoop()
  168. return nil
  169. }
  170. func (s *Service) Close() error {
  171. s.cancel()
  172. if done := s.done; done != nil {
  173. <-done
  174. }
  175. return nil
  176. }
  177. func (s *Service) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
  178. s.access.RLock()
  179. certificate := s.currentCertificate
  180. s.access.RUnlock()
  181. if certificate == nil {
  182. return nil, E.New("Cloudflare Origin CA certificate is unavailable")
  183. }
  184. return certificate, nil
  185. }
  186. func (s *Service) refreshLoop() {
  187. defer close(s.done)
  188. var retryDelay time.Duration
  189. for {
  190. waitDuration := retryDelay
  191. if waitDuration == 0 {
  192. s.access.RLock()
  193. leaf := s.currentLeaf
  194. s.access.RUnlock()
  195. if leaf == nil {
  196. waitDuration = minimumRenewRetryDelay
  197. } else {
  198. refreshAt := leaf.NotAfter.Add(-s.effectiveRenewBefore(leaf))
  199. waitDuration = refreshAt.Sub(s.timeFunc())
  200. if waitDuration < minimumRenewRetryDelay {
  201. waitDuration = minimumRenewRetryDelay
  202. }
  203. }
  204. }
  205. timer := time.NewTimer(waitDuration)
  206. select {
  207. case <-s.ctx.Done():
  208. if !timer.Stop() {
  209. select {
  210. case <-timer.C:
  211. default:
  212. }
  213. }
  214. return
  215. case <-timer.C:
  216. }
  217. err := s.issueAndStoreCertificate()
  218. if err != nil {
  219. s.logger.Error(E.Cause(err, "renew Cloudflare Origin CA certificate"))
  220. s.access.RLock()
  221. leaf := s.currentLeaf
  222. s.access.RUnlock()
  223. if leaf == nil {
  224. retryDelay = minimumRenewRetryDelay
  225. } else {
  226. remaining := leaf.NotAfter.Sub(s.timeFunc())
  227. switch {
  228. case remaining <= minimumRenewRetryDelay:
  229. retryDelay = minimumRenewRetryDelay
  230. case remaining < maximumRenewRetryDelay:
  231. retryDelay = max(remaining/2, minimumRenewRetryDelay)
  232. default:
  233. retryDelay = maximumRenewRetryDelay
  234. }
  235. }
  236. continue
  237. }
  238. retryDelay = 0
  239. }
  240. }
  241. func (s *Service) shouldRenew(leaf *x509.Certificate, now time.Time) bool {
  242. return !now.Before(leaf.NotAfter.Add(-s.effectiveRenewBefore(leaf)))
  243. }
  244. func (s *Service) effectiveRenewBefore(leaf *x509.Certificate) time.Duration {
  245. lifetime := leaf.NotAfter.Sub(leaf.NotBefore)
  246. if lifetime <= 0 {
  247. return 0
  248. }
  249. return min(lifetime/3, defaultRenewBefore)
  250. }
  251. func (s *Service) issueAndStoreCertificate() error {
  252. err := s.storage.Lock(s.ctx, s.storageLockKey)
  253. if err != nil {
  254. return E.Cause(err, "lock Cloudflare Origin CA certificate storage")
  255. }
  256. defer func() {
  257. err = s.storage.Unlock(context.WithoutCancel(s.ctx), s.storageLockKey)
  258. if err != nil {
  259. s.logger.Warn(E.Cause(err, "unlock Cloudflare Origin CA certificate storage"))
  260. }
  261. }()
  262. cachedCertificate, cachedLeaf, err := s.loadCachedCertificate()
  263. if err != nil {
  264. s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate"))
  265. } else if cachedCertificate != nil && !s.shouldRenew(cachedLeaf, s.timeFunc()) {
  266. s.setCurrentCertificate(cachedCertificate, cachedLeaf)
  267. return nil
  268. }
  269. certificatePEM, privateKeyPEM, tlsCertificate, leaf, err := s.requestCertificate(s.ctx)
  270. if err != nil {
  271. return err
  272. }
  273. issuerData, err := json.Marshal(originCAIssuerData{
  274. RequestType: s.requestType,
  275. RequestedValidity: s.requestedValidity,
  276. })
  277. if err != nil {
  278. return E.Cause(err, "encode Cloudflare Origin CA certificate metadata")
  279. }
  280. err = storeCertificateResource(s.ctx, s.storage, s.storageIssuerKey, certmagic.CertificateResource{
  281. SANs: slices.Clone(s.domain),
  282. CertificatePEM: certificatePEM,
  283. PrivateKeyPEM: privateKeyPEM,
  284. IssuerData: issuerData,
  285. })
  286. if err != nil {
  287. return E.Cause(err, "store Cloudflare Origin CA certificate")
  288. }
  289. s.setCurrentCertificate(tlsCertificate, leaf)
  290. s.logger.Info("updated Cloudflare Origin CA certificate, expires at ", leaf.NotAfter.Format(time.RFC3339))
  291. return nil
  292. }
  293. func (s *Service) requestCertificate(ctx context.Context) ([]byte, []byte, *tls.Certificate, *x509.Certificate, error) {
  294. var privateKey crypto.Signer
  295. switch s.requestType {
  296. case option.CloudflareOriginCARequestTypeOriginRSA:
  297. rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
  298. if err != nil {
  299. return nil, nil, nil, nil, err
  300. }
  301. privateKey = rsaKey
  302. case option.CloudflareOriginCARequestTypeOriginECC:
  303. ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  304. if err != nil {
  305. return nil, nil, nil, nil, err
  306. }
  307. privateKey = ecKey
  308. default:
  309. return nil, nil, nil, nil, E.New("unsupported Cloudflare Origin CA request type: ", s.requestType)
  310. }
  311. privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
  312. if err != nil {
  313. return nil, nil, nil, nil, E.Cause(err, "encode private key")
  314. }
  315. privateKeyPEM := pem.EncodeToMemory(&pem.Block{
  316. Type: "PRIVATE KEY",
  317. Bytes: privateKeyDER,
  318. })
  319. certificateRequestDER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
  320. Subject: pkix.Name{CommonName: s.domain[0]},
  321. DNSNames: s.domain,
  322. }, privateKey)
  323. if err != nil {
  324. return nil, nil, nil, nil, E.Cause(err, "create certificate request")
  325. }
  326. certificateRequestPEM := pem.EncodeToMemory(&pem.Block{
  327. Type: "CERTIFICATE REQUEST",
  328. Bytes: certificateRequestDER,
  329. })
  330. requestBody, err := json.Marshal(originCARequest{
  331. CSR: string(certificateRequestPEM),
  332. Hostnames: s.domain,
  333. RequestType: string(s.requestType),
  334. RequestedValidity: uint16(s.requestedValidity),
  335. })
  336. if err != nil {
  337. return nil, nil, nil, nil, E.Cause(err, "marshal request")
  338. }
  339. request, err := http.NewRequestWithContext(ctx, http.MethodPost, cloudflareOriginCAEndpoint, bytes.NewReader(requestBody))
  340. if err != nil {
  341. return nil, nil, nil, nil, E.Cause(err, "create request")
  342. }
  343. request.Header.Set("Accept", "application/json")
  344. request.Header.Set("Content-Type", "application/json")
  345. request.Header.Set("User-Agent", "sing-box/"+C.Version)
  346. if s.apiToken != "" {
  347. request.Header.Set("Authorization", "Bearer "+s.apiToken)
  348. } else {
  349. request.Header.Set("X-Auth-User-Service-Key", s.originCAKey)
  350. }
  351. defer s.httpClient.CloseIdleConnections()
  352. response, err := s.httpClient.Do(request)
  353. if err != nil {
  354. return nil, nil, nil, nil, E.Cause(err, "request certificate from Cloudflare")
  355. }
  356. defer response.Body.Close()
  357. responseBody, err := io.ReadAll(response.Body)
  358. if err != nil {
  359. return nil, nil, nil, nil, E.Cause(err, "read Cloudflare response")
  360. }
  361. var responseEnvelope originCAResponse
  362. err = json.Unmarshal(responseBody, &responseEnvelope)
  363. if err != nil && response.StatusCode >= http.StatusOK && response.StatusCode < http.StatusMultipleChoices {
  364. return nil, nil, nil, nil, E.Cause(err, "decode Cloudflare response")
  365. }
  366. if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
  367. return nil, nil, nil, nil, buildOriginCAError(response.StatusCode, responseEnvelope.Errors, responseBody)
  368. }
  369. if !responseEnvelope.Success {
  370. return nil, nil, nil, nil, buildOriginCAError(response.StatusCode, responseEnvelope.Errors, responseBody)
  371. }
  372. if responseEnvelope.Result.Certificate == "" {
  373. return nil, nil, nil, nil, E.New("Cloudflare Origin CA response is missing certificate data")
  374. }
  375. certificatePEM := []byte(responseEnvelope.Result.Certificate)
  376. tlsCertificate, leaf, err := parseKeyPair(certificatePEM, privateKeyPEM)
  377. if err != nil {
  378. return nil, nil, nil, nil, E.Cause(err, "parse issued certificate")
  379. }
  380. if !s.matchesCertificate(leaf) {
  381. return nil, nil, nil, nil, E.New("issued Cloudflare Origin CA certificate does not match requested hostnames or key type")
  382. }
  383. return certificatePEM, privateKeyPEM, tlsCertificate, leaf, nil
  384. }
  385. func (s *Service) loadCachedCertificate() (*tls.Certificate, *x509.Certificate, error) {
  386. certificateResource, err := loadCertificateResource(s.ctx, s.storage, s.storageIssuerKey, s.storageNamesKey)
  387. if err != nil {
  388. if errors.Is(err, fs.ErrNotExist) {
  389. return nil, nil, nil
  390. }
  391. return nil, nil, err
  392. }
  393. tlsCertificate, leaf, err := parseKeyPair(certificateResource.CertificatePEM, certificateResource.PrivateKeyPEM)
  394. if err != nil {
  395. return nil, nil, E.Cause(err, "parse cached key pair")
  396. }
  397. if s.timeFunc().After(leaf.NotAfter) {
  398. return nil, nil, nil
  399. }
  400. if !s.matchesCertificate(leaf) {
  401. return nil, nil, nil
  402. }
  403. return tlsCertificate, leaf, nil
  404. }
  405. func (s *Service) matchesCertificate(leaf *x509.Certificate) bool {
  406. if leaf == nil {
  407. return false
  408. }
  409. leafHostnames := leaf.DNSNames
  410. if len(leafHostnames) == 0 && leaf.Subject.CommonName != "" {
  411. leafHostnames = []string{leaf.Subject.CommonName}
  412. }
  413. normalizedLeafHostnames, err := normalizeHostnames(leafHostnames)
  414. if err != nil {
  415. return false
  416. }
  417. if !slices.Equal(normalizedLeafHostnames, s.domain) {
  418. return false
  419. }
  420. switch s.requestType {
  421. case option.CloudflareOriginCARequestTypeOriginRSA:
  422. return leaf.PublicKeyAlgorithm == x509.RSA
  423. case option.CloudflareOriginCARequestTypeOriginECC:
  424. return leaf.PublicKeyAlgorithm == x509.ECDSA
  425. default:
  426. return false
  427. }
  428. }
  429. func (s *Service) setCurrentCertificate(certificate *tls.Certificate, leaf *x509.Certificate) {
  430. s.access.Lock()
  431. s.currentCertificate = certificate
  432. s.currentLeaf = leaf
  433. s.access.Unlock()
  434. }
  435. func normalizeHostnames(hostnames []string) ([]string, error) {
  436. normalizedHostnames := make([]string, 0, len(hostnames))
  437. seen := make(map[string]struct{}, len(hostnames))
  438. for _, hostname := range hostnames {
  439. normalizedHostname := strings.ToLower(strings.TrimSpace(strings.TrimSuffix(hostname, ".")))
  440. if normalizedHostname == "" {
  441. return nil, E.New("hostname is empty")
  442. }
  443. if net.ParseIP(normalizedHostname) != nil {
  444. return nil, E.New("hostname cannot be an IP address: ", normalizedHostname)
  445. }
  446. if strings.Contains(normalizedHostname, "*") {
  447. if !strings.HasPrefix(normalizedHostname, "*.") || strings.Count(normalizedHostname, "*") != 1 {
  448. return nil, E.New("invalid wildcard hostname: ", normalizedHostname)
  449. }
  450. suffix := strings.TrimPrefix(normalizedHostname, "*.")
  451. if strings.Count(suffix, ".") == 0 {
  452. return nil, E.New("wildcard hostname must cover a multi-label domain: ", normalizedHostname)
  453. }
  454. normalizedHostname = "*." + suffix
  455. }
  456. if _, loaded := seen[normalizedHostname]; loaded {
  457. continue
  458. }
  459. seen[normalizedHostname] = struct{}{}
  460. normalizedHostnames = append(normalizedHostnames, normalizedHostname)
  461. }
  462. slices.Sort(normalizedHostnames)
  463. return normalizedHostnames, nil
  464. }
  465. func parseKeyPair(certificatePEM []byte, privateKeyPEM []byte) (*tls.Certificate, *x509.Certificate, error) {
  466. keyPair, err := tls.X509KeyPair(certificatePEM, privateKeyPEM)
  467. if err != nil {
  468. return nil, nil, err
  469. }
  470. if len(keyPair.Certificate) == 0 {
  471. return nil, nil, E.New("certificate chain is empty")
  472. }
  473. leaf, err := x509.ParseCertificate(keyPair.Certificate[0])
  474. if err != nil {
  475. return nil, nil, err
  476. }
  477. keyPair.Leaf = leaf
  478. return &keyPair, leaf, nil
  479. }
  480. func storeCertificateResource(ctx context.Context, storage certmagic.Storage, issuerKey string, certificateResource certmagic.CertificateResource) error {
  481. metaBytes, err := json.MarshalIndent(certificateResource, "", "\t")
  482. if err != nil {
  483. return err
  484. }
  485. namesKey := certificateResource.NamesKey()
  486. keyValueList := []struct {
  487. key string
  488. value []byte
  489. }{
  490. {
  491. key: certmagic.StorageKeys.SitePrivateKey(issuerKey, namesKey),
  492. value: certificateResource.PrivateKeyPEM,
  493. },
  494. {
  495. key: certmagic.StorageKeys.SiteCert(issuerKey, namesKey),
  496. value: certificateResource.CertificatePEM,
  497. },
  498. {
  499. key: certmagic.StorageKeys.SiteMeta(issuerKey, namesKey),
  500. value: metaBytes,
  501. },
  502. }
  503. for i, item := range keyValueList {
  504. err = storage.Store(ctx, item.key, item.value)
  505. if err != nil {
  506. for j := i - 1; j >= 0; j-- {
  507. storage.Delete(ctx, keyValueList[j].key)
  508. }
  509. return err
  510. }
  511. }
  512. return nil
  513. }
  514. func loadCertificateResource(ctx context.Context, storage certmagic.Storage, issuerKey string, namesKey string) (certmagic.CertificateResource, error) {
  515. privateKeyPEM, err := storage.Load(ctx, certmagic.StorageKeys.SitePrivateKey(issuerKey, namesKey))
  516. if err != nil {
  517. return certmagic.CertificateResource{}, err
  518. }
  519. certificatePEM, err := storage.Load(ctx, certmagic.StorageKeys.SiteCert(issuerKey, namesKey))
  520. if err != nil {
  521. return certmagic.CertificateResource{}, err
  522. }
  523. metaBytes, err := storage.Load(ctx, certmagic.StorageKeys.SiteMeta(issuerKey, namesKey))
  524. if err != nil {
  525. return certmagic.CertificateResource{}, err
  526. }
  527. var certificateResource certmagic.CertificateResource
  528. err = json.Unmarshal(metaBytes, &certificateResource)
  529. if err != nil {
  530. return certmagic.CertificateResource{}, E.Cause(err, "decode Cloudflare Origin CA certificate metadata")
  531. }
  532. certificateResource.PrivateKeyPEM = privateKeyPEM
  533. certificateResource.CertificatePEM = certificatePEM
  534. return certificateResource, nil
  535. }
  536. func buildOriginCAError(statusCode int, responseErrors []originCAResponseError, responseBody []byte) error {
  537. if len(responseErrors) > 0 {
  538. messageList := make([]string, 0, len(responseErrors))
  539. for _, responseError := range responseErrors {
  540. if responseError.Message == "" {
  541. continue
  542. }
  543. if responseError.Code != 0 {
  544. messageList = append(messageList, responseError.Message+" (code "+strconv.Itoa(responseError.Code)+")")
  545. } else {
  546. messageList = append(messageList, responseError.Message)
  547. }
  548. }
  549. if len(messageList) > 0 {
  550. return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode, " ", strings.Join(messageList, ", "))
  551. }
  552. }
  553. responseText := strings.TrimSpace(string(responseBody))
  554. if responseText == "" {
  555. return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode)
  556. }
  557. return E.New("Cloudflare Origin CA request failed: HTTP ", statusCode, " ", responseText)
  558. }
  559. type originCARequest struct {
  560. CSR string `json:"csr"`
  561. Hostnames []string `json:"hostnames"`
  562. RequestType string `json:"request_type"`
  563. RequestedValidity uint16 `json:"requested_validity"`
  564. }
  565. type originCAResponse struct {
  566. Success bool `json:"success"`
  567. Errors []originCAResponseError `json:"errors"`
  568. Result originCAResponseResult `json:"result"`
  569. }
  570. type originCAResponseError struct {
  571. Code int `json:"code"`
  572. Message string `json:"message"`
  573. }
  574. type originCAResponseResult struct {
  575. Certificate string `json:"certificate"`
  576. }
  577. type originCAIssuerData struct {
  578. RequestType option.CloudflareOriginCARequestType `json:"request_type,omitempty"`
  579. RequestedValidity option.CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
  580. }