Repository: vmware/govmomi
Pull request #3548 (2024-09-11 18:54:58)

api: Helpers for key management servers
## Description

This patch adds support for the `crypto` package, which introduces manager for the `CryptoManagerKmip` instance and several of its APIs, such as getting the default provider, checking if the provider is native, listing providers, and generating a new key.

Closes: `NA`

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

The following command may be run to test the client:

```shell
go test -v -count 1 -cover ./crypto
```

It has >80% code coverage (it is difficult to produce more without simulating errors):

```shell
=== RUN   TestCryptoManagerKmip
=== RUN   TestCryptoManagerKmip/RegisterKmipCluster
=== RUN   TestCryptoManagerKmip/GetClusterStatus
=== RUN   TestCryptoManagerKmip/UnregisterKmsCluster
=== RUN   TestCryptoManagerKmip/IsValidProvider
=== RUN   TestCryptoManagerKmip/GetDefaultKmsClusterID
=== RUN   TestCryptoManagerKmip/RegisterKmipServer
=== RUN   TestCryptoManagerKmip/GetServerStatus
=== RUN   TestCryptoManagerKmip/ListKmipServers
=== RUN   TestCryptoManagerKmip/UpdateKmipServer
=== RUN   TestCryptoManagerKmip/RemoveKmipServer
=== RUN   TestCryptoManagerKmip/IsValidServer
=== RUN   TestCryptoManagerKmip/GetStatus
=== RUN   TestCryptoManagerKmip/IsDefaultProviderNative
=== RUN   TestCryptoManagerKmip/MarkDefault
=== RUN   TestCryptoManagerKmip/SetDefaultKmsClusterId
=== RUN   TestCryptoManagerKmip/GenerateKey
=== RUN   TestCryptoManagerKmip/ListKeys
=== RUN   TestCryptoManagerKmip/IsValidKey
--- PASS: TestCryptoManagerKmip (7.20s)
    --- PASS: TestCryptoManagerKmip/RegisterKmipCluster (0.44s)
    --- PASS: TestCryptoManagerKmip/GetClusterStatus (0.41s)
    --- PASS: TestCryptoManagerKmip/UnregisterKmsCluster (0.42s)
    --- PASS: TestCryptoManagerKmip/IsValidProvider (0.43s)
    --- PASS: TestCryptoManagerKmip/GetDefaultKmsClusterID (0.34s)
    --- PASS: TestCryptoManagerKmip/RegisterKmipServer (0.40s)
    --- PASS: TestCryptoManagerKmip/GetServerStatus (0.39s)
    --- PASS: TestCryptoManagerKmip/ListKmipServers (0.38s)
    --- PASS: TestCryptoManagerKmip/UpdateKmipServer (0.43s)
    --- PASS: TestCryptoManagerKmip/RemoveKmipServer (0.40s)
    --- PASS: TestCryptoManagerKmip/IsValidServer (0.41s)
    --- PASS: TestCryptoManagerKmip/GetStatus (0.39s)
    --- PASS: TestCryptoManagerKmip/IsDefaultProviderNative (0.36s)
    --- PASS: TestCryptoManagerKmip/MarkDefault (0.39s)
    --- PASS: TestCryptoManagerKmip/SetDefaultKmsClusterId (0.35s)
    --- PASS: TestCryptoManagerKmip/GenerateKey (0.42s)
    --- PASS: TestCryptoManagerKmip/ListKeys (0.45s)
    --- PASS: TestCryptoManagerKmip/IsValidKey (0.40s)
PASS
coverage: 82.6% of statements
ok  	github.com/vmware/govmomi/crypto	7.610s	coverage: 82.6% of statements
```

Using VSCode, I can show that the simulator is also 100% covered:

<img width="508" alt="image" src="https://github.com/user-attachments/assets/58c31a13-d1ad-4c3e-a627-129061db7fb6">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/c3dd522c-b0f0-441d-8841-3ed7cd221652">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/4ae4541a-02ce-480d-8a24-f1ec4666eb26">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/0f8c56d6-05e1-423d-bdcb-08506876e339">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/891fa419-d756-42f1-9043-c3bd75235b00">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/f3897b57-ed46-435b-8b4f-f8be91409f83">

<img width="508" alt="image" src="https://github.com/user-attachments/assets/0228f717-58e9-4cd7-b4ca-cc51c2f947bc">



## Checklist:

