mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-08 02:25:34 +08:00
organize essential APIs into platform, workflow and pkg folders (#337)
Add more logic to the way code is organized. /pkg -- library code not directly connected to micromdm /mdm -- packages meant for the services devices interract with. The MDM protocol. /dep -- DEP API and related packages. /platform -- Core APIs the server provides. Commands API, Devices API, queue, pubsub etc. /workflow -- Packages/API that build on top of platform. Today that's the webhook package. Depending on what ends up here, the workflow folder might become its own repository.
This commit is contained in:
109
pkg/crypto/apple.go
Normal file
109
pkg/crypto/apple.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/*
|
||||
These certificates are not currently used but they represent the chain
|
||||
of certificates to verify a device's certificate in DEP & OTA requests.
|
||||
|
||||
const appleRootCAPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET
|
||||
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0
|
||||
MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw
|
||||
bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
|
||||
FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+
|
||||
+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1
|
||||
XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w
|
||||
tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW
|
||||
q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM
|
||||
aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E
|
||||
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
||||
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE
|
||||
ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93
|
||||
d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl
|
||||
IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0
|
||||
YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj
|
||||
b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp
|
||||
Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc
|
||||
NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP
|
||||
y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7
|
||||
R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg
|
||||
xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP
|
||||
IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX
|
||||
UKqK1drk/NAJBzewdXUh
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const appleiPhoneCertificateAuthorityPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIID8zCCAtugAwIBAgIBFzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET
|
||||
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDcwNDEyMTc0
|
||||
MzI4WhcNMjIwNDEyMTc0MzI4WjB5MQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw
|
||||
bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
|
||||
LTArBgNVBAMTJEFwcGxlIGlQaG9uZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMevvBHwLSeEFtGpLghuE+G
|
||||
IXAoRWBcHMPICmRjiPv8ae74VPzpW7cGTgQvw2szr0RM6kuACbSH9lu0/WTds3Lg
|
||||
E7P9F9m856jtwoxhwir57M6lXtZp62QLjQiPuKBQRgncGeTlsJRtu/eZmMTom0FO
|
||||
1PFl4xtSetzoA9luHdoQVYakKVhJDOpH1xU0M/bAoERKcL4stSowN4wuFevR5GyX
|
||||
OFVWsTUrWOpEoyaF7shmSuTPifA9Y60p3q26WrPcpaOapwlOgBY1ZaSFDWN7PmOK
|
||||
2n1KRuyjORg0ucYoZRi8E2Ccf1esFMmJ7aG2h2hStoROuMiD7PmeGauzwQuGx58C
|
||||
AwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
|
||||
HQ4EFgQU5zQqLiLeOWBrtJTOd4NhLzGgfDUwHwYDVR0jBBgwFoAUK9BpR5R2Cf70
|
||||
a40uQKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5hcHBsZS5j
|
||||
b20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAHdHVe910TtcX
|
||||
/IItDJmbXkJy8mnc1WteDQxrSz57FCXes5TooPoPgInyFz0AAqKRkb50V9yvmp+h
|
||||
Cn0wvgAqzCFZ6/1JrG51GeiaegPRhvbn9rAOS0n6o7dButfR41ahfYOrl674UUom
|
||||
wYVCEyaNA1RmEF5ghAUSMStrVMCgyEG8VB7nVK0TANJKx7vBiq+BCI7wRgq/J6a+
|
||||
3M85OoBwGSMyo2tmXZ5NqEdJsntFtVEzp3RnCU62bG9I9yy5MwVEa0W+dEtvsoaR
|
||||
tD4lKCWes8JRhvxP5a87qrtELAFJ4nSzNPpE7xTCEfItGRpRidMISkFsWFbemzrh
|
||||
BVflYs/SDw==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
*/
|
||||
|
||||
// TODO: This certificate expired 2014, but is required.
|
||||
const appleiPhoneDeviceCAPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIDaTCCAlGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB5MQswCQYDVQQGEwJVUzET
|
||||
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkxLTArBgNVBAMTJEFwcGxlIGlQaG9uZSBDZXJ0aWZpY2F0aW9u
|
||||
IEF1dGhvcml0eTAeFw0wNzA0MTYyMjU0NDZaFw0xNDA0MTYyMjU0NDZaMFoxCzAJ
|
||||
BgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMRUwEwYDVQQLEwxBcHBsZSBp
|
||||
UGhvbmUxHzAdBgNVBAMTFkFwcGxlIGlQaG9uZSBEZXZpY2UgQ0EwgZ8wDQYJKoZI
|
||||
hvcNAQEBBQADgY0AMIGJAoGBAPGUSsnquloYYK3Lok1NTlQZaRdZB2bLl+hmmkdf
|
||||
Rq5nerVKc1SxywT2vTa4DFU4ioSDMVJl+TPhl3ecK0wmsCU/6TKqewh0lOzBSzgd
|
||||
Z04IUpRai1mjXNeT9KD+VYW7TEaXXm6yd0UvZ1y8Cxi/WblshvcqdXbSGXH0KWO5
|
||||
JQuvAgMBAAGjgZ4wgZswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
HQYDVR0OBBYEFLL+ISNEhpVqedWBJo5zENinTI50MB8GA1UdIwQYMBaAFOc0Ki4i
|
||||
3jlga7SUzneDYS8xoHw1MDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly93d3cuYXBw
|
||||
bGUuY29tL2FwcGxlY2EvaXBob25lLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAd13P
|
||||
Z3pMViukVHe9WUg8Hum+0I/0kHKvjhwVd/IMwGlXyU7DhUYWdja2X/zqj7W24Aq5
|
||||
7dEKm3fqqxK5XCFVGY5HI0cRsdENyTP7lxSiiTRYj2mlPedheCn+k6T5y0U4Xr40
|
||||
FXwWb2nWqCF1AgIudhgvVbxlvqcxUm8Zz7yDeJ0JFovXQhyO5fLUHRLCQFssAbf8
|
||||
B4i8rYYsBUhYTspVJcxVpIIltkYpdIRSIARA49HNvKK4hzjzMS/OhKQpVKw+OCEZ
|
||||
xptCVeN2pjbdt9uzi175oVo/u6B2ArKAW17u6XEHIdDMOe7cb33peVI6TD15W4MI
|
||||
pyQPbp8orlXe+tA8JA==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
// VerifyFromAppleDeviceCA verifies a certificate was signed by Apple's iPhone Device CA.
|
||||
// TODO: We want to have more intensive verification (like the whole provided chain).
|
||||
// TODO: Implement some sort of cache so we don't need to parse PEM & DER every invocation.
|
||||
func VerifyFromAppleDeviceCA(c *x509.Certificate) error {
|
||||
block, _ := pem.Decode([]byte(appleiPhoneDeviceCAPEM))
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
panic("appleiPhoneDeviceCAPEM: invalid PEM block")
|
||||
}
|
||||
parent, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("appleiPhoneDeviceCAPEM: err parsing: %s", err))
|
||||
}
|
||||
// Note we CheckSignatureFrom() as we cannot Verify the certificate chain
|
||||
// (known expired intermediate)
|
||||
return c.CheckSignatureFrom(parent)
|
||||
}
|
||||
129
pkg/crypto/helpers.go
Normal file
129
pkg/crypto/helpers.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateRandomCertificateSerialNumber() (*big.Int, error) {
|
||||
limit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
return rand.Int(rand.Reader, limit)
|
||||
}
|
||||
|
||||
func SimpleSelfSignedRSAKeypair(cn string, days int) (key *rsa.PrivateKey, cert *x509.Certificate, err error) {
|
||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return key, cert, err
|
||||
}
|
||||
|
||||
serialNumber, err := GenerateRandomCertificateSerialNumber()
|
||||
if err != nil {
|
||||
return key, cert, err
|
||||
}
|
||||
timeNow := time.Now()
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
},
|
||||
NotBefore: timeNow,
|
||||
NotAfter: timeNow.Add(time.Duration(days) * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{cn},
|
||||
}
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return key, cert, err
|
||||
}
|
||||
cert, err = x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return key, cert, err
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return pem.Encode(
|
||||
file,
|
||||
&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
})
|
||||
}
|
||||
|
||||
func WritePEMRSAKeyFile(key *rsa.PrivateKey, path string) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return pem.Encode(
|
||||
file,
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
})
|
||||
}
|
||||
|
||||
// TopicFromCert extracts the push certificate topic from the provided certificate.
|
||||
func TopicFromCert(cert *x509.Certificate) (string, error) {
|
||||
var oidASN1UserID = asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 1}
|
||||
for _, v := range cert.Subject.Names {
|
||||
if v.Type.Equal(oidASN1UserID) {
|
||||
return v.Value.(string), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("could not find Push Topic (UserID OID) in certificate")
|
||||
}
|
||||
285
pkg/crypto/mdmcertutil/certutil.go
Normal file
285
pkg/crypto/mdmcertutil/certutil.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// Package mdmcertutil contains helpers for requesting MDM Push Certifificates.
|
||||
// The process is described by Apple at
|
||||
// https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/MobileDeviceManagementProtocolRef/7-MDMVendorCSRSigningOverview/MDMVendorCSRSigningOverview.html#//apple_ref/doc/uid/TP40017387-CH6-SW4
|
||||
package mdmcertutil
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/groob/plist"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CSRConfig defines arguments required to create a new CSR.
|
||||
type CSRConfig struct {
|
||||
CommonName, Country, Email string
|
||||
PrivateKeyPassword []byte
|
||||
PrivateKeyPath, CSRPath string
|
||||
}
|
||||
|
||||
// CreateCSR creates a new private key and CSR, saving both as PEM encoded files.
|
||||
func CreateCSR(req *CSRConfig) error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pemKey, err := encryptedKey(key, req.PrivateKeyPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(req.PrivateKeyPath, pemKey, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
derBytes, err := newCSR(key, strings.ToLower(req.Email), strings.ToUpper(req.Country), req.CommonName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pemCSR := pemCSR(derBytes)
|
||||
return ioutil.WriteFile(req.CSRPath, pemCSR, 0600)
|
||||
}
|
||||
|
||||
// The PushCertificateRequest structure required by identity.apple.com
|
||||
// to create an MDM Push certificate.
|
||||
type PushCertificateRequest struct {
|
||||
PushCertRequestCSR string
|
||||
PushCertCertificateChain string
|
||||
PushCertSignature string
|
||||
}
|
||||
|
||||
// Encode marshals a PushCertificateRequest to an XML Plist file and returns a base64 encoded byte representation
|
||||
// of the request.
|
||||
func (p *PushCertificateRequest) Encode() ([]byte, error) {
|
||||
data, err := plist.MarshalIndent(p, " ")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshal PushCertificateRequest")
|
||||
}
|
||||
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(encoded, data)
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
const (
|
||||
wwdrIntermediaryURL = "https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
|
||||
appleRootCAURL = "http://www.apple.com/appleca/AppleIncRootCertificate.cer"
|
||||
)
|
||||
|
||||
// CreatePushCertificateRequest creates a request structure required by identity.apple.com.
|
||||
// It requires a "MDM CSR" certificate (the vendor certificate), a push CSR (the customer specific CSR),
|
||||
// and the vendor private key.
|
||||
func CreatePushCertificateRequest(mdmCertPath, pushCSRPath, pKeyPath string, pKeyPass []byte) (*PushCertificateRequest, error) {
|
||||
// private key of the mdm vendor cert
|
||||
key, err := loadKeyFromFile(pKeyPath, pKeyPass)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "load private key from %s", pKeyPath)
|
||||
}
|
||||
|
||||
// push csr
|
||||
csr, err := loadCSRfromFile(pushCSRPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "load push CSR from %s", pushCSRPath)
|
||||
}
|
||||
|
||||
// csr signature
|
||||
signature, err := signPushCSR(csr.Raw, key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "sign push CSR with private key")
|
||||
}
|
||||
|
||||
// vendor cert
|
||||
mdmCertBytes, err := loadDERCertFromFile(mdmCertPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "load vendor certificate from path %s", mdmCertPath)
|
||||
}
|
||||
mdmPEM := pemCert(mdmCertBytes)
|
||||
|
||||
// wwdr cert
|
||||
wwdrCertBytes, err := loadCertfromHTTP(wwdrIntermediaryURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "load WWDR certificate from %s", wwdrIntermediaryURL)
|
||||
}
|
||||
wwdrPEM := pemCert(wwdrCertBytes)
|
||||
|
||||
// apple root certificate
|
||||
rootCertBytes, err := loadCertfromHTTP(appleRootCAURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "load root certificate from %s", appleRootCAURL)
|
||||
}
|
||||
rootPEM := pemCert(rootCertBytes)
|
||||
|
||||
csrB64 := base64.StdEncoding.EncodeToString(csr.Raw)
|
||||
sig64 := base64.StdEncoding.EncodeToString(signature)
|
||||
pushReq := &PushCertificateRequest{
|
||||
PushCertRequestCSR: csrB64,
|
||||
PushCertCertificateChain: makeCertChain(mdmPEM, wwdrPEM, rootPEM),
|
||||
PushCertSignature: sig64,
|
||||
}
|
||||
return pushReq, nil
|
||||
}
|
||||
|
||||
func makeCertChain(mdmPEM, wwdrPEM, rootPEM []byte) string {
|
||||
return string(mdmPEM) + string(wwdrPEM) + string(rootPEM)
|
||||
}
|
||||
|
||||
func signPushCSR(csrData []byte, key *rsa.PrivateKey) ([]byte, error) {
|
||||
h := sha1.New()
|
||||
h.Write(csrData)
|
||||
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA1, h.Sum(nil))
|
||||
return signature, errors.Wrap(err, "signing push CSR")
|
||||
}
|
||||
|
||||
const (
|
||||
csrPEMBlockType = "CERTIFICATE REQUEST"
|
||||
)
|
||||
|
||||
// create a CSR using the same parameters as Keychain Access would produce
|
||||
func newCSR(priv *rsa.PrivateKey, email, country, cname string) ([]byte, error) {
|
||||
subj := pkix.Name{
|
||||
Country: []string{country},
|
||||
CommonName: cname,
|
||||
ExtraNames: []pkix.AttributeTypeAndValue{pkix.AttributeTypeAndValue{
|
||||
Type: []int{1, 2, 840, 113549, 1, 9, 1},
|
||||
Value: email,
|
||||
}},
|
||||
}
|
||||
template := &x509.CertificateRequest{
|
||||
Subject: subj,
|
||||
}
|
||||
return x509.CreateCertificateRequest(rand.Reader, template, priv)
|
||||
}
|
||||
|
||||
// convert DER to PEM format
|
||||
func pemCSR(derBytes []byte) []byte {
|
||||
pemBlock := &pem.Block{
|
||||
Type: csrPEMBlockType,
|
||||
Headers: nil,
|
||||
Bytes: derBytes,
|
||||
}
|
||||
out := pem.EncodeToMemory(pemBlock)
|
||||
return out
|
||||
}
|
||||
|
||||
// load PEM encoded CSR from file
|
||||
func loadCSRfromFile(path string) (*x509.CertificateRequest, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pemBlock, _ := pem.Decode(data)
|
||||
if pemBlock == nil {
|
||||
return nil, errors.New("cannot find the next PEM formatted block")
|
||||
}
|
||||
if pemBlock.Type != csrPEMBlockType || len(pemBlock.Headers) != 0 {
|
||||
return nil, errors.New("unmatched type or headers")
|
||||
}
|
||||
return x509.ParseCertificateRequest(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
const (
|
||||
rsaPrivateKeyPEMBlockType = "RSA PRIVATE KEY"
|
||||
)
|
||||
|
||||
// protect an rsa key with a password
|
||||
func encryptedKey(key *rsa.PrivateKey, password []byte) ([]byte, error) {
|
||||
privBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
privPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, rsaPrivateKeyPEMBlockType, privBytes, password, x509.PEMCipher3DES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := pem.EncodeToMemory(privPEMBlock)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// load an encrypted private key from disk
|
||||
func loadKeyFromFile(path string, password []byte) (*rsa.PrivateKey, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemBlock, _ := pem.Decode(data)
|
||||
if pemBlock == nil {
|
||||
return nil, errors.New("PEM decode failed")
|
||||
}
|
||||
if pemBlock.Type != rsaPrivateKeyPEMBlockType {
|
||||
return nil, errors.New("unmatched type or headers")
|
||||
}
|
||||
|
||||
if string(password) != "" {
|
||||
b, err := x509.DecryptPEMBlock(pemBlock, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(b)
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
const (
|
||||
certificatePEMBlockType = "CERTIFICATE"
|
||||
)
|
||||
|
||||
func pemCert(derBytes []byte) []byte {
|
||||
pemBlock := &pem.Block{
|
||||
Type: certificatePEMBlockType,
|
||||
Headers: nil,
|
||||
Bytes: derBytes,
|
||||
}
|
||||
out := pem.EncodeToMemory(pemBlock)
|
||||
return out
|
||||
}
|
||||
|
||||
func loadDERCertFromFile(path string) ([]byte, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crt, err := x509.ParseCertificate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crt.Raw, nil
|
||||
}
|
||||
|
||||
func loadCertfromHTTP(url string) ([]byte, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create GET request for %s", url)
|
||||
}
|
||||
req.Header.Set("Accept", "*/*") // required by Apple at some point.
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GET request to %s", url)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("got %s when trying to http.Get %s", resp.Status, url)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "reading get certificate request response body")
|
||||
}
|
||||
|
||||
crt, err := x509.ParseCertificate(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse wwdr intermediate certificate")
|
||||
}
|
||||
return crt.Raw, nil
|
||||
}
|
||||
74
pkg/crypto/password/password.go
Normal file
74
pkg/crypto/password/password.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package password provides utilities for creating and verifying macOS
|
||||
// passwords for MDM account configuration.
|
||||
package password
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// macKeyLen is the length of a PBKDF2 salt.
|
||||
const macKeyLen = 128
|
||||
|
||||
// ErrNoMatch is returned by Verify if the password does not match.
|
||||
var ErrNoMatch = errors.New("password does not match")
|
||||
|
||||
// SaltedSha512PBKDF2Dictionary is a SHA512 PBKDF2 dictionary.
|
||||
type SaltedSHA512PBKDF2Dictionary struct {
|
||||
Iterations int `plist:"iterations"`
|
||||
Salt []byte `plist:"salt"`
|
||||
Entropy []byte `plist:"entropy"`
|
||||
}
|
||||
|
||||
// SaltedSHA512PBKDF2 creates a SALTED-SHA512-PBKDF2 dictionary
|
||||
// from a plaintext password. The hash function will use a 128 bit
|
||||
// salt.
|
||||
func SaltedSHA512PBKDF2(plaintext string) (SaltedSHA512PBKDF2Dictionary, error) {
|
||||
salt := make([]byte, 32)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return SaltedSHA512PBKDF2Dictionary{}, err
|
||||
}
|
||||
iterations, err := secureRandInt(20000, 40000)
|
||||
if err != nil {
|
||||
return SaltedSHA512PBKDF2Dictionary{}, err
|
||||
}
|
||||
return SaltedSHA512PBKDF2Dictionary{
|
||||
Iterations: iterations,
|
||||
Salt: salt,
|
||||
Entropy: pbkdf2.Key([]byte(plaintext),
|
||||
salt, iterations, macKeyLen, sha512.New),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a plaintext password against a existing SALTED-SHA512-PBKDF2
|
||||
// password dictionary.
|
||||
func Verify(plaintext string, h SaltedSHA512PBKDF2Dictionary) error {
|
||||
hashed := pbkdf2.Key([]byte(plaintext), h.Salt, h.Iterations, macKeyLen, sha512.New)
|
||||
if !bytes.Equal(h.Entropy, hashed) {
|
||||
return ErrNoMatch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CCCalibratePBKDF uses a pseudorandom value returned within 100 milliseconds.
|
||||
// Use a random int from crypto/rand between 20,000 and 40,000 instead.
|
||||
func secureRandInt(min, max int64) (int, error) {
|
||||
var random int
|
||||
for {
|
||||
iter, err := rand.Int(rand.Reader, big.NewInt(max))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if iter.Int64() >= min {
|
||||
random = int(iter.Int64())
|
||||
break
|
||||
}
|
||||
}
|
||||
return random, nil
|
||||
}
|
||||
Reference in New Issue
Block a user