Skip to content

Commit 8c0fb41

Browse files
committed
Initial commit
1 parent 372f44a commit 8c0fb41

12 files changed

Lines changed: 1533 additions & 0 deletions

File tree

Makefile

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
SHELL := bash
2+
.ONESHELL:
3+
.SHELLFLAGS := -eu -o pipefail -c
4+
MAKEFLAGS += --warn-undefined-variables
5+
MAKEFLAGS += --no-builtin-rules
6+
7+
BUILDDATE := $(shell date +'%Y-%m-%d')
8+
BUILDTIME := $(shell date +'%H:%M:%S')
9+
10+
CURPATH := ${shell pwd}
11+
12+
APP := devcert
13+
#APP_INPUT := ${CURPATH}/main.go
14+
APP_INPUT := .
15+
APP_OUTPUT := /usr/local/bin/${APP}
16+
17+
RELEASE_OUTPUT := ./.bin/${APP}
18+
RELEASE_WIN_AMD64_EXT := ${RELEASE_OUTPUT}_win_amd64.exe
19+
RELEASE_DARWIN_AMD64_EXT := ${RELEASE_OUTPUT}_darwin_amd64
20+
RELEASE_DARWIN_ARM64_EXT := ${RELEASE_OUTPUT}_darwin_arm64
21+
RELEASE_LINUX_AMD64_EXT := ${RELEASE_OUTPUT}_linux_amd64
22+
RELEASE_LINUX_ARM64_EXT := ${RELEASE_OUTPUT}_linux_arm64
23+
24+
.PHONY: clean format release
25+
26+
update-deps:
27+
go mod tidy
28+
go get -u ./...
29+
30+
clean:
31+
go clean ${APP_INPUT}
32+
33+
format:
34+
gofmt -w ${CURPATH}
35+
36+
build:
37+
go build \
38+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
39+
-o $(APP_OUTPUT) \
40+
$(APP_INPUT)
41+
42+
release-win-amd64:
43+
env GOOS=windows GOARCH=amd64 \
44+
go build \
45+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
46+
-o $(RELEASE_WIN_AMD64_EXT) \
47+
$(APP_INPUT)
48+
49+
release-darwin-amd64:
50+
env GOOS=darwin GOARCH=amd64 \
51+
go build \
52+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
53+
-o $(RELEASE_DARWIN_AMD64_EXT) \
54+
$(APP_INPUT)
55+
56+
57+
release-darwin-arm64:
58+
env GOOS=darwin GOARCH=arm64 \
59+
go build \
60+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
61+
-o $(RELEASE_DARWIN_ARM64_EXT) \
62+
$(APP_INPUT)
63+
64+
release-linux-amd64:
65+
env GOOS=linux GOARCH=amd64 \
66+
go build \
67+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
68+
-o $(RELEASE_LINUX_AMD64_EXT) \
69+
$(APP_INPUT)
70+
71+
release-linux-arm64:
72+
env GOOS=linux GOARCH=arm64 \
73+
go build \
74+
-ldflags "-X main.BuildDate=$(BUILDDATE) -X main.BuildTime=$(BUILDTIME)" \
75+
-o $(RELEASE_LINUX_ARM64_EXT) \
76+
$(APP_INPUT)
77+
78+
79+
compile-releases: release-win-amd64 release-darwin-amd64 release-darwin-arm64 release-linux-amd64 release-linux-arm64
80+
echo "Compiled releases"
81+
82+
exec: clean format build
83+
${APP_OUTPUT}

