mirror of
https://github.com/google/glazier.git
synced 2025-12-19 18:27:35 -05:00
299 lines
11 KiB
Go
299 lines
11 KiB
Go
// Copyright 2021 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build windows
|
|
// +build windows
|
|
|
|
// Package bitlocker provides functionality for managing Bitlocker.
|
|
package bitlocker
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/google/deck"
|
|
"github.com/scjalliance/comshim"
|
|
"github.com/go-ole/go-ole"
|
|
"github.com/go-ole/go-ole/oleutil"
|
|
"github.com/iamacarpet/go-win64api"
|
|
)
|
|
|
|
var (
|
|
// Test Helpers
|
|
funcBackup = winapi.BackupBitLockerRecoveryKeys
|
|
funcRecoveryInfo = winapi.GetBitLockerRecoveryInfo
|
|
)
|
|
|
|
// BackupToAD backs up Bitlocker recovery keys to Active Directory.
|
|
func BackupToAD() error {
|
|
infos, err := funcRecoveryInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
volIDs := []string{}
|
|
for _, i := range infos {
|
|
if i.ConversionStatus != 1 {
|
|
deck.Warningf("Skipping volume %s due to conversion status (%d).", i.DriveLetter, i.ConversionStatus)
|
|
continue
|
|
}
|
|
deck.Infof("Backing up Bitlocker recovery password for drive %q.", i.DriveLetter)
|
|
volIDs = append(volIDs, i.PersistentVolumeID)
|
|
}
|
|
return funcBackup(volIDs)
|
|
}
|
|
|
|
// Encryption Methods
|
|
// https://docs.microsoft.com/en-us/windows/win32/secprov/getencryptionmethod-win32-encryptablevolume
|
|
type EncryptionMethod int32
|
|
|
|
const (
|
|
None EncryptionMethod = iota
|
|
AES128WithDiffuser
|
|
AES256WithDiffuser
|
|
AES128
|
|
AES256
|
|
HardwareEncryption
|
|
XtsAES128
|
|
XtsAES256
|
|
)
|
|
|
|
// Encryption Flags
|
|
// https://docs.microsoft.com/en-us/windows/win32/secprov/encrypt-win32-encryptablevolume
|
|
type EncryptionFlag int32
|
|
|
|
const (
|
|
EncryptDataOnly EncryptionFlag = 0x00000001
|
|
EncryptDemandWipe EncryptionFlag = 0x00000002
|
|
EncryptSynchronous EncryptionFlag = 0x00010000
|
|
|
|
// Error Codes
|
|
ERROR_IO_DEVICE int32 = -2147023779
|
|
FVE_E_EDRIVE_INCOMPATIBLE_VOLUME int32 = -2144272206
|
|
FVE_E_NO_TPM_WITH_PASSPHRASE int32 = -2144272212
|
|
FVE_E_PASSPHRASE_TOO_LONG int32 = -2144272214
|
|
FVE_E_POLICY_PASSPHRASE_NOT_ALLOWED int32 = -2144272278
|
|
FVE_E_NOT_DECRYPTED int32 = -2144272327
|
|
FVE_E_INVALID_PASSWORD_FORMAT int32 = -2144272331
|
|
FVE_E_BOOTABLE_CDDVD int32 = -2144272336
|
|
FVE_E_PROTECTOR_EXISTS int32 = -2144272335
|
|
)
|
|
|
|
func encryptErrHandler(val int32) error {
|
|
switch val {
|
|
case ERROR_IO_DEVICE:
|
|
return fmt.Errorf("an I/O error has occurred during encryption; the device may need to be reset")
|
|
case FVE_E_EDRIVE_INCOMPATIBLE_VOLUME:
|
|
return fmt.Errorf("the drive specified does not support hardware-based encryption")
|
|
case FVE_E_NO_TPM_WITH_PASSPHRASE:
|
|
return fmt.Errorf("a TPM key protector cannot be added because a password protector exists on the drive")
|
|
case FVE_E_PASSPHRASE_TOO_LONG:
|
|
return fmt.Errorf("the passphrase cannot exceed 256 characters")
|
|
case FVE_E_POLICY_PASSPHRASE_NOT_ALLOWED:
|
|
return fmt.Errorf("Group Policy settings do not permit the creation of a password")
|
|
case FVE_E_NOT_DECRYPTED:
|
|
return fmt.Errorf("the drive must be fully decrypted to complete this operation")
|
|
case FVE_E_INVALID_PASSWORD_FORMAT:
|
|
return fmt.Errorf("the format of the recovery password provided is invalid")
|
|
case FVE_E_BOOTABLE_CDDVD:
|
|
return fmt.Errorf("BitLocker Drive Encryption detected bootable media (CD or DVD) in the computer. " +
|
|
"Remove the media and restart the computer before configuring BitLocker.")
|
|
case FVE_E_PROTECTOR_EXISTS:
|
|
return fmt.Errorf("key protector cannot be added; only one key protector of this type is allowed for this drive")
|
|
default:
|
|
return fmt.Errorf("error code returned during encryption: %d", val)
|
|
}
|
|
}
|
|
|
|
// A Volume tracks an open encryptable volume.
|
|
type Volume struct {
|
|
letter string
|
|
handle *ole.IDispatch
|
|
wmiIntf *ole.IDispatch
|
|
wmiSvc *ole.IDispatch
|
|
}
|
|
|
|
// Close frees all resources associated with a volume.
|
|
func (v *Volume) Close() {
|
|
v.handle.Release()
|
|
v.wmiIntf.Release()
|
|
v.wmiSvc.Release()
|
|
comshim.Done()
|
|
}
|
|
|
|
// Connect connects to an encryptable volume in order to manage it.
|
|
// You must call Close() to release the volume when finished.
|
|
//
|
|
// Example: bitlocker.Connect("c:")
|
|
func Connect(driveLetter string) (Volume, error) {
|
|
comshim.Add(1)
|
|
v := Volume{letter: driveLetter}
|
|
|
|
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
|
|
if err != nil {
|
|
comshim.Done()
|
|
return v, fmt.Errorf("CreateObject: %w", err)
|
|
}
|
|
defer unknown.Release()
|
|
v.wmiIntf, err = unknown.QueryInterface(ole.IID_IDispatch)
|
|
if err != nil {
|
|
comshim.Done()
|
|
return v, fmt.Errorf("QueryInterface: %w", err)
|
|
}
|
|
serviceRaw, err := oleutil.CallMethod(v.wmiIntf, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftVolumeEncryption`)
|
|
if err != nil {
|
|
v.Close()
|
|
return v, fmt.Errorf("ConnectServer: %w", err)
|
|
}
|
|
v.wmiSvc = serviceRaw.ToIDispatch()
|
|
|
|
raw, err := oleutil.CallMethod(v.wmiSvc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume WHERE DriveLetter = '"+driveLetter+"'")
|
|
if err != nil {
|
|
v.Close()
|
|
return v, fmt.Errorf("ExecQuery: %w", err)
|
|
}
|
|
result := raw.ToIDispatch()
|
|
defer result.Release()
|
|
|
|
itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0)
|
|
if err != nil {
|
|
v.Close()
|
|
return v, fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err)
|
|
}
|
|
v.handle = itemRaw.ToIDispatch()
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// Encrypt encrypts the volume.
|
|
//
|
|
// Example: vol.Encrypt(bitlocker.XtsAES256, bitlocker.EncryptDataOnly)
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/encrypt-win32-encryptablevolume
|
|
func (v *Volume) Encrypt(method EncryptionMethod, flags EncryptionFlag) error {
|
|
resultRaw, err := oleutil.CallMethod(v.handle, "Encrypt", int32(method), int32(flags))
|
|
if err != nil {
|
|
return fmt.Errorf("Encrypt(%s): %w", v.letter, err)
|
|
} else if val, ok := resultRaw.Value().(int32); val != 0 || !ok {
|
|
return fmt.Errorf("Encrypt(%s): %w", v.letter, encryptErrHandler(val))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DiscoveryVolumeType specifies the type of discovery volume to be used by Prepare.
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/preparevolume-win32-encryptablevolume
|
|
type DiscoveryVolumeType string
|
|
|
|
const (
|
|
// VolumeTypeNone indicates no discovery volume. This value creates a native BitLocker volume.
|
|
VolumeTypeNone DiscoveryVolumeType = "<none>"
|
|
// VolumeTypeDefault indicates the default behavior.
|
|
VolumeTypeDefault DiscoveryVolumeType = "<default>"
|
|
// VolumeTypeFAT32 creates a FAT32 discovery volume.
|
|
VolumeTypeFAT32 DiscoveryVolumeType = "FAT32"
|
|
)
|
|
|
|
// ForceEncryptionType specifies the encryption type to be used when calling Prepare on the volume.
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/preparevolume-win32-encryptablevolume
|
|
type ForceEncryptionType int32
|
|
|
|
const (
|
|
// EncryptionTypeUnspecified indicates that the encryption type is not specified.
|
|
EncryptionTypeUnspecified ForceEncryptionType = 0
|
|
// EncryptionTypeSoftware specifies software encryption.
|
|
EncryptionTypeSoftware ForceEncryptionType = 1
|
|
// EncryptionTypeHardware specifies hardware encryption.
|
|
EncryptionTypeHardware ForceEncryptionType = 2
|
|
)
|
|
|
|
// Prepare prepares a new Bitlocker Volume. This should be called BEFORE any key protectors are added.
|
|
//
|
|
// Example: vol.Prepare(bitlocker.VolumeTypeDefault, bitlocker.EncryptionTypeHardware)
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/preparevolume-win32-encryptablevolume
|
|
func (v *Volume) Prepare(volType DiscoveryVolumeType, encType ForceEncryptionType) error {
|
|
resultRaw, err := oleutil.CallMethod(v.handle, "PrepareVolume", string(volType), int32(encType))
|
|
if err != nil {
|
|
return fmt.Errorf("PrepareVolume(%s): %w", v.letter, err)
|
|
} else if val, ok := resultRaw.Value().(int32); val != 0 || !ok {
|
|
return fmt.Errorf("PrepareVolume(%s): %w", v.letter, encryptErrHandler(val))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProtectWithNumericalPassword adds a numerical password key protector.
|
|
//
|
|
// Leave password as a blank string to have one auto-generated by Windows. (Recommended)
|
|
//
|
|
// In Powershell this is referred to as a RecoveryPasswordProtector.
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/protectkeywithnumericalpassword-win32-encryptablevolume
|
|
func (v *Volume) ProtectWithNumericalPassword(password string) error {
|
|
var volumeKeyProtectorID ole.VARIANT
|
|
ole.VariantInit(&volumeKeyProtectorID)
|
|
var resultRaw *ole.VARIANT
|
|
var err error
|
|
if password != "" {
|
|
resultRaw, err = oleutil.CallMethod(v.handle, "ProtectKeyWithNumericalPassword", nil, password, &volumeKeyProtectorID)
|
|
} else {
|
|
resultRaw, err = oleutil.CallMethod(v.handle, "ProtectKeyWithNumericalPassword", nil, nil, &volumeKeyProtectorID)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("ProtectKeyWithNumericalPassword(%s): %w", v.letter, err)
|
|
} else if val, ok := resultRaw.Value().(int32); val != 0 || !ok {
|
|
return fmt.Errorf("ProtectKeyWithNumericalPassword(%s): %w", v.letter, encryptErrHandler(val))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProtectWithPassphrase adds a passphrase key protector.
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/protectkeywithpassphrase-win32-encryptablevolume
|
|
func (v *Volume) ProtectWithPassphrase(passphrase string) error {
|
|
var volumeKeyProtectorID ole.VARIANT
|
|
ole.VariantInit(&volumeKeyProtectorID)
|
|
resultRaw, err := oleutil.CallMethod(v.handle, "ProtectKeyWithPassphrase", nil, passphrase, &volumeKeyProtectorID)
|
|
if err != nil {
|
|
return fmt.Errorf("ProtectWithPassphrase(%s): %w", v.letter, err)
|
|
} else if val, ok := resultRaw.Value().(int32); val != 0 || !ok {
|
|
return fmt.Errorf("ProtectWithPassphrase(%s): %w", v.letter, encryptErrHandler(val))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProtectWithTPM adds the TPM key protector.
|
|
//
|
|
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/protectkeywithtpm-win32-encryptablevolume
|
|
func (v *Volume) ProtectWithTPM(platformValidationProfile *[]uint8) error {
|
|
var volumeKeyProtectorID ole.VARIANT
|
|
ole.VariantInit(&volumeKeyProtectorID)
|
|
var resultRaw *ole.VARIANT
|
|
var err error
|
|
if platformValidationProfile == nil {
|
|
resultRaw, err = oleutil.CallMethod(v.handle, "ProtectKeyWithTPM", nil, nil, &volumeKeyProtectorID)
|
|
} else {
|
|
resultRaw, err = oleutil.CallMethod(v.handle, "ProtectKeyWithTPM", nil, *platformValidationProfile, &volumeKeyProtectorID)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("ProtectKeyWithTPM(%s): %w", v.letter, err)
|
|
} else if val, ok := resultRaw.Value().(int32); val != 0 || !ok {
|
|
return fmt.Errorf("ProtectKeyWithTPM(%s): %w", v.letter, encryptErrHandler(val))
|
|
}
|
|
|
|
return nil
|
|
}
|