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 static com.google.common.base.Preconditions.checkState; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.base.Preconditions; 23 import io.grpc.ChannelLogger; 24 import io.grpc.ChannelLogger.ChannelLogLevel; 25 import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub; 26 import io.netty.buffer.ByteBufAllocator; 27 import java.nio.Buffer; 28 import java.nio.ByteBuffer; 29 import java.security.GeneralSecurityException; 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * Negotiates a grpc channel key to be used by the TsiFrameProtector, using ALTs handshaker service. 35 */ 36 public final class AltsTsiHandshaker implements TsiHandshaker { 37 private final ChannelLogger logger; 38 39 public static final String TSI_SERVICE_ACCOUNT_PEER_PROPERTY = "service_account"; 40 41 private final boolean isClient; 42 private final AltsHandshakerClient handshaker; 43 44 private ByteBuffer outputFrame; 45 46 /** Starts a new TSI handshaker with client options. */ AltsTsiHandshaker( boolean isClient, HandshakerServiceStub stub, AltsHandshakerOptions options, ChannelLogger logger)47 private AltsTsiHandshaker( 48 boolean isClient, 49 HandshakerServiceStub stub, 50 AltsHandshakerOptions options, 51 ChannelLogger logger) { 52 this.isClient = isClient; 53 this.logger = logger; 54 handshaker = new AltsHandshakerClient(stub, options, logger); 55 } 56 57 @VisibleForTesting AltsTsiHandshaker(boolean isClient, AltsHandshakerClient handshaker, ChannelLogger logger)58 AltsTsiHandshaker(boolean isClient, AltsHandshakerClient handshaker, ChannelLogger logger) { 59 this.isClient = isClient; 60 this.handshaker = handshaker; 61 this.logger = logger; 62 } 63 64 /** 65 * Process the bytes received from the peer. 66 * 67 * @param bytes The buffer containing the handshake bytes from the peer. 68 * @return true, if the handshake has all the data it needs to process and false, if the method 69 * must be called again to complete processing. 70 */ 71 @Override processBytesFromPeer(ByteBuffer bytes)72 public boolean processBytesFromPeer(ByteBuffer bytes) throws GeneralSecurityException { 73 // If we're the client and we haven't given an output frame, we shouldn't be processing any 74 // bytes. 75 if (outputFrame == null && isClient) { 76 return true; 77 } 78 // If we already have bytes to write, just return. 79 if (outputFrame != null && outputFrame.hasRemaining()) { 80 return true; 81 } 82 int remaining = bytes.remaining(); 83 // Call handshaker service to proceess the bytes. 84 if (outputFrame == null) { 85 checkState(!isClient, "Client handshaker should not process any frame at the beginning."); 86 outputFrame = handshaker.startServerHandshake(bytes); 87 } else { 88 logger.log(ChannelLogLevel.DEBUG, "Receive ALTS handshake from downstream"); 89 outputFrame = handshaker.next(bytes); 90 } 91 // If handshake has finished or we already have bytes to write, just return true. 92 if (handshaker.isFinished() || outputFrame.hasRemaining()) { 93 return true; 94 } 95 // We have done processing input bytes, but no bytes to write. Thus we need more data. 96 if (!bytes.hasRemaining()) { 97 return false; 98 } 99 // There are still remaining bytes. Thus we need to continue processing the bytes. 100 // Prevent infinite loop by checking some bytes are consumed by handshaker. 101 checkState(bytes.remaining() < remaining, "Handshaker did not consume any bytes."); 102 return processBytesFromPeer(bytes); 103 } 104 105 /** 106 * Returns the peer extracted from a completed handshake. 107 * 108 * @return the extracted peer. 109 */ 110 @Override 111 public TsiPeer extractPeer() throws GeneralSecurityException { 112 Preconditions.checkState(!isInProgress(), "Handshake is not complete."); 113 List<TsiPeer.Property<?>> peerProperties = new ArrayList<>(); 114 peerProperties.add( 115 new TsiPeer.StringProperty( 116 TSI_SERVICE_ACCOUNT_PEER_PROPERTY, 117 handshaker.getResult().getPeerIdentity().getServiceAccount())); 118 return new TsiPeer(peerProperties); 119 } 120 121 /** 122 * Returns the peer extracted from a completed handshake. 123 * 124 * @return the extracted peer. 125 */ 126 @Override 127 public Object extractPeerObject() throws GeneralSecurityException { 128 Preconditions.checkState(!isInProgress(), "Handshake is not complete."); 129 return new AltsInternalContext(handshaker.getResult()); 130 } 131 132 /** Creates a new TsiHandshaker for use by the client. */ 133 public static TsiHandshaker newClient( 134 HandshakerServiceStub stub, AltsHandshakerOptions options, ChannelLogger logger) { 135 return new AltsTsiHandshaker(true, stub, options, logger); 136 } 137 138 /** Creates a new TsiHandshaker for use by the server. */ 139 public static TsiHandshaker newServer( 140 HandshakerServiceStub stub, AltsHandshakerOptions options, ChannelLogger logger) { 141 return new AltsTsiHandshaker(false, stub, options, logger); 142 } 143 144 /** 145 * Gets bytes that need to be sent to the peer. 146 * 147 * @param bytes The buffer to put handshake bytes. 148 */ 149 @Override 150 public void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityException { 151 if (outputFrame == null) { // A null outputFrame indicates we haven't started the handshake. 152 if (isClient) { 153 logger.log(ChannelLogLevel.DEBUG, "Initial ALTS handshake to downstream"); 154 outputFrame = handshaker.startClientHandshake(); 155 } else { 156 // The server needs bytes to process before it can start the handshake. 157 return; 158 } 159 } 160 logger.log(ChannelLogLevel.DEBUG, "Send ALTS request to downstream"); 161 // Write as many bytes as we are able. 162 ByteBuffer outputFrameAlias = outputFrame; 163 if (outputFrame.remaining() > bytes.remaining()) { 164 outputFrameAlias = outputFrame.duplicate(); 165 ((Buffer) outputFrameAlias).limit(outputFrameAlias.position() + bytes.remaining()); 166 } 167 bytes.put(outputFrameAlias); 168 ((Buffer) outputFrame).position(outputFrameAlias.position()); 169 } 170 171 /** 172 * Returns true if and only if the handshake is still in progress. 173 * 174 * @return true, if the handshake is still in progress, false otherwise. 175 */ 176 @Override isInProgress()177 public boolean isInProgress() { 178 return !handshaker.isFinished() || outputFrame.hasRemaining(); 179 } 180 181 /** 182 * Creates a frame protector from a completed handshake. No other methods may be called after the 183 * frame protector is created. 184 * 185 * @param maxFrameSize the requested max frame size, the callee is free to ignore. 186 * @param alloc used for allocating ByteBufs. 187 * @return a new TsiFrameProtector. 188 */ 189 @Override createFrameProtector(int maxFrameSize, ByteBufAllocator alloc)190 public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc) { 191 Preconditions.checkState(!isInProgress(), "Handshake is not complete."); 192 193 byte[] key = handshaker.getKey(); 194 Preconditions.checkState(key.length == AltsChannelCrypter.getKeyLength(), "Bad key length."); 195 196 // Frame size negotiation is not performed if the peer does not send max frame size (e.g. peer 197 // is gRPC Go or peer uses an old binary). 198 int peerMaxFrameSize = handshaker.getResult().getMaxFrameSize(); 199 if (peerMaxFrameSize != 0) { 200 maxFrameSize = Math.min(peerMaxFrameSize, AltsTsiFrameProtector.getMaxFrameSize()); 201 maxFrameSize = Math.max(AltsTsiFrameProtector.getMinFrameSize(), maxFrameSize); 202 } 203 logger.log(ChannelLogLevel.INFO, "Maximum frame size value is {0}.", maxFrameSize); 204 return new AltsTsiFrameProtector(maxFrameSize, new AltsChannelCrypter(key, isClient), alloc); 205 } 206 207 /** 208 * Creates a frame protector from a completed handshake. No other methods may be called after the 209 * frame protector is created. 210 * 211 * @param alloc used for allocating ByteBufs. 212 * @return a new TsiFrameProtector. 213 */ 214 @Override createFrameProtector(ByteBufAllocator alloc)215 public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) { 216 return createFrameProtector(AltsTsiFrameProtector.getMinFrameSize(), alloc); 217 } 218 219 @Override close()220 public void close() { 221 handshaker.close(); 222 } 223 } 224