From 8a67b37557748c5e301d0e770c5ad64d9b8e578c Mon Sep 17 00:00:00 2001 From: Jesse Peterson Date: Mon, 29 May 2017 19:43:43 -0700 Subject: [PATCH] Add anchor certificate to DEP profile template (#181) * Add anchor certificate to DEP profile template * Add -use-server-cert flag to generate certificate from server TLS certificate * Update CHANGELOG * Support adding certificate chains (rather than just individual certs) to DEP anchor certs * Resolves #107 --- CHANGELOG.md | 1 + cmd/mdmctl/apply_dep_profile.go | 65 ++++++++++++++++++++++++++++++--- crypto/helpers.go | 34 +++++++++++++++++ 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 433a3a89..efa84d71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v1.0.1 TBD +* Option to include SSL certificates in DEP profile template (-anchor and -use-server-cert) #107 * /push and /v1/commands API endpoints require API authentication #157 * Add `mdmctl` binary for interacting with the server over API. #127 * Save DEP cursor for use after restart. #109 diff --git a/cmd/mdmctl/apply_dep_profile.go b/cmd/mdmctl/apply_dep_profile.go index bc5e9001..7bf03279 100644 --- a/cmd/mdmctl/apply_dep_profile.go +++ b/cmd/mdmctl/apply_dep_profile.go @@ -2,20 +2,43 @@ package main import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "flag" "fmt" + "net/url" "os" "github.com/micromdm/dep" + "github.com/micromdm/micromdm/crypto" "github.com/pkg/errors" ) +func certificatesFromURL(serverURL string, insecure bool) ([]*x509.Certificate, error) { + urlParsed, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + addr := urlParsed.Host + if urlParsed.Port() == "" { + addr += ":443" + } + conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: insecure}) + if err != nil { + return nil, err + } + defer conn.Close() + return conn.ConnectionState().PeerCertificates, nil +} + func (cmd *applyCommand) applyDEPProfile(args []string) error { flagset := flag.NewFlagSet("dep-profiles", flag.ExitOnError) var ( flProfilePath = flagset.String("f", "", "filename of DEP profile to apply") flTemplate = flagset.Bool("template", false, "print a JSON example of a DEP profile") + flAnchorFile = flagset.String("anchor", "", "filename of PEM certificate to add to trusted anchors") + flUseServer = flagset.Bool("use-server-cert", false, "use the certificate presented by the server") ) flagset.Usage = usageFor(flagset, "mdmctl apply dep-profiles [flags]") if err := flagset.Parse(args); err != nil { @@ -23,8 +46,22 @@ func (cmd *applyCommand) applyDEPProfile(args []string) error { } if *flTemplate { - printDEPProfileTemplate() - return nil + var anchorCerts []*x509.Certificate + if *flAnchorFile != "" { + certs, err := crypto.ReadPEMCertificatesFile(*flAnchorFile) + if err != nil { + return err + } + anchorCerts = append(anchorCerts, certs...) + } + if *flUseServer { + certs, err := certificatesFromURL(cmd.config.ServerURL, cmd.config.SkipVerify) + if err != nil { + return err + } + anchorCerts = append(anchorCerts, certs...) + } + return printDEPProfileTemplate(anchorCerts) } if *flProfilePath == "" { @@ -54,9 +91,24 @@ func (cmd *applyCommand) applyDEPProfile(args []string) error { return nil } -func printDEPProfileTemplate() { +func printDEPProfileTemplate(anchorCerts []*x509.Certificate) error { + var anchorCertStr string = "[]" - resp := ` + // convert certificates into base64 encoded strings + // json.Marshal does this for us for byte[] arrays + if len(anchorCerts) > 0 { + var certs [][]byte + for _, cert := range anchorCerts { + certs = append(certs, cert.Raw) + } + jsonBytes, err := json.Marshal(certs) + if err != nil { + return nil + } + anchorCertStr = string(jsonBytes) + } + + resp := fmt.Sprintf(` { "profile_name": "(Required) Human readable name", "url": "https://mymdm.example.org/mdm/enroll", @@ -69,12 +121,13 @@ func printDEPProfileTemplate() { "support_phone_number": "(Optional) +1 408 555 1010", "support_email_address": "(Optional) support@example.com", "org_magic": "(Optional)", - "anchor_certs": [], + "anchor_certs": %s, "supervising_host_certs": [], "skip_setup_items": ["AppleID", "Android"], "department": "(Optional) support@example.com", "devices": ["SERIAL1","SERIAL2"] } -` +`, anchorCertStr) fmt.Println(resp) + return nil } diff --git a/crypto/helpers.go b/crypto/helpers.go index c0b427c0..27271eac 100644 --- a/crypto/helpers.go +++ b/crypto/helpers.go @@ -6,6 +6,8 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" + "io/ioutil" "math/big" "os" "time" @@ -51,6 +53,38 @@ func SimpleSelfSignedRSAKeypair(cn string, days int) (key *rsa.PrivateKey, cert return key, cert, err } +func ReadPEMCertificateFile(path string) (*x509.Certificate, error) { + certs, err := ReadPEMCertificatesFile(path) + if err != nil { + return nil, err + } + if len(certs) != 1 { + return nil, errors.New("incorrect number of certificates") + } + return certs[0], nil +} + +func ReadPEMCertificatesFile(path string) ([]*x509.Certificate, error) { + pemData, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var asn1data []byte + rest := pemData + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil || block.Type != "CERTIFICATE" { + return nil, errors.New("failed to decode PEM block containing certificate") + } + asn1data = append(asn1data, block.Bytes...) + if len(rest) == 0 { + break + } + } + return x509.ParseCertificates(asn1data) +} + func WritePEMCertificateFile(cert *x509.Certificate, path string) error { file, err := os.Create(path) if err != nil {