forked from micromdm/micromdm
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmdmcert.download.go
More file actions
284 lines (249 loc) · 9.12 KB
/
Copy pathmdmcert.download.go
File metadata and controls
284 lines (249 loc) · 9.12 KB
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
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/fullsailor/pkcs7"
"github.com/go-kit/kit/log"
"github.com/micromdm/micromdm/pkg/crypto"
"github.com/micromdm/micromdm/pkg/crypto/mdmcertutil"
"github.com/pkg/errors"
)
const (
mdmcertRequestURL = "https://mdmcert.download/api/v1/signrequest"
// see
// https://github.com/jessepeterson/commandment/blob/1352b51ba6697260d1111eccc3a5a0b5b9af60d0/commandment/mdmcert.py#L23-L28
mdmcertAPIKey = "f847aea2ba06b41264d587b229e2712c89b1490a1208b7ff1aafab5bb40d47bc"
)
// format of a signing request to mdmcert.download
type signRequest struct {
CSR string `json:"csr"` // base64 encoded PEM CSR
Email string `json:"email"`
Key string `json:"key"` // server key from above
Encrypt string `json:"encrypt"` // mdmcert pki cert
}
type mdmcertDownloadCommand struct {
*remoteServices
}
func (cmd *mdmcertDownloadCommand) setup() error {
logger := log.NewLogfmtLogger(os.Stderr)
remote, err := setupClient(logger)
if err != nil {
return err
}
cmd.remoteServices = remote
return nil
}
func (cmd *mdmcertDownloadCommand) Usage() error {
const usageText = `
Request new MDM Push Certificate from https://mdmcert.download
This utility helps obtain an MDM Push Certificate using the service
at mdmcert.download.
First we'll generate the initial request (which also generates a private key):
mdmctl mdmcert.download -new -email=cool.mdm.admin@example.org
This will output the private key into the file mdmcert.download.key.
Then, after you check your email and download the request file you just
need to decrypt the push certificate request:
mdmctl mdmcert.download -decrypt=~/Downloads/mdm_signed_request.20171122_094910_220.plist.b64.p7
This will output the push certificate request to mdmcert.download.req.
Upload this file to https://identity.apple.com and download the signed
certificate. Then use the 'mdmctl mdmcert upload' command to upload it,
(and the above private key) into MicroMDM.
`
fmt.Println(usageText)
return nil
}
func (cmd *mdmcertDownloadCommand) Run(args []string) error {
flagset := flag.NewFlagSet("mdmcert.download", flag.ExitOnError)
flagset.Usage = usageFor(flagset, "mdmctl mdmcert.download [flags]")
var (
flNew = flagset.Bool("new", false, "Generates a new privkey and uploads new MDM request")
flDecrypt = flagset.String("decrypt", "", "Decrypts and mdmcert.download push certificate request")
flEmail = flagset.String("email", "", "Email address to use in mdmcert request & CSR Subject")
flCountry = flagset.String("country", "US", "Two letter country code for the CSR Subject (example: US).")
flCN = flagset.String("cn", "mdm-push", "CommonName for the CSR Subject.")
flCertPath = flagset.String("pki-cert", "mdmcert.download.pki.crt", "Path for generated MDMCert pki exchange certificate")
flKeyPath = flagset.String("pki-private-key", "mdmcert.download.pki.key", "Path for generated MDMCert pki exchange private key")
flPKeyPass = flagset.String("pki-password", "", "Password to encrypt/read the RSA key.")
flCCSRPath = flagset.String("push-csr", "mdmcert.download.push.csr", "Path for generated Push Certificate CSR")
flCReqPath = flagset.String("push-req", "mdmcert.download.push.req", "Path for generated Push Certificate Request")
flCKeyPath = flagset.String("push-private-key", "mdmcert.download.push.key", "Path to the generated Push Cert private key")
flCPKeyPass = flagset.String("push-password", "", "Password to encrypt/read the push RSA key.")
)
if err := flagset.Parse(args); err != nil {
cmd.Usage()
return err
}
// neither flag was used
if !*flNew && *flDecrypt == "" {
cmd.Usage()
return errors.New("bad input: must either use -new or -decrypt")
}
// both flags used
if *flNew && (*flDecrypt != "") {
// cmd.Usage()
return errors.New("bad input: can't use both -new and -decrypt")
}
if *flNew {
if *flEmail == "" {
return errors.New("bad input: must provide -email")
}
paths := []string{*flCertPath, *flKeyPath, *flCCSRPath, *flCKeyPath}
for _, path := range paths {
if _, err := os.Stat(path); err == nil {
return fmt.Errorf("file already exists: %s", path)
}
}
pkiKey, pkiCert, err := crypto.SimpleSelfSignedRSAKeypair("mdmcert.download", 365)
if err != nil {
return errors.Wrap(err, "could not create PKI keypair")
}
pemBlock := &pem.Block{
Type: "CERTIFICATE",
Headers: nil,
Bytes: pkiCert.Raw,
}
pemPkiCert := pem.EncodeToMemory(pemBlock)
if err := crypto.WritePEMCertificateFile(pkiCert, *flCertPath); err != nil {
return errors.Wrap(err, "could not write PKI cert")
}
if *flPKeyPass != "" {
err = crypto.WriteEncryptedPEMRSAKeyFile(pkiKey, []byte(*flPKeyPass), *flKeyPath)
} else {
err = crypto.WritePEMRSAKeyFile(pkiKey, *flKeyPath)
}
if err != nil {
return errors.Wrap(err, "could not write private key")
}
pushKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return errors.Wrap(err, "could not generate push private key")
}
if *flCPKeyPass != "" {
err = crypto.WriteEncryptedPEMRSAKeyFile(pushKey, []byte(*flCPKeyPass), *flCKeyPath)
} else {
err = crypto.WritePEMRSAKeyFile(pushKey, *flCKeyPath)
}
if err != nil {
return errors.Wrap(err, "could not write push private key")
}
derBytes, err := mdmcertutil.NewCSR(pushKey, *flEmail, *flCountry, *flCN)
if err != nil {
return errors.Wrap(err, "could not generate push CSR")
}
pemCSR := mdmcertutil.PemCSR(derBytes)
// Do we even need to write-out the CSR?
err = ioutil.WriteFile(*flCCSRPath, pemCSR, 0600)
if err != nil {
return errors.Wrap(err, "could not write PEM file")
}
sign := newMdmcertDownloadSignRequest(*flEmail, pemCSR, pemPkiCert)
req, err := sign.HTTPRequest()
if err != nil {
return errors.Wrap(err, "could not create http request")
}
err = sendMdmcertDownloadRequest(http.DefaultClient, req)
if err != nil {
return errors.Wrap(err, "error sending http request")
}
fmt.Print("Request successfully sent to mdmcert.download. Your CSR should now\n" +
"be signed. Check your email for next steps. Then use the -decrypt option\n" +
"to extract the CSR request which will then be uploaded to Apple.\n")
} else { // -decrypt switch
if _, err := os.Stat(*flCReqPath); err == nil {
return fmt.Errorf("file already exists: %s", *flCReqPath)
}
hexBytes, err := ioutil.ReadFile(*flDecrypt)
if err != nil {
return errors.Wrap(err, "reading encrypted file")
}
pkcsBytes, err := hex.DecodeString(string(hexBytes))
if err != nil {
return errors.Wrap(err, "error decoding hex")
}
pkiCert, err := crypto.ReadPEMCertificateFile(*flCertPath)
if err != nil {
return errors.Wrap(err, "reading PKI certificate")
}
var pkiKey *rsa.PrivateKey
if *flPKeyPass != "" {
pkiKey, err = crypto.ReadEncryptedPEMRSAKeyFile(*flKeyPath, []byte(*flPKeyPass))
} else {
pkiKey, err = crypto.ReadPEMRSAKeyFile(*flKeyPath)
}
if err != nil {
return errors.Wrap(err, "reading PKI private key")
}
ioutil.WriteFile("/tmp/fubar.p7", pkcsBytes, 0666)
p7, err := pkcs7.Parse(pkcsBytes)
if err != nil {
return errors.Wrap(err, "parsing mdmcert PKCS7 response")
}
// fmt.Println(p7)
content, err := p7.Decrypt(pkiCert, pkiKey)
if err != nil {
return errors.Wrap(err, "decrypting mdmcert PKCS7 response")
}
err = ioutil.WriteFile(*flCReqPath, content, 0666)
if err != nil {
return errors.Wrap(err, "writing Push Request response")
}
fmt.Printf("Successfully able to decrypt the MDM Push Certificate request! Please upload\n"+
"the file '%s' to Apple by visiting https://identity.apple.com\n"+
"Once your Push Certificate is signed by Apple you can download it\n"+
"and import it into MicroMDM using the `mdmctl mdmcert upload` command\n", *flCReqPath)
}
return nil
}
func newMdmcertDownloadSignRequest(email string, pemCSR []byte, serverCertificate []byte) *signRequest {
encodedCSR := base64.StdEncoding.EncodeToString(pemCSR)
encodedServerCert := base64.StdEncoding.EncodeToString(serverCertificate)
return &signRequest{
CSR: encodedCSR,
Email: email,
Key: mdmcertAPIKey,
Encrypt: encodedServerCert,
}
}
func (sign *signRequest) HTTPRequest() (*http.Request, error) {
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(sign); err != nil {
return nil, err
}
req, err := http.NewRequest("POST", mdmcertRequestURL, ioutil.NopCloser(buf))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", "micromdm/certhelper")
return req, nil
}
func sendMdmcertDownloadRequest(client *http.Client, req *http.Request) error {
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received bad status from mdmcert.download. status=%q", resp.Status)
}
var jsn = struct {
Result string
}{}
if err := json.NewDecoder(resp.Body).Decode(&jsn); err != nil {
return err
}
if jsn.Result != "success" {
return fmt.Errorf("got unexpected result body: %q\n", jsn.Result)
}
return nil
}