Skip to content

Commit e799526

Browse files
committed
tests.
1 parent 3510643 commit e799526

File tree

6 files changed

+302
-21
lines changed

6 files changed

+302
-21
lines changed

gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@
6363
import io.grpc.auth.MoreCallCredentials;
6464
import io.grpc.s2a.S2AChannelCredentials;
6565
import java.io.File;
66+
import java.io.FileInputStream;
67+
import java.io.FileNotFoundException;
6668
import java.io.IOException;
69+
import java.io.InputStream;
6770
import java.nio.charset.StandardCharsets;
6871
import java.security.GeneralSecurityException;
6972
import java.security.KeyStore;
@@ -102,7 +105,8 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
102105
@VisibleForTesting
103106
static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS";
104107

105-
private static final String S2A_ENV_ENABLE_USE_S2A = "EXPERIMENTAL_GOOGLE_API_USE_S2A";
108+
@VisibleForTesting static final String S2A_ENV_ENABLE_USE_S2A = "EXPERIMENTAL_GOOGLE_API_USE_S2A";
109+
106110
private static final String MTLS_MDS_ROOT = "/run/google-mds-mtls/root.crt";
107111
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
108112
// followed by a PEM-encoded private key.
@@ -118,9 +122,11 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
118122
private final HeaderProvider headerProvider;
119123
private final String endpoint;
120124
private final String mtlsEndpoint;
121-
// TODO: remove. envProvider currently provides DirectPath environment variable, and is only used
122-
// during initial rollout for DirectPath. This provider will be removed once the DirectPath
123-
// environment is not used.
125+
// TODO: remove.
126+
// envProvider currently provides DirectPath and S2A environment variables, and is only used
127+
// during initial rollout for DirectPath and S2A. This provider will be removed once the
128+
// DirectPath
129+
// and S2A environment variables are not used.
124130
private final EnvironmentProvider envProvider;
125131
@Nullable private final GrpcInterceptorProvider interceptorProvider;
126132
@Nullable private final Integer maxInboundMessageSize;
@@ -472,21 +478,14 @@ boolean shouldUseS2A() {
472478
}
473479

474480
@VisibleForTesting
475-
ChannelCredentials createMtlsToS2AChannelCredentials() throws IOException {
476-
if (!isOnComputeEngine()) {
477-
// Currently, MTLS to MDS is only available on GCE. See:
478-
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
479-
return null;
480-
}
481-
File privateKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
482-
File certChainFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
483-
File trustBundleFile = new File(MTLS_MDS_ROOT);
484-
if (!privateKeyFile.isFile() || !certChainFile.isFile() || !trustBundleFile.isFile()) {
481+
ChannelCredentials createMtlsToS2AChannelCredentials(
482+
InputStream trustBundle, InputStream privateKey, InputStream certChain) throws IOException {
483+
if (trustBundle == null || privateKey == null || certChain == null) {
485484
return null;
486485
}
487486
return TlsChannelCredentials.newBuilder()
488-
.keyManager(privateKeyFile, certChainFile)
489-
.trustManager(trustBundleFile)
487+
.keyManager(privateKey, certChain)
488+
.trustManager(trustBundle)
490489
.build();
491490
}
492491

@@ -496,14 +495,29 @@ ChannelCredentials createS2ASecuredChannelCredentials() {
496495
String plaintextAddress = s2aUtils.getPlaintextS2AAddress();
497496
String mtlsAddress = s2aUtils.getMtlsS2AAddress();
498497
if (!mtlsAddress.isEmpty()) {
498+
// Currently, MTLS to MDS is only available on GCE. See:
499+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
500+
// Try to load MTLS-MDS creds.
501+
InputStream trustBundle = null;
502+
InputStream privateKey = null;
503+
InputStream certChain = null;
504+
try {
505+
trustBundle = new FileInputStream(MTLS_MDS_ROOT);
506+
privateKey = new FileInputStream(MTLS_MDS_CERT_CHAIN_AND_KEY);
507+
certChain = new FileInputStream(MTLS_MDS_CERT_CHAIN_AND_KEY);
508+
} catch (FileNotFoundException ignore) {
509+
// Fallback to plaintext-to-S2A connection.
510+
}
511+
ChannelCredentials mtlsToS2AChannelCredentials = null;
499512
try {
500513
// Try to connect to S2A using mTLS.
501-
ChannelCredentials mtlsToS2AChannelCredentials = createMtlsToS2AChannelCredentials();
502-
if (mtlsToS2AChannelCredentials != null) {
503-
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
504-
}
514+
mtlsToS2AChannelCredentials =
515+
createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain);
505516
} catch (IOException ignore) {
506-
// Fallback to plaintext connection to S2A.
517+
// Fallback to plaintext-to-S2A connection.
518+
}
519+
if (mtlsToS2AChannelCredentials != null) {
520+
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
507521
}
508522
}
509523

gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@
5656
import com.google.common.truth.Truth;
5757
import io.grpc.ManagedChannel;
5858
import io.grpc.ManagedChannelBuilder;
59+
import io.grpc.TlsChannelCredentials;
5960
import io.grpc.alts.ComputeEngineChannelBuilder;
6061
import java.io.IOException;
62+
import java.io.InputStream;
6163
import java.security.GeneralSecurityException;
6264
import java.time.Duration;
6365
import java.util.ArrayList;
@@ -83,6 +85,7 @@
8385

8486
class InstantiatingGrpcChannelProviderTest extends AbstractMtlsTransportChannelTest {
8587
private static final String DEFAULT_ENDPOINT = "test.googleapis.com:443";
88+
private static final String DEFAULT_MTLS_ENDPOINT = "test.mtls.googleapis.com:443";
8689
private static final String API_KEY_HEADER_VALUE = "fake_api_key_2";
8790
private static final String API_KEY_AUTH_HEADER_KEY = "x-goog-api-key";
8891
private static String originalOSName;
@@ -129,6 +132,35 @@ void testEndpointBadPort() {
129132
() -> InstantiatingGrpcChannelProvider.newBuilder().setEndpoint("localhost:abcd"));
130133
}
131134

135+
@Test
136+
void testMtlsEndpoint() {
137+
InstantiatingGrpcChannelProvider.Builder builder =
138+
InstantiatingGrpcChannelProvider.newBuilder();
139+
builder.setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT);
140+
assertEquals(builder.getMtlsEndpoint(), DEFAULT_MTLS_ENDPOINT);
141+
142+
InstantiatingGrpcChannelProvider provider = builder.build();
143+
assertEquals(provider.getMtlsEndpoint(), DEFAULT_MTLS_ENDPOINT);
144+
}
145+
146+
@Test
147+
void testMtlsEndpointNoPort() {
148+
assertThrows(
149+
IllegalArgumentException.class,
150+
() ->
151+
InstantiatingGrpcChannelProvider.newBuilder()
152+
.setMtlsEndpoint("test.mtls.googleapis.com"));
153+
}
154+
155+
@Test
156+
void testMtlsEndpointBadPort() {
157+
assertThrows(
158+
IllegalArgumentException.class,
159+
() ->
160+
InstantiatingGrpcChannelProvider.newBuilder()
161+
.setEndpoint("test.mtls.googleapis.com:abcd"));
162+
}
163+
132164
@Test
133165
void testKeepAlive() {
134166
final long millis = 15;
@@ -230,6 +262,7 @@ void testToBuilder() {
230262
InstantiatingGrpcChannelProvider.newBuilder()
231263
.setProcessorCount(2)
232264
.setEndpoint("fake.endpoint:443")
265+
.setMtlsEndpoint("fake.endpoint:443")
233266
.setMaxInboundMessageSize(12345678)
234267
.setMaxInboundMetadataSize(4096)
235268
.setKeepAliveTimeDuration(keepaliveTime)
@@ -243,6 +276,7 @@ void testToBuilder() {
243276
InstantiatingGrpcChannelProvider.Builder builder = provider.toBuilder();
244277

245278
assertThat(builder.getEndpoint()).isEqualTo("fake.endpoint:443");
279+
assertThat(builder.getMtlsEndpoint()).isEqualTo("fake.endpoint:443");
246280
assertThat(builder.getMaxInboundMessageSize()).isEqualTo(12345678);
247281
assertThat(builder.getMaxInboundMetadataSize()).isEqualTo(4096);
248282
assertThat(builder.getKeepAliveTimeDuration()).isEqualTo(keepaliveTime);
@@ -980,6 +1014,142 @@ private FixedHeaderProvider getHeaderProviderWithApiKeyHeader() {
9801014
return FixedHeaderProvider.create(header);
9811015
}
9821016

1017+
@Test
1018+
void isGoogleS2AEnabled_envVarNotSet_returnsFalse() {
1019+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1020+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1021+
.thenReturn("false");
1022+
InstantiatingGrpcChannelProvider provider =
1023+
InstantiatingGrpcChannelProvider.newBuilder().build();
1024+
Truth.assertThat(provider.isGoogleS2AEnabled()).isFalse();
1025+
}
1026+
1027+
@Test
1028+
void isGoogleS2AEnabled_envVarNotSet_returnsTrue() {
1029+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1030+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1031+
.thenReturn("true");
1032+
InstantiatingGrpcChannelProvider provider =
1033+
InstantiatingGrpcChannelProvider.newBuilder().build();
1034+
Truth.assertThat(provider.isGoogleS2AEnabled()).isFalse();
1035+
}
1036+
1037+
@Test
1038+
void shouldUseS2A_envVarNotSet_returnsFalse() {
1039+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1040+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1041+
.thenReturn("false");
1042+
InstantiatingGrpcChannelProvider provider =
1043+
InstantiatingGrpcChannelProvider.newBuilder()
1044+
.setEndpoint(DEFAULT_MTLS_ENDPOINT)
1045+
.setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT)
1046+
.setEnvProvider(envProvider)
1047+
.build();
1048+
Truth.assertThat(provider.shouldUseS2A()).isFalse();
1049+
}
1050+
1051+
@Test
1052+
void shouldUseS2A_mtlsEndpointNotSet_returnsFalse() {
1053+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1054+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1055+
.thenReturn("true");
1056+
InstantiatingGrpcChannelProvider provider =
1057+
InstantiatingGrpcChannelProvider.newBuilder()
1058+
.setEndpoint(DEFAULT_ENDPOINT)
1059+
.setEnvProvider(envProvider)
1060+
.build();
1061+
Truth.assertThat(provider.shouldUseS2A()).isFalse();
1062+
}
1063+
1064+
@Test
1065+
void shouldUseS2A_endpointOverrideIsSet_returnsFalse() {
1066+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1067+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1068+
.thenReturn("true");
1069+
InstantiatingGrpcChannelProvider provider =
1070+
InstantiatingGrpcChannelProvider.newBuilder()
1071+
.setEndpoint(DEFAULT_ENDPOINT)
1072+
.setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT)
1073+
.setEnvProvider(envProvider)
1074+
.build();
1075+
Truth.assertThat(provider.shouldUseS2A()).isFalse();
1076+
}
1077+
1078+
@Test
1079+
void shouldUseS2A_nonGDUUniverse_returnsFalse() {
1080+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1081+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1082+
.thenReturn("true");
1083+
InstantiatingGrpcChannelProvider provider =
1084+
InstantiatingGrpcChannelProvider.newBuilder()
1085+
.setEndpoint("test.mtls.abcd.com:443")
1086+
.setMtlsEndpoint("test.mtls.abcd.com:443")
1087+
.setEnvProvider(envProvider)
1088+
.build();
1089+
Truth.assertThat(provider.shouldUseS2A()).isFalse();
1090+
}
1091+
1092+
@Test
1093+
void shouldUseS2A_returnsTrue() {
1094+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
1095+
Mockito.when(envProvider.getenv(InstantiatingGrpcChannelProvider.S2A_ENV_ENABLE_USE_S2A))
1096+
.thenReturn("true");
1097+
InstantiatingGrpcChannelProvider provider =
1098+
InstantiatingGrpcChannelProvider.newBuilder()
1099+
.setEndpoint(DEFAULT_MTLS_ENDPOINT)
1100+
.setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT)
1101+
.setEnvProvider(envProvider)
1102+
.build();
1103+
Truth.assertThat(provider.shouldUseS2A()).isTrue();
1104+
}
1105+
1106+
@Test
1107+
void createMtlsToS2AChannelCredentials_missingAllFiles_throws() throws IOException {
1108+
InstantiatingGrpcChannelProvider provider =
1109+
InstantiatingGrpcChannelProvider.newBuilder().build();
1110+
assertThat(provider.createMtlsToS2AChannelCredentials(null, null, null)).isNull();
1111+
}
1112+
1113+
@Test
1114+
void createMtlsToS2AChannelCredentials_missingRootFile_throws() throws IOException {
1115+
InstantiatingGrpcChannelProvider provider =
1116+
InstantiatingGrpcChannelProvider.newBuilder().build();
1117+
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
1118+
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
1119+
assertThat(provider.createMtlsToS2AChannelCredentials(null, privateKey, certChain)).isNull();
1120+
}
1121+
1122+
@Test
1123+
void createMtlsToS2AChannelCredentials_missingKeyFile_throws() throws IOException {
1124+
InstantiatingGrpcChannelProvider provider =
1125+
InstantiatingGrpcChannelProvider.newBuilder().build();
1126+
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
1127+
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
1128+
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, null, certChain)).isNull();
1129+
}
1130+
1131+
@Test
1132+
void createMtlsToS2AChannelCredentials_missingCertChainFile_throws() throws IOException {
1133+
InstantiatingGrpcChannelProvider provider =
1134+
InstantiatingGrpcChannelProvider.newBuilder().build();
1135+
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
1136+
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
1137+
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, null)).isNull();
1138+
}
1139+
1140+
@Test
1141+
void createMtlsToS2AChannelCredentials_success() throws IOException {
1142+
InstantiatingGrpcChannelProvider provider =
1143+
InstantiatingGrpcChannelProvider.newBuilder().build();
1144+
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
1145+
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
1146+
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
1147+
assertThat(trustBundle).isNotNull();
1148+
assertEquals(
1149+
provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain).getClass(),
1150+
TlsChannelCredentials.class);
1151+
}
1152+
9831153
private static class FakeLogHandler extends Handler {
9841154

9851155
List<LogRecord> records = new ArrayList<>();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generating certificates and keys for testing mTLS-S2A
2+
3+
Create root CA
4+
5+
```
6+
openssl req -x509 -sha256 -days 7305 -newkey rsa:2048 -keyout root_key.pem -out
7+
root_cert.pem
8+
```
9+
10+
Generate private key
11+
12+
```
13+
openssl genrsa -out client_key.pem 2048
14+
```
15+
16+
Generate CSR (set Common Name to localhost, leave all
17+
other fields blank)
18+
19+
```
20+
openssl req -key client_key.pem -new -out client.csr -config config.cnf
21+
```
22+
23+
Sign CSR for client
24+
25+
```
26+
openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305
27+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDPTCCAiWgAwIBAgIUaarddwSWeE4jDC9kwxEr446ehqUwDQYJKoZIhvcNAQEL
3+
BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
4+
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
5+
DTI0MTAwMTIxNTk1NFoXDTQ0MTAwMTIxNTk1NFowFDESMBAGA1UEAwwJbG9jYWxo
6+
b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlNsldt7yAU4KRuS
7+
2D2/FjNIE1US5olBm4HteTr++41WaELZJqNLRPPp052jEQU3aKSYNGZvUUO6buu7
8+
eFpz2SBNUVMyvmzzocjVAyyf4NQvDazYHWOb+/YCeUppTRWriz4V5sn47qJTQ8cd
9+
CGrTFeLHxUjx4nh/OiqVXP/KnF3EqPEuqph0ky7+GirnJgPRe+C5ERuGkJye8dmP
10+
yWGA2lSS6MeDe7JZTAMi08bAn7BuNpeBkOzz1msGGI9PnUanUs7GOPWTDdcQAVY8
11+
KMvHCuGaNMGpb4rOR2mm8LlbAbpTPz8Pkw4QtMCLkgsrz2CzXpVwnLsU7nDXJAIO
12+
B155lQIDAQABo0IwQDAdBgNVHQ4EFgQUSZEyIHLzkIw7AwkBaUjYfIrGVR4wHwYD
13+
VR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4umbiwwDQYJKoZIhvcNAQELBQADggEB
14+
AAz0bZ4ayrZLhA45xn0yvdpdqiCtiWikCRtxgE7VXHg/ziZJVMpBpAhbIGO5tIyd
15+
lttnRXHwz5DUwKiba4/bCEFe229BshQEql5qaqcbGbFfSly11WeqqnwR1N7c8Gpv
16+
pD9sVrx22seN0rTUk87MY/S7mzCxHqAx35zm/LTW3pWcgCTMKFHy4Gt4mpTnXkNA
17+
WkhP2OhW5RLiu6Whi0BEdb2TGG1+ctamgijKXb+gJeef5ehlHXG8eU862KF5UlEA
18+
NeQKBm/PpQxOMe0NdpatjN8QRoczku0Itiodng+OZ1o+2iSNG988uFRb3CUSnjtE
19+
R/HL6ULAFzo59EpIYxruU/w=
20+
-----END CERTIFICATE-----
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGU2yV23vIBTgp
3+
G5LYPb8WM0gTVRLmiUGbge15Ov77jVZoQtkmo0tE8+nTnaMRBTdopJg0Zm9RQ7pu
4+
67t4WnPZIE1RUzK+bPOhyNUDLJ/g1C8NrNgdY5v79gJ5SmlNFauLPhXmyfjuolND
5+
xx0IatMV4sfFSPHieH86KpVc/8qcXcSo8S6qmHSTLv4aKucmA9F74LkRG4aQnJ7x
6+
2Y/JYYDaVJLox4N7sllMAyLTxsCfsG42l4GQ7PPWawYYj0+dRqdSzsY49ZMN1xAB
7+
Vjwoy8cK4Zo0walvis5HaabwuVsBulM/Pw+TDhC0wIuSCyvPYLNelXCcuxTucNck
8+
Ag4HXnmVAgMBAAECggEAKuW9jXaBgiS63o1jyFkmvWcPNntG0M2sfrXuRzQfFgse
9+
vwOCk8xrSflWQNsOe+58ayp6746ekl3LdBWSIbiy6SqG/sm3pp/LXNmjVYHv/QH4
10+
QYV643R5t1ihdVnGiBFhXwdpVleme/tpdjYZzgnJKak5W69o/nrgzhSK5ShAy2xM
11+
j0XXbgdqG+4JxPb5BZmjHHfXAXUfgSORMdfArkbgFBRc9wL/6JVTXjeAMy5WX9qe
12+
5UQsSOYkwc9P2snifC/jdIhjHQOkkx59O0FgukJEFZPoagVG1duWQbnNDr7QVHCJ
13+
jV6dg9tIT4SXD3uPSPbgNGlRUseIakCzrhHARJuA2wKBgQD/h8zoh0KaqKyViCYw
14+
XKOFpm1pAFnp2GiDOblxNubNFAXEWnC+FlkvO/z1s0zVuYELUqfxcYMSXJFEVelK
15+
rfjZtoC5oxqWGqLo9iCj7pa8t+ipulYcLt2SWc7eZPD4T4lzeEf1Qz77aKcz34sa
16+
dv9lzQkDvhR/Mv1VeEGFHiq2VwKBgQDGsLcTGH5Yxs//LRSY8TigBkQEDrH5NvXu
17+
2jtAzZhy1Yhsoa5eiZkhnnzM6+n05ovfZLcy6s7dnwP1Y+C79vs+DKMBsodtDG5z
18+
YpsB0VrXYa6P6pCqkcz0Bz9xdo5sOhAK3AKnX6jd29XBDdeYsw/lxHLG24wProTD
19+
cCYFqtaj8wKBgQCaqKT68DL9zK14a8lBaDCIyexaqx3AjXzkP+Hfhi03XrEG4P5v
20+
7rLYBeTbCUSt7vMN2V9QoTWFvYUm6SCkVJvTmcRblz6WL1T+z0l+LwAJBP7LC77m
21+
m+77j2PH8yxt/iXhP6G97o+GNxdMLDbTM8bs5KZaH4fkXQY73uc5HMMZTQKBgEZS
22+
7blYhf+t/ph2wD+RwVUCYrh86wkmJs2veCFro3WhlnO8lhbn5Mc9bTaqmVgQ8ZjT
23+
8POYoDdYvPHxs+1TcYF4v4kuQziZmc5FLE/sZZauADb38tQsXrpQhmgGakpsEpmF
24+
XXsYJJDB6lo2KATn+8x7R5SSyHQUdPEnlI2U9ft5AoGBAJw0NJiM1EzRS8xq0DmO
25+
AvQaPjo01o2hH6wghws8gDQwrj0eHraHgVi7zo0VkaHJbO7ahKPudset3N7owJhA
26+
CUAPPRtv5wn0amAyNz77f1dz4Gys3AkcchflqhbEaQpzKYx4kX0adclur4WJ/DVm
27+
P7DI977SHCVB4FVMbXMEkBjN
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)