Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

Commit

Permalink
add mdmcertdownload request (#1)
Browse files Browse the repository at this point in the history
* add mdmcertdownload request

* document decode function and add help text for the user.
  • Loading branch information
groob authored Dec 2, 2016
1 parent 51d4509 commit a53d3e9
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 16 deletions.
68 changes: 67 additions & 1 deletion certhelper/certhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"

Expand Down Expand Up @@ -48,13 +49,25 @@ func main() {
providerCSRCountry := providerCMD.String("country", "US", "two letter country flag for CSR Subject(example: US)")
providerCSRCName := providerCMD.String("cn", "", "common name for certificate request")
providerPKeyPass := providerCMD.String("password", "", "rsa private key password")

// mdmcert.download
mdmcertdCMD := flag.NewFlagSet("mdmcert.download", flag.ExitOnError)
var (
mdmcertdEmail = mdmcertdCMD.String("email", "", "email address registered with mdmcert.download")
mdmcertdCSR = mdmcertdCMD.String("csr", providerCSRFilename, "path to PEM encoded csr. default generated by provider command.")
mdmcertdCERT = mdmcertdCMD.String("cert", "", "PEM encoded server certificate. Use the TLS certificate of your mdm server.")
mdmcertdPriv = mdmcertdCMD.String("key", "", "path to PEM encoded server private key(for use with -decode)")
mdmcertdPass = mdmcertdCMD.String("password", "", "rsa private key password(for server cert)")
mdmcertdDec = mdmcertdCMD.String("decode", "", "path to mdm_signed_request.p7 file")
)
// general flags
flVersion := flag.Bool("version", false, "prints the version")
// set usage
flag.Usage = func() {
fmt.Println("usage: certhelper <command> [<args>]")
fmt.Println(" vendor <args> manage mdm vendor certs")
fmt.Println(" provider <args> manage certs as a provider(mdm server administrator)")
fmt.Println(" mdmcert.download <args> request a certificate from mdmcert.download(mdm server administrator)")
fmt.Println("type <command> --help to see usage for each subcommand")
}

Expand All @@ -76,6 +89,8 @@ func main() {
vendorCMD.Parse(os.Args[2:])
case "provider":
providerCMD.Parse(os.Args[2:])
case "mdmcert.download":
mdmcertdCMD.Parse(os.Args[2:])
default:
fmt.Printf("%q is not valid command.\n", os.Args[1])
os.Exit(2)
Expand Down Expand Up @@ -149,6 +164,57 @@ func main() {
}
}
}

if mdmcertdCMD.Parsed() {
if *mdmcertdDec != "" {
if err := decodeSignedRequest(*mdmcertdDec, *mdmcertdCERT, *mdmcertdPriv, *mdmcertdPass); err != nil {
fmt.Printf("err: %s\n", err)
os.Exit(1)
}
fmt.Printf("mdmcert.download_%s created. You can now upload it to https://identity.apple.com/\n", pushRequestFilename)
fmt.Println("Once signed by Apple, you will be able to send push notifications to MDM enrolled devices.")
return
}

fmt.Printf("requesting cert from mdmcert.download with \n\temail=%q\n\tcsr=%q\n\tservercert=%q\n",
*mdmcertdEmail,
*mdmcertdCSR,
*mdmcertdCERT,
)
e := new(errReader)
csrBytes := e.ReadFile(*mdmcertdCSR)
certBytes := e.ReadFile(*mdmcertdCERT)
if e.err != nil {
fmt.Printf("err: %s\n", e.err)
os.Exit(1)
}
sign := newSignRequest(*mdmcertdEmail, csrBytes, certBytes)
req, err := sign.HTTPRequest()
if err != nil {
fmt.Printf("err: %s\n", err)
os.Exit(1)
}
client := http.DefaultClient
if err := sendRequest(client, req); err != nil {
fmt.Printf("err: %s\n", err)
os.Exit(1)
}
fmt.Println(`mdmcert.download successfuly signed your certificate. check your email for next steps.
the -decode flag can be used to extract the certificate in the email attachment.`)
}
}

type errReader struct {
err error
}

func (e *errReader) ReadFile(path string) []byte {
if e.err != nil {
return nil
}
var d []byte
d, e.err = ioutil.ReadFile(path)
return d
}

