forked from albertogviana/easyrsa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
easyrsa.go
204 lines (161 loc) · 5.34 KB
/
easyrsa.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package easyrsa
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
"regexp"
)
// EasyRSA struct
type EasyRSA struct {
Config
}
// Config has all the configuration needed to run easyrsa
type Config struct {
BinDir string // Easy-RSA top-level dir, where the easyrsa script is located.
PKIDir string // Used to hold all PKI-specific files
CommonName string // Common name used to generate the certificates
KeySize int // Set the keysize in bits to generate
CAExpire int // In how many days should the root CA key expire?
ServerName string // Server name
CountryCode string
Province string
City string
Organization string
Email string
OrganizationUnit string
}
const errCAAlreadyExist = "Easy-RSA error:\n\nUnable to create a CA as you already seem to have one set up.\nIf you intended to start a new CA, run init-pki first.\n"
// NewEasyRSA returns an instance of EasyRSA
func NewEasyRSA(config Config) (*EasyRSA, error) {
err := validate(config)
if err != nil {
return nil, err
}
if config.KeySize == 0 {
config.KeySize = 2048
}
if config.CAExpire == 0 {
config.CAExpire = 3650
}
easyRSA := &EasyRSA{
config,
}
return easyRSA, nil
}
// InitPKI initializes a directory for the PKI.
func (e *EasyRSA) InitPKI() error {
_, privateErr := os.Stat(path.Join(e.PKIDir, "private"))
_, reqsErr := os.Stat(path.Join(e.PKIDir, "reqs"))
if privateErr == nil && reqsErr == nil {
return nil
}
return e.run("init-pki")
}
// BuildCA generates the Certificate Authority (CA)
func (e *EasyRSA) BuildCA() error {
err := e.run("build-ca", "nopass")
if err == nil {
return nil
}
re := regexp.MustCompile("Easy-RSA error:(?s)(.*)")
regexResult := re.FindString(string(err.Error()))
if regexResult == errCAAlreadyExist {
return errors.New(errCAAlreadyExist)
}
return err
}
// GenReq generates a keypair and request
func (e *EasyRSA) GenReq(requestName string) error {
return e.run("gen-req", requestName, "nopass")
}
// SignReq signs a request, and you can have the following types:
// - client - A TLS client, suitable for a VPN user or web browser (web client)
// - server - A TLS server, suitable for a VPN or web server
func (e *EasyRSA) SignReq(typeSign, requestName string) error {
if typeSign != "server" && typeSign != "client" {
return errors.New("invalid type, please use server or client")
}
return e.run("sign-req", typeSign, requestName)
}
// ImportReq import requests from external systems that are requesting
// a signed certificate from this CA
func (e *EasyRSA) ImportReq(requestFile, requestName string) error {
if _, err := os.Stat(requestFile); os.IsNotExist(err) {
return err
}
return e.run("import-req", requestFile, requestName)
}
// GenDH creates a strong Diffie-Hellman key to use during key exchange
func (e *EasyRSA) GenDH() error {
return e.run("gen-dh")
}
// Revoke revokes a certificate, after you revoke the certificate it is a
// good practive to update the CRL file, and send it to the VPN Server
// https://github.com/OpenVPN/easy-rsa/blob/v3.0.6/doc/EasyRSA-Readme.md#revoking-and-publishing-crls
func (e *EasyRSA) Revoke(requestName string) error {
return e.run("revoke", requestName)
}
// GenCRL generates a CRL suitable for publishing to systems that rely, otherwise
// the revoke certificate will be available
func (e *EasyRSA) GenCRL() error {
return e.run("gen-crl")
}
func (e *EasyRSA) getEnvironmentVariable() []string {
var vars []string
vars = append(vars, fmt.Sprintf("EASYRSA=%s", e.BinDir))
vars = append(vars, fmt.Sprintf("EASYRSA_PKI=%s", e.PKIDir))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_CN=%s", e.CommonName))
vars = append(vars, fmt.Sprintf("EASYRSA_CA_EXPIRE=%d", e.CAExpire))
vars = append(vars, fmt.Sprintf("EASYRSA_KEY_SIZE=%d", e.KeySize))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_COUNTRY=%s", e.CountryCode))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_PROVINCE=%s", e.Province))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_CITY=%s", e.City))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_ORG=%s", e.Organization))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_EMAIL=%s", e.Email))
vars = append(vars, fmt.Sprintf("EASYRSA_REQ_OU=%s", e.OrganizationUnit))
vars = append(vars, "EASYRSA_BATCH=1")
return vars
}
func (e *EasyRSA) run(args ...string) error {
environment := e.getEnvironmentVariable()
var stderrBuf bytes.Buffer
cmd := exec.Command(path.Join(e.BinDir, "easyrsa"), args...)
cmd.Env = append(os.Environ(), environment...)
stderrIn, _ := cmd.StderrPipe()
stderr := io.MultiWriter(os.Stderr, &stderrBuf)
cmd.Stdout = os.Stdout
err := cmd.Start()
if err != nil {
return fmt.Errorf("cmd.Start() failed with '%s'", err)
}
go func() {
io.Copy(stderr, stderrIn)
}()
err = cmd.Wait()
if err == nil {
return nil
}
return errors.New(string(stderrBuf.Bytes()))
}
func validate(config Config) error {
if config.BinDir == "" {
return errors.New("the path to easy-rsa directory was not define")
}
if config.PKIDir == "" {
return errors.New("the path to the pki directory was not define")
}
if config.CommonName == "" {
return errors.New("the common name was not define")
}
if _, err := os.Stat(config.BinDir); os.IsNotExist(err) {
return err
}
if _, err := os.Stat(path.Join(config.BinDir, "easyrsa")); os.IsNotExist(err) {
return err
}
return nil
}