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