• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.alts.internal;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import com.google.common.base.Preconditions;
21 import com.google.common.base.Strings;
22 import com.google.protobuf.ByteString;
23 import io.grpc.Status;
24 import io.grpc.alts.internal.Handshaker.HandshakeProtocol;
25 import io.grpc.alts.internal.Handshaker.HandshakerReq;
26 import io.grpc.alts.internal.Handshaker.HandshakerResp;
27 import io.grpc.alts.internal.Handshaker.HandshakerResult;
28 import io.grpc.alts.internal.Handshaker.HandshakerStatus;
29 import io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq;
30 import io.grpc.alts.internal.Handshaker.ServerHandshakeParameters;
31 import io.grpc.alts.internal.Handshaker.StartClientHandshakeReq;
32 import io.grpc.alts.internal.Handshaker.StartServerHandshakeReq;
33 import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub;
34 import java.io.IOException;
35 import java.nio.ByteBuffer;
36 import java.security.GeneralSecurityException;
37 import java.util.logging.Level;
38 import java.util.logging.Logger;
39 
40 /** An API for conducting handshakes via ALTS handshaker service. */
41 class AltsHandshakerClient {
42   private static final Logger logger = Logger.getLogger(AltsHandshakerClient.class.getName());
43 
44   private static final String APPLICATION_PROTOCOL = "grpc";
45   private static final String RECORD_PROTOCOL = "ALTSRP_GCM_AES128_REKEY";
46   private static final int KEY_LENGTH = AltsChannelCrypter.getKeyLength();
47 
48   private final AltsHandshakerStub handshakerStub;
49   private final AltsHandshakerOptions handshakerOptions;
50   private HandshakerResult result;
51   private HandshakerStatus status;
52 
53   /** Starts a new handshake interacting with the handshaker service. */
AltsHandshakerClient(HandshakerServiceStub stub, AltsHandshakerOptions options)54   AltsHandshakerClient(HandshakerServiceStub stub, AltsHandshakerOptions options) {
55     handshakerStub = new AltsHandshakerStub(stub);
56     handshakerOptions = options;
57   }
58 
59   @VisibleForTesting
AltsHandshakerClient(AltsHandshakerStub handshakerStub, AltsHandshakerOptions options)60   AltsHandshakerClient(AltsHandshakerStub handshakerStub, AltsHandshakerOptions options) {
61     this.handshakerStub = handshakerStub;
62     handshakerOptions = options;
63   }
64 
getApplicationProtocol()65   static String getApplicationProtocol() {
66     return APPLICATION_PROTOCOL;
67   }
68 
getRecordProtocol()69   static String getRecordProtocol() {
70     return RECORD_PROTOCOL;
71   }
72 
73   /** Sets the start client fields for the passed handshake request. */
setStartClientFields(HandshakerReq.Builder req)74   private void setStartClientFields(HandshakerReq.Builder req) {
75     // Sets the default values.
76     StartClientHandshakeReq.Builder startClientReq =
77         StartClientHandshakeReq.newBuilder()
78             .setHandshakeSecurityProtocol(HandshakeProtocol.ALTS)
79             .addApplicationProtocols(APPLICATION_PROTOCOL)
80             .addRecordProtocols(RECORD_PROTOCOL);
81     // Sets handshaker options.
82     if (handshakerOptions.getRpcProtocolVersions() != null) {
83       startClientReq.setRpcVersions(handshakerOptions.getRpcProtocolVersions());
84     }
85     if (handshakerOptions instanceof AltsClientOptions) {
86       AltsClientOptions clientOptions = (AltsClientOptions) handshakerOptions;
87       if (!Strings.isNullOrEmpty(clientOptions.getTargetName())) {
88         startClientReq.setTargetName(clientOptions.getTargetName());
89       }
90       for (String serviceAccount : clientOptions.getTargetServiceAccounts()) {
91         startClientReq.addTargetIdentitiesBuilder().setServiceAccount(serviceAccount);
92       }
93     }
94     req.setClientStart(startClientReq);
95   }
96 
97   /** Sets the start server fields for the passed handshake request. */
setStartServerFields(HandshakerReq.Builder req, ByteBuffer inBytes)98   private void setStartServerFields(HandshakerReq.Builder req, ByteBuffer inBytes) {
99     ServerHandshakeParameters serverParameters =
100         ServerHandshakeParameters.newBuilder().addRecordProtocols(RECORD_PROTOCOL).build();
101     StartServerHandshakeReq.Builder startServerReq =
102         StartServerHandshakeReq.newBuilder()
103             .addApplicationProtocols(APPLICATION_PROTOCOL)
104             .putHandshakeParameters(HandshakeProtocol.ALTS.getNumber(), serverParameters)
105             .setInBytes(ByteString.copyFrom(inBytes.duplicate()));
106     if (handshakerOptions.getRpcProtocolVersions() != null) {
107       startServerReq.setRpcVersions(handshakerOptions.getRpcProtocolVersions());
108     }
109     req.setServerStart(startServerReq);
110   }
111 
112   /** Returns true if the handshake is complete. */
isFinished()113   public boolean isFinished() {
114     // If we have a HandshakeResult, we are done.
115     if (result != null) {
116       return true;
117     }
118     // If we have an error status, we are done.
119     if (status != null && status.getCode() != Status.Code.OK.value()) {
120       return true;
121     }
122     return false;
123   }
124 
125   /** Returns the handshake status. */
getStatus()126   public HandshakerStatus getStatus() {
127     return status;
128   }
129 
130   /** Returns the result data of the handshake, if the handshake is completed. */
getResult()131   public HandshakerResult getResult() {
132     return result;
133   }
134 
135   /**
136    * Returns the resulting key of the handshake, if the handshake is completed. Note that the key
137    * data returned from the handshake may be more than the key length required for the record
138    * protocol, thus we need to truncate to the right size.
139    */
getKey()140   public byte[] getKey() {
141     if (result == null) {
142       return null;
143     }
144     if (result.getKeyData().size() < KEY_LENGTH) {
145       throw new IllegalStateException("Could not get enough key data from the handshake.");
146     }
147     byte[] key = new byte[KEY_LENGTH];
148     result.getKeyData().copyTo(key, 0, 0, KEY_LENGTH);
149     return key;
150   }
151 
152   /**
153    * Parses a handshake response, setting the status, result, and closing the handshaker, as needed.
154    */
handleResponse(HandshakerResp resp)155   private void handleResponse(HandshakerResp resp) throws GeneralSecurityException {
156     status = resp.getStatus();
157     if (resp.hasResult()) {
158       result = resp.getResult();
159       close();
160     }
161     if (status.getCode() != Status.Code.OK.value()) {
162       String error = "Handshaker service error: " + status.getDetails();
163       logger.log(Level.INFO, error);
164       close();
165       throw new GeneralSecurityException(error);
166     }
167   }
168 
169   /**
170    * Starts a client handshake. A GeneralSecurityException is thrown if the handshaker service is
171    * interrupted or fails. Note that isFinished() must be false before this function is called.
172    *
173    * @return the frame to give to the peer.
174    * @throws GeneralSecurityException or IllegalStateException
175    */
startClientHandshake()176   public ByteBuffer startClientHandshake() throws GeneralSecurityException {
177     Preconditions.checkState(!isFinished(), "Handshake has already finished.");
178     HandshakerReq.Builder req = HandshakerReq.newBuilder();
179     setStartClientFields(req);
180     HandshakerResp resp;
181     try {
182       resp = handshakerStub.send(req.build());
183     } catch (IOException | InterruptedException e) {
184       throw new GeneralSecurityException(e);
185     }
186     handleResponse(resp);
187     return resp.getOutFrames().asReadOnlyByteBuffer();
188   }
189 
190   /**
191    * Starts a server handshake. A GeneralSecurityException is thrown if the handshaker service is
192    * interrupted or fails. Note that isFinished() must be false before this function is called.
193    *
194    * @param inBytes the bytes received from the peer.
195    * @return the frame to give to the peer.
196    * @throws GeneralSecurityException or IllegalStateException
197    */
startServerHandshake(ByteBuffer inBytes)198   public ByteBuffer startServerHandshake(ByteBuffer inBytes) throws GeneralSecurityException {
199     Preconditions.checkState(!isFinished(), "Handshake has already finished.");
200     HandshakerReq.Builder req = HandshakerReq.newBuilder();
201     setStartServerFields(req, inBytes);
202     HandshakerResp resp;
203     try {
204       resp = handshakerStub.send(req.build());
205     } catch (IOException | InterruptedException e) {
206       throw new GeneralSecurityException(e);
207     }
208     handleResponse(resp);
209     inBytes.position(inBytes.position() + resp.getBytesConsumed());
210     return resp.getOutFrames().asReadOnlyByteBuffer();
211   }
212 
213   /**
214    * Processes the next bytes in a handshake. A GeneralSecurityException is thrown if the handshaker
215    * service is interrupted or fails. Note that isFinished() must be false before this function is
216    * called.
217    *
218    * @param inBytes the bytes received from the peer.
219    * @return the frame to give to the peer.
220    * @throws GeneralSecurityException or IllegalStateException
221    */
next(ByteBuffer inBytes)222   public ByteBuffer next(ByteBuffer inBytes) throws GeneralSecurityException {
223     Preconditions.checkState(!isFinished(), "Handshake has already finished.");
224     HandshakerReq.Builder req =
225         HandshakerReq.newBuilder()
226             .setNext(
227                 NextHandshakeMessageReq.newBuilder()
228                     .setInBytes(ByteString.copyFrom(inBytes.duplicate()))
229                     .build());
230     HandshakerResp resp;
231     try {
232       resp = handshakerStub.send(req.build());
233     } catch (IOException | InterruptedException e) {
234       throw new GeneralSecurityException(e);
235     }
236     handleResponse(resp);
237     inBytes.position(inBytes.position() + resp.getBytesConsumed());
238     return resp.getOutFrames().asReadOnlyByteBuffer();
239   }
240 
241   /** Closes the connection. */
close()242   public void close() {
243     handshakerStub.close();
244   }
245 }
246