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