- [x] My code follows the `CONTRIBUTION` [guidelines] of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/crypto/manager_kmip.go b/crypto/manager_kmip.go
new file mode 100644
index 00000000..e0e51afb
--- /dev/null
+++ b/crypto/manager_kmip.go
@@ -0,0 +1,428 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+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.
+*/
+
+package crypto
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/methods"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type ManagerKmip struct {
+	object.Common
+}
+
+// GetManagerKmip wraps NewManager, returning ErrNotSupported when the client is
+// not connected to a vCenter instance.
+func GetManagerKmip(c *vim25.Client) (*ManagerKmip, error) {
+	if c.ServiceContent.CryptoManager == nil {
+		return nil, object.ErrNotSupported
+	}
+	return NewManagerKmip(c), nil
+}
+
+func NewManagerKmip(c *vim25.Client) *ManagerKmip {
+	m := ManagerKmip{
+		Common: object.NewCommon(c, *c.ServiceContent.CryptoManager),
+	}
+	return &m
+}
+
+func (m ManagerKmip) ListKmipServers(
+	ctx context.Context,
+	limit *int32) ([]types.KmipClusterInfo, error) {
+
+	req := types.ListKmipServers{
+		This:  m.Reference(),
+		Limit: limit,
+	}
+	res, err := methods.ListKmipServers(ctx, m.Client(), &req)
+	if err != nil {
+		return nil, err
+	}
+	return res.Returnval, nil
+}
+
+func (m ManagerKmip) IsDefaultProviderNative(
+	ctx context.Context,
+	entity *types.ManagedObjectReference,
+	defaultsToParent bool) (bool, error) {
+
+	defaultProviderID, err := m.GetDefaultKmsClusterID(
+		ctx, entity, defaultsToParent)
+	if err != nil {
+		return false, err
+	}
+	if defaultProviderID == "" {
+		return false, nil
+	}
+	return m.IsNativeProvider(ctx, defaultProviderID)
+}
+
+func (m ManagerKmip) IsNativeProvider(
+	ctx context.Context,
+	providerID string) (bool, error) {
+
+	info, err := m.GetClusterStatus(ctx, providerID)
+	if err != nil {
+		return false, err
+	}
+	if info == nil {
+		return false, nil
+	}
+	return info.ManagementType == string(
+		types.KmipClusterInfoKmsManagementTypeNativeProvider), nil
+}
+
+func (m ManagerKmip) GetDefaultKmsClusterID(
+	ctx context.Context,
+	entity *types.ManagedObjectReference,
+	defaultsToParent bool) (string, error) {
+
+	req := types.GetDefaultKmsCluster{
+		This:             m.Reference(),
+		Entity:           entity,
+		DefaultsToParent: &defaultsToParent,
+	}
+	res, err := methods.GetDefaultKmsCluster(ctx, m.Client(), &req)
+	if err != nil {
+		return "", err
+	}
+	if res.Returnval != nil {
+		return res.Returnval.Id, nil
+	}
+	return "", nil
+}
+
+func (m ManagerKmip) GetStatus(
+	ctx context.Context,
+	clusters ...types.KmipClusterInfo) ([]types.CryptoManagerKmipClusterStatus, error) {
+
+	req := types.RetrieveKmipServersStatus_Task{
+		This:     m.Reference(),
+		Clusters: clusters,
+	}
+	res, err := methods.RetrieveKmipServersStatus_Task(ctx, m.Client(), &req)
+	if err != nil {
+		return nil, err
+	}
+
+	task := object.NewTask(m.Client(), res.Returnval)
+	taskInfo, err := task.WaitForResult(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	if taskInfo.Result == nil {
+		return nil, nil
+	}
+	result, ok := taskInfo.Result.(types.ArrayOfCryptoManagerKmipClusterStatus)
+	if !ok {
+		return nil, nil
+	}
+	if len(result.CryptoManagerKmipClusterStatus) == 0 {
+		return nil, nil
+	}
+
+	return result.CryptoManagerKmipClusterStatus, nil
+}
+
+func (m ManagerKmip) GetClusterStatus(
+	ctx context.Context,
+	providerID string) (*types.CryptoManagerKmipClusterStatus, error) {
+
+	result, err := m.GetStatus(
+		ctx,
+		types.KmipClusterInfo{
+			ClusterId: types.KeyProviderId{
+				Id: providerID,
+			},
+		})
+	if err != nil {
+		return nil, err
+	}
+	if len(result) == 0 {
+		return nil, fmt.Errorf("invalid cluster ID")
+	}
+	return &result[0], nil
+}
+
+func (m ManagerKmip) GetServerStatus(
+	ctx context.Context,
+	providerID, serverName string) (*types.CryptoManagerKmipServerStatus, error) {
+
+	result, err := m.GetStatus(
+		ctx,
+		types.KmipClusterInfo{
+			ClusterId: types.KeyProviderId{
+				Id: providerID,
+			},
+			Servers: []types.KmipServerInfo{
+				{
+					Name: serverName,
+				},
+			},
+		})
+	if err != nil {
+		return nil, err
+	}
+	if len(result) == 0 {
+		return nil, fmt.Errorf("invalid cluster ID")
+	}
+	if len(result[0].Servers) == 0 {
+		return nil, fmt.Errorf("invalid server name")
+	}
+	return &result[0].Servers[0], nil
+}
+
+func (m ManagerKmip) MarkDefault(
+	ctx context.Context,
+	providerID string) error {
+
+	req := types.MarkDefault{
+		This:      m.Reference(),
+		ClusterId: types.KeyProviderId{Id: providerID},
+	}
+	_, err := methods.MarkDefault(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) SetDefaultKmsClusterId(
+	ctx context.Context,
+	providerID string,
+	entity *types.ManagedObjectReference) error {
+
+	req := types.SetDefaultKmsCluster{
+		This:   m.Reference(),
+		Entity: entity,
+	}
+	if providerID != "" {
+		req.ClusterId = &types.KeyProviderId{
+			Id: providerID,
+		}
+	}
+	_, err := methods.SetDefaultKmsCluster(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) RegisterKmipCluster(
+	ctx context.Context,
+	providerID string,
+	managementType types.KmipClusterInfoKmsManagementType) error {
+
+	req := types.RegisterKmsCluster{
+		This: m.Reference(),
+		ClusterId: types.KeyProviderId{
+			Id: providerID,
+		},
+		ManagementType: string(managementType),
+	}
+	_, err := methods.RegisterKmsCluster(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) UnregisterKmsCluster(
+	ctx context.Context,
+	providerID string) error {
+
+	req := types.UnregisterKmsCluster{
+		This: m.Reference(),
+		ClusterId: types.KeyProviderId{
+			Id: providerID,
+		},
+	}
+	_, err := methods.UnregisterKmsCluster(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) RegisterKmipServer(
+	ctx context.Context,
+	server types.KmipServerSpec) error {
+
+	req := types.RegisterKmipServer{
+		This:   m.Reference(),
+		Server: server,
+	}
+	_, err := methods.RegisterKmipServer(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) UpdateKmipServer(
+	ctx context.Context,
+	server types.KmipServerSpec) error {
+
+	req := types.UpdateKmipServer{
+		This:   m.Reference(),
+		Server: server,
+	}
+	_, err := methods.UpdateKmipServer(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) RemoveKmipServer(
+	ctx context.Context,
+	providerID, serverName string) error {
+
+	req := types.RemoveKmipServer{
+		This: m.Reference(),
+		ClusterId: types.KeyProviderId{
+			Id: providerID,
+		},
+		ServerName: serverName,
+	}
+	_, err := methods.RemoveKmipServer(ctx, m.Client(), &req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m ManagerKmip) ListKeys(
+	ctx context.Context,
+	limit *int32) ([]types.CryptoKeyId, error) {
+
+	req := types.ListKeys{
+		This:  m.Reference(),
+		Limit: limit,
+	}
+	res, err := methods.ListKeys(ctx, m.Client(), &req)
+	if err != nil {
+		return nil, err
+	}
+	return res.Returnval, nil
+}
+
+func (m ManagerKmip) IsValidKey(
+	ctx context.Context,
+	keyID string) (bool, error) {
+
+	keys, err := m.ListKeys(ctx, nil)
+	if err != nil {
+		return false, err
+	}
+
+	for i := range keys {
+		if keys[i].KeyId == keyID {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+func (m ManagerKmip) IsValidProvider(
+	ctx context.Context,
+	providerID string) (bool, error) {
+
+	clusters, err := m.ListKmipServers(ctx, nil)
+	if err != nil {
+		return false, err
+	}
+
+	for i := range clusters {
+		if clusters[i].ClusterId.Id == providerID {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+func (m ManagerKmip) IsValidServer(
+	ctx context.Context,
+	providerID, serverName string) (bool, error) {
+
+	clusters, err := m.ListKmipServers(ctx, nil)
+	if err != nil {
+		return false, err
+	}
+
+	for i := range clusters {
+		if clusters[i].ClusterId.Id == providerID {
+			for j := range clusters[i].Servers {
+				if clusters[i].Servers[j].Name == serverName {
+					return true, nil
+				}
+			}
+		}
+	}
+
+	return false, nil
+}
+
+func (m ManagerKmip) GenerateKey(
+	ctx context.Context,
+	providerID string) (string, error) {
+
+	req := types.GenerateKey{
+		This: m.Reference(),
+	}
+
+	if providerID != "" {
+		req.KeyProvider = &types.KeyProviderId{
+			Id: providerID,
+		}
+	}
+	res, err := methods.GenerateKey(ctx, m.Client(), &req)
+	if err != nil {
+		return "", err
+	}
+	if !res.Returnval.Success {
+		err := generateKeyError{reason: res.Returnval.Reason}
+		if res.Returnval.Fault != nil {
+			err.LocalizedMethodFault = *res.Returnval.Fault
+		}
+		return "", err
+	}
+	return res.Returnval.KeyId.KeyId, nil
+}
+
+type generateKeyError struct {
+	types.LocalizedMethodFault
+	reason string
+}
+
+func (e generateKeyError) Error() string {
+
+	return e.reason
+}
+
+func (e generateKeyError) GetLocalizedMethodFault() *types.LocalizedMethodFault {
+	return &e.LocalizedMethodFault
+}
diff --git a/govc/datastore/ls.go b/govc/datastore/ls.go
index 6bb2a4e6..48bf19dc 100644
--- a/govc/datastore/ls.go
+++ b/govc/datastore/ls.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -26,7 +26,6 @@ import (
 	"strings"
 	"text/tabwriter"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/object"
@@ -76,7 +75,14 @@ func (cmd *ls) Usage() string {
 }
 
 func isInvalid(err error) bool {
-	return fault.Is(err, &types.InvalidArgument{})
+	if f, ok := err.(types.HasFault); ok {
+		switch f.Fault().(type) {
+		case *types.InvalidArgument:
+			return true
+		}
+	}
+
+	return false
 }
 
 func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
diff --git a/govc/datastore/mkdir.go b/govc/datastore/mkdir.go
index aabd8a4b..7cc21e76 100644
--- a/govc/datastore/mkdir.go
+++ b/govc/datastore/mkdir.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,10 +22,10 @@ import (
 	"flag"
 	"fmt"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -105,8 +105,11 @@ func (cmd *mkdir) Run(ctx context.Context, f *flag.FlagSet) error {
 
 		// ignore EEXIST if -p flag is given
 		if err != nil && cmd.createParents {
-			if fault.Is(err, &types.FileAlreadyExists{}) {
-				return nil
+			if soap.IsSoapFault(err) {
+				soapFault := soap.ToSoapFault(err)
+				if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
+					return nil
+				}
 			}
 		}
 	}
diff --git a/govc/disk/ls.go b/govc/disk/ls.go
index 4c7cb12a..9e170649 100644
--- a/govc/disk/ls.go
+++ b/govc/disk/ls.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -25,10 +25,10 @@ import (
 	"text/tabwriter"
 	"time"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/units"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 	"github.com/vmware/govmomi/vslm"
 )
@@ -165,9 +165,12 @@ func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
 	for _, id := range ids {
 		o, err := m.Retrieve(ctx, ds, id)
 		if err != nil {
-			if filterNotFound && fault.Is(err, &types.NotFound{}) {
-				// The case when an FCD is deleted by something other than DeleteVStorageObject_Task, such as VM destroy
-				return fmt.Errorf("%s not found: use 'disk.ls -R' to reconcile datastore inventory", id)
+			if filterNotFound && soap.IsSoapFault(err) {
+				fault := soap.ToSoapFault(err).Detail.Fault
+				if _, ok := fault.(types.NotFound); ok {
+					// The case when an FCD is deleted by something other than DeleteVStorageObject_Task, such as VM destroy
+					return fmt.Errorf("%s not found: use 'disk.ls -R' to reconcile datastore inventory", id)
+				}
 			}
 			return fmt.Errorf("retrieve %q: %s", id, err)
 		}
diff --git a/govc/flags/host_connect.go b/govc/flags/host_connect.go
index bd88fc7c..c64e4368 100644
--- a/govc/flags/host_connect.go
+++ b/govc/flags/host_connect.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,7 +22,6 @@ import (
 	"fmt"
 	"net/url"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/types"
@@ -86,9 +85,15 @@ func (flag *HostConnectFlag) Spec(c *vim25.Client) types.HostConnectSpec {
 
 // Fault checks if error is SSLVerifyFault, including the thumbprint if so
 func (flag *HostConnectFlag) Fault(err error) error {
-	var verify *types.SSLVerifyFault
-	if _, ok := fault.As(err, &verify); ok {
-		return fmt.Errorf("%s thumbprint=%s", err, verify.Thumbprint)
+	if err == nil {
+		return nil
+	}
+
+	if f, ok := err.(types.HasFault); ok {
+		switch fault := f.Fault().(type) {
+		case *types.SSLVerifyFault:
+			return fmt.Errorf("%s thumbprint=%s", err, fault.Thumbprint)
+		}
 	}
 
 	return err
diff --git a/govc/flags/search.go b/govc/flags/search.go
index f2ab7128..ba884090 100644
--- a/govc/flags/search.go
+++ b/govc/flags/search.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -23,10 +23,10 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/find"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -205,8 +205,11 @@ func (flag *SearchFlag) searchByUUID(c *vim25.Client, dc *object.Datacenter) (ob
 	for _, iu := range []*bool{nil, types.NewBool(true)} {
 		ref, err = flag.searchIndex(c).FindByUuid(ctx, dc, flag.byUUID, isVM, iu)
 		if err != nil {
-			if fault.Is(err, &types.InvalidArgument{}) {
-				continue
+			if soap.IsSoapFault(err) {
+				fault := soap.ToSoapFault(err).VimFault()
+				if _, ok := fault.(types.InvalidArgument); ok {
+					continue
+				}
 			}
 			return nil, err
 		}
diff --git a/govc/object/find.go b/govc/object/find.go
index c7c4823d..83adc98e 100644
--- a/govc/object/find.go
+++ b/govc/object/find.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -25,7 +25,6 @@ import (
 	"strings"
 	"text/tabwriter"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/internal"
@@ -34,6 +33,7 @@ import (
 	"github.com/vmware/govmomi/view"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -388,8 +388,11 @@ func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error {
 		if cmd.long || !cmd.ref {
 			e, err := finder.Element(ctx, o)
 			if err != nil {
-				if fault.Is(err, &types.ManagedObjectNotFound{}) {
-					continue // object was deleted after v.Find() returned
+				if soap.IsSoapFault(err) {
+					_, ok := soap.ToSoapFault(err).VimFault().(types.ManagedObjectNotFound)
+					if ok {
+						continue // object was deleted after v.Find() returned
+					}
 				}
 				return err
 			}
diff --git a/govc/object/save.go b/govc/object/save.go
index 0a88b6da..a2d52a22 100644
--- a/govc/object/save.go
+++ b/govc/object/save.go
@@ -24,7 +24,6 @@ import (
 	"path/filepath"
 	"sort"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/property"
@@ -32,6 +31,7 @@ import (
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 	"github.com/vmware/govmomi/vim25/xml"
 )
@@ -177,6 +177,16 @@ var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedO
 	"AlarmManager":                   saveAlarmManager,
 }
 
+func isNotConnected(err error) bool {
+	if soap.IsSoapFault(err) {
+		switch soap.ToSoapFault(err).VimFault().(type) {
+		case types.HostNotConnected:
+			return true
+		}
+	}
+	return false
+}
+
 func (cmd *save) save(content []types.ObjectContent) error {
 	for _, x := range content {
 		x.MissingSet = nil // drop any NoPermission faults
@@ -198,7 +208,7 @@ func (cmd *save) save(content []types.ObjectContent) error {
 		if method, ok := saveObjects[x.Obj.Type]; ok {
 			objs, err := method(context.Background(), c, x.Obj)
 			if err != nil {
-				if fault.Is(err, &types.HostNotConnected{}) {
+				if isNotConnected(err) {
 					continue
 				}
 				return err
diff --git a/govc/option/set.go b/govc/option/set.go
index 281bec88..86f74495 100644
--- a/govc/option/set.go
+++ b/govc/option/set.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,10 +22,10 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -63,6 +63,17 @@ Examples:
   govc option.set logger.Vsan verbose`
 }
 
+func isInvalidName(err error) bool {
+	if soap.IsSoapFault(err) {
+		soapFault := soap.ToSoapFault(err)
+		if _, ok := soapFault.VimFault().(types.InvalidName); ok {
+			return ok
+		}
+	}
+
+	return false
+}
+
 func (cmd *Set) Update(ctx context.Context, f *flag.FlagSet, m *object.OptionManager) error {
 	if f.NArg() != 2 {
 		return flag.ErrHelp
@@ -73,7 +84,7 @@ func (cmd *Set) Update(ctx context.Context, f *flag.FlagSet, m *object.OptionMan
 
 	opts, err := m.Query(ctx, name)
 	if err != nil {
-		if fault.Is(err, &types.InvalidName{}) {
+		if isInvalidName(err) {
 			// If the option doesn't exist, creating one can only have a string Value.
 			// The Key prefix is limited in this case too, it seems to the config.* namespace.
 			return m.Update(ctx, []types.BaseOptionValue{&types.OptionValue{
diff --git a/govc/permissions/remove.go b/govc/permissions/remove.go
index 8200ca90..7f720328 100644
--- a/govc/permissions/remove.go
+++ b/govc/permissions/remove.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -20,8 +20,8 @@ import (
 	"context"
 	"flag"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -78,8 +78,9 @@ func (cmd *remove) Run(ctx context.Context, f *flag.FlagSet) error {
 	for _, ref := range refs {
 		err = m.RemoveEntityPermission(ctx, ref, cmd.Principal, cmd.Group)
 		if err != nil {
-			if cmd.force {
-				if fault.Is(err, &types.NotFound{}) {
+			if cmd.force && soap.IsSoapFault(err) {
+				_, ok := soap.ToSoapFault(err).VimFault().(types.NotFound)
+				if ok {
 					continue
 				}
 			}
diff --git a/govc/vapp/destroy.go b/govc/vapp/destroy.go
index 9428a51e..a7af0582 100644
--- a/govc/vapp/destroy.go
+++ b/govc/vapp/destroy.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -20,7 +20,6 @@ import (
 	"context"
 	"flag"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/find"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
@@ -81,8 +80,11 @@ func (cmd *destroy) Run(ctx context.Context, f *flag.FlagSet) error {
 				err = task.Wait(ctx)
 				if err != nil {
 					// it's safe to ignore if the vapp is already powered off
-					if fault.Is(err, &types.InvalidPowerState{}) {
-						return nil
+					if f, ok := err.(types.HasFault); ok {
+						switch f.Fault().(type) {
+						case *types.InvalidPowerState:
+							return nil
+						}
 					}
 					return err
 				}
diff --git a/govc/vm/guest/mkdir.go b/govc/vm/guest/mkdir.go
index 37ce0f89..541f6d7f 100644
--- a/govc/vm/guest/mkdir.go
+++ b/govc/vm/guest/mkdir.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -20,8 +20,8 @@ import (
 	"context"
 	"flag"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -71,8 +71,11 @@ func (cmd *mkdir) Run(ctx context.Context, f *flag.FlagSet) error {
 
 	// ignore EEXIST if -p flag is given
 	if err != nil && cmd.createParents {
-		if fault.Is(err, &types.FileAlreadyExists{}) {
-			return nil
+		if soap.IsSoapFault(err) {
+			soapFault := soap.ToSoapFault(err)
+			if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
+				return nil
+			}
 		}
 	}
 
diff --git a/govc/vm/guest/mv.go b/govc/vm/guest/mv.go
index f73ca048..4890a6f8 100644
--- a/govc/vm/guest/mv.go
+++ b/govc/vm/guest/mv.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -20,8 +20,8 @@ import (
 	"context"
 	"flag"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -73,8 +73,11 @@ func (cmd *mv) Run(ctx context.Context, f *flag.FlagSet) error {
 	err = m.MoveFile(ctx, cmd.Auth(), src, dst, !cmd.noclobber)
 
 	if err != nil {
-		if fault.Is(err, &types.NotAFile{}) {
-			err = m.MoveDirectory(ctx, cmd.Auth(), src, dst)
+		if soap.IsSoapFault(err) {
+			soapFault := soap.ToSoapFault(err)
+			if _, ok := soapFault.VimFault().(types.NotAFile); ok {
+				err = m.MoveDirectory(ctx, cmd.Auth(), src, dst)
+			}
 		}
 	}
 
diff --git a/govc/vm/guest/touch.go b/govc/vm/guest/touch.go
index cbcf3c65..741bf2b4 100644
--- a/govc/vm/guest/touch.go
+++ b/govc/vm/guest/touch.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,7 +22,6 @@ import (
 	"flag"
 	"time"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
@@ -97,8 +96,9 @@ func (cmd *touch) Run(ctx context.Context, f *flag.FlagSet) error {
 	}
 
 	err = m.ChangeFileAttributes(ctx, cmd.Auth(), name, &attr)
-	if err != nil && !cmd.nocreate {
-		if fault.Is(err, &types.FileNotFound{}) {
+	if err != nil && !cmd.nocreate && soap.IsSoapFault(err) {
+		fault := soap.ToSoapFault(err)
+		if _, ok := fault.VimFault().(types.FileNotFound); ok {
 			// create a new empty file
 			url, cerr := m.InitiateFileTransferToGuest(ctx, cmd.Auth(), name, &attr, 0, false)
 			if cerr != nil {
diff --git a/govc/vm/power.go b/govc/vm/power.go
index 0e32a643..d3c8c8e5 100644
--- a/govc/vm/power.go
+++ b/govc/vm/power.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -21,10 +21,10 @@ import (
 	"flag"
 	"fmt"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -107,7 +107,14 @@ func (cmd *power) Process(ctx context.Context) error {
 }
 
 func isToolsUnavailable(err error) bool {
-	return fault.Is(err, &types.ToolsUnavailable{})
+	if soap.IsSoapFault(err) {
+		soapFault := soap.ToSoapFault(err)
+		if _, ok := soapFault.VimFault().(types.ToolsUnavailable); ok {
+			return ok
+		}
+	}
+
+	return false
 }
 
 // this is annoying, but the likely use cases for Datacenter.PowerOnVM outside of this command would
diff --git a/govc/vm/upgrade.go b/govc/vm/upgrade.go
index ce69fd38..1b436aed 100644
--- a/govc/vm/upgrade.go
+++ b/govc/vm/upgrade.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2022 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -21,9 +21,9 @@ import (
 	"flag"
 	"fmt"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/task"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -36,6 +36,15 @@ func init() {
 	cli.Register("vm.upgrade", &upgrade{})
 }
 
+func isAlreadyUpgraded(err error) bool {
+	if fault, ok := err.(task.Error); ok {
+		_, ok = fault.Fault().(*types.AlreadyUpgraded)
+		return ok
+	}
+
+	return false
+}
+
 func (cmd *upgrade) Register(ctx context.Context, f *flag.FlagSet) {
 	cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
 	cmd.VirtualMachineFlag.Register(ctx, f)
@@ -76,7 +85,7 @@ func (cmd *upgrade) Run(ctx context.Context, f *flag.FlagSet) error {
 	}
 	err = task.Wait(ctx)
 	if err != nil {
-		if fault.Is(err, &types.AlreadyUpgraded{}) {
+		if isAlreadyUpgraded(err) {
 			fmt.Println(err.Error())
 		} else {
 			return err
diff --git a/list/lister.go b/list/lister.go
index dd1a540e..92a40e8b 100644
--- a/list/lister.go
+++ b/list/lister.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,9 +22,9 @@ import (
 	"path"
 	"reflect"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/property"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -132,8 +132,12 @@ func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProper
 	for _, p := range res.Returnval {
 		v, err := mo.ObjectContentToType(p)
 		if err != nil {
-			if fault.Is(err, &types.ManagedObjectNotFound{}) {
-				continue
+			// Ignore fault if it is ManagedObjectNotFound
+			if soap.IsVimFault(err) {
+				switch soap.ToVimFault(err).(type) {
+				case *types.ManagedObjectNotFound:
+					continue
+				}
 			}
 
 			return err
diff --git a/object/datacenter.go b/object/datacenter.go
index 46facadc..8caea3eb 100644
--- a/object/datacenter.go
+++ b/object/datacenter.go
@@ -20,7 +20,6 @@ import (
 	"context"
 	"path"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
@@ -122,8 +121,10 @@ func (d Datacenter) PowerOnVM(ctx context.Context, vm []types.ManagedObjectRefer
 		err = task.Wait(ctx)
 		if err != nil {
 			// Ignore any InvalidPowerState fault, as it indicates the VM is already powered on
-			if !fault.Is(err, &types.InvalidPowerState{}) {
-				return nil, err
+			if f, ok := err.(types.HasFault); ok {
+				if _, ok = f.Fault().(*types.InvalidPowerState); !ok {
+					return nil, err
+				}
 			}
 		}
 	}
diff --git a/object/host_certificate_manager.go b/object/host_certificate_manager.go
index 8c8f581d..30787e7a 100644
--- a/object/host_certificate_manager.go
+++ b/object/host_certificate_manager.go
@@ -19,11 +19,11 @@ package object
 import (
 	"context"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/property"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -119,8 +119,10 @@ func (m HostCertificateManager) InstallServerCertificate(ctx context.Context, ce
 	}
 
 	err = m.Client().RoundTrip(ctx, &body, &body)
-	if err != nil && fault.Is(err, &types.MethodNotFound{}) {
-		return nil
+	if err != nil && soap.IsSoapFault(err) {
+		if _, ok := soap.ToSoapFault(err).VimFault().(types.MethodNotFound); ok {
+			return nil
+		}
 	}
 	return err
 }
diff --git a/session/cache/session.go b/session/cache/session.go
index 0144afe7..c6c3955c 100644
--- a/session/cache/session.go
+++ b/session/cache/session.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2020-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2020 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -26,7 +26,6 @@ import (
 	"os/user"
 	"path/filepath"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/session"
 	"github.com/vmware/govmomi/vapi/rest"
 	"github.com/vmware/govmomi/vim25"
@@ -228,9 +227,12 @@ func soapSessionValid(ctx context.Context, client *vim25.Client) (bool, error) {
 	m := session.NewManager(client)
 	u, err := m.UserSession(ctx)
 	if err != nil {
-		if fault.Is(err, &types.ManagedObjectNotFound{}) {
+		if soap.IsSoapFault(err) {
+			fault := soap.ToSoapFault(err).VimFault()
 			// If the PropertyCollector is not found, the saved session for this URL is not valid
-			return false, nil
+			if _, ok := fault.(types.ManagedObjectNotFound); ok {
+				return false, nil
+			}
 		}
 
 		return false, err
diff --git a/session/manager.go b/session/manager.go
index da993c54..6a24fb95 100644
--- a/session/manager.go
+++ b/session/manager.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -22,7 +22,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/property"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
@@ -182,8 +181,11 @@ func (sm *Manager) UserSession(ctx context.Context) (*types.UserSession, error)
 	err := pc.RetrieveOne(ctx, sm.Reference(), []string{"currentSession"}, &mgr)
 	if err != nil {
 		// It's OK if we can't retrieve properties because we're not authenticated
-		if fault.Is(err, &types.NotAuthenticated{}) {
-			return nil, nil
+		if f, ok := err.(types.HasFault); ok {
+			switch f.Fault().(type) {
+			case *types.NotAuthenticated:
+				return nil, nil
+			}
 		}
 
 		return nil, err
diff --git a/simulator/crypto_manager_kmip.go b/simulator/crypto_manager_kmip.go
new file mode 100644
index 00000000..021ce218
--- /dev/null
+++ b/simulator/crypto_manager_kmip.go
@@ -0,0 +1,511 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+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.
+*/
+
+package simulator
+
+import (
+	"slices"
+
+	"github.com/google/uuid"
+
+	"github.com/vmware/govmomi/vim25/methods"
+	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+const (
+	nativeKeyProvider = string(types.KmipClusterInfoKmsManagementTypeNativeProvider)
+)
+
+type CryptoManagerKmip struct {
+	mo.CryptoManagerKmip
+
+	keyIDToProviderID map[string]string
+}
+
+func (m *CryptoManagerKmip) init(r *Registry) {
+	if m.keyIDToProviderID == nil {
+		m.keyIDToProviderID = map[string]string{}
+	}
+}
+
+func (m *CryptoManagerKmip) ListKmipServers(
+	ctx *Context, req *types.ListKmipServers) soap.HasFault {
+
+	body := methods.ListKmipServersBody{
+		Res: &types.ListKmipServersResponse{},
+	}
+
+	if len(m.KmipServers) > 0 {
+		limit := len(m.KmipServers)
+		if req.Limit != nil {
+			if reqLimit := int(*req.Limit); reqLimit >= 0 && reqLimit < limit {
+				limit = reqLimit
+			}
+		}
+		body.Res.Returnval = m.KmipServers[0:limit]
+	}
+
+	return &body
+
+}
+
+// TODO: Implement req.DefaultsToParent
+func (m *CryptoManagerKmip) GetDefaultKmsCluster(
+	ctx *Context, req *types.GetDefaultKmsCluster) soap.HasFault {
+
+	var (
+		providerID string
+		body       methods.GetDefaultKmsClusterBody
+	)
+
+	for i := range m.KmipServers {
+		c := m.KmipServers[i]
+		if req.Entity != nil {
+			for j := range c.UseAsEntityDefault {
+				if *req.Entity == c.UseAsEntityDefault[j] {
+					providerID = c.ClusterId.Id
+				}
+			}
+		} else if c.UseAsDefault {
+			providerID = c.ClusterId.Id
+		}
+		if providerID != "" {
+			break
+		}
+	}
+
+	if providerID == "" {
+		body.Fault_ = Fault("No default provider", &types.RuntimeFault{})
+	} else {
+		body.Res = &types.GetDefaultKmsClusterResponse{
+			Returnval: &types.KeyProviderId{Id: providerID},
+		}
+	}
+
+	return &body
+}
+
+type retrieveKmipServerStatusTask struct {
+	*CryptoManagerKmip
+	get []types.KmipClusterInfo
+	ctx *Context
+}
+
+func (c *retrieveKmipServerStatusTask) Run(
+	task *Task) (types.AnyType, types.BaseMethodFault) {
+
+	var result []types.CryptoManagerKmipClusterStatus
+
+	if len(c.get) == 0 {
+		c.get = make([]types.KmipClusterInfo, len(c.KmipServers))
+		copy(c.get, c.KmipServers)
+	}
+
+	for i := range c.get {
+		g := &c.get[i]
+		if len(g.Servers) == 0 {
+			for j := range c.KmipServers {
+				if g.ClusterId.Id == c.KmipServers[j].ClusterId.Id {
+					g.Servers = make(
+						[]types.KmipServerInfo, len(c.KmipServers[j].Servers))
+					copy(g.Servers, c.KmipServers[j].Servers)
+				}
+			}
+		}
+	}
+
+	for i := range c.KmipServers {
+		for j := range c.get {
+			if c.KmipServers[i].ClusterId.Id == c.get[j].ClusterId.Id {
+				clusterStatus := types.CryptoManagerKmipClusterStatus{
+					ClusterId: types.KeyProviderId{
+						Id: c.KmipServers[i].ClusterId.Id,
+					},
+					ManagementType: c.KmipServers[i].ManagementType,
+					OverallStatus:  types.ManagedEntityStatusGreen,
+				}
+				for k := range c.KmipServers[i].Servers {
+					for l := range c.get[j].Servers {
+						if c.KmipServers[i].Servers[k].Name == c.get[j].Servers[l].Name {
+							clusterStatus.Servers = append(
+								clusterStatus.Servers,
+								types.CryptoManagerKmipServerStatus{
+									Name:   c.KmipServers[i].Servers[k].Name,
+									Status: types.ManagedEntityStatusGreen,
+								},
+							)
+						}
+					}
+				}
+				result = append(result, clusterStatus)
+			}
+		}
+	}
+
+	return types.ArrayOfCryptoManagerKmipClusterStatus{
+		CryptoManagerKmipClusterStatus: result,
+	}, nil
+}
+
+func (m *CryptoManagerKmip) RetrieveKmipServersStatusTask(
+	ctx *Context, req *types.RetrieveKmipServersStatus_Task) soap.HasFault {
+
+	var body methods.RetrieveKmipServersStatus_TaskBody
+
+	runner := &retrieveKmipServerStatusTask{
+		CryptoManagerKmip: m,
+		ctx:               ctx,
+		get:               req.Clusters,
+	}
+	task := CreateTask(
+		runner.Reference(),
+		"retrieveKmipServerStatus",
+		runner.Run)
+
+	body.Res = &types.RetrieveKmipServersStatus_TaskResponse{
+		Returnval: task.Run(ctx),
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) MarkDefault(
+	ctx *Context, req *types.MarkDefault) soap.HasFault {
+
+	return m.SetDefaultKmsCluster(
+		ctx,
+		&types.SetDefaultKmsCluster{
+			This: req.This,
+			ClusterId: &types.KeyProviderId{
+				Id: req.ClusterId.Id,
+			},
+		})
+
+}
+
+func (m *CryptoManagerKmip) SetDefaultKmsCluster(
+	ctx *Context, req *types.SetDefaultKmsCluster) soap.HasFault {
+
+	var (
+		validClusterID bool
+		body           methods.SetDefaultKmsClusterBody
+	)
+
+	for i := range m.KmipServers {
+		c := &m.KmipServers[i]
+		if req.ClusterId != nil && req.ClusterId.Id != "" {
+			if c.ClusterId.Id != req.ClusterId.Id {
+				c.UseAsDefault = false
+				c.UseAsEntityDefault = nil
+			} else {
+				validClusterID = true
+				if req.Entity == nil {
+					c.UseAsDefault = true
+				} else {
+					found := false
+					for j := range c.UseAsEntityDefault {
+						if *req.Entity == c.UseAsEntityDefault[j] {
+							found = true
+							break
+						}
+					}
+					if !found {
+						c.UseAsEntityDefault = append(
+							c.UseAsEntityDefault,
+							*req.Entity)
+					}
+				}
+			}
+		} else if req.Entity != nil {
+			x := -1
+			for j := range c.UseAsEntityDefault {
+				if *req.Entity == c.UseAsEntityDefault[j] {
+					x = j
+					break
+				}
+			}
+			if x >= 0 {
+				c.UseAsEntityDefault = slices.Delete(
+					c.UseAsEntityDefault, x, x+1)
+			}
+		} else {
+			c.UseAsDefault = false
+		}
+	}
+
+	if req.ClusterId != nil && req.ClusterId.Id != "" && !validClusterID {
+		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
+	} else {
+		body.Res = &types.SetDefaultKmsClusterResponse{}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) RegisterKmsCluster(
+	ctx *Context, req *types.RegisterKmsCluster) soap.HasFault {
+
+	var body methods.RegisterKmsClusterBody
+
+	for i := range m.KmipServers {
+		if req.ClusterId.Id == m.KmipServers[i].ClusterId.Id {
+			body.Fault_ = Fault("Already registered", &types.RuntimeFault{})
+		}
+	}
+	if body.Fault_ == nil {
+		body.Res = &types.RegisterKmsClusterResponse{}
+		m.KmipServers = append(m.KmipServers,
+			types.KmipClusterInfo{
+				ClusterId: types.KeyProviderId{
+					Id: req.ClusterId.Id,
+				},
+				ManagementType: req.ManagementType,
+			})
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) UnregisterKmsCluster(
+	ctx *Context, req *types.UnregisterKmsCluster) soap.HasFault {
+
+	var body methods.UnregisterKmsClusterBody
+
+	x := -1
+	for i := range m.KmipServers {
+		if req.ClusterId.Id == m.KmipServers[i].ClusterId.Id {
+			x = i
+		}
+	}
+
+	if x < 0 {
+		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
+	} else {
+		m.KmipServers = slices.Delete(m.KmipServers, x, x+1)
+		body.Res = &types.UnregisterKmsClusterResponse{}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) RegisterKmipServer(
+	ctx *Context, req *types.RegisterKmipServer) soap.HasFault {
+
+	var (
+		validClusterID    bool
+		alreadyRegistered bool
+		body              methods.RegisterKmipServerBody
+	)
+
+	for i := range m.KmipServers {
+		c := &m.KmipServers[i]
+
+		if req.Server.ClusterId.Id == c.ClusterId.Id {
+			validClusterID = true
+			for j := range c.Servers {
+				if req.Server.Info.Name == c.Servers[j].Name {
+					alreadyRegistered = true
+					break
+				}
+			}
+			if !alreadyRegistered {
+				c.Servers = append(c.Servers, req.Server.Info)
+			}
+		}
+
+		if validClusterID || alreadyRegistered {
+			break
+		}
+	}
+
+	if !validClusterID {
+		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
+	} else if alreadyRegistered {
+		body.Fault_ = Fault("Already registered", &types.RuntimeFault{})
+	} else {
+		body.Res = &types.RegisterKmipServerResponse{}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) RemoveKmipServer(
+	ctx *Context, req *types.RemoveKmipServer) soap.HasFault {
+
+	var (
+		validClusterID  bool
+		validServerName bool
+		body            methods.RemoveKmipServerBody
+	)
+
+	for i := range m.KmipServers {
+		c := &m.KmipServers[i]
+
+		if req.ClusterId.Id == c.ClusterId.Id {
+			validClusterID = true
+
+			x := -1
+			for j := range c.Servers {
+				if req.ServerName == c.Servers[j].Name {
+					x = j
+					break
+				}
+			}
+
+			if x >= 0 {
+				validServerName = true
+				c.Servers = slices.Delete(c.Servers, x, x+1)
+			}
+		}
+
+		if validClusterID {
+			break
+		}
+	}
+
+	if !validClusterID {
+		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
+	} else if !validServerName {
+		body.Fault_ = Fault("Invalid server name", &types.RuntimeFault{})
+	} else {
+		body.Res = &types.RemoveKmipServerResponse{}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) UpdateKmipServer(
+	ctx *Context, req *types.UpdateKmipServer) soap.HasFault {
+
+	var (
+		validClusterID  bool
+		validServerName bool
+		body            methods.UpdateKmipServerBody
+	)
+
+	for i := range m.KmipServers {
+		c := &m.KmipServers[i]
+
+		if req.Server.ClusterId.Id == c.ClusterId.Id {
+			validClusterID = true
+			for j := range c.Servers {
+				if req.Server.Info.Name == c.Servers[j].Name {
+					validServerName = true
+					c.Servers[j] = req.Server.Info
+					break
+				}
+			}
+		}
+
+		if validClusterID {
+			break
+		}
+	}
+
+	if !validClusterID {
+		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
+	} else if !validServerName {
+		body.Fault_ = Fault("Invalid server name", &types.RuntimeFault{})
+	} else {
+		body.Res = &types.UpdateKmipServerResponse{}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) GenerateKey(
+	ctx *Context, req *types.GenerateKey) soap.HasFault {
+
+	var (
+		provider types.KmipClusterInfo
+		body     methods.GenerateKeyBody
+	)
+
+	for i := range m.KmipServers {
+		c := m.KmipServers[i]
+		if req.KeyProvider == nil {
+			if c.UseAsDefault {
+				provider = c
+			}
+		} else if req.KeyProvider.Id == c.ClusterId.Id {
+			provider = c
+		}
+		if provider.ClusterId.Id != "" {
+			break
+		}
+	}
+
+	if provider.ClusterId.Id == "" {
+		body.Fault_ = Fault("No default provider", &types.RuntimeFault{})
+	} else if provider.ManagementType == nativeKeyProvider {
+		body.Fault_ = Fault(
+			"Cannot generate keys with native key provider",
+			&types.RuntimeFault{})
+	} else {
+		newKey := uuid.NewString()
+		m.keyIDToProviderID[newKey] = provider.ClusterId.Id
+
+		body.Res = &types.GenerateKeyResponse{
+			Returnval: types.CryptoKeyResult{
+				Success: true,
+				KeyId: types.CryptoKeyId{
+					KeyId: newKey,
+					ProviderId: &types.KeyProviderId{
+						Id: provider.ClusterId.Id,
+					},
+				},
+			},
+		}
+	}
+
+	return &body
+}
+
+func (m *CryptoManagerKmip) ListKeys(
+	ctx *Context, req *types.ListKeys) soap.HasFault {
+
+	body := methods.ListKeysBody{
+		Res: &types.ListKeysResponse{},
+	}
+
+	if len(m.keyIDToProviderID) > 0 {
+		var (
+			i     int
+			limit = len(m.keyIDToProviderID)
+		)
+		if req.Limit != nil {
+			if reqLimit := int(*req.Limit); reqLimit >= 0 && reqLimit < limit {
+				limit = reqLimit
+			}
+		}
+		for keyID, providerID := range m.keyIDToProviderID {
+			if i >= limit {
+				break
+			}
+			i++
+			body.Res.Returnval = append(body.Res.Returnval, types.CryptoKeyId{
+				KeyId: keyID,
+				ProviderId: &types.KeyProviderId{
+					Id: providerID,
+				},
+			})
+		}
+	}
+
+	return &body
+}
diff --git a/simulator/model.go b/simulator/model.go
index a595f189..f16efc2e 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -237,6 +237,7 @@ var kinds = map[string]reflect.Type{
 	"ClusterComputeResource":             reflect.TypeOf((*ClusterComputeResource)(nil)).Elem(),
 	"CustomFieldsManager":                reflect.TypeOf((*CustomFieldsManager)(nil)).Elem(),
 	"CustomizationSpecManager":           reflect.TypeOf((*CustomizationSpecManager)(nil)).Elem(),
+	"CryptoManagerKmip":                  reflect.TypeOf((*CryptoManagerKmip)(nil)).Elem(),
 	"Datacenter":                         reflect.TypeOf((*Datacenter)(nil)).Elem(),
 	"Datastore":                          reflect.TypeOf((*Datastore)(nil)).Elem(),
 	"DatastoreNamespaceManager":          reflect.TypeOf((*DatastoreNamespaceManager)(nil)).Elem(),
diff --git a/simulator/registry.go b/simulator/registry.go
index eebfec90..70146e72 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -535,6 +535,11 @@ func (r *Registry) FileManager() *FileManager {
 	return r.Get(r.content().FileManager.Reference()).(*FileManager)
 }
 
+// CryptoManager returns the CryptoManagerKmip singleton
+func (r *Registry) CryptoManager() *CryptoManagerKmip {
+	return r.Get(r.content().CryptoManager.Reference()).(*CryptoManagerKmip)
+}
+
 type VirtualDiskManagerInterface interface {
 	mo.Reference
 	MO() mo.VirtualDiskManager
diff --git a/vim25/soap/error.go b/vim25/soap/error.go
index b98da7bb..5d4fff45 100644
--- a/vim25/soap/error.go
+++ b/vim25/soap/error.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -27,6 +27,14 @@ import (
 	"github.com/vmware/govmomi/vim25/types"
 )
 
+type regularError struct {
+	err error
+}
+
+func (r regularError) Error() string {
+	return r.err.Error()
+}
+
 type soapFaultError struct {
 	fault *Fault
 }
@@ -91,6 +99,32 @@ func (v vimFaultError) Fault() types.BaseMethodFault {
 	return v.fault
 }
 
+func Wrap(err error) error {
+	switch err.(type) {
+	case regularError:
+		return err
+	case soapFaultError:
+		return err
+	case vimFaultError:
+		return err
+	}
+
+	return WrapRegularError(err)
+}
+
+func WrapRegularError(err error) error {
+	return regularError{err}
+}
+
+func IsRegularError(err error) bool {
+	_, ok := err.(regularError)
+	return ok
+}
+
+func ToRegularError(err error) error {
+	return err.(regularError).err
+}
+
 func WrapSoapFault(f *Fault) error {
 	return soapFaultError{f}
 }
@@ -121,11 +155,15 @@ func IsCertificateUntrusted(err error) bool {
 	// golang 1.20 introduce a new type to wrap 509 errors. So instead of
 	// casting the type, now we check the error chain contains the
 	// x509 error or not.
-	if errors.Is(err, &x509.UnknownAuthorityError{}) {
+	x509UnknownAuthorityErr := &x509.UnknownAuthorityError{}
+	ok := errors.As(err, x509UnknownAuthorityErr)
+	if ok {
 		return true
 	}
 
-	if errors.Is(err, &x509.HostnameError{}) {
+	x509HostNameErr := &x509.HostnameError{}
+	ok = errors.As(err, x509HostNameErr)
+	if ok {
 		return true
 	}
 


Test patch:
diff --git a/crypto/manager_kmip_test.go b/crypto/manager_kmip_test.go
new file mode 100644
index 00000000..ca044f6b
--- /dev/null
+++ b/crypto/manager_kmip_test.go
@@ -0,0 +1,998 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+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.
+*/
+
+package crypto_test
+
+import (
+	"context"
+	"math"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/fault"
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/simulator"
+	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+// CryptoManagerKmip should implement the Reference interface.
+var _ object.Reference = crypto.ManagerKmip{}
+
+func TestCryptoManagerKmip(t *testing.T) {
+
+	t.Run("RegisterKmipCluster", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			isValid, err := m.IsValidProvider(ctx, providerID)
+			assert.NoError(t, err)
+			assert.True(t, isValid)
+
+			err = m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown)
+			assert.EqualError(t, err, "ServerFaultCode: Already registered")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+		})
+	})
+
+	t.Run("GetClusterStatus", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+
+			status, err := m.GetClusterStatus(ctx, providerID)
+			assert.EqualError(t, err, "invalid cluster ID")
+			assert.Nil(t, status)
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			status, err = m.GetClusterStatus(ctx, providerID)
+			assert.NoError(t, err)
+			assert.NotNil(t, status)
+			assert.Equal(t, providerID, status.ClusterId.Id)
+		})
+	})
+
+	t.Run("UnregisterKmsCluster", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+
+			err = m.UnregisterKmsCluster(ctx, providerID)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			isValid, err := m.IsValidProvider(ctx, providerID)
+			assert.NoError(t, err)
+			assert.True(t, isValid)
+
+			assert.NoError(t, m.UnregisterKmsCluster(ctx, providerID))
+
+			isValid, err = m.IsValidProvider(ctx, providerID)
+			assert.NoError(t, err)
+			assert.False(t, isValid)
+		})
+	})
+
+	t.Run("IsValidProvider", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+
+			ok, err := m.IsValidProvider(ctx, providerID)
+			assert.NoError(t, err)
+			assert.False(t, ok)
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			ok, err = m.IsValidProvider(ctx, providerID)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+		})
+	})
+
+	t.Run("GetDefaultKmsClusterID", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			defaultProviderID, err := m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+
+			assert.NoError(t, m.MarkDefault(ctx, provider3ID))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider3ID, defaultProviderID)
+
+			// Assert setting the default a second time does not return an
+			// error.
+			assert.NoError(t, m.MarkDefault(ctx, provider3ID))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider3ID, defaultProviderID)
+
+			fakeMoRef := types.ManagedObjectReference{
+				Type:  "fake",
+				Value: "fake",
+			}
+
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, &fakeMoRef, true)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+
+			assert.NoError(t, m.SetDefaultKmsClusterId(
+				ctx, provider2ID, &fakeMoRef))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, &fakeMoRef, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider2ID, defaultProviderID)
+
+			// Assert setting the default for an entity a second time does not
+			// return an error.
+			assert.NoError(t, m.SetDefaultKmsClusterId(
+				ctx, provider2ID, &fakeMoRef))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, &fakeMoRef, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider2ID, defaultProviderID)
+
+			// Remove the default for the entity.
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, "", &fakeMoRef))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, &fakeMoRef, true)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+
+			// Remove the default.
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, "", nil))
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+		})
+	})
+
+	t.Run("RegisterKmipServer", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+			serverName := uuid.NewString()
+
+			serverSpec := types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: providerID,
+				},
+				Info: types.KmipServerInfo{
+					Name: serverName,
+				},
+			}
+
+			err = m.RegisterKmipServer(ctx, serverSpec)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, serverSpec))
+
+			ok, err := m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+
+			err = m.RegisterKmipServer(ctx, serverSpec)
+			assert.EqualError(t, err, "ServerFaultCode: Already registered")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+		})
+	})
+
+	t.Run("GetServerStatus", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+			serverName := uuid.NewString()
+
+			status, err := m.GetServerStatus(ctx, providerID, serverName)
+			assert.EqualError(t, err, "invalid cluster ID")
+			assert.Nil(t, status)
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			status, err = m.GetServerStatus(ctx, providerID, serverName)
+			assert.EqualError(t, err, "invalid server name")
+			assert.Nil(t, status)
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: providerID,
+				},
+				Info: types.KmipServerInfo{
+					Name: serverName,
+				},
+			}))
+
+			status, err = m.GetServerStatus(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.NotNil(t, status)
+			assert.Equal(t, serverName, status.Name)
+		})
+	})
+
+	t.Run("ListKmipServers", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			clusters, err := m.ListKmipServers(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 0)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			provider1serverName1 := uuid.NewString()
+			provider1serverName2 := uuid.NewString()
+			provider2serverName1 := uuid.NewString()
+			provider2serverName2 := uuid.NewString()
+			provider2serverName3 := uuid.NewString()
+			provider3serverName1 := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider1ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider1serverName1,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider1ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider1serverName2,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName1,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName2,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName3,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider3ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider3serverName1,
+				},
+			}))
+
+			clusters, err = m.ListKmipServers(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 3)
+
+			assert.Equal(t, clusters[0].ClusterId.Id, provider1ID)
+			assert.Equal(t, clusters[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[0].Servers, 2)
+			assert.Equal(t, clusters[0].Servers[0].Name, provider1serverName1)
+			assert.Equal(t, clusters[0].Servers[1].Name, provider1serverName2)
+
+			assert.Equal(t, clusters[1].ClusterId.Id, provider2ID)
+			assert.Equal(t, clusters[1].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[1].Servers, 3)
+			assert.Equal(t, clusters[1].Servers[0].Name, provider2serverName1)
+			assert.Equal(t, clusters[1].Servers[1].Name, provider2serverName2)
+			assert.Equal(t, clusters[1].Servers[2].Name, provider2serverName3)
+
+			assert.Equal(t, clusters[2].ClusterId.Id, provider3ID)
+			assert.Equal(t, clusters[2].ManagementType, string(types.KmipClusterInfoKmsManagementTypeNativeProvider))
+			assert.Len(t, clusters[2].Servers, 1)
+			assert.Equal(t, clusters[2].Servers[0].Name, provider3serverName1)
+
+			// List all with a limit.
+			clusters, err = m.ListKmipServers(ctx, types.NewInt32(math.MaxInt32))
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 3)
+
+			assert.Equal(t, clusters[0].ClusterId.Id, provider1ID)
+			assert.Equal(t, clusters[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[0].Servers, 2)
+			assert.Equal(t, clusters[0].Servers[0].Name, provider1serverName1)
+			assert.Equal(t, clusters[0].Servers[1].Name, provider1serverName2)
+
+			assert.Equal(t, clusters[1].ClusterId.Id, provider2ID)
+			assert.Equal(t, clusters[1].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[1].Servers, 3)
+			assert.Equal(t, clusters[1].Servers[0].Name, provider2serverName1)
+			assert.Equal(t, clusters[1].Servers[1].Name, provider2serverName2)
+			assert.Equal(t, clusters[1].Servers[2].Name, provider2serverName3)
+
+			assert.Equal(t, clusters[2].ClusterId.Id, provider3ID)
+			assert.Equal(t, clusters[2].ManagementType, string(types.KmipClusterInfoKmsManagementTypeNativeProvider))
+			assert.Len(t, clusters[2].Servers, 1)
+			assert.Equal(t, clusters[2].Servers[0].Name, provider3serverName1)
+
+			// List the first cluster.
+			clusters, err = m.ListKmipServers(ctx, types.NewInt32(1))
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 1)
+
+			assert.Equal(t, clusters[0].ClusterId.Id, provider1ID)
+			assert.Equal(t, clusters[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[0].Servers, 2)
+			assert.Equal(t, clusters[0].Servers[0].Name, provider1serverName1)
+			assert.Equal(t, clusters[0].Servers[1].Name, provider1serverName2)
+
+			// List the first and second cluster.
+			clusters, err = m.ListKmipServers(ctx, types.NewInt32(2))
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 2)
+
+			assert.Equal(t, clusters[0].ClusterId.Id, provider1ID)
+			assert.Equal(t, clusters[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[0].Servers, 2)
+			assert.Equal(t, clusters[0].Servers[0].Name, provider1serverName1)
+			assert.Equal(t, clusters[0].Servers[1].Name, provider1serverName2)
+
+			assert.Equal(t, clusters[1].ClusterId.Id, provider2ID)
+			assert.Equal(t, clusters[1].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, clusters[1].Servers, 3)
+			assert.Equal(t, clusters[1].Servers[0].Name, provider2serverName1)
+			assert.Equal(t, clusters[1].Servers[1].Name, provider2serverName2)
+			assert.Equal(t, clusters[1].Servers[2].Name, provider2serverName3)
+		})
+	})
+
+	t.Run("UpdateKmipServer", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+			serverName := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			spec := types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: providerID,
+				},
+				Info: types.KmipServerInfo{
+					Name: serverName,
+				},
+			}
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, spec))
+
+			ok, err := m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+
+			spec.ClusterId.Id = "invalid"
+			spec.Info.Name = "invalid"
+			spec.Info.Port = 123
+
+			err = m.UpdateKmipServer(ctx, spec)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			clusters, err := m.ListKmipServers(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 1)
+			assert.Len(t, clusters[0].Servers, 1)
+			assert.Equal(t, int32(0), clusters[0].Servers[0].Port)
+
+			spec.ClusterId.Id = providerID
+
+			err = m.UpdateKmipServer(ctx, spec)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid server name")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			clusters, err = m.ListKmipServers(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 1)
+			assert.Len(t, clusters[0].Servers, 1)
+			assert.Equal(t, int32(0), clusters[0].Servers[0].Port)
+
+			spec.Info.Name = serverName
+
+			assert.NoError(t, m.UpdateKmipServer(ctx, spec))
+
+			clusters, err = m.ListKmipServers(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, clusters, 1)
+			assert.Len(t, clusters[0].Servers, 1)
+			assert.Equal(t, int32(123), clusters[0].Servers[0].Port)
+		})
+	})
+
+	t.Run("RemoveKmipServer", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+			serverName := uuid.NewString()
+
+			err = m.RemoveKmipServer(ctx, providerID, serverName)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			err = m.RemoveKmipServer(ctx, providerID, serverName)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid server name")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: providerID,
+				},
+				Info: types.KmipServerInfo{
+					Name: serverName,
+				},
+			}))
+
+			ok, err := m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+
+			assert.NoError(t, m.RemoveKmipServer(ctx, providerID, serverName))
+
+			ok, err = m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.False(t, ok)
+		})
+	})
+
+	t.Run("IsValidServer", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+			serverName := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: providerID,
+				},
+				Info: types.KmipServerInfo{
+					Name: serverName,
+				},
+			}))
+
+			ok, err := m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+
+			assert.NoError(t, m.RemoveKmipServer(ctx, providerID, serverName))
+
+			ok, err = m.IsValidServer(ctx, providerID, serverName)
+			assert.NoError(t, err)
+			assert.False(t, ok)
+		})
+	})
+
+	t.Run("GetStatus", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			status, err := m.GetStatus(ctx)
+			assert.NoError(t, err)
+			assert.Nil(t, status)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			provider1serverName1 := uuid.NewString()
+			provider1serverName2 := uuid.NewString()
+			provider2serverName1 := uuid.NewString()
+			provider2serverName2 := uuid.NewString()
+			provider2serverName3 := uuid.NewString()
+			provider3serverName1 := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider1ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider1serverName1,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider1ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider1serverName2,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName1,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName2,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider2serverName3,
+				},
+			}))
+			assert.NoError(t, m.RegisterKmipServer(ctx, types.KmipServerSpec{
+				ClusterId: types.KeyProviderId{
+					Id: provider3ID,
+				},
+				Info: types.KmipServerInfo{
+					Name: provider3serverName1,
+				},
+			}))
+
+			status, err = m.GetStatus(ctx)
+			assert.NoError(t, err)
+			assert.NotNil(t, status)
+			assert.Len(t, status, 3)
+
+			assert.Equal(t, status[0].ClusterId.Id, provider1ID)
+			assert.Equal(t, status[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, status[0].Servers, 2)
+			assert.Equal(t, status[0].Servers[0].Name, provider1serverName1)
+			assert.Equal(t, status[0].Servers[1].Name, provider1serverName2)
+
+			assert.Equal(t, status[1].ClusterId.Id, provider2ID)
+			assert.Equal(t, status[1].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, status[1].Servers, 3)
+			assert.Equal(t, status[1].Servers[0].Name, provider2serverName1)
+			assert.Equal(t, status[1].Servers[1].Name, provider2serverName2)
+			assert.Equal(t, status[1].Servers[2].Name, provider2serverName3)
+
+			assert.Equal(t, status[2].ClusterId.Id, provider3ID)
+			assert.Equal(t, status[2].ManagementType, string(types.KmipClusterInfoKmsManagementTypeNativeProvider))
+			assert.Len(t, status[2].Servers, 1)
+			assert.Equal(t, status[2].Servers[0].Name, provider3serverName1)
+
+			status, err = m.GetStatus(ctx, types.KmipClusterInfo{
+				ClusterId: types.KeyProviderId{
+					Id: provider2ID,
+				},
+				Servers: []types.KmipServerInfo{
+					{
+						Name: provider2serverName2,
+					},
+				},
+			})
+			assert.NoError(t, err)
+			assert.NotNil(t, status)
+			assert.Len(t, status, 1)
+
+			assert.Equal(t, status[0].ClusterId.Id, provider2ID)
+			assert.Equal(t, status[0].ManagementType, string(types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.Len(t, status[0].Servers, 1)
+			assert.Equal(t, status[0].Servers[0].Name, provider2serverName2)
+		})
+	})
+
+	t.Run("IsDefaultProviderNative", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			ok, err := m.IsDefaultProviderNative(ctx, nil, false)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.False(t, ok)
+
+			assert.NoError(t, m.MarkDefault(ctx, provider3ID))
+
+			ok, err = m.IsDefaultProviderNative(ctx, nil, false)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+		})
+	})
+
+	t.Run("MarkDefault", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			assert.NoError(t, m.MarkDefault(ctx, provider2ID))
+			defaultProviderID, err := m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider2ID, defaultProviderID)
+
+			assert.NoError(t, m.MarkDefault(ctx, provider1ID))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider1ID, defaultProviderID)
+
+			assert.NoError(t, m.MarkDefault(ctx, provider3ID))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider3ID, defaultProviderID)
+
+			assert.NoError(t, m.MarkDefault(ctx, ""))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+		})
+	})
+
+	t.Run("SetDefaultKmsClusterId", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			provider1ID := uuid.NewString()
+			provider2ID := uuid.NewString()
+			provider3ID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider1ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider2ID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				provider3ID,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, provider2ID, nil))
+			defaultProviderID, err := m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider2ID, defaultProviderID)
+
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, provider1ID, nil))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider1ID, defaultProviderID)
+
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, provider3ID, nil))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.NoError(t, err)
+			assert.Equal(t, provider3ID, defaultProviderID)
+
+			err = m.SetDefaultKmsClusterId(ctx, "invalid", nil)
+			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+
+			assert.NoError(t, m.SetDefaultKmsClusterId(ctx, "", nil))
+			defaultProviderID, err = m.GetDefaultKmsClusterID(ctx, nil, true)
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, defaultProviderID)
+		})
+	})
+
+	t.Run("GenerateKey", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID1 := uuid.NewString()
+			providerID2 := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID1,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			keyID, err := m.GenerateKey(ctx, "")
+			assert.EqualError(t, err, "ServerFaultCode: No default provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, keyID)
+
+			assert.NoError(t, m.MarkDefault(ctx, providerID1))
+
+			keyID, err = m.GenerateKey(ctx, providerID1)
+			assert.EqualError(t, err,
+				"ServerFaultCode: Cannot generate keys with native key provider")
+			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
+			assert.Empty(t, keyID)
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID2,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			keyID, err = m.GenerateKey(ctx, providerID2)
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID)
+
+			assert.NoError(t, m.MarkDefault(ctx, providerID2))
+
+			keyID, err = m.GenerateKey(ctx, "")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID)
+		})
+	})
+
+	t.Run("ListKeys", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID1 := uuid.NewString()
+			providerID2 := uuid.NewString()
+			providerID3 := uuid.NewString()
+
+			keys, err := m.ListKeys(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, keys, 0)
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID1,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID2,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID3,
+				types.KmipClusterInfoKmsManagementTypeNativeProvider))
+
+			keyID1, err := m.GenerateKey(ctx, providerID2)
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID1)
+
+			assert.NoError(t, m.MarkDefault(ctx, providerID2))
+			keyID2, err := m.GenerateKey(ctx, "")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID2)
+
+			assert.NoError(t, m.MarkDefault(ctx, providerID1))
+			keyID3, err := m.GenerateKey(ctx, "")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID3)
+
+			keys, err = m.ListKeys(ctx, nil)
+			assert.NoError(t, err)
+			assert.Len(t, keys, 3)
+			assert.ElementsMatch(t, keys, []types.CryptoKeyId{
+				{
+					KeyId:      keyID1,
+					ProviderId: &types.KeyProviderId{Id: providerID2},
+				},
+				{
+					KeyId:      keyID2,
+					ProviderId: &types.KeyProviderId{Id: providerID2},
+				},
+				{
+					KeyId:      keyID3,
+					ProviderId: &types.KeyProviderId{Id: providerID1},
+				},
+			})
+
+			keys, err = m.ListKeys(ctx, types.NewInt32(math.MaxInt32))
+			assert.NoError(t, err)
+			assert.Len(t, keys, 3)
+			assert.ElementsMatch(t, keys, []types.CryptoKeyId{
+				{
+					KeyId:      keyID1,
+					ProviderId: &types.KeyProviderId{Id: providerID2},
+				},
+				{
+					KeyId:      keyID2,
+					ProviderId: &types.KeyProviderId{Id: providerID2},
+				},
+				{
+					KeyId:      keyID3,
+					ProviderId: &types.KeyProviderId{Id: providerID1},
+				},
+			})
+
+			keys, err = m.ListKeys(ctx, types.NewInt32(1))
+			assert.NoError(t, err)
+			assert.Len(t, keys, 1)
+
+			keys, err = m.ListKeys(ctx, types.NewInt32(2))
+			assert.NoError(t, err)
+			assert.Len(t, keys, 2)
+		})
+	})
+
+	t.Run("IsValidKey", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			m, err := crypto.GetManagerKmip(c)
+			assert.NoError(t, err)
+
+			providerID := uuid.NewString()
+
+			assert.NoError(t, m.RegisterKmipCluster(
+				ctx,
+				providerID,
+				types.KmipClusterInfoKmsManagementTypeUnknown))
+
+			assert.NoError(t, m.MarkDefault(ctx, providerID))
+
+			keyID, err := m.GenerateKey(ctx, "")
+			assert.NoError(t, err)
+			assert.NotEmpty(t, keyID)
+
+			ok, err := m.IsValidKey(ctx, keyID)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+		})
+	})
+}
diff --git a/simulator/race_test.go b/simulator/race_test.go
index be8fb2c9..2f4bc318 100644
--- a/simulator/race_test.go
+++ b/simulator/race_test.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2018 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -25,9 +25,9 @@ import (
 
 	"github.com/vmware/govmomi"
 	"github.com/vmware/govmomi/event"
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/find"
 	"github.com/vmware/govmomi/property"
+	"github.com/vmware/govmomi/task"
 	"github.com/vmware/govmomi/view"
 	"github.com/vmware/govmomi/vim25/types"
 )
@@ -254,6 +254,11 @@ func TestRace(t *testing.T) {
 	}
 }
 
+func isManagedObjectNotFound(err error) bool {
+	_, ok := err.(task.Error).Fault().(*types.ManagedObjectNotFound)
+	return ok
+}
+
 // Race VirtualMachine.Destroy vs Folder.Destroy; the latter removes the VM parent and the former should not panic in that case
 func TestRaceDestroy(t *testing.T) {
 	ctx := context.Background()
@@ -300,7 +305,7 @@ func TestRaceDestroy(t *testing.T) {
 			}
 			err = task.Wait(ctx)
 			if err != nil {
-				if fault.Is(err, &types.ManagedObjectNotFound{}) {
+				if isManagedObjectNotFound(err) {
 					notFound = true
 				} else {
 					t.Error(err)
diff --git a/simulator/session_manager_test.go b/simulator/session_manager_test.go
index b9e49563..a5484d2b 100644
--- a/simulator/session_manager_test.go
+++ b/simulator/session_manager_test.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -24,16 +24,26 @@ import (
 	"testing"
 
 	"github.com/vmware/govmomi"
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/property"
 	"github.com/vmware/govmomi/session"
 	"github.com/vmware/govmomi/simulator/vpx"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
+func isNotAuthenticated(err error) bool {
+	if soap.IsSoapFault(err) {
+		switch soap.ToSoapFault(err).VimFault().(type) {
+		case types.NotAuthenticated:
+			return true
+		}
+	}
+	return false
+}
+
 func TestSessionManagerAuth(t *testing.T) {
 	ctx := context.Background()
 
@@ -90,7 +100,7 @@ func TestSessionManagerAuth(t *testing.T) {
 	}
 
 	_, err = methods.GetCurrentTime(ctx, c)
-	if !fault.Is(err, &types.NotAuthenticated{}) {
+	if !isNotAuthenticated(err) {
 		t.Error("expected NotAuthenticated")
 	}
 
diff --git a/vim25/mo/retrieve_test.go b/vim25/mo/retrieve_test.go
index c6d26f9c..ca52e0c8 100644
--- a/vim25/mo/retrieve_test.go
+++ b/vim25/mo/retrieve_test.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 
 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
+    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,
@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/vmware/govmomi/fault"
 	"github.com/vmware/govmomi/vim25/soap"
 	"github.com/vmware/govmomi/vim25/types"
 	"github.com/vmware/govmomi/vim25/xml"
@@ -54,9 +53,8 @@ func TestNotAuthenticatedFault(t *testing.T) {
 		t.Errorf("Expected IsVimFault")
 	}
 
-	var not *types.NotAuthenticated
-	fault.As(err, &not)
-	if not.PrivilegeId != "System.View" {
+	fault := soap.ToVimFault(err).(*types.NotAuthenticated)
+	if fault.PrivilegeId != "System.View" {
 		t.Errorf("Expected first fault to be returned")
 	}
 }