func checkCSRFlags(cname, country, email string, password []byte) error {
Expand Down Expand Up @@ -186,7 +252,7 @@ type csrRequest struct {

// create a private key and CSR and save both to disk
func makeCSR(req *csrRequest) error {
key, err := newRSAKey(2048)
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
Expand Down
21 changes: 7 additions & 14 deletions certhelper/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ func encryptedKey(key *rsa.PrivateKey, password []byte) ([]byte, error) {
return out, nil
}

// create a new RSA private key
func newRSAKey(bits int) (*rsa.PrivateKey, error) {
private, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
return private, nil
}

// load an encrypted private key from disk
func loadKeyFromFile(path string, password []byte) (*rsa.PrivateKey, error) {
data, err := ioutil.ReadFile(path)
Expand All @@ -49,10 +40,12 @@ func loadKeyFromFile(path string, password []byte) (*rsa.PrivateKey, error) {
return nil, errors.New("unmatched type or headers")
}

b, err := x509.DecryptPEMBlock(pemBlock, password)
if err != nil {
return nil, err
if string(password) != "" {
b, err := x509.DecryptPEMBlock(pemBlock, password)
if err != nil {
return nil, err
}
return x509.ParsePKCS1PrivateKey(b)
}
return x509.ParsePKCS1PrivateKey(b)

return x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
}
125 changes: 125 additions & 0 deletions certhelper/mdmcert_download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Integration with Jesse's mdmcert.download

package main

import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net/http"

"github.com/fullsailor/pkcs7"
)

const (
mdmcertRequestURL = "https://mdmcert.download/api/v1/signrequest"
// see
// https://github.com/jessepeterson/commandment/blob/1352b51ba6697260d1111eccc3a5a0b5b9af60d0/commandment/mdmcert.py#L23-L28
mdmcertServerKey = "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"` // server cert
}

func newSignRequest(email string, pemCSR []byte, serverCertificate []byte) *signRequest {
encodedCSR := base64.StdEncoding.EncodeToString(pemCSR)
encodedServerCert := base64.StdEncoding.EncodeToString(serverCertificate)
return &signRequest{
CSR: encodedCSR,
Email: email,
Key: mdmcertServerKey,
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 sendRequest(client *http.Client, req *http.Request) error {
resp, err := client.Do(req)
if err != nil {
return nil
}
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
}

// The user will receive a hex encoded p7 file as an email attachment.
// the file contents is a pkcs7 file, encrypted using the server certificate as the
// intended recipient.
// We use the server private key to decode the pkcs7 envelope and extract a
// base64 encoded plist (same format as the once created by `mapkePushRequestPlist`
// Once the pkcs7 file is decrypted, we save the file to disk for the user to upload
// to identity.apple.com for a push certificate.
func decodeSignedRequest(p7Path, certPath, privPath, privPass string) error {
hexBytes, err := ioutil.ReadFile(p7Path)
if err != nil {
return err
}
key, err := loadKeyFromFile(privPath, []byte(privPass))
if err != nil {
return err
}
certPemBytes, err := ioutil.ReadFile(certPath)
if err != nil {
return err
}
pemBlock, _ := pem.Decode(certPemBytes)
if pemBlock == nil {
return errors.New("PEM decode failed")
}
if pemBlock.Type != "CERTIFICATE" {
return errors.New("unmatched type or headers")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return err
}
pkcsBytes, err := hex.DecodeString(string(hexBytes))
if err != nil {
return err
}
p7, err := pkcs7.Parse(pkcsBytes)
if err != nil {
return err
}
content, err := p7.Decrypt(cert, key)
if err != nil {
return err
}
return ioutil.WriteFile(fmt.Sprintf("mdmcert.download_%s", pushRequestFilename), content, 0666)
}
48 changes: 48 additions & 0 deletions certhelper/mdmcert_download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"encoding/json"
"reflect"
"testing"
)

func Test_signRequest_HTTP(t *testing.T) {
tests := []struct {
name string
fields *signRequest
wantErr bool
}{
{
name: "default",
fields: newSignRequest("[email protected]", []byte("fakecsr"), []byte("fakecert")),
},
}
for _, tt := range tests {
sign := &signRequest{
CSR: tt.fields.CSR,
Email: tt.fields.Email,
Key: tt.fields.Key,
Encrypt: tt.fields.Encrypt,
}
got, err := sign.HTTPRequest()
if (err != nil) != tt.wantErr {
t.Errorf("%q. signRequest.HTTP() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}

if have, want := got.Header.Get("Content-Type"), "application/json"; have != want {
t.Errorf("have %q, want %q\n", have, want)
}
if have, want := got.Method, "POST"; have != want {
t.Errorf("have %q, want %q\n", have, want)
}

var have signRequest
if err := json.NewDecoder(got.Body).Decode(&have); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(have, *sign) {
t.Errorf("have %#v, want %#v", have, *sign)
}
}
}
2 changes: 1 addition & 1 deletion certhelper/release.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

VERSION="1.0.1"
VERSION="1.2.0"
NAME=certhelper
OUTPUT=../build

Expand Down

0 comments on commit a53d3e9

Please sign in to comment.