1package cloudflare
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"io/ioutil"
8	"net/http"
9	"net/url"
10	"time"
11
12	"github.com/pkg/errors"
13)
14
15// OriginCACertificate represents a Cloudflare-issued certificate.
16//
17// API reference: https://api.cloudflare.com/#cloudflare-ca
18type OriginCACertificate struct {
19	ID              string    `json:"id"`
20	Certificate     string    `json:"certificate"`
21	Hostnames       []string  `json:"hostnames"`
22	ExpiresOn       time.Time `json:"expires_on"`
23	RequestType     string    `json:"request_type"`
24	RequestValidity int       `json:"requested_validity"`
25	RevokedAt       time.Time `json:"revoked_at,omitempty"`
26	CSR             string    `json:"csr"`
27}
28
29// UnmarshalJSON handles custom parsing from an API response to an OriginCACertificate
30// http://choly.ca/post/go-json-marshalling/
31func (c *OriginCACertificate) UnmarshalJSON(data []byte) error {
32	type alias OriginCACertificate
33
34	aux := &struct {
35		ExpiresOn string `json:"expires_on"`
36		*alias
37	}{
38		alias: (*alias)(c),
39	}
40
41	var err error
42
43	if err = json.Unmarshal(data, &aux); err != nil {
44		return err
45	}
46
47	// This format comes from time.Time.String() source
48	c.ExpiresOn, err = time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", aux.ExpiresOn)
49
50	if err != nil {
51		c.ExpiresOn, err = time.Parse(time.RFC3339, aux.ExpiresOn)
52	}
53
54	if err != nil {
55		return err
56	}
57
58	return nil
59}
60
61// OriginCACertificateListOptions represents the parameters used to list Cloudflare-issued certificates.
62type OriginCACertificateListOptions struct {
63	ZoneID string
64}
65
66// OriginCACertificateID represents the ID of the revoked certificate from the Revoke Certificate endpoint.
67type OriginCACertificateID struct {
68	ID string `json:"id"`
69}
70
71// originCACertificateResponse represents the response from the Create Certificate and the Certificate Details endpoints.
72type originCACertificateResponse struct {
73	Response
74	Result OriginCACertificate `json:"result"`
75}
76
77// originCACertificateResponseList represents the response from the List Certificates endpoint.
78type originCACertificateResponseList struct {
79	Response
80	Result     []OriginCACertificate `json:"result"`
81	ResultInfo ResultInfo            `json:"result_info"`
82}
83
84// originCACertificateResponseRevoke represents the response from the Revoke Certificate endpoint.
85type originCACertificateResponseRevoke struct {
86	Response
87	Result OriginCACertificateID `json:"result"`
88}
89
90// CreateOriginCertificate creates a Cloudflare-signed certificate.
91//
92// This function requires api.APIUserServiceKey be set to your Certificates API key.
93//
94// API reference: https://api.cloudflare.com/#cloudflare-ca-create-certificate
95func (api *API) CreateOriginCertificate(ctx context.Context, certificate OriginCACertificate) (*OriginCACertificate, error) {
96	uri := "/certificates"
97	res, err := api.makeRequestWithAuthType(ctx, http.MethodPost, uri, certificate, AuthUserService)
98
99	if err != nil {
100		return nil, err
101	}
102
103	var originResponse *originCACertificateResponse
104
105	err = json.Unmarshal(res, &originResponse)
106
107	if err != nil {
108		return nil, errors.Wrap(err, errUnmarshalError)
109	}
110
111	if !originResponse.Success {
112		return nil, errors.New(errRequestNotSuccessful)
113	}
114
115	return &originResponse.Result, nil
116}
117
118// OriginCertificates lists all Cloudflare-issued certificates.
119//
120// This function requires api.APIUserServiceKey be set to your Certificates API key.
121//
122// API reference: https://api.cloudflare.com/#cloudflare-ca-list-certificates
123func (api *API) OriginCertificates(ctx context.Context, options OriginCACertificateListOptions) ([]OriginCACertificate, error) {
124	v := url.Values{}
125	if options.ZoneID != "" {
126		v.Set("zone_id", options.ZoneID)
127	}
128	uri := fmt.Sprintf("/certificates?%s", v.Encode())
129	res, err := api.makeRequestWithAuthType(ctx, http.MethodGet, uri, nil, AuthUserService)
130
131	if err != nil {
132		return nil, err
133	}
134
135	var originResponse *originCACertificateResponseList
136
137	err = json.Unmarshal(res, &originResponse)
138
139	if err != nil {
140		return nil, errors.Wrap(err, errUnmarshalError)
141	}
142
143	if !originResponse.Success {
144		return nil, errors.New(errRequestNotSuccessful)
145	}
146
147	return originResponse.Result, nil
148}
149
150// OriginCertificate returns the details for a Cloudflare-issued certificate.
151//
152// This function requires api.APIUserServiceKey be set to your Certificates API key.
153//
154// API reference: https://api.cloudflare.com/#cloudflare-ca-certificate-details
155func (api *API) OriginCertificate(ctx context.Context, certificateID string) (*OriginCACertificate, error) {
156	uri := fmt.Sprintf("/certificates/%s", certificateID)
157	res, err := api.makeRequestWithAuthType(ctx, http.MethodGet, uri, nil, AuthUserService)
158
159	if err != nil {
160		return nil, err
161	}
162
163	var originResponse *originCACertificateResponse
164
165	err = json.Unmarshal(res, &originResponse)
166
167	if err != nil {
168		return nil, errors.Wrap(err, errUnmarshalError)
169	}
170
171	if !originResponse.Success {
172		return nil, errors.New(errRequestNotSuccessful)
173	}
174
175	return &originResponse.Result, nil
176}
177
178// RevokeOriginCertificate revokes a created certificate for a zone.
179//
180// This function requires api.APIUserServiceKey be set to your Certificates API key.
181//
182// API reference: https://api.cloudflare.com/#cloudflare-ca-revoke-certificate
183func (api *API) RevokeOriginCertificate(ctx context.Context, certificateID string) (*OriginCACertificateID, error) {
184	uri := fmt.Sprintf("/certificates/%s", certificateID)
185	res, err := api.makeRequestWithAuthType(ctx, http.MethodDelete, uri, nil, AuthUserService)
186
187	if err != nil {
188		return nil, err
189	}
190
191	var originResponse *originCACertificateResponseRevoke
192
193	err = json.Unmarshal(res, &originResponse)
194
195	if err != nil {
196		return nil, errors.Wrap(err, errUnmarshalError)
197	}
198
199	if !originResponse.Success {
200		return nil, errors.New(errRequestNotSuccessful)
201	}
202
203	return &originResponse.Result, nil
204}
205
206// Gets the Cloudflare Origin CA Root Certificate for a given algorithm in PEM format.
207// Algorithm must be one of ['ecc', 'rsa'].
208func OriginCARootCertificate(algorithm string) ([]byte, error) {
209	var url string
210	switch algorithm {
211	case "ecc":
212		url = originCARootCertEccURL
213	case "rsa":
214		url = originCARootCertRsaURL
215	default:
216		return nil, fmt.Errorf("invalid algorithm: must be one of ['ecc', 'rsa']")
217	}
218
219	resp, err := http.Get(url)
220	if err != nil {
221		return nil, errors.Wrap(err, "HTTP request failed")
222	}
223	defer resp.Body.Close()
224	body, err := ioutil.ReadAll(resp.Body)
225	if err != nil {
226		return nil, errors.Wrap(err, "Response body could not be read")
227	}
228
229	return body, nil
230}
231