• 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 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