8000 added samples for KMS Asymmetric Keys (#1186) · almubinsultan/java-docs-samples@bd0ed67 · GitHub 10000
[go: up one dir, main page]

Skip to content

Commit bd0ed67

Browse files
added samples for KMS Asymmetric Keys (GoogleCloudPlatform#1186)
* added samples for KMS Asymmetric Keys
1 parent 075b57b commit bd0ed67

File tree

3 files changed

+374
-1
lines changed

3 files changed

+374
-1
lines changed

kms/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@
5353
<artifactId>args4j</artifactId>
5454
<version>2.33</version>
5555
</dependency>
56-
56+
<dependency>
57+
<groupId>org.bouncycastle</groupId>
58+
<artifactId>bcpkix-jdk15on</artifactId>
59+
<version>1.50</version>
60+
</dependency>
5761
<!-- test dependencies -->
5862
<dependency>
5963
<groupId>junit</groupId>
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
20+
import com.google.api.client.http.HttpTransport;
21+
import com.google.api.client.http.javanet.NetHttpTransport;
22+
import com.google.api.client.json.JsonFactory;
23+
import com.google.api.client.json.jackson2.JacksonFactory;
24+
import com.google.api.services.cloudkms.v1.CloudKMS;
25+
import com.google.api.services.cloudkms.v1.CloudKMSScopes;
26+
import com.google.api.services.cloudkms.v1.model.AsymmetricDecryptRequest;
27+
import com.google.api.services.cloudkms.v1.model.AsymmetricDecryptResponse;
28+
import com.google.api.services.cloudkms.v1.model.AsymmetricSignRequest;
29+
import com.google.api.services.cloudkms.v1.model.AsymmetricSignResponse;
30+
import com.google.api.services.cloudkms.v1.model.Digest;
31+
import com.google.api.services.cloudkms.v1.model.KeyRing;
32+
import com.google.api.services.cloudkms.v1.model.ListKeyRingsResponse;
33+
import java.io.IOException;
34+
import java.io.StringReader;
35+
import java.nio.charset.StandardCharsets;
36+
import java.security.InvalidKeyException;
37+
import java.security.KeyFactory;
38+
import java.security.MessageDigest;
39+
import java.security.NoSuchAlgorithmException;
40+
import java.security.NoSuchProviderException;
41+
import java.security.PublicKey;
42+
import java.security.Security;
43+
import java.security.Signature;
44+
import java.security.SignatureException;
45+
import java.security.spec.InvalidKeySpecException;
46+
import java.security.spec.PKCS8EncodedKeySpec;
47+
import java.security.spec.X509EncodedKeySpec;
48+
import java.util.Base64;
49+
import javax.crypto.BadPaddingException;
50+
import javax.crypto.Cipher;
51+
import javax.crypto.IllegalBlockSizeException;
52+
import javax.crypto.NoSuchPaddingException;
53+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
54+
import org.bouncycastle.util.io.pem.PemReader;
55+
56+
@SuppressWarnings("checkstyle:abbreviationaswordinname")
57+
public class Asymmetric {
58+
59+
// [START kms_get_asymmetric_pu 17AE blic]
60+
/** Retrieves the public key from a saved asymmetric key pair on Cloud KMS */
61+
public static PublicKey getAsymmetricPublicKey(CloudKMS client, String keyPath)
62+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
63+
NoSuchProviderException {
64+
Security.addProvider(new BouncyCastleProvider());
65+
com.google.api.services.cloudkms.v1.model.PublicKey response;
66+
response = client.projects()
67+
.locations()
68+
.keyRings()
69+
.cryptoKeys()
70+
.cryptoKeyVersions()
71+
.getPublicKey(keyPath)
72+
.execute();
73+
PemReader reader = new PemReader(new StringReader(response.getPem()));
74+
byte[] pem = reader.readPemObject().getContent();
75+
X509EncodedKeySpec abstractKey = new X509EncodedKeySpec(pem);
76+
try {
77+
return KeyFactory.getInstance("RSA", "BC").generatePublic(abstractKey);
78+
} catch (InvalidKeySpecException e) {
79+
return KeyFactory.getInstance("ECDSA", "BC").generatePublic(abstractKey);
80+
}
81+
}
82+
// [END kms_get_asymmetric_public]
83+
84+
// [START kms_decrypt_rsa]
85+
/**
86+
* Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key
87+
* stored on Cloud KMS
88+
*/
89+
public static String decryptRSA(String ciphertext, CloudKMS client, String keyPath)
90+
throws IOException {
91+
AsymmetricDecryptRequest request = new AsymmetricDecryptRequest().setCiphertext(ciphertext);
92+
AsymmetricDecryptResponse response = client.projects()
93+
.locations()
94+
.keyRings()
95+
.cryptoKeys()
96+
.cryptoKeyVersions()
97+
.asymmetricDecrypt(keyPath, request)
98+
.execute();
99+
return new String(response.decodePlaintext());
100+
}
101+
// [END kms_decrypt_rsa]
102+
103+
// [START kms_encrypt_rsa]
104+
/**
105+
* Encrypt message locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key
106+
* retrieved from Cloud KMS
107+
*/
108+
public static String encryptRSA(String message, CloudKMS client, String keyPath)
109+
throws IOException, IllegalBlockSizeException, NoSuchPaddingException,
110+
InvalidKeySpecException, NoSuchProviderException, BadPaddingException,
111+
NoSuchAlgorithmException, InvalidKeyException {
112+
Security.addProvider(new BouncyCastleProvider());
113+
PublicKey rsaKey = getAsymmetricPublicKey(client, keyPath);
114+
115+
Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING", "BC");
116+
cipher.init(Cipher.ENCRYPT_MODE, rsaKey);
117+
byte[] ciphertext = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
118+
return Base64.getEncoder().encodeToString(ciphertext);
119+
}
120+
// [END kms_encrypt_rsa]
121+
122+
// [START kms_sign_asymmetric]
123+
/** Create a signature for a message using a private key stored on Cloud KMS */
124+
public static String signAsymmetric(String message, CloudKMS client, String keyPath)
125+
throws IOException, NoSuchAlgorithmException {
126+
byte[] msgBytes = message.getBytes(StandardCharsets.UTF_8);
127+
Digest digest = new Digest();
128+
// Note: some key algorithms will require a different hash function
129+
// For example, EC_SIGN_P384_SHA384 requires SHA-384
130+
digest.encodeSha256(MessageDigest.getInstance("SHA-256").digest(msgBytes));
131+
132+
AsymmetricSignRequest signRequest = new AsymmetricSignRequest();
133+
signRequest.setDigest(digest);
134+
135+
AsymmetricSignResponse response = client.projects()
136+
.locations()
137+
.keyRings()
138+
.cryptoKeys()
139+
.cryptoKeyVersions()
140+
.asymmetricSign(keyPath, signRequest)
141+
.execute();
142+
return response.getSignature();
143+
}
144+
// [END kms_sign_asymmetric]
145+
146+
// [START kms_verify_signature_rsa]
147+
/**
148+
* Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the
149+
* specified plaintext message
150+
*/
151+
public static boolean verifySignatureRSA(String signature, String message, CloudKMS client,
152+
String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
153+
SignatureException, NoSuchProviderException, InvalidKeyException {
154+
Security.addProvider(new BouncyCastleProvider());
155+
PublicKey rsaKey = getAsymmetricPublicKey(client, keyPath);
156+
157+
Signature rsaVerify = Signature.getInstance("SHA256withRSA/PSS");
158+
159+
rsaVerify.initVerify(rsaKey);
160+
rsaVerify.update(message.getBytes(StandardCharsets.UTF_8));
161+
byte[] sigBytes = Base64.getMimeDecoder().decode(signature);
162+
return rsaVerify.verify(sigBytes);
163+
}
164+
// [END kms_verify_signature_rsa]
165+
166+
// [START kms_verify_signature_ec]
167+
/**
168+
* Verify the validity of an 'EC_SIGN_P256_SHA256' signature for the
169+
* specified plaintext message
170+
*/
171+
public static boolean verifySignatureEC(String signature, String message, CloudKMS client,
172+
String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
173+
SignatureException, NoSuchProviderException, InvalidKeyException {
174+
Security.addProvider(new BouncyCastleProvider());
175+
PublicKey ecKey = getAsymmetricPublicKey(client, keyPath);
176+
177+
Signature ecVerify = Signature.getInstance("SHA256withECDSA", "BC");
178+
179+
ecVerify.initVerify(ecKey);
180+
ecVerify.update(message.getBytes(StandardCharsets.UTF_8));
181+
byte[] sigBytes = Base64.getMimeDecoder().decode(signature);
182+
return ecVerify.verify(sigBytes);
183+
}
184+
// [END kms_verify_signature_ec]
185+
186+
public static CloudKMS createAuthorizedClient() throws IOException {
187+
HttpTransport transport = new NetHttpTransport();
188+
JsonFactory jsonFactory = new JacksonFactory();
189+
GoogleCredential credential = GoogleCredential.getApplicationDefault(transport, jsonFactory);
190+
if (credential.createScopedRequired()) {
191+
credential = credential.createScoped(CloudKMSScopes.all());
192+
}
193+
return new CloudKMS.Builder(transport, jsonFactory, credential)
194+
.setApplicationName("CloudKMS snippets")
195+
.build();
196+
}
197+
198+
}
199+
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static junit.framework.Assert.fail;
21+
import static junit.framework.TestCase.assertEquals;
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertTrue;
24+
25+
import com.google.api.client.googleapis.json.GoogleJsonError;
26+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
27+
import com.google.api.services.cloudkms.v1.CloudKMS;
28+
import com.google.api.services.cloudkms.v1.model.CryptoKey;
29+
import com.google.api.services.cloudkms.v1.model.CryptoKeyVersionTemplate;
30+
import com.google.api.services.cloudkms.v1.model.KeyRing;
31+
import java.io.ByteArrayOutputStream;
32+
import java.io.IOException;
33+
import java.io.PrintStream;
34+
import java.security.NoSuchAlgorithmException;
35+
import java.security.NoSuchProviderException;
36+
import java.security.PublicKey;
37+
import java.security.spec.InvalidKeySpecException;
38+
import java.util.UUID;
39+
import java.util.regex.Matcher;
40+
import java.util.regex.Pattern;
41+
import org.junit.After;
42+
import org.junit.AfterClass;
43+
import org.junit.Before;
44+
import org.junit.BeforeClass;
45+
import org.junit.Test;
46+
import org.junit.runner.RunWith;
47+
import org.junit.runners.JUnit4;
48+
/**
49+
* Integration (system) tests for {@link Asymmetric}.
50+
*/
51+
52+
@RunWith(JUnit4.class)
53+
@SuppressWarnings("checkstyle:abbreviationaswordinname")
54+
public class AsymmetricIT {
55+
private static final String projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
56+
private static final String parent = "projects/" + projectId + "/locations/global";
57+
private static final String keyRing = "kms-asymmetric-sample";
58+
private static final String message = "test message 123";
59+
60+
private static final String rsaDecryptId = "rsa-decrypt";
61+
private static final String rsaSignId = "rsa-sign";
62+
private static final String ecSignId = "ec-sign";
63+
64+
private static final String keyParent = parent + "/keyRings/" + keyRing + "/cryptoKeys/";
65+
private static final String rsaDecrypt = keyParent + rsaDecryptId + "/cryptoKeyVersions/1";
66+
private static final String rsaSign = keyParent + rsaSignId + "/cryptoKeyVersions/1";
67+
private static final String ecSign = keyParent + ecSignId + "/cryptoKeyVersions/1";
68+
69+
private static CloudKMS client;
70+
71+
72+
private static boolean createKeyHelper(String keyId, String purpose, String algorithm)
73+
throws NoSuchAlgorithmException, InvalidKeySpecException,
74+
NoSuchProviderException, IOException {
75+
76+
String keyPath = keyParent + keyId + "/cryptoKeyVersions/1";
77+
try {
78+
Asymmetric.getAsymmetricPublicKey(client, keyPath);
79+
// got key; don't need to create one
80+
return false;
81+
} catch (GoogleJsonResponseException e) {
82+
// key doesn't exist. Create it
83+
CryptoKey cryptoKey = new CryptoKey();
84+
cryptoKey.setPurpose(purpose);
85+
CryptoKeyVersionTemplate version = new CryptoKeyVersionTemplate();
86+
version.setAlgorithm(algorithm);
87+
cryptoKey.setVersionTemplate(version);
88+
89+
client.projects().locations().keyRings().cryptoKeys()
90+
.create(parent + "/keyRings/" + keyRing, cryptoKey)
91+
.setCryptoKeyId(keyId)
92+
.execute();
93+
return true;
94+
}
95+
}
96+
97+
@BeforeClass
98+
public static void setUpClass() throws Exception {
99+
client = Asymmetric.createAuthorizedClient();
100+
try {
101+
// attempt to create keyRing
102+
client.projects().locations().keyRings()
103+
.create(parent, new KeyRing())
104+
.setKeyRingId(keyRing)
105+
.execute();
106+
} catch (IOException e) {
107+
// keyRing already exists. Skip
108+
}
109+
110+
boolean s1 = createKeyHelper(rsaDecryptId, "ASYMMETRIC_DECRYPT","RSA_DECRYPT_OAEP_2048_SHA256");
111+
boolean s2 = createKeyHelper(rsaSignId, "ASYMMETRIC_SIGN", "RSA_SIGN_PSS_2048_SHA256");
112+
boolean s3 = createKeyHelper(ecSignId, "ASYMMETRIC_SIGN", "EC_SIGN_P256_SHA256");
113+
if (s1 || s2 || s3) {
114+
// leave time for keys to initialize
115+
Thread.sleep(20000);
116+
}
117+
}
118+
119+
@Test
120+
public void testGetPublicKey() throws Exception {
121+
PublicKey rsaDecryptKey = Asymmetric.getAsymmetricPublicKey(client, rsaDecrypt);
122+
assertEquals("invalid RSA key.", "RSA", rsaDecryptKey.getAlgorithm());
123+
PublicKey rsaSignKey = Asymmetric.getAsymmetricPublicKey(client, rsaSign);
124+
assertEquals("invalid RSA key.", "RSA", rsaSignKey.getAlgorithm());
125+
PublicKey ecSignKey = Asymmetric.getAsymmetricPublicKey(client, ecSign);
126+
assertEquals("invalid ECDSA key.", "ECDSA", ecSignKey.getAlgorithm());
127+
String fakeKeyPath = keyParent + "fake-key/cryptoKeyVersions/1";
128+
try {
129+
Asymmetric.getAsymmetricPublicKey(client, fakeKeyPath);
130+
// should throw excpetion above
131+
fail("GoogleJsonResponseException expected for non-existent key");
132+
} catch (GoogleJsonResponseException e) {
133+
// exception expected
134+
}
135+
}
136+
137+
@Test
138+
public void testRSAEncryptDecrypt() throws Exception {
139+
String ciphertext = Asymmetric.encryptRSA(message, client, rsaDecrypt);
140+
assertEquals("incorrect RSA ciphertext length.", 344, ciphertext.length());
141+
assertEquals("incorrect ciphertext final character.", '=', ciphertext.charAt(343));
142+
143+
String plaintext = Asymmetric.decryptRSA(ciphertext, client, rsaDecrypt);
144+
assertEquals("decryption failed.", message, plaintext);
145+
}
146+
147+
@Test
148+
public void testRSASignVerify() throws Exception {
149+
String sig = Asymmetric.signAsymmetric(message, client, rsaSign);
150+
assertEquals("invalid ciphertext length", 344, sig.length());
151+
assertEquals("incorrect ciphertext final character.", '=', sig.charAt(343));
152+
153+
boolean success = Asymmetric.verifySignatureRSA(sig, message, client, rsaSign);
154+
assertTrue("RSA verification failed.", success);
155+
boolean shouldFail = Asymmetric.verifySignatureRSA(sig, message + ".", client, rsaSign);
156+
assertFalse("RSA verification failed.", shouldFail);
157+
}
158+
159+
@Test
160+
public void testECSignVerify() throws Exception {
161+
String sig = Asymmetric.signAsymmetric(message, client, ecSign);
162+
assertTrue("invalid ciphertext length", sig.length() > 50 && sig.length() < 300);
163+
164+
boolean success = Asymmetric.verifySignatureEC(sig, message, client, ecSign);
165+
assertTrue("RSA verification failed.", success);
166+
boolean shouldFail = Asymmetric.verifySignatureEC(sig, message + ".", client, ecSign);
167+
assertFalse("RSA verification failed.", shouldFail);
168+
}
169+
170+
}

0 commit comments

Comments
 (0)
0