• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19307

24 Jun 2024 08:09PM CUT coverage: 88.429% (+0.006%) from 88.423%
#19307

push

github

ejona86
util: Add ExperimentalApi to AdvancedTlsX509KeyManager

There may be reordering of `updateIdentityCredentialsFromFile()`
arguments. Avoid making it stable while it is being evaluated.

32288 of 36513 relevant lines covered (88.43%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.1
/../util/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
1
/*
2
 * Copyright 2021 The gRPC Authors
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 io.grpc.util;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import io.grpc.ExperimentalApi;
22
import java.io.File;
23
import java.io.FileInputStream;
24
import java.io.IOException;
25
import java.net.Socket;
26
import java.security.GeneralSecurityException;
27
import java.security.Principal;
28
import java.security.PrivateKey;
29
import java.security.cert.X509Certificate;
30
import java.util.Arrays;
31
import java.util.concurrent.ScheduledExecutorService;
32
import java.util.concurrent.ScheduledFuture;
33
import java.util.concurrent.TimeUnit;
34
import java.util.logging.Level;
35
import java.util.logging.Logger;
36
import javax.net.ssl.SSLEngine;
37
import javax.net.ssl.X509ExtendedKeyManager;
38

39
/**
40
 * AdvancedTlsX509KeyManager is an {@code X509ExtendedKeyManager} that allows users to configure
41
 * advanced TLS features, such as private key and certificate chain reloading.
42
 */
43
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
44
public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
1✔
45
  private static final Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
1✔
46
  // Minimum allowed period for refreshing files with credential information.
47
  private static final int MINIMUM_REFRESH_PERIOD_IN_MINUTES = 1 ;
48
  // The credential information to be sent to peers to prove our identity.
49
  private volatile KeyInfo keyInfo;
50

51
  @Override
52
  public PrivateKey getPrivateKey(String alias) {
53
    if (alias.equals("default")) {
1✔
54
      return this.keyInfo.key;
1✔
55
    }
56
    return null;
×
57
  }
58

59
  @Override
60
  public X509Certificate[] getCertificateChain(String alias) {
61
    if (alias.equals("default")) {
1✔
62
      return Arrays.copyOf(this.keyInfo.certs, this.keyInfo.certs.length);
1✔
63
    }
64
    return null;
×
65
  }
66

67
  @Override
68
  public String[] getClientAliases(String keyType, Principal[] issuers) {
69
    return new String[] {"default"};
1✔
70
  }
71

72
  @Override
73
  public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
74
    return "default";
1✔
75
  }
76

77
  @Override
78
  public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
79
    return "default";
1✔
80
  }
81

82
  @Override
83
  public String[] getServerAliases(String keyType, Principal[] issuers) {
84
    return new String[] {"default"};
1✔
85
  }
86

87
  @Override
88
  public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
89
    return "default";
1✔
90
  }
91

92
  @Override
93
  public String chooseEngineServerAlias(String keyType, Principal[] issuers,
94
      SSLEngine engine) {
95
    return "default";
1✔
96
  }
97

98
  /**
99
   * Updates the current cached private key and cert chains.
100
   *
101
   * @param key  the private key that is going to be used
102
   * @param certs  the certificate chain that is going to be used
103
   */
104
  public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs) {
105
    this.keyInfo = new KeyInfo(checkNotNull(key, "key"), checkNotNull(certs, "certs"));
1✔
106
  }
1✔
107

108
  /**
109
   * Schedules a {@code ScheduledExecutorService} to read private key and certificate chains from
110
   * the local file paths periodically, and update the cached identity credentials if they are both
111
   * updated. You must close the returned Closeable before calling this method again or other update
112
   * methods ({@link AdvancedTlsX509KeyManager#updateIdentityCredentials}, {@link
113
   * AdvancedTlsX509KeyManager#updateIdentityCredentialsFromFile(File, File)}).
114
   * Before scheduling the task, the method synchronously executes {@code  readAndUpdate} once. The
115
   * minimum refresh period of 1 minute is enforced.
116
   *
117
   * @param keyFile  the file on disk holding the private key
118
   * @param certFile  the file on disk holding the certificate chain
119
   * @param period the period between successive read-and-update executions
120
   * @param unit the time unit of the initialDelay and period parameters
121
   * @param executor the execute service we use to read and update the credentials
122
   * @return an object that caller should close when the file refreshes are not needed
123
   */
124
  public Closeable updateIdentityCredentialsFromFile(File keyFile, File certFile,
125
      long period, TimeUnit unit, ScheduledExecutorService executor) throws IOException,
126
      GeneralSecurityException {
127
    UpdateResult newResult = readAndUpdate(keyFile, certFile, 0, 0);
1✔
128
    if (!newResult.success) {
1✔
129
      throw new GeneralSecurityException(
×
130
          "Files were unmodified before their initial update. Probably a bug.");
131
    }
132
    if (checkNotNull(unit, "unit").toMinutes(period) < MINIMUM_REFRESH_PERIOD_IN_MINUTES) {
1✔
133
      log.log(Level.FINE,
1✔
134
          "Provided refresh period of {0} {1} is too small. Default value of {2} minute(s) "
135
          + "will be used.", new Object[] {period, unit.name(), MINIMUM_REFRESH_PERIOD_IN_MINUTES});
1✔
136
      period = MINIMUM_REFRESH_PERIOD_IN_MINUTES;
1✔
137
      unit = TimeUnit.MINUTES;
1✔
138
    }
139
    final ScheduledFuture<?> future =
1✔
140
        checkNotNull(executor, "executor").scheduleWithFixedDelay(
1✔
141
            new LoadFilePathExecution(keyFile, certFile), period, period, unit);
142
    return () -> future.cancel(false);
1✔
143
  }
144

145
  /**
146
   * Updates the private key and certificate chains from the local file paths.
147
   *
148
   * @param keyFile  the file on disk holding the private key
149
   * @param certFile  the file on disk holding the certificate chain
150
   */
151
  public void updateIdentityCredentialsFromFile(File keyFile, File certFile) throws IOException,
152
      GeneralSecurityException {
153
    UpdateResult newResult = readAndUpdate(keyFile, certFile, 0, 0);
1✔
154
    if (!newResult.success) {
1✔
155
      throw new GeneralSecurityException(
×
156
          "Files were unmodified before their initial update. Probably a bug.");
157
    }
158
  }
1✔
159

160
  private static class KeyInfo {
161
    // The private key and the cert chain we will use to send to peers to prove our identity.
162
    final PrivateKey key;
163
    final X509Certificate[] certs;
164

165
    public KeyInfo(PrivateKey key, X509Certificate[] certs) {
1✔
166
      this.key = key;
1✔
167
      this.certs = certs;
1✔
168
    }
1✔
169
  }
170

171
  private class LoadFilePathExecution implements Runnable {
172
    File keyFile;
173
    File certFile;
174
    long currentKeyTime;
175
    long currentCertTime;
176

177
    public LoadFilePathExecution(File keyFile, File certFile) {
1✔
178
      this.keyFile = keyFile;
1✔
179
      this.certFile = certFile;
1✔
180
      this.currentKeyTime = 0;
1✔
181
      this.currentCertTime = 0;
1✔
182
    }
1✔
183

184
    @Override
185
    public void run() {
186
      try {
187
        UpdateResult newResult = readAndUpdate(this.keyFile, this.certFile, this.currentKeyTime,
×
188
            this.currentCertTime);
189
        if (newResult.success) {
×
190
          this.currentKeyTime = newResult.keyTime;
×
191
          this.currentCertTime = newResult.certTime;
×
192
        }
193
      } catch (IOException | GeneralSecurityException e) {
×
194
        log.log(Level.SEVERE, String.format("Failed refreshing private key and certificate"
×
195
                + " chain from files. Using previous ones (keyFile lastModified = %s, certFile "
196
                + "lastModified = %s)", keyFile.lastModified(), certFile.lastModified()), e);
×
197
      }
×
198
    }
×
199
  }
200

201
  private static class UpdateResult {
202
    boolean success;
203
    long keyTime;
204
    long certTime;
205

206
    public UpdateResult(boolean success, long keyTime, long certTime) {
1✔
207
      this.success = success;
1✔
208
      this.keyTime = keyTime;
1✔
209
      this.certTime = certTime;
1✔
210
    }
1✔
211
  }
212

213
  /**
214
   * Reads the private key and certificates specified in the path locations. Updates {@code key} and
215
   * {@code cert} if both of their modified time changed since last read.
216
   *
217
   * @param keyFile  the file on disk holding the private key
218
   * @param certFile  the file on disk holding the certificate chain
219
   * @param oldKeyTime the time when the private key file is modified during last execution
220
   * @param oldCertTime the time when the certificate chain file is modified during last execution
221
   * @return the result of this update execution
222
   */
223
  private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, long oldCertTime)
224
      throws IOException, GeneralSecurityException {
225
    long newKeyTime = checkNotNull(keyFile, "keyFile").lastModified();
1✔
226
    long newCertTime = checkNotNull(certFile, "certFile").lastModified();
1✔
227
    // We only update when both the key and the certs are updated.
228
    if (newKeyTime != oldKeyTime && newCertTime != oldCertTime) {
1✔
229
      FileInputStream keyInputStream = new FileInputStream(keyFile);
1✔
230
      try {
231
        PrivateKey key = CertificateUtils.getPrivateKey(keyInputStream);
1✔
232
        FileInputStream certInputStream = new FileInputStream(certFile);
1✔
233
        try {
234
          X509Certificate[] certs = CertificateUtils.getX509Certificates(certInputStream);
1✔
235
          updateIdentityCredentials(key, certs);
1✔
236
          return new UpdateResult(true, newKeyTime, newCertTime);
1✔
237
        } finally {
238
          certInputStream.close();
1✔
239
        }
240
      } finally {
241
        keyInputStream.close();
1✔
242
      }
243
    }
244
    return new UpdateResult(false, oldKeyTime, oldCertTime);
×
245
  }
246

247
  /**
248
   * Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
249
   */
250
  public interface Closeable extends java.io.Closeable {
251
    @Override
252
    void close();
253
  }
254
}
255

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc