cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.5.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

sshgen/sshgen.go

188lines · modeblame

fa17b020Austin Cherry7 years ago1package sshgen
2
3import (
4"bytes"
5"crypto/ecdsa"
6"crypto/elliptic"
7"crypto/rand"
8"crypto/x509"
9"encoding/json"
10"encoding/pem"
11"errors"
38d78f98Austin Cherry7 years ago12"fmt"
13"io"
fa17b020Austin Cherry7 years ago14"io/ioutil"
15"net/http"
16"net/url"
17"time"
18
19"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
20cfpath "github.com/cloudflare/cloudflared/cmd/cloudflared/path"
21"github.com/coreos/go-oidc/jose"
22homedir "github.com/mitchellh/go-homedir"
23gossh "golang.org/x/crypto/ssh"
24)
25
26const (
27signEndpoint = "/cdn-cgi/access/cert_sign"
28keyName = "cf_key"
29)
30
31// signPayload represents the request body sent to the sign handler API
32type signPayload struct {
33PublicKey string `json:"public_key"`
34JWT string `json:"jwt"`
35Issuer string `json:"issuer"`
36}
37
38// signResponse represents the response body from the sign handler API
39type signResponse struct {
40KeyID string `json:"id"`
41Certificate string `json:"certificate"`
42ExpiresAt time.Time `json:"expires_at"`
43}
44
38d78f98Austin Cherry7 years ago45// ErrorResponse struct stores error information after any error-prone function
46type errorResponse struct {
47Status int `json:"status"`
48Message string `json:"message"`
49}
50
51var mockRequest func(url, contentType string, body io.Reader) (*http.Response, error) = nil
52
fa17b020Austin Cherry7 years ago53// GenerateShortLivedCertificate generates and stores a keypair for short lived certs
54func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
55fullName, err := cfpath.GenerateFilePathFromURL(appURL, keyName)
56if err != nil {
57return err
58}
59
60cert, err := handleCertificateGeneration(token, fullName)
61if err != nil {
62return err
63}
64
65name := fullName + "-cert.pub"
66if err := writeKey(name, []byte(cert)); err != nil {
67return err
68}
69
70return nil
71}
72
73// handleCertificateGeneration takes a JWT and uses it build a signPayload
74// to send to the Sign endpoint with the public key from the keypair it generated
75func handleCertificateGeneration(token, fullName string) (string, error) {
76if token == "" {
77return "", errors.New("invalid token")
78}
79
80jwt, err := jose.ParseJWT(token)
81if err != nil {
82return "", err
83}
84
85claims, err := jwt.Claims()
86if err != nil {
87return "", err
88}
89
90issuer, _, err := claims.StringClaim("iss")
91if err != nil {
92return "", err
93}
94
95pub, err := generateKeyPair(fullName)
96if err != nil {
97return "", err
98}
99
100buf, err := json.Marshal(&signPayload{
101PublicKey: string(pub),
102JWT: token,
103Issuer: issuer,
104})
105if err != nil {
106return "", err
107}
108
38d78f98Austin Cherry7 years ago109var res *http.Response
110if mockRequest != nil {
111res, err = mockRequest(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
112} else {
113res, err = http.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
114}
115
fa17b020Austin Cherry7 years ago116if err != nil {
117return "", err
118}
119defer res.Body.Close()
120
121decoder := json.NewDecoder(res.Body)
38d78f98Austin Cherry7 years ago122
123if res.StatusCode != 200 {
124var errResponse errorResponse
125if err := decoder.Decode(&errResponse); err != nil {
126return "", err
127}
128return "", fmt.Errorf("%d: %s", errResponse.Status, errResponse.Message)
129}
130
fa17b020Austin Cherry7 years ago131var signRes signResponse
132if err := decoder.Decode(&signRes); err != nil {
133return "", err
134}
135return signRes.Certificate, err
136}
137
138// generateKeyPair creates a EC keypair (P256) and stores them in the homedir.
139// returns the generated public key from the successful keypair generation
140func generateKeyPair(fullName string) ([]byte, error) {
141pubKeyName := fullName + ".pub"
142
143exist, err := config.FileExists(pubKeyName)
144if err != nil {
145return nil, err
146}
147if exist {
148return ioutil.ReadFile(pubKeyName)
149}
150
151key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
152if err != nil {
153return nil, err
154}
155parsed, err := x509.MarshalECPrivateKey(key)
156if err != nil {
157return nil, err
158}
159
160if err := writeKey(fullName, pem.EncodeToMemory(&pem.Block{
161Type: "EC PRIVATE KEY",
162Bytes: parsed,
163})); err != nil {
164return nil, err
165}
166
167pub, err := gossh.NewPublicKey(&key.PublicKey)
168if err != nil {
169return nil, err
170}
171data := gossh.MarshalAuthorizedKey(pub)
172
173if err := writeKey(pubKeyName, data); err != nil {
174return nil, err
175}
176
177return data, nil
178}
179
180// writeKey will write a key to disk in DER format (it's a standard pem key)
181func writeKey(filename string, data []byte) error {
182filepath, err := homedir.Expand(filename)
183if err != nil {
184return err
185}
186
187return ioutil.WriteFile(filepath, data, 0600)
188}