Implement deptoken sub-command helper. Generates self-signed cert for working with DEP & decrypts deploy.apple.com tokens.

This commit is contained in:
Jesse Peterson
2017-03-18 02:18:39 -07:00
parent 952fd560df
commit 4dcc18907d
3 changed files with 274 additions and 1 deletions

246
deptoken.go Normal file
View File

@@ -0,0 +1,246 @@
package main
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"net/textproto"
"os"
"path"
"time"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"github.com/boltdb/bolt"
"github.com/fullsailor/pkcs7"
"github.com/micromdm/dep"
)
const (
depTokenRSAKeyFilename = "deptoken.key"
depTokenCertFilename = "deptoken.pem"
depTokenBucket = "mdm.DEPToken"
)
type DEPTokenJSON struct {
ConsumerKey string `json:"consumer_key"`
ConsumerSecret string `json:"consumer_secret"`
AccessToken string `json:"access_token"`
AccessSecret string `json:"access_secret"`
AccessTokenExpiry time.Time `json:"access_token_expiry"`
}
func depToken(args []string) error {
flagset := flag.NewFlagSet("deptoken", flag.ExitOnError)
var (
flPublicKey = flagset.String("public-key", "", "filename of public key to write (to be uploaded to deploy.apple.com)")
flTokenFile = flagset.String("token", "", "filename of p7 encrypted token file")
)
flagset.Usage = usageFor(flagset, "micromdm deptoken [flags]")
if err := flagset.Parse(args); err != nil {
return err
}
keyPath := path.Join(configDBPath, depTokenRSAKeyFilename)
var pk *rsa.PrivateKey
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
// key doesn't yet exist, make it
pk, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
pemBlock := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)}
certOut, err := os.Create(keyPath)
if err != nil {
return err
}
pem.Encode(certOut, &pemBlock)
certOut.Close()
// fmt.Println("generated and saved key", keyPath)
} else {
// key exists, load it
pemKey, err := ioutil.ReadFile(keyPath)
if err != nil {
return err
}
block, _ := pem.Decode(pemKey)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return errors.New("invalid DEP token private key")
}
if pk, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
return err
}
// fmt.Println("loaded key", keyPath)
}
certPath := path.Join(configDBPath, depTokenCertFilename)
var cert []byte
if _, err := os.Stat(certPath); os.IsNotExist(err) {
// cert doesn't yet exist, make it
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "micromdm-dep-token",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &pk.PublicKey, pk)
if err != nil {
return err
}
certOut, err := os.Create(certPath)
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
certOut.Close()
// fmt.Println("generated and saved cert", certPath)
} else {
// cert exists, load it
pemCert, err := ioutil.ReadFile(certPath)
if err != nil {
return err
}
block, _ := pem.Decode(pemCert)
if block == nil || block.Type != "CERTIFICATE" {
return errors.New("invalid DEP token cert")
}
cert = block.Bytes
if _, err = x509.ParseCertificate(cert); err != nil {
return err
}
// fmt.Println("loaded cert", keyPath)
}
if *flPublicKey == "" && *flTokenFile == "" {
flagset.Usage()
return nil
}
if *flPublicKey != "" {
if _, err := os.Stat(certPath); err == nil {
return errors.New("public key filename already exists, please choose another")
}
certOut, err := os.Create(*flPublicKey)
if err != nil {
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
certOut.Close()
fmt.Println("wrote", *flPublicKey)
}
if *flTokenFile != "" {
f, err := os.Open(*flTokenFile)
if err != nil {
return err
}
defer f.Close()
tr := textproto.NewReader(bufio.NewReader(f))
if _, err := tr.ReadMIMEHeader(); err != nil {
return err
}
dec := base64.NewDecoder(base64.StdEncoding, tr.DotReader())
buf := new(bytes.Buffer)
io.Copy(buf, dec)
p7, err := pkcs7.Parse(buf.Bytes())
if err != nil {
return err
}
parsedCert, err := x509.ParseCertificate(cert)
if err != nil {
return err
}
decrypted, err := p7.Decrypt(parsedCert, pk)
if err != nil {
return err
}
// the contained decrypted data is also wrapped in a textproto-like
// wrapper. strip it, too.
tr = textproto.NewReader(bufio.NewReader(bytes.NewReader(decrypted)))
if _, err := tr.ReadMIMEHeader(); err != nil {
return err
}
body, err := ioutil.ReadAll(tr.DotReader())
// the body part of the textproto is an almost PEM-like structure.
// unpack it as well.
startFound := bytes.Index(body, []byte("-----BEGIN MESSAGE-----"))
endFound := bytes.Index(body, []byte("-----END MESSAGE-----"))
if endFound <= startFound {
return errors.New("invalid format in decrypted message")
}
// finally the JSON for the token values!
tokenJSON := body[startFound+24 : endFound-1]
depToken := &DEPTokenJSON{}
err = json.Unmarshal(tokenJSON, depToken)
if err != nil {
return err
}
// copy over values
depConfig := &dep.Config{}
depConfig.ConsumerKey = depToken.ConsumerKey
depConfig.ConsumerSecret = depToken.ConsumerSecret
depConfig.AccessToken = depToken.AccessToken
depConfig.AccessSecret = depToken.AccessSecret
sm := &config{}
sm.setupBolt()
err = sm.db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(depTokenBucket))
if err == nil {
err = b.Put([]byte(depConfig.ConsumerKey), tokenJSON)
}
return err
})
if err != nil {
return err
}
fmt.Println("saved token", depConfig.ConsumerKey)
}
return nil
}

View File

@@ -1,5 +1,5 @@
{
"memo": "8f28fd12310c838617234aced70466223894111f2be4f1696581595e4403d1ae",
"memo": "8083a2cf13559fb206e86028e580bb739c2704e8e052742970dd35b5424aaeb9",
"projects": [
{
"name": "github.com/RobotsAndPencils/buford",
@@ -19,6 +19,22 @@
"."
]
},
{
"name": "github.com/fullsailor/pkcs7",
"branch": "master",
"revision": "eb67e7e564b9eae64dc7d95fae0784d6086a5fc4",
"packages": [
"."
]
},
{
"name": "github.com/garyburd/go-oauth",
"branch": "master",
"revision": "4f191fd0442e75c0c73bd00e4f5f27d4c08312ee",
"packages": [
"oauth"
]
},
{
"name": "github.com/go-kit/kit",
"version": "v0.4.0",
@@ -94,6 +110,14 @@
"."
]
},
{
"name": "github.com/micromdm/dep",
"branch": "master",
"revision": "38c81b11544f31c2be14e0b3bcfa12f656fcc709",
"packages": [
"."
]
},
{
"name": "github.com/micromdm/mdm",
"branch": "master",

View File

@@ -23,6 +23,8 @@ func main() {
switch strings.ToLower(os.Args[1]) {
case "serve":
run = serve
case "deptoken":
run = depToken
default:
usage()
os.Exit(1)
@@ -39,6 +41,7 @@ func usage() error {
Available Commands:
serve
deptoken
version
Use micromdm <command> -h for additional usage of each command.