cert_auth.go

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"crypto/rand"
6+
"crypto/rsa"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
10+
"errors"
11+
"fmt"
12+
"io/fs"
13+
"io/ioutil"
14+
"math/big"
15+
mrand "math/rand"
16+
"os"
17+
"strings"
18+
"time"
19+
)
20+
21+
// CA represents a certificate authority cert and private key.
22+
type CA struct {
23+
Valid bool
24+
Cert *x509.Certificate
25+
PrivKey *rsa.PrivateKey
26+
}
27+
28+
func buildCAPaths() (crtPath, keyPath string, err error) {
29+
devcertDir, err := buildDevcertDir()
30+
if err != nil {
31+
err = fmt.Errorf("Building CA paths failed: %w", err)
32+
return
33+
}
34+
35+
var crtB, keyB strings.Builder
36+
crtB.WriteString(devcertDir)
37+
crtB.WriteString("devcert_ca.crt")
38+
39+
keyB.WriteString(devcertDir)
40+
keyB.WriteString("devcert_ca.key")
41+
42+
crtPath = crtB.String()
43+
keyPath = keyB.String()
44+
45+
return
46+
}
47+
48+
// loadCA will load the certificate authority data from the files.
49+
func loadCA() (ca *CA, err error) {
50+
51+
certPath, keyPath, err := buildCAPaths()
52+
if err != nil {
53+
err = fmt.Errorf("Loading CA failed: %w", err)
54+
return
55+
}
56+
57+
caExist, err := caFilesExist()
58+
if err != nil {
59+
err = fmt.Errorf("Loading CA failed: %w", err)
60+
return
61+
}
62+
63+
ca = &CA{
64+
Valid: false,
65+
}
66+
67+
// Files doesn't exist
68+
if caExist == false {
69+
return
70+
}
71+
72+
crtBytes, err := ioutil.ReadFile(certPath)
73+
if err != nil {
74+
return ca, nil
75+
}
76+
77+
keyBytes, err := ioutil.ReadFile(keyPath)
78+
if err != nil {
79+
return ca, nil
80+
}
81+
82+
crtBlock, _ := pem.Decode(crtBytes)
83+
ca.Cert, err = x509.ParseCertificate(crtBlock.Bytes)
84+
if err != nil {
85+
return ca, nil
86+
}
87+
88+
keyBlock, _ := pem.Decode(keyBytes)
89+
ca.PrivKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
90+
if err != nil {
91+
return ca, nil
92+
}
93+
94+
// Check if the CA has expired
95+
now := time.Now()
96+
if ca.Cert.NotBefore.After(now) == true || ca.Cert.NotAfter.Before(now) == true {
97+
return ca, nil
98+
}
99+
100+
// Certificate is valid
101+
ca.Valid = true
102+
103+
return ca, nil
104+
}
105+
106+
// caFilesExist check if the certificate authority files exist.
107+
func caFilesExist() (exist bool, err error) {
108+
certPath, keyPath, err := buildCAPaths()
109+
if err != nil {
110+
err = fmt.Errorf("Checking CA files exist failed: %w", err)
111+
return
112+
}
113+
114+
// Check cert file
115+
_, err = os.Stat(certPath)
116+
certNotExist := errors.Is(err, fs.ErrNotExist)
117+
118+
if err != nil && certNotExist == false {
119+
err = fmt.Errorf("Checking CA files exist failed: %w", err)
120+
return
121+
}
122+
123+
// Check key file
124+
_, err = os.Stat(keyPath)
125+
keyNotExist := errors.Is(err, fs.ErrNotExist)
126+
127+
if err != nil && keyNotExist == false {
128+
err = fmt.Errorf("Checking CA files exist failed: %w", err)
129+
return
130+
}
131+
132+
err = nil
133+
if certNotExist == false && keyNotExist == false {
134+
exist = true
135+
}
136+
137+
return
138+
}
139+
140+
func createCA() (err error) {
141+
fmt.Printf("Creating certificate authority (CA) files...\n")
142+
143+
ca, err := loadCA()
144+
if err != nil {
145+
err = fmt.Errorf("Creating CA failed: %w", err)
146+
return
147+
}
148+
149+
// Certificate is valid, nothing to do.
150+
if ca.Valid == true {
151+
fmt.Printf("Certificate authority (CA) already created.\n")
152+
return
153+
}
154+
155+
// Certificate is invalid and the CA files exists, remove and re-generate it.
156+
157+
exist, err := caFilesExist()
158+
if err != nil {
159+
err = fmt.Errorf("Creating CA failed: %w", err)
160+
return
161+
}
162+
163+
// Remove files if exist
164+
if exist == true {
165+
err = removeCAFiles()
166+
if err != nil {
167+
err = fmt.Errorf("Creating CA failed: %w", err)
168+
return
169+
}
170+
}
171+
172+
// Create CA
173+
err = generateCA()
174+
if err != nil {
175+
fmt.Errorf("Creating CA failed: %w", err)
176+
return
177+
}
178+
179+
return
180+
}
181+
182+
// generateCA creates the certificate authority files.
183+
func generateCA() (err error) {
184+
ca := &x509.Certificate{
185+
SerialNumber: big.NewInt(mrand.Int63()),
186+
Subject: pkix.Name{
187+
CommonName: "Devcert Certificate Authority (CA)",
188+
},
189+
NotBefore: time.Now(),
190+
NotAfter: time.Now().AddDate(5, 0, 0),
191+
IsCA: true,
192+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
193+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
194+
BasicConstraintsValid: true,
195+
}
196+
197+
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
198+
if err != nil {
199+
err = fmt.Errorf("Generating CA failed: %w", err)
200+
return
201+
}
202+
203+
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
204+
if err != nil {
205+
err = fmt.Errorf("Generating CA failed: %w", err)
206+
return
207+
}
208+
209+
// Create the files on the file system
210+
crtPath, keyPath, err := buildCAPaths()
211+
if err != nil {
212+
err = fmt.Errorf("Generating CA failed: %w", err)
213+
return
214+
}
215+
216+
caFile, err := os.Create(crtPath)
217+
if err != nil {
218+
err = fmt.Errorf("Generating CA failed: %w", err)
219+
return
220+
}
221+
222+
defer caFile.Close()
223+
224+
caPrivKeyFile, err := os.Create(keyPath)
225+
if err != nil {
226+
err = fmt.Errorf("Generating CA failed: %w", err)
227+
return
228+
}
229+
230+
defer caPrivKeyFile.Close()
231+
232+
caPEMWriter := bufio.NewWriter(caFile)
233+
pem.Encode(caPEMWriter, &pem.Block{
234+
Type: "CERTIFICATE",
235+
Bytes: caBytes,
236+
})
237+
238+
caPEMWriter.Flush()
239+
240+
caPrivKeyPEMWriter := bufio.NewWriter(caPrivKeyFile)
241+
pem.Encode(caPrivKeyPEMWriter, &pem.Block{
242+
Type: "RSA PRIVATE KEY",
243+
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
244+
})
245+
246+
caPrivKeyPEMWriter.Flush()
247+
248+
fmt.Printf("Certificate authority (CA) created at\n Certificate: %s\n Private Key: %s\n", crtPath, keyPath)
249+
250+
return
251+
}
252+
253+
// removeCAFiles removes the CA crt and key files from the devcert folder.
254+
func removeCAFiles() (err error) {
255+
crtPath, keyPath, err := buildCAPaths()
256+
if err != nil {
257+
err = fmt.Errorf("Removing CA files failed: %w", err)
258+
return
259+
}
260+
261+
err = os.Remove(crtPath)
262+
if err != nil {
263+
err = fmt.Errorf("Removing CA files failed: %w", err)
264+
return
265+
}
266+
267+
err = os.Remove(keyPath)
268+
if err != nil {
269+
err = fmt.Errorf("Removing CA files failed: %w", err)
270+
return
271+
}
272+
273+
return
274+
}

cleanup.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"os"
5+
)
6+
7+
// attemptCleanupDevcertDir attempts to remove the .devcert directory. Errors are suppressed.
8+
func attemptCleanupDevcertDir() {
9+
devcertDir, err := buildDevcertDir()
10+
if err != nil {
11+
return
12+
}
13+
14+
os.Remove(devcertDir)
15+
}
16+
17+
// attemptCleanupCA attempts to remove the CA files. Errors are suppressed.
18+
func attemptCleanupCA() {
19+
crtPath, keyPath, err := buildCAPaths()
20+
if err != nil {
21+
return
22+
}
23+
24+
os.Remove(crtPath)
25+
os.Remove(keyPath)
26+
}

0 commit comments

Comments
 (0)