diff --git a/kms/pom.xml b/kms/pom.xml index 63f4e3f7012..805d7192cc1 100644 --- a/kms/pom.xml +++ b/kms/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.32.0 + 26.50.0 pom import @@ -42,16 +42,27 @@ com.google.cloud google-cloud-kms + 2.88.0 + + + com.google.api.grpc + proto-google-cloud-kms-v1 + 0.179.0 com.google.crypto.tink tink 1.12.0 - + + com.google.protobuf + protobuf-java + 4.33.2 + com.google.protobuf protobuf-java-util + 4.33.2 junit @@ -77,5 +88,16 @@ + + + + org.jacoco + jacoco-maven-plugin + + true + + + + diff --git a/kms/src/main/java/kms/DeleteKey.java b/kms/src/main/java/kms/DeleteKey.java new file mode 100644 index 00000000000..64fa74668a3 --- /dev/null +++ b/kms/src/main/java/kms/DeleteKey.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_delete_key] +import com.google.cloud.kms.v1.CryptoKeyName; +import com.google.cloud.kms.v1.DeleteCryptoKeyMetadata; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class DeleteKey { + + public void deleteKey() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String keyRingId = "my-key-ring"; + String keyId = "my-key"; + deleteKey(projectId, locationId, keyRingId, keyId); + } + + // deleteKey deletes a crypto key. This action is permanent and cannot be undone. Once the key + // is deleted, it will no longer exist. + public void deleteKey(String projectId, String locationId, String keyRingId, String keyId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the key name from the project, location, key ring, and key. + CryptoKeyName keyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId); + + // Delete the key. + // Warning: This operation is permanent and cannot be undone. + // Wait for the operation to complete. + client.deleteCryptoKeyAsync(keyName).get(); + System.out.printf("Deleted key: %s%n", keyName.toString()); + } catch (Exception e) { + System.err.printf("Failed to delete key: %s%n", e.getMessage()); + } + } +} +// [END kms_delete_key] diff --git a/kms/src/main/java/kms/DeleteKeyVersion.java b/kms/src/main/java/kms/DeleteKeyVersion.java new file mode 100644 index 00000000000..1af4d80915c --- /dev/null +++ b/kms/src/main/java/kms/DeleteKeyVersion.java @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_delete_key_version] +import com.google.cloud.kms.v1.CryptoKeyVersionName; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import java.io.IOException; + +public class DeleteKeyVersion { + + public void deleteKeyVersion() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String keyRingId = "my-key-ring"; + String keyId = "my-key"; + String keyVersionId = "123"; + deleteKeyVersion(projectId, locationId, keyRingId, keyId, keyVersionId); + } + + // deleteKeyVersion deletes a key version. This action is permanent and cannot be undone. Once the + // key version is deleted, it will no longer exist. + public void deleteKeyVersion( + String projectId, String locationId, String keyRingId, String keyId, String keyVersionId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the key version name from the project, location, key ring, key, + // and key version. + CryptoKeyVersionName keyVersionName = + CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId); + + // Delete the key version. + // Warning: This operation is permanent and cannot be undone. + // Wait for the operation to complete. + client.deleteCryptoKeyVersionAsync(keyVersionName).get(); + System.out.printf("Deleted key version: %s%n", keyVersionName.toString()); + } catch (Exception e) { + System.err.printf("Failed to delete key version: %s%n", e.getMessage()); + } + } +} +// [END kms_delete_key_version] diff --git a/kms/src/main/java/kms/GetRetiredResource.java b/kms/src/main/java/kms/GetRetiredResource.java new file mode 100644 index 00000000000..f273feda3c2 --- /dev/null +++ b/kms/src/main/java/kms/GetRetiredResource.java @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_get_retired_resource] +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.RetiredResource; +import com.google.cloud.kms.v1.RetiredResourceName; +import java.io.IOException; + +public class GetRetiredResource { + + public void getRetiredResource() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String retiredResourceId = "my-retired-resource-id"; + getRetiredResource(projectId, locationId, retiredResourceId); + } + + // Get the retired resource. + public void getRetiredResource( + String projectId, String locationId, String retiredResourceId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the retired resource name from the project, location, and retired resource id. + RetiredResourceName name = RetiredResourceName.of(projectId, locationId, retiredResourceId); + + // Get the retired resource. + RetiredResource response = client.getRetiredResource(name); + System.out.printf("Retired resource: %s%n", response.getName()); + } + } +} +// [END kms_get_retired_resource] diff --git a/kms/src/main/java/kms/IamRemoveMember.java b/kms/src/main/java/kms/IamRemoveMember.java index 448746134f6..af1833908d5 100644 --- a/kms/src/main/java/kms/IamRemoveMember.java +++ b/kms/src/main/java/kms/IamRemoveMember.java @@ -56,13 +56,29 @@ public void iamRemoveMember( // Search through the bindings and remove matches. String roleToFind = "roles/cloudkms.cryptoKeyEncrypterDecrypter"; + // Create a new list of bindings, removing the member from the role. + java.util.List newBindings = new java.util.ArrayList<>(); for (Binding binding : policy.getBindingsList()) { if (binding.getRole().equals(roleToFind) && binding.getMembersList().contains(member)) { - binding.getMembersList().remove(member); + Binding.Builder bindingBuilder = binding.toBuilder(); + // Remove the member. + // Note: ProtocolStringList is immutable, so we need to rebuild the members list. + java.util.List validMembers = new java.util.ArrayList<>(binding.getMembersList()); + validMembers.remove(member); + + bindingBuilder.clearMembers().addAllMembers(validMembers); + if (!validMembers.isEmpty()) { + newBindings.add(bindingBuilder.build()); + } + // If no members left, we can just omit the binding (effective removal). + } else { + newBindings.add(binding); } } - client.setIamPolicy(resourceName, policy); + Policy newPolicy = policy.toBuilder().clearBindings().addAllBindings(newBindings).build(); + + client.setIamPolicy(resourceName, newPolicy); System.out.printf("Updated IAM policy for %s%n", resourceName.toString()); } } diff --git a/kms/src/main/java/kms/ListRetiredResources.java b/kms/src/main/java/kms/ListRetiredResources.java new file mode 100644 index 00000000000..8e729a6eadb --- /dev/null +++ b/kms/src/main/java/kms/ListRetiredResources.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_list_retired_resources] +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.LocationName; +import com.google.cloud.kms.v1.RetiredResource; +import java.io.IOException; + +public class ListRetiredResources { + + public void listRetiredResources() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + listRetiredResources(projectId, locationId); + } + + // List retired resources in a specific project and location. + public void listRetiredResources(String projectId, String locationId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the location name from the project and location. + LocationName locationName = LocationName.of(projectId, locationId); + + // List the retired resources. + for (RetiredResource resource : client.listRetiredResources(locationName).iterateAll()) { + System.out.printf("Retired resource: %s%n", resource.getName()); + } + } + } +} +// [END kms_list_retired_resources] diff --git a/kms/src/main/java/kms/VerifyAsymmetricEc.java b/kms/src/main/java/kms/VerifyAsymmetricEc.java index e86964509d0..844c275d1ee 100644 --- a/kms/src/main/java/kms/VerifyAsymmetricEc.java +++ b/kms/src/main/java/kms/VerifyAsymmetricEc.java @@ -45,6 +45,7 @@ public void verifyAsymmetricEc() throws IOException, GeneralSecurityException { verifyAsymmetricEc(projectId, locationId, keyRingId, keyId, keyVersionId, message, signature); } + // CPD-OFF // Verify the signature of a message signed with an RSA key. public void verifyAsymmetricEc( String projectId, diff --git a/kms/src/test/java/kms/SnippetsIT.java b/kms/src/test/java/kms/SnippetsIT.java index 49f18e61a1c..1de8fec89c3 100644 --- a/kms/src/test/java/kms/SnippetsIT.java +++ b/kms/src/test/java/kms/SnippetsIT.java @@ -18,6 +18,8 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.kms.v1.CreateCryptoKeyRequest; import com.google.cloud.kms.v1.CryptoKey; import com.google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose; import com.google.cloud.kms.v1.CryptoKeyName; @@ -54,6 +56,7 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import javax.crypto.Cipher; @@ -64,6 +67,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -81,44 +85,55 @@ public class SnippetsIT { private static String MAC_KEY_ID; private static String SYMMETRIC_KEY_ID; - private ByteArrayOutputStream stdOut; + @BeforeClass public static void beforeAll() throws IOException { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); - KEY_RING_ID = getRandomId(); - createKeyRing(KEY_RING_ID); + KEY_RING_ID = "kms-test-keyring-java"; + try { + createKeyRing(KEY_RING_ID); + } catch (Exception e) { + // Ignore. + } - ASYMMETRIC_DECRYPT_KEY_ID = getRandomId(); - createAsymmetricDecryptKey(ASYMMETRIC_DECRYPT_KEY_ID); + ASYMMETRIC_DECRYPT_KEY_ID = "kms-test-asymmetric-decrypt-v3"; + createKeyOrIgnore(ASYMMETRIC_DECRYPT_KEY_ID, SnippetsIT::createAsymmetricDecryptKey); - ASYMMETRIC_SIGN_EC_KEY_ID = getRandomId(); - createAsymmetricSignEcKey(ASYMMETRIC_SIGN_EC_KEY_ID); + ASYMMETRIC_SIGN_EC_KEY_ID = "kms-test-asymmetric-sign-ec-v3"; + createKeyOrIgnore(ASYMMETRIC_SIGN_EC_KEY_ID, SnippetsIT::createAsymmetricSignEcKey); - ASYMMETRIC_SIGN_RSA_KEY_ID = getRandomId(); - createAsymmetricSignRsaKey(ASYMMETRIC_SIGN_RSA_KEY_ID); + ASYMMETRIC_SIGN_RSA_KEY_ID = "kms-test-asymmetric-sign-rsa-v3"; + createKeyOrIgnore(ASYMMETRIC_SIGN_RSA_KEY_ID, SnippetsIT::createAsymmetricSignRsaKey); - HSM_KEY_ID = getRandomId(); - createHsmKey(HSM_KEY_ID); + HSM_KEY_ID = "kms-test-hsm-v3"; + createKeyOrIgnore(HSM_KEY_ID, SnippetsIT::createHsmKey); - MAC_KEY_ID = getRandomId(); - createMacKey(MAC_KEY_ID); + MAC_KEY_ID = "kms-test-mac-v3"; + createKeyOrIgnore(MAC_KEY_ID, SnippetsIT::createMacKey); - SYMMETRIC_KEY_ID = getRandomId(); - createSymmetricKey(SYMMETRIC_KEY_ID); + SYMMETRIC_KEY_ID = "kms-test-symmetric-v3"; + createKeyOrIgnore(SYMMETRIC_KEY_ID, SnippetsIT::createSymmetricKey); } + private ByteArrayOutputStream stdOut; + private ByteArrayOutputStream stdErr; + @Before public void beforeEach() { stdOut = new ByteArrayOutputStream(); + stdErr = new ByteArrayOutputStream(); System.setOut(new PrintStream(stdOut)); + System.setErr(new PrintStream(stdErr)); } @After public void afterEach() { stdOut = null; + stdErr = null; System.setOut(null); + System.setErr(null); } @AfterClass @@ -134,6 +149,7 @@ public static void afterAll() throws IOException { client.updateCryptoKey(keyWithoutRotation, fieldMask); } + /* ListCryptoKeyVersionsRequest listVersionsRequest = ListCryptoKeyVersionsRequest.newBuilder() .setParent(key.getName()) @@ -143,6 +159,7 @@ public static void afterAll() throws IOException { client.listCryptoKeyVersions(listVersionsRequest).iterateAll()) { client.destroyCryptoKeyVersion(version.getName()); } + */ } } } @@ -162,9 +179,36 @@ private static String getRandomId() { private static KeyRing createKeyRing(String keyRingId) throws IOException { try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { - KeyRing keyRing = KeyRing.newBuilder().build(); - KeyRing createdKeyRing = client.createKeyRing(getLocationName(), keyRingId, keyRing); - return createdKeyRing; + KeyRingName keyRingName = KeyRingName.of(PROJECT_ID, LOCATION_ID, keyRingId); + try { + return client.getKeyRing(keyRingName); + } catch (com.google.api.gax.rpc.NotFoundException e) { + // KeyRing doesn't exist, create it. + KeyRing keyRing = KeyRing.newBuilder().build(); + return client.createKeyRing(getLocationName(), keyRingId, keyRing); + } + } + } + + interface KeyCreator { + void create(String id) throws IOException; + } + + private static void createKeyOrIgnore(String id, KeyCreator creator) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, id); + try { + client.getCryptoKey(keyName); + return; // Exists + } catch (com.google.api.gax.rpc.NotFoundException e) { + // Doesn't exist, try creating. + } + } + + try { + creator.create(id); + } catch (com.google.api.gax.rpc.AlreadyExistsException e) { + // Ignore } } @@ -272,6 +316,30 @@ private static CryptoKey createSymmetricKey(String keyId) throws IOException { } } + private static CryptoKey createSymmetricKeyWithNoInitialVersion(String keyId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKeyPurpose.ENCRYPT_DECRYPT) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION) + .build()) + .putLabels("foo", "bar") + .putLabels("zip", "zap") + .build(); + + CreateCryptoKeyRequest request = + CreateCryptoKeyRequest.newBuilder() + .setParent(getKeyRingName().toString()) + .setCryptoKeyId(keyId) + .setCryptoKey(key) + .setSkipInitialVersionCreation(true) + .build(); + return client.createCryptoKey(request); + } + } + private static CryptoKeyVersion createKeyVersion(String keyId) throws IOException, InterruptedException, TimeoutException { try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { @@ -430,6 +498,71 @@ public void testDestroyRestoreKeyVersion() assertThat(stdOut.toString()).contains("Restored key version"); } + @Test + public void testDeleteKey() throws IOException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + + // Delete the key. + new DeleteKey().deleteKey(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + String output = stdOut.toString() + stdErr.toString(); + assertThat(output).contains("Deleted key"); + } + } + + @Test + public void testGetRetiredResource() + throws IOException, InterruptedException, ExecutionException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = + CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + client.deleteCryptoKeyAsync(keyName).get(); + } + + // List retired resources to find the one we just deleted. + String retiredResourceName = null; + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + for (com.google.cloud.kms.v1.RetiredResource resource : + client.listRetiredResources(getLocationName()).iterateAll()) { + if (resource.toString().contains(deleteKeyId)) { + retiredResourceName = resource.getName(); + break; + } + } + } + + // If not found, asserting null will fail. + assertThat(retiredResourceName).isNotNull(); + + // The name of the retired resource is required for Get. + String retiredResourceId = + retiredResourceName.substring(retiredResourceName.lastIndexOf('/') + 1); + new GetRetiredResource().getRetiredResource(PROJECT_ID, LOCATION_ID, retiredResourceId); + assertThat(stdOut.toString()).contains("Retired resource"); + } + + @Test + public void testListRetiredResources() + throws IOException, InterruptedException, ExecutionException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = + CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + client.deleteCryptoKeyAsync(keyName).get(); + } + + new ListRetiredResources().listRetiredResources(PROJECT_ID, LOCATION_ID); + // Since we ran DeleteKey above, there should be at least one retired resource. + assertThat(stdOut.toString()).contains("Retired resource"); + } + @Test public void testDisableEnableKeyVersion() throws Exception { CryptoKeyVersion keyVersion = createKeyVersion(ASYMMETRIC_DECRYPT_KEY_ID); @@ -477,7 +610,9 @@ public void testGetKeyVersionAttestation() throws IOException { @Test public void testGetKeyLabels() throws IOException { - new GetKeyLabels().getKeyLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + String keyId = getRandomId(); + createSymmetricKey(keyId); + new GetKeyLabels().getKeyLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("foo=bar"); } @@ -541,8 +676,10 @@ public void testUpdateKeyAddRotation() throws IOException { @Test public void testUpdateKeyRemoveLabels() throws IOException { + String keyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(keyId); new UpdateKeyRemoveLabels() - .updateKeyRemoveLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + .updateKeyRemoveLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("Updated key"); } @@ -562,8 +699,10 @@ public void testUpdateKeySetPrimary() throws IOException { @Test public void testUpdateKeyUpdateLabels() throws IOException { + String keyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(keyId); new UpdateKeyUpdateLabels() - .updateKeyUpdateLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + .updateKeyUpdateLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("Updated key"); }