ื“ืœื’ ืœืชื•ื›ืŸ

FileVault Key Rotation ๐Ÿš€

ืชื•ื›ืŸ ื–ื” ืื™ื ื• ื–ืžื™ืŸ ืขื“ื™ื™ืŸ ื‘ืฉืคื” ืฉืœืš.

This PowerShell script provides automated FileVault recovery key rotation for macOS devices managed through Microsoft Intune. The script enhances security by regularly rotating FileVault recovery keys while maintaining proper audit trails and backup procedures.

  • macOS: 10.15 (Catalina) or later
  • Microsoft Intune: Device management and script deployment
  • PowerShell: 7.0 or later for cross-platform compatibility
  • Administrator Privileges: Required for FileVault operations
  • FileVault: Must be enabled on target devices
  • Intune Management: Device enrolled in Intune
  • Secure Storage: Encrypted location for key backup
  • Logging Infrastructure: Centralized logging system
Terminal window
param(
[Parameter(Mandatory=$false)]
[string]$KeyBackupLocation = "/var/log/filevault-keys",
[Parameter(Mandatory=$false)]
[string]$LogPath = "/var/log/filevault-rotation.log",
[Parameter(Mandatory=$false)]
[int]$KeyRetentionDays = 90,
[Parameter(Mandatory=$false)]
[switch]$ForceRotation = $false,
[Parameter(Mandatory=$false)]
[switch]$TestMode = $false
)
Terminal window
# Configuration settings
$ScriptVersion = "2.1.0"
$CompanyName = "YourCompany"
$SupportEmail = "support@yourcompany.com"
$MaxRotationAttempts = 3
$RotationIntervalDays = 30
Terminal window
function Start-FileVaultKeyRotation {
param(
[string]$BackupPath,
[string]$LogPath,
[int]$RetentionDays,
[bool]$Force,
[bool]$Test
)
try {
# Initialize logging
Initialize-Logging -LogPath $LogPath
Write-Log "Starting FileVault key rotation - Version $ScriptVersion"
Write-Log "Parameters: BackupPath=$BackupPath, RetentionDays=$RetentionDays, Force=$Force, Test=$Test"
# Check prerequisites
if (-not (Test-Prerequisites)) {
Write-Log "Prerequisites check failed" -Level "ERROR"
return $false
}
# Get current FileVault status
$fVaultStatus = Get-FileVaultStatus
if (-not $fVaultStatus.Enabled) {
Write-Log "FileVault is not enabled on this device" -Level "WARNING"
return $false
}
# Check if rotation is needed
if (-not $Force -and -not (Test-RotationNeeded -LastRotation $fVaultStatus.LastRotation)) {
Write-Log "Key rotation not needed at this time"
return $true
}
# Perform key rotation
$rotationResult = Invoke-KeyRotation -TestMode $Test
if ($rotationResult.Success) {
Write-Log "Key rotation completed successfully"
# Backup new key
$backupResult = Backup-RecoveryKey -Key $rotationResult.NewKey -BackupPath $BackupPath
if ($backupResult.Success) {
Write-Log "Recovery key backed up successfully"
} else {
Write-Log "Failed to backup recovery key" -Level "ERROR"
}
# Clean up old keys
Remove-OldKeys -RetentionDays $RetentionDays -BackupPath $BackupPath
return $true
} else {
Write-Log "Key rotation failed: $($rotationResult.ErrorMessage)" -Level "ERROR"
return $false
}
} catch {
Write-Log "Unexpected error in Start-FileVaultKeyRotation: $($_.Exception.Message)" -Level "ERROR"
return $false
}
}
Terminal window
function Test-Prerequisites {
try {
# Check if running on macOS
if ($PSVersionTable.Platform -ne "Unix" -or $env:OS -ne "Darwin") {
Write-Log "This script must be run on macOS" -Level "ERROR"
return $false
}
# Check FileVault availability
$fVaultCheck = & fdesetup status 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Log "FileVault command not available" -Level "ERROR"
return $false
}
# Check admin privileges
if (-not (Test-AdminPrivileges)) {
Write-Log "Administrator privileges required" -Level "ERROR"
return $false
}
# Create backup directory if needed
if (-not (Test-Path $KeyBackupLocation)) {
New-Item -Path $KeyBackupLocation -ItemType Directory -Force | Out-Null
Write-Log "Created backup directory: $KeyBackupLocation"
}
return $true
} catch {
Write-Log "Prerequisites check failed: $($_.Exception.Message)" -Level "ERROR"
return $false
}
}
Terminal window
function Get-FileVaultStatus {
try {
$statusOutput = & fdesetup status
$enabled = $statusOutput -match "FileVault is (On|Off)"
$lastRotation = Get-LastRotationDate
return [PSCustomObject]@{
Enabled = ($statusOutput -match "FileVault is On")
Status = $statusOutput
LastRotation = $lastRotation
RecoveryKeyPresent = Test-RecoveryKeyPresent
}
} catch {
Write-Log "Failed to get FileVault status: $($_.Exception.Message)" -Level "ERROR"
return $null
}
}
Terminal window
function Invoke-KeyRotation {
param([bool]$TestMode)
try {
Write-Log "Starting key rotation process"
if ($TestMode) {
Write-Log "Running in test mode - no actual rotation will occur"
return [PSCustomObject]@{
Success = $true
NewKey = "TEST-KEY-$(Get-Random -Maximum 9999)"
RotationDate = Get-Date
TestMode = $true
}
}
# Generate new recovery key
$newKey = & fdesetup changerecoverykey -personal 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Failed to generate new recovery key: $newKey"
}
# Extract key from output
$keyPattern = 'Recovery key: ([A-Z0-9\-]+)'
if ($newKey -match $keyPattern) {
$extractedKey = $matches[1]
} else {
throw "Could not extract recovery key from output"
}
# Update last rotation date
Set-LastRotationDate -Date (Get-Date)
Write-Log "Successfully generated new recovery key"
return [PSCustomObject]@{
Success = $true
NewKey = $extractedKey
RotationDate = Get-Date
TestMode = $false
}
} catch {
Write-Log "Key rotation failed: $($_.Exception.Message)" -Level "ERROR"
return [PSCustomObject]@{
Success = $false
ErrorMessage = $_.Exception.Message
RotationDate = $null
}
}
}
Terminal window
function Backup-RecoveryKey {
param(
[string]$Key,
[string]$BackupPath
)
try {
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$hostname = & hostname
$filename = "filevault-key-$hostname-$timestamp.txt"
$fullPath = Join-Path $BackupPath $filename
# Create backup file with metadata
$backupContent = @"
FileVault Recovery Key Backup
==============================
Generated: $(Get-Date)
Hostname: $hostname
Key: $Key
Script Version: $ScriptVersion
Company: $CompanyName
IMPORTANT: Store this key in a secure location.
This key can unlock the encrypted disk if the user forgets their password.
"@
# Write backup file
Set-Content -Path $fullPath -Value $backupContent -Encoding UTF8
# Set secure permissions
& chmod 600 $fullPath
# Encrypt backup file if possible
if (Test-CommandAvailable "gpg") {
try {
& gpg --symmetric --cipher-algo AES256 --compress-algo 1 --s2k-mode 3 --s2k-digest-algo SHA512 --s2k-count 65536 --passphrase-file "/etc/filevault-passphrase" --output "$fullPath.gpg" $fullPath
if ($LASTEXITCODE -eq 0) {
Remove-Item $fullPath -Force
$fullPath = "$fullPath.gpg"
Write-Log "Recovery key backup encrypted with GPG"
}
} catch {
Write-Log "GPG encryption failed, keeping plaintext backup" -Level "WARNING"
}
}
Write-Log "Recovery key backed up to: $fullPath"
return [PSCustomObject]@{
Success = $true
BackupPath = $fullPath
Timestamp = $timestamp
}
} catch {
Write-Log "Failed to backup recovery key: $($_.Exception.Message)" -Level "ERROR"
return [PSCustomObject]@{
Success = $false
ErrorMessage = $_.Exception.Message
}
}
}
Terminal window
function Remove-OldKeys {
param(
[int]$RetentionDays,
[string]$BackupPath
)
try {
$cutoffDate = (Get-Date).AddDays(-$RetentionDays)
$keyFiles = Get-ChildItem -Path $BackupPath -Filter "filevault-key-*.txt*" -File
$removedCount = 0
foreach ($file in $keyFiles) {
if ($file.CreationTime -lt $cutoffDate) {
Write-Log "Removing old key file: $($file.Name)"
Remove-Item $file.FullName -Force
$removedCount++
}
}
if ($removedCount -gt 0) {
Write-Log "Removed $removedCount old key files older than $RetentionDays days"
} else {
Write-Log "No old key files to remove"
}
} catch {
Write-Log "Failed to remove old keys: $($_.Exception.Message)" -Level "ERROR"
}
}
Terminal window
function Write-Log {
param(
[string]$Message,
[ValidateSet("INFO", "WARNING", "ERROR", "DEBUG")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$hostname = & hostname
$logEntry = "[$timestamp] [$hostname] [$Level] $Message"
# Write to log file
Add-Content -Path $LogPath -Value $logEntry -Encoding UTF8
# Write to console if running interactively
if (-not $env:USERPROFILE) {
switch ($Level) {
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
"WARNING" { Write-Host $logEntry -ForegroundColor Yellow }
"DEBUG" { Write-Host $logEntry -ForegroundColor Gray }
default { Write-Host $logEntry -ForegroundColor Green }
}
}
}
Terminal window
function Test-AdminPrivileges {
$currentUser = & id -u
return $currentUser -eq 0
}
Terminal window
function Test-CommandAvailable {
param([string]$Command)
try {
$null = & which $Command 2>&1
return $LASTEXITCODE -eq 0
} catch {
return $false
}
}
Terminal window
function Get-LastRotationDate {
$stateFile = "/var/filevault-rotation-state.json"
if (Test-Path $stateFile) {
try {
$state = Get-Content $stateFile | ConvertFrom-Json
return [DateTime]$state.LastRotation
} catch {
Write-Log "Failed to read rotation state file" -Level "WARNING"
return (Get-Date).AddDays(-$RotationIntervalDays - 1)
}
} else {
return (Get-Date).AddDays(-$RotationIntervalDays - 1)
}
}
function Set-LastRotationDate {
param([DateTime]$Date)
$stateFile = "/var/filevault-rotation-state.json"
$state = @{
LastRotation = $Date.ToString("yyyy-MM-ddTHH:mm:ssZ")
ScriptVersion = $ScriptVersion
Hostname = & hostname
}
try {
Set-Content -Path $stateFile -Value ($state | ConvertTo-Json -Depth 3) -Encoding UTF8
} catch {
Write-Log "Failed to write rotation state file: $($_.Exception.Message)" -Level "WARNING"
}
}
  1. Create Script Package:

    • Save script as FileVaultKeyRotation.ps1
    • Create deployment package with configuration files
    • Test script in development environment
  2. Configure Intune Settings:

    • Navigate to Microsoft Endpoint Manager
    • Create new PowerShell script deployment
    • Set script parameters for your environment
    • Assign to appropriate device groups
  3. Deployment Configuration:

{
"scriptSettings": {
"fileName": "FileVaultKeyRotation.ps1",
"runAsAccount": "system",
"enforceSignatureCheck": false,
"runAs32Bit": false
},
"scheduleSettings": {
"runOnce": false,
"recurrence": "weekly",
"startDateTime": "2024-01-01T02:00:00Z"
}
}
Terminal window
# Create monitoring script for Intune
function Monitor-FileVaultRotation {
$logPath = "/var/log/filevault-rotation.log"
$errorPattern = "ERROR"
$recentErrors = Select-String -Path $logPath -Pattern $errorPattern -Context 2 |
Where-Object { $_.LineNumber -gt (Get-Content $logPath).Count - 100 }
if ($recentErrors.Count -gt 0) {
# Send alert to IT team
Send-Alert -Message "FileVault rotation errors detected" -Details $recentErrors
}
}
  • Secure Storage: Store backup keys in encrypted location
  • Access Control: Limit access to backup files
  • Audit Trail: Log all key operations
  • Retention Policy: Define key retention periods
  • Data Protection: Ensure compliance with data protection regulations
  • Audit Logs: Maintain comprehensive audit trail
  • Key Escrow: Implement secure key escrow process
  • Documentation: Maintain security procedures documentation
  1. Permission Errors: Ensure script runs with appropriate privileges
  2. FileVault Status: Verify FileVault is properly enabled
  3. Backup Failures: Check storage space and permissions
  4. Network Issues: Ensure connectivity for centralized logging
Terminal window
# Enable debug logging
$env:FILEVAULT_DEBUG = "true"
Start-FileVaultKeyRotation -TestMode -Verbose
  1. Lost Keys: Use backup keys from secure storage
  2. Failed Rotation: Manual intervention may be required
  3. System Issues: Reboot and retry rotation process

This FileVault key rotation script provides automated security enhancement for macOS devices in enterprise environments. By implementing regular key rotation with proper backup and audit procedures, organizations can maintain strong security posture while ensuring operational continuity.

The scriptโ€™s modular design allows for customization based on specific organizational requirements while maintaining security best practices and compliance with regulatory requirements.

Regular monitoring and maintenance of the rotation process ensures continued effectiveness and helps identify potential issues before they impact security operations.