fleet/server/mdm/apple/util.go
Roberto Dip a23d208b1d
gate DEP enrollment behind SSO when configured (#11309)
#10739

Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com>
Co-authored-by: gillespi314 <73313222+gillespi314@users.noreply.github.com>
2023-04-27 09:43:20 -03:00

172 lines
4.6 KiB
Go

package apple_mdm
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"math"
"math/big"
"net/url"
"path"
"strings"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/micromdm/nanomdm/mdm"
"golang.org/x/crypto/pbkdf2"
)
// Note Apple rejects CSRs if the key size is not 2048.
const rsaKeySize = 2048
// newPrivateKey creates an RSA private key
func newPrivateKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, rsaKeySize)
}
// EncodeCertPEM returns PEM-endcoded certificate data.
func EncodeCertPEM(cert *x509.Certificate) []byte {
block := pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
return pem.EncodeToMemory(&block)
}
func DecodeCertPEM(encoded []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(encoded)
if block == nil {
return nil, errors.New("no PEM-encoded data found")
}
if block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("unexpected block type %s", block.Type)
}
return x509.ParseCertificate(block.Bytes)
}
func EncodeCertRequestPEM(cert *x509.CertificateRequest) []byte {
pemBlock := &pem.Block{
Type: "CERTIFICATE REQUEST",
Headers: nil,
Bytes: cert.Raw,
}
return pem.EncodeToMemory(pemBlock)
}
// EncodePrivateKeyPEM returns PEM-encoded private key data
func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
block := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
return pem.EncodeToMemory(&block)
}
// DecodePrivateKeyPEM decodes PEM-encoded private key data.
func DecodePrivateKeyPEM(encoded []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(encoded)
if block == nil {
return nil, errors.New("no PEM-encoded data found")
}
if block.Type != "RSA PRIVATE KEY" {
return nil, fmt.Errorf("unexpected block type %s", block.Type)
}
return x509.ParsePKCS1PrivateKey(block.Bytes)
}
// GenerateRandomPin generates a `lenght`-digit PIN number that takes into
// account the current time as described in rfc4226 (for one time passwords)
//
// The implementation details have been mostly taken from https://github.com/pquerna/otp
func GenerateRandomPin(length int) string {
counter := uint64(time.Now().Unix())
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)
m := sha256.New()
m.Write(buf)
sum := m.Sum(nil)
offset := sum[len(sum)-1] & 0xf
value := int64(((int(sum[offset]) & 0x7f) << 24) |
((int(sum[offset+1] & 0xff)) << 16) |
((int(sum[offset+2] & 0xff)) << 8) |
(int(sum[offset+3]) & 0xff))
v := int32(value % int64(math.Pow10(length)))
f := fmt.Sprintf("%%0%dd", length)
return fmt.Sprintf(f, v)
}
const pbkdf2KeyLen = 128
// SaltedSHA512PBKDF2 creates a SALTED-SHA512-PBKDF2 dictionary
// from a plaintext password.
//
// This implementation has been taken from micromdm's `password` package
// https://github.com/micromdm/micromdm/blob/974ba0d2060c55dbcf588e832acd89e5b2aa5f41/pkg/crypto/password/password.go#L31-L47
func SaltedSHA512PBKDF2(plaintext string) (fleet.SaltedSHA512PBKDF2Dictionary, error) {
salt := make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return fleet.SaltedSHA512PBKDF2Dictionary{}, err
}
iterations, err := secureRandInt(20000, 40000)
if err != nil {
return fleet.SaltedSHA512PBKDF2Dictionary{}, err
}
return fleet.SaltedSHA512PBKDF2Dictionary{
Iterations: iterations,
Salt: salt,
Entropy: pbkdf2.Key([]byte(plaintext), salt, iterations, pbkdf2KeyLen, sha512.New),
}, nil
}
// CCCalibratePBKDF uses a pseudorandom value returned within 100 milliseconds.
// Use a random int from crypto/rand between 20,000 and 40,000 instead.
//
// This implementation has been taken from micromdm's `password` package
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
}
func FmtErrorChain(chain []mdm.ErrorChain) string {
var sb strings.Builder
for _, mdmErr := range chain {
desc := mdmErr.USEnglishDescription
if desc == "" {
desc = mdmErr.LocalizedDescription
}
sb.WriteString(fmt.Sprintf("%s (%d): %s\n", mdmErr.ErrorDomain, mdmErr.ErrorCode, desc))
}
return sb.String()
}
func EnrollURL(token string, appConfig *fleet.AppConfig) (string, error) {
enrollURL, err := url.Parse(appConfig.ServerSettings.ServerURL)
if err != nil {
return "", err
}
enrollURL.Path = path.Join(enrollURL.Path, EnrollPath)
q := enrollURL.Query()
q.Set("token", token)
enrollURL.RawQuery = q.Encode()
return enrollURL.String(), nil
}