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:
Victor Vrantchan
2017-11-23 22:07:57 -05:00
committed by GitHub
parent bc34ace413
commit 91c236c8c3
112 changed files with 165 additions and 233 deletions

109
pkg/crypto/apple.go Normal file
View 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
View 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")
}

View 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
}

View 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
}