mirror of
https://github.com/fleetdm/fleet
synced 2026-05-20 07:29:08 +00:00
Fixes #30458 Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651 # Checklist for submitter - We will add changes file later. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Added/updated automated tests - Did not do manual QA since the SCEP client I have doesn't support ECC. Will rely on next subtasks for manual QA. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol) support, enabling secure host identity certificate enrollment and management. * Added new API endpoints for Host Identity SCEP, including certificate issuance and retrieval. * Implemented MySQL-backed storage and management for host identity SCEP certificates and serials. * Added new database tables for storing host identity SCEP certificates and serial numbers. * Provided utilities for encoding certificates and keys, and handling ECDSA public keys. * **Bug Fixes** * None. * **Tests** * Added comprehensive integration and unit tests for Host Identity SCEP functionality, including certificate issuance, validation, and error scenarios. * **Chores** * Updated test utilities to support unique test names and new SCEP storage options. * Extended mock datastore and interfaces for new host identity certificate methods. * **Documentation** * Added comments and documentation for new SCEP-related interfaces, methods, and database schema changes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
107 lines
3 KiB
Go
107 lines
3 KiB
Go
package apple_mdm
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/binary"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
func EncodeCertRequestPEM(cert *x509.CertificateRequest) []byte {
|
|
pemBlock := &pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Headers: nil,
|
|
Bytes: cert.Raw,
|
|
}
|
|
|
|
return pem.EncodeToMemory(pemBlock)
|
|
}
|
|
|
|
// 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()) //nolint:gosec // dismiss G115
|
|
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))) //nolint:gosec // dismiss G115
|
|
f := fmt.Sprintf("%%0%dd", length)
|
|
return fmt.Sprintf(f, v)
|
|
}
|
|
|
|
// FmtErrorChain formats Command error message for macOS MDM v1
|
|
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()
|
|
}
|
|
|
|
// FmtDDMError formats a DDM error message
|
|
func FmtDDMError(reasons []fleet.MDMAppleDDMStatusErrorReason) string {
|
|
var errMsg strings.Builder
|
|
for _, r := range reasons {
|
|
errMsg.WriteString(fmt.Sprintf("%s: %s %+v\n", r.Code, r.Description, r.Details))
|
|
}
|
|
return errMsg.String()
|
|
}
|
|
|
|
func EnrollURL(token string, appConfig *fleet.AppConfig) (string, error) {
|
|
enrollURL, err := url.Parse(appConfig.MDMUrl())
|
|
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
|
|
}
|
|
|
|
// IsLessThanVersion returns true if the current version is less than the target version.
|
|
// If either version is invalid, an error is returned.
|
|
func IsLessThanVersion(current string, target string) (bool, error) {
|
|
cv, err := fleet.VersionToSemverVersion(current)
|
|
if err != nil {
|
|
return false, fmt.Errorf("invalid current version: %w", err)
|
|
}
|
|
tv, err := fleet.VersionToSemverVersion(target)
|
|
if err != nil {
|
|
return false, fmt.Errorf("invalid target version: %w", err)
|
|
}
|
|
|
|
return cv.LessThan(tv), nil
|
|
}
|