forked from micromdm/micromdm
/
mdmcert.go
315 lines (265 loc) · 10.1 KB
/
mdmcert.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package main
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/go-kit/kit/log"
"github.com/pkg/errors"
"golang.org/x/crypto/pkcs12"
"github.com/micromdm/micromdm/pkg/crypto"
"github.com/micromdm/micromdm/pkg/crypto/mdmcertutil"
)
type mdmcertCommand struct {
*remoteServices
}
func (cmd *mdmcertCommand) setup() error {
logger := log.NewLogfmtLogger(os.Stderr)
remote, err := setupClient(logger)
if err != nil {
return err
}
cmd.remoteServices = remote
return nil
}
func (cmd *mdmcertCommand) Usage() error {
const usageText = `
Create new MDM Push Certificate.
This utility helps obtain a MDM Push Certificate using the Apple Developer MDM CSR option in the enterprise developer portal.
First you must create a vendor CSR which you will upload to the enterprise developer portal and get a signed MDM Vendor certificate. Use the MDM-CSR option in the dev portal when creating the certificate.
The MDM Vendor certificate is required in order to obtain the MDM push certificate. After you complete the MDM-CSR step, copy the downloaded file to the same folder as the private key. By default this will be
mdm-certificates/
mdmctl mdmcert vendor -password=secret -country=US -email=admin@acme.co
Next, create a push CSR. This step generates a CSR required to get a signed a push certificate.
mdmctl mdmcert push -password=secret -country=US -email=admin@acme.co
Once you created the push CSR, you mush sign the push CSR with the MDM Vendor Certificate, and get a push certificate request file.
mdmctl mdmcert vendor -sign -cert=./mdm-certificates/mdm.cer -password=secret
Once generated, upload the PushCertificateRequest.plist file to https://identity.apple.com to obtain your MDM Push Certificate.
Use the push private key and the push cert you got from identity.apple.com in your MDM server.
Commands:
vendor
push
upload
`
fmt.Println(usageText)
return nil
}
func (cmd *mdmcertCommand) Run(args []string) error {
if len(args) < 1 {
cmd.Usage()
os.Exit(1)
}
if err := cmd.setup(); err != nil {
return err
}
var run func([]string) error
switch strings.ToLower(args[0]) {
case "vendor":
run = cmd.runVendor
case "push":
run = cmd.runPush
case "upload":
run = cmd.runUpload
default:
cmd.Usage()
os.Exit(1)
}
return run(args[1:])
}
const (
pushCSRFilename = "PushCertificateRequest.csr"
pushCertificatePrivateKeyFilename = "PushCertificatePrivateKey.key"
vendorPKeyFilename = "VendorPrivateKey.key"
vendorCSRFilename = "VendorCertificateRequest.csr"
pushRequestFilename = "PushCertificateRequest.plist"
mdmcertdir = "mdm-certificates"
)
func (cmd *mdmcertCommand) runVendor(args []string) error {
flagset := flag.NewFlagSet("vendor", flag.ExitOnError)
flagset.Usage = usageFor(flagset, "mdmctl mdmcert vendor [flags]")
var (
flSign = flagset.Bool("sign", false, "Signs a user CSR with the MDM vendor certificate.")
flEmail = flagset.String("email", "", "Email address to use in CSR Subject.")
flCountry = flagset.String("country", "US", "Two letter country code for the CSR Subject(example: US).")
flCN = flagset.String("cn", "micromdm-vendor", "CommonName for the CSR Subject.")
flPKeyPass = flagset.String("password", "", "Password to encrypt/read the RSA key.")
flVendorcertPath = flagset.String("cert", filepath.Join(mdmcertdir, "mdm.cer"), "Path to the MDM Vendor certificate from dev portal.")
flPushCSRPath = flagset.String("push-csr", filepath.Join(mdmcertdir, pushCSRFilename), "Path to the user CSR(required for the -sign step).")
flKeyPath = flagset.String("private-key", filepath.Join(mdmcertdir, vendorPKeyFilename), "Path to the vendor private key. A new RSA key will be created at this path.")
flCSRPath = flagset.String("out", filepath.Join(mdmcertdir, vendorCSRFilename), "Path to save the MDM Vendor CSR.")
)
if err := flagset.Parse(args); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(*flCSRPath), 0755); err != nil {
return errors.Wrapf(err, "create directory %s", filepath.Dir(*flCSRPath))
}
password := []byte(*flPKeyPass)
if *flSign {
request, err := mdmcertutil.CreatePushCertificateRequest(
*flVendorcertPath,
*flPushCSRPath,
*flKeyPath,
password,
)
if err != nil {
return errors.Wrap(err, "signing push certificate request with vendor private key")
}
encoded, err := request.Encode()
if err != nil {
return errors.Wrap(err, "encode base64 push certificate request")
}
err = ioutil.WriteFile(filepath.Join(mdmcertdir, pushRequestFilename), encoded, 0600)
return errors.Wrapf(err, "write %s to file", pushRequestFilename)
}
if err := checkCSRFlags(*flCN, *flCountry, *flEmail, password); err != nil {
return errors.Wrap(err, "Private key password, CN, Email, and country code must be specified when creating a CSR.")
}
request := &mdmcertutil.CSRConfig{
CommonName: *flCN,
Country: *flCountry,
Email: *flEmail,
PrivateKeyPassword: password,
PrivateKeyPath: *flKeyPath,
CSRPath: *flCSRPath,
}
err := mdmcertutil.CreateCSR(request)
return errors.Wrap(err, "creating MDM vendor CSR")
}
func (cmd *mdmcertCommand) runPush(args []string) error {
flagset := flag.NewFlagSet("push", flag.ExitOnError)
flagset.Usage = usageFor(flagset, "mdmctl mdmcert push [flags]")
var (
flEmail = flagset.String("email", "", "Email address to use in CSR Subject.")
flCountry = flagset.String("country", "US", "Two letter country code for the CSR Subject(Example: US).")
flCN = flagset.String("cn", "micromdm-user", "CommonName for the CSR Subject.")
flPKeyPass = flagset.String("password", "", "Password to encrypt/read the RSA key.")
flKeyPath = flagset.String("private-key", filepath.Join(mdmcertdir, pushCertificatePrivateKeyFilename), "Path to the push certificate private key. A new RSA key will be created at this path.")
flCSRPath = flagset.String("out", filepath.Join(mdmcertdir, pushCSRFilename), "Path to save the MDM Push Certificate request.")
)
if err := flagset.Parse(args); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(*flCSRPath), 0755); err != nil {
errors.Wrapf(err, "create directory %s", filepath.Dir(*flCSRPath))
}
password := []byte(*flPKeyPass)
if err := checkCSRFlags(*flCN, *flCountry, *flEmail, password); err != nil {
return errors.Wrap(err, "Private key password, CN, Email, and country code must be specified when creating a CSR.")
}
request := &mdmcertutil.CSRConfig{
CommonName: *flCN,
Country: *flCountry,
Email: *flEmail,
PrivateKeyPassword: password,
PrivateKeyPath: *flKeyPath,
CSRPath: *flCSRPath,
}
err := mdmcertutil.CreateCSR(request)
return errors.Wrap(err, "creating MDM Push certificate request.")
}
func (cmd *mdmcertCommand) runUpload(args []string) error {
flagset := flag.NewFlagSet("upload", flag.ExitOnError)
flagset.Usage = usageFor(flagset, "mdmctl mdmcert upload [flags]")
var (
flKeyPass = flagset.String("password", "", "Password to encrypt/read the RSA key.")
flKeyPath = flagset.String("private-key", filepath.Join(mdmcertdir, pushCertificatePrivateKeyFilename), "Path to the push certificate private key.")
flCertPath = flagset.String("cert", "", "Path to the MDM Push Certificate.")
)
if err := flagset.Parse(args); err != nil {
return err
}
cert, key, err := loadPushCerts(*flCertPath, *flKeyPath, *flKeyPass)
if err != nil {
return errors.Wrap(err, "load push certificate")
}
if err := cmd.configsvc.SavePushCertificate(context.Background(), cert, key); err != nil {
return errors.Wrap(err, "upload push certificate and key to server")
}
return nil
}
func loadPushCerts(certPath, keyPath, keyPass string) (cert, key []byte, err error) {
isP12 := filepath.Ext(certPath) == ".p12"
if isP12 {
pkcs12Data, err := ioutil.ReadFile(certPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "read p12 path %s", certPath)
}
pkeyi, certificate, err := pkcs12.Decode(pkcs12Data, keyPass)
if err != nil {
return nil, nil, errors.Wrap(err, "decode pkcs12 file")
}
pkey, ok := pkeyi.(*rsa.PrivateKey)
if !ok {
return nil, nil, errors.New("private key not a valid rsa key")
}
pemKey := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(pkey),
})
pemCert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certificate.Raw,
})
return pemCert, pemKey, nil
}
keyData, err := ioutil.ReadFile(keyPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "read push certificate private key at path %s", keyPath)
}
keyDataBlock, _ := pem.Decode(keyData)
if keyDataBlock == nil {
return nil, nil, errors.Errorf("invalid PEM data for private key %s", keyPath)
}
var pemKeyData []byte
if x509.IsEncryptedPEMBlock(keyDataBlock) {
b, err := x509.DecryptPEMBlock(keyDataBlock, []byte(keyPass))
if err != nil {
return nil, nil, fmt.Errorf("decrypting DES private key %s", err)
}
pemKeyData = b
} else {
pemKeyData = keyDataBlock.Bytes
}
priv, err := x509.ParsePKCS1PrivateKey(pemKeyData)
if err != nil {
return nil, nil, errors.Wrapf(err, "parse push certiificate private key %s", keyPath)
}
pemKey := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
})
certificate, err := crypto.ReadPEMCertificateFile(certPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "read push certificate from pem file %s", certPath)
}
pemCert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certificate.Raw,
})
return pemCert, pemKey, nil
}
func checkCSRFlags(cname, country, email string, password []byte) error {
if cname == "" {
return errors.New("cn flag not specified")
}
if email == "" {
return errors.New("email flag not specified")
}
if country == "" {
return errors.New("country flag not specified")
}
if len(password) == 0 {
return errors.New("private key password empty")
}
if len(country) != 2 {
return errors.New("must be a two letter country code")
}
return nil
}