snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package authinfo provide authentication information returned by handshakers.
package authinfo
import (
"google.golang.org/grpc/credentials"
altspb "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
)
var _ credentials.AuthInfo = (*altsAuthInfo)(nil)
// altsAuthInfo exposes security information from the ALTS handshake to the
// application. altsAuthInfo is immutable and implements credentials.AuthInfo.
type altsAuthInfo struct {
p *altspb.AltsContext
credentials.CommonAuthInfo
}
// New returns a new altsAuthInfo object given handshaker results.
func New(result *altspb.HandshakerResult) credentials.AuthInfo {
return newAuthInfo(result)
}
func newAuthInfo(result *altspb.HandshakerResult) *altsAuthInfo {
return &altsAuthInfo{
p: &altspb.AltsContext{
ApplicationProtocol: result.GetApplicationProtocol(),
RecordProtocol: result.GetRecordProtocol(),
// TODO: assign security level from result.
SecurityLevel: altspb.SecurityLevel_INTEGRITY_AND_PRIVACY,
PeerServiceAccount: result.GetPeerIdentity().GetServiceAccount(),
LocalServiceAccount: result.GetLocalIdentity().GetServiceAccount(),
PeerRpcVersions: result.GetPeerRpcVersions(),
PeerAttributes: result.GetPeerIdentity().GetAttributes(),
},
CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity},
}
}
// AuthType identifies the context as providing ALTS authentication information.
func (s *altsAuthInfo) AuthType() string {
return "alts"
}
// ApplicationProtocol returns the context's application protocol.
func (s *altsAuthInfo) ApplicationProtocol() string {
return s.p.GetApplicationProtocol()
}
// RecordProtocol returns the context's record protocol.
func (s *altsAuthInfo) RecordProtocol() string {
return s.p.GetRecordProtocol()
}
// SecurityLevel returns the context's security level.
func (s *altsAuthInfo) SecurityLevel() altspb.SecurityLevel {
return s.p.GetSecurityLevel()
}
// PeerServiceAccount returns the context's peer service account.
func (s *altsAuthInfo) PeerServiceAccount() string {
return s.p.GetPeerServiceAccount()
}
// LocalServiceAccount returns the context's local service account.
func (s *altsAuthInfo) LocalServiceAccount() string {
return s.p.GetLocalServiceAccount()
}
// PeerRPCVersions returns the context's peer RPC versions.
func (s *altsAuthInfo) PeerRPCVersions() *altspb.RpcProtocolVersions {
return s.p.GetPeerRpcVersions()
}
// PeerAttributes returns the context's peer attributes.
func (s *altsAuthInfo) PeerAttributes() map[string]string {
return s.p.GetPeerAttributes()
}

View File

@@ -0,0 +1,156 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package authinfo
import (
"reflect"
"testing"
altspb "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
const (
testAppProtocol = "my_app"
testRecordProtocol = "very_secure_protocol"
testPeerAccount = "peer_service_account"
testLocalAccount = "local_service_account"
testPeerHostname = "peer_hostname"
testLocalHostname = "local_hostname"
testLocalPeerAttributeKey = "peer"
testLocalPeerAttributeValue = "attributes"
)
func (s) TestALTSAuthInfo(t *testing.T) {
testPeerAttributes := make(map[string]string)
testPeerAttributes[testLocalPeerAttributeKey] = testLocalPeerAttributeValue
for _, tc := range []struct {
result *altspb.HandshakerResult
outAppProtocol string
outRecordProtocol string
outSecurityLevel altspb.SecurityLevel
outPeerAccount string
outLocalAccount string
outPeerRPCVersions *altspb.RpcProtocolVersions
outPeerAttributes map[string]string
}{
{
&altspb.HandshakerResult{
ApplicationProtocol: testAppProtocol,
RecordProtocol: testRecordProtocol,
PeerIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_ServiceAccount{
ServiceAccount: testPeerAccount,
},
Attributes: testPeerAttributes,
},
LocalIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_ServiceAccount{
ServiceAccount: testLocalAccount,
},
},
},
testAppProtocol,
testRecordProtocol,
altspb.SecurityLevel_INTEGRITY_AND_PRIVACY,
testPeerAccount,
testLocalAccount,
nil,
testPeerAttributes,
},
{
&altspb.HandshakerResult{
ApplicationProtocol: testAppProtocol,
RecordProtocol: testRecordProtocol,
PeerIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_Hostname{
Hostname: testPeerHostname,
},
Attributes: testPeerAttributes,
},
LocalIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_Hostname{
Hostname: testLocalHostname,
},
},
PeerRpcVersions: &altspb.RpcProtocolVersions{
MaxRpcVersion: &altspb.RpcProtocolVersions_Version{
Major: 20,
Minor: 21,
},
MinRpcVersion: &altspb.RpcProtocolVersions_Version{
Major: 10,
Minor: 11,
},
},
},
testAppProtocol,
testRecordProtocol,
altspb.SecurityLevel_INTEGRITY_AND_PRIVACY,
"",
"",
&altspb.RpcProtocolVersions{
MaxRpcVersion: &altspb.RpcProtocolVersions_Version{
Major: 20,
Minor: 21,
},
MinRpcVersion: &altspb.RpcProtocolVersions_Version{
Major: 10,
Minor: 11,
},
},
testPeerAttributes,
},
} {
authInfo := newAuthInfo(tc.result)
if got, want := authInfo.AuthType(), "alts"; got != want {
t.Errorf("authInfo.AuthType()=%v, want %v", got, want)
}
if got, want := authInfo.ApplicationProtocol(), tc.outAppProtocol; got != want {
t.Errorf("authInfo.ApplicationProtocol()=%v, want %v", got, want)
}
if got, want := authInfo.RecordProtocol(), tc.outRecordProtocol; got != want {
t.Errorf("authInfo.RecordProtocol()=%v, want %v", got, want)
}
if got, want := authInfo.SecurityLevel(), tc.outSecurityLevel; got != want {
t.Errorf("authInfo.SecurityLevel()=%v, want %v", got, want)
}
if got, want := authInfo.PeerServiceAccount(), tc.outPeerAccount; got != want {
t.Errorf("authInfo.PeerServiceAccount()=%v, want %v", got, want)
}
if got, want := authInfo.LocalServiceAccount(), tc.outLocalAccount; got != want {
t.Errorf("authInfo.LocalServiceAccount()=%v, want %v", got, want)
}
if got, want := authInfo.PeerRPCVersions(), tc.outPeerRPCVersions; !reflect.DeepEqual(got, want) {
t.Errorf("authinfo.PeerRpcVersions()=%v, want %v", got, want)
}
if got, want := authInfo.PeerAttributes(), tc.outPeerAttributes; !reflect.DeepEqual(got, want) {
t.Errorf("authinfo.PeerAttributes()=%v, want %v", got, want)
}
}
}

View File

@@ -0,0 +1,67 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package internal contains common core functionality for ALTS.
package internal
import (
"context"
"net"
"google.golang.org/grpc/credentials"
)
const (
// ClientSide identifies the client in this communication.
ClientSide Side = iota
// ServerSide identifies the server in this communication.
ServerSide
)
// PeerNotRespondingError is returned when a peer server is not responding
// after a channel has been established. It is treated as a temporary connection
// error and re-connection to the server should be attempted.
var PeerNotRespondingError = &peerNotRespondingError{}
// Side identifies the party's role: client or server.
type Side int
type peerNotRespondingError struct{}
// Return an error message for the purpose of logging.
func (e *peerNotRespondingError) Error() string {
return "peer server is not responding and re-connection should be attempted."
}
// Temporary indicates if this connection error is temporary or fatal.
func (e *peerNotRespondingError) Temporary() bool {
return true
}
// Handshaker defines a ALTS handshaker interface.
type Handshaker interface {
// ClientHandshake starts and completes a client-side handshaking and
// returns a secure connection and corresponding auth information.
ClientHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
// ServerHandshake starts and completes a server-side handshaking and
// returns a secure connection and corresponding auth information.
ServerHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
// Close terminates the Handshaker. It should be called when the caller
// obtains the secure connection.
Close()
}

View File

@@ -0,0 +1,131 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"fmt"
"strconv"
)
// rekeyAEAD holds the necessary information for an AEAD based on
// AES-GCM that performs nonce-based key derivation and XORs the
// nonce with a random mask.
type rekeyAEAD struct {
kdfKey []byte
kdfCounter []byte
nonceMask []byte
nonceBuf []byte
gcmAEAD cipher.AEAD
}
// KeySizeError signals that the given key does not have the correct size.
type KeySizeError int
func (k KeySizeError) Error() string {
return "alts/conn: invalid key size " + strconv.Itoa(int(k))
}
// newRekeyAEAD creates a new instance of aes128gcm with rekeying.
// The key argument should be 44 bytes, the first 32 bytes are used as a key
// for HKDF-expand and the remaining 12 bytes are used as a random mask for
// the counter.
func newRekeyAEAD(key []byte) (*rekeyAEAD, error) {
k := len(key)
if k != kdfKeyLen+nonceLen {
return nil, KeySizeError(k)
}
return &rekeyAEAD{
kdfKey: key[:kdfKeyLen],
kdfCounter: make([]byte, kdfCounterLen),
nonceMask: key[kdfKeyLen:],
nonceBuf: make([]byte, nonceLen),
gcmAEAD: nil,
}, nil
}
// Seal rekeys if nonce[2:8] is different than in the last call, masks the nonce,
// and calls Seal for aes128gcm.
func (s *rekeyAEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if err := s.rekeyIfRequired(nonce); err != nil {
panic(fmt.Sprintf("Rekeying failed with: %s", err.Error()))
}
maskNonce(s.nonceBuf, nonce, s.nonceMask)
return s.gcmAEAD.Seal(dst, s.nonceBuf, plaintext, additionalData)
}
// Open rekeys if nonce[2:8] is different than in the last call, masks the nonce,
// and calls Open for aes128gcm.
func (s *rekeyAEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if err := s.rekeyIfRequired(nonce); err != nil {
return nil, err
}
maskNonce(s.nonceBuf, nonce, s.nonceMask)
return s.gcmAEAD.Open(dst, s.nonceBuf, ciphertext, additionalData)
}
// rekeyIfRequired creates a new aes128gcm AEAD if the existing AEAD is nil
// or cannot be used with given nonce.
func (s *rekeyAEAD) rekeyIfRequired(nonce []byte) error {
newKdfCounter := nonce[kdfCounterOffset : kdfCounterOffset+kdfCounterLen]
if s.gcmAEAD != nil && bytes.Equal(newKdfCounter, s.kdfCounter) {
return nil
}
copy(s.kdfCounter, newKdfCounter)
a, err := aes.NewCipher(hkdfExpand(s.kdfKey, s.kdfCounter))
if err != nil {
return err
}
s.gcmAEAD, err = cipher.NewGCM(a)
return err
}
// maskNonce XORs the given nonce with the mask and stores the result in dst.
func maskNonce(dst, nonce, mask []byte) {
nonce1 := binary.LittleEndian.Uint64(nonce[:sizeUint64])
nonce2 := binary.LittleEndian.Uint32(nonce[sizeUint64:])
mask1 := binary.LittleEndian.Uint64(mask[:sizeUint64])
mask2 := binary.LittleEndian.Uint32(mask[sizeUint64:])
binary.LittleEndian.PutUint64(dst[:sizeUint64], nonce1^mask1)
binary.LittleEndian.PutUint32(dst[sizeUint64:], nonce2^mask2)
}
// NonceSize returns the required nonce size.
func (s *rekeyAEAD) NonceSize() int {
return s.gcmAEAD.NonceSize()
}
// Overhead returns the ciphertext overhead.
func (s *rekeyAEAD) Overhead() int {
return s.gcmAEAD.Overhead()
}
// hkdfExpand computes the first 16 bytes of the HKDF-expand function
// defined in RFC5869.
func hkdfExpand(key, info []byte) []byte {
mac := hmac.New(sha256.New, key)
mac.Write(info)
mac.Write([]byte{0x01}[:])
return mac.Sum(nil)[:aeadKeyLen]
}

View File

@@ -0,0 +1,263 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"encoding/hex"
"testing"
)
// cryptoTestVector is struct for a rekey test vector
type rekeyAEADTestVector struct {
desc string
key, nonce, plaintext, aad, ciphertext []byte
}
// Test encrypt and decrypt using (adapted) test vectors for AES-GCM.
func (s) TestAES128GCMRekeyEncrypt(t *testing.T) {
for _, test := range []rekeyAEADTestVector{
// NIST vectors from:
// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
//
// IEEE vectors from:
// http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
//
// Key expanded by setting
// expandedKey = (key ||
// key ^ {0x01,..,0x01} ||
// key ^ {0x02,..,0x02})[0:44].
{
desc: "Derived from NIST test vector 1",
key: dehex("0000000000000000000000000000000001010101010101010101010101010101020202020202020202020202"),
nonce: dehex("000000000000000000000000"),
aad: dehex(""),
plaintext: dehex(""),
ciphertext: dehex("85e873e002f6ebdc4060954eb8675508"),
},
{
desc: "Derived from NIST test vector 2",
key: dehex("0000000000000000000000000000000001010101010101010101010101010101020202020202020202020202"),
nonce: dehex("000000000000000000000000"),
aad: dehex(""),
plaintext: dehex("00000000000000000000000000000000"),
ciphertext: dehex("51e9a8cb23ca2512c8256afff8e72d681aca19a1148ac115e83df4888cc00d11"),
},
{
desc: "Derived from NIST test vector 3",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("cafebabefacedbaddecaf888"),
aad: dehex(""),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255"),
ciphertext: dehex("1018ed5a1402a86516d6576d70b2ffccca261b94df88b58f53b64dfba435d18b2f6e3b7869f9353d4ac8cf09afb1663daa7b4017e6fc2c177c0c087c0df1162129952213cee1bc6e9c8495dd705e1f3d"),
},
{
desc: "Derived from NIST test vector 4",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("cafebabefacedbaddecaf888"),
aad: dehex("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"),
ciphertext: dehex("1018ed5a1402a86516d6576d70b2ffccca261b94df88b58f53b64dfba435d18b2f6e3b7869f9353d4ac8cf09afb1663daa7b4017e6fc2c177c0c087c4764565d077e9124001ddb27fc0848c5"),
},
{
desc: "Derived from adapted NIST test vector 4 for KDF counter boundary (flip nonce bit 15)",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("ca7ebabefacedbaddecaf888"),
aad: dehex("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"),
ciphertext: dehex("e650d3c0fb879327f2d03287fa93cd07342b136215adbca00c3bd5099ec41832b1d18e0423ed26bb12c6cd09debb29230a94c0cee15903656f85edb6fc509b1b28216382172ecbcc31e1e9b1"),
},
{
desc: "Derived from adapted NIST test vector 4 for KDF counter boundary (flip nonce bit 16)",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("cafebbbefacedbaddecaf888"),
aad: dehex("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"),
ciphertext: dehex("c0121e6c954d0767f96630c33450999791b2da2ad05c4190169ccad9ac86ff1c721e3d82f2ad22ab463bab4a0754b7dd68ca4de7ea2531b625eda01f89312b2ab957d5c7f8568dd95fcdcd1f"),
},
{
desc: "Derived from adapted NIST test vector 4 for KDF counter boundary (flip nonce bit 63)",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("cafebabefacedb2ddecaf888"),
aad: dehex("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"),
ciphertext: dehex("8af37ea5684a4d81d4fd817261fd9743099e7e6a025eaacf8e54b124fb5743149e05cb89f4a49467fe2e5e5965f29a19f99416b0016b54585d12553783ba59e9f782e82e097c336bf7989f08"),
},
{
desc: "Derived from adapted NIST test vector 4 for KDF counter boundary (flip nonce bit 64)",
key: dehex("feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f688d96"),
nonce: dehex("cafebabefacedbaddfcaf888"),
aad: dehex("feedfacedeadbeeffeedfacedeadbeefabaddad2"),
plaintext: dehex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"),
ciphertext: dehex("fbd528448d0346bfa878634864d407a35a039de9db2f1feb8e965b3ae9356ce6289441d77f8f0df294891f37ea438b223e3bf2bdc53d4c5a74fb680bb312a8dec6f7252cbcd7f5799750ad78"),
},
{
desc: "Derived from IEEE 2.1.1 54-byte auth",
key: dehex("ad7a2bd03eac835a6f620fdcb506b345ac7b2ad13fad825b6e630eddb407b244af7829d23cae81586d600dde"),
nonce: dehex("12153524c0895e81b2c28465"),
aad: dehex("d609b1f056637a0d46df998d88e5222ab2c2846512153524c0895e8108000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340001"),
plaintext: dehex(""),
ciphertext: dehex("3ea0b584f3c85e93f9320ea591699efb"),
},
{
desc: "Derived from IEEE 2.1.2 54-byte auth",
key: dehex("e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72e1c2888d04c4e1af97a50755"),
nonce: dehex("12153524c0895e81b2c28465"),
aad: dehex("d609b1f056637a0d46df998d88e5222ab2c2846512153524c0895e8108000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340001"),
plaintext: dehex(""),
ciphertext: dehex("294e028bf1fe6f14c4e8f7305c933eb5"),
},
{
desc: "Derived from IEEE 2.2.1 60-byte crypt",
key: dehex("ad7a2bd03eac835a6f620fdcb506b345ac7b2ad13fad825b6e630eddb407b244af7829d23cae81586d600dde"),
nonce: dehex("12153524c0895e81b2c28465"),
aad: dehex("d609b1f056637a0d46df998d88e52e00b2c2846512153524c0895e81"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a0002"),
ciphertext: dehex("db3d25719c6b0a3ca6145c159d5c6ed9aff9c6e0b79f17019ea923b8665ddf52137ad611f0d1bf417a7ca85e45afe106ff9c7569d335d086ae6c03f00987ccd6"),
},
{
desc: "Derived from IEEE 2.2.2 60-byte crypt",
key: dehex("e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72e1c2888d04c4e1af97a50755"),
nonce: dehex("12153524c0895e81b2c28465"),
aad: dehex("d609b1f056637a0d46df998d88e52e00b2c2846512153524c0895e81"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a0002"),
ciphertext: dehex("1641f28ec13afcc8f7903389787201051644914933e9202bb9d06aa020c2a67ef51dfe7bc00a856c55b8f8133e77f659132502bad63f5713d57d0c11e0f871ed"),
},
{
desc: "Derived from IEEE 2.3.1 60-byte auth",
key: dehex("071b113b0ca743fecccf3d051f737382061a103a0da642ffcdce3c041e727283051913390ea541fccecd3f07"),
nonce: dehex("f0761e8dcd3d000176d457ed"),
aad: dehex("e20106d7cd0df0761e8dcd3d88e5400076d457ed08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a0003"),
plaintext: dehex(""),
ciphertext: dehex("58837a10562b0f1f8edbe58ca55811d3"),
},
{
desc: "Derived from IEEE 2.3.2 60-byte auth",
key: dehex("691d3ee909d7f54167fd1ca0b5d769081f2bde1aee655fdbab80bd5295ae6be76b1f3ceb0bd5f74365ff1ea2"),
nonce: dehex("f0761e8dcd3d000176d457ed"),
aad: dehex("e20106d7cd0df0761e8dcd3d88e5400076d457ed08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a0003"),
plaintext: dehex(""),
ciphertext: dehex("c2722ff6ca29a257718a529d1f0c6a3b"),
},
{
desc: "Derived from IEEE 2.4.1 54-byte crypt",
key: dehex("071b113b0ca743fecccf3d051f737382061a103a0da642ffcdce3c041e727283051913390ea541fccecd3f07"),
nonce: dehex("f0761e8dcd3d000176d457ed"),
aad: dehex("e20106d7cd0df0761e8dcd3d88e54c2a76d457ed"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340004"),
ciphertext: dehex("fd96b715b93a13346af51e8acdf792cdc7b2686f8574c70e6b0cbf16291ded427ad73fec48cd298e0528a1f4c644a949fc31dc9279706ddba33f"),
},
{
desc: "Derived from IEEE 2.4.2 54-byte crypt",
key: dehex("691d3ee909d7f54167fd1ca0b5d769081f2bde1aee655fdbab80bd5295ae6be76b1f3ceb0bd5f74365ff1ea2"),
nonce: dehex("f0761e8dcd3d000176d457ed"),
aad: dehex("e20106d7cd0df0761e8dcd3d88e54c2a76d457ed"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340004"),
ciphertext: dehex("b68f6300c2e9ae833bdc070e24021a3477118e78ccf84e11a485d861476c300f175353d5cdf92008a4f878e6cc3577768085c50a0e98fda6cbb8"),
},
{
desc: "Derived from IEEE 2.5.1 65-byte auth",
key: dehex("013fe00b5f11be7f866d0cbbc55a7a90003ee10a5e10bf7e876c0dbac45b7b91033de2095d13bc7d846f0eb9"),
nonce: dehex("7cfde9f9e33724c68932d612"),
aad: dehex("84c5d513d2aaf6e5bbd2727788e523008932d6127cfde9f9e33724c608000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f0005"),
plaintext: dehex(""),
ciphertext: dehex("cca20eecda6283f09bb3543dd99edb9b"),
},
{
desc: "Derived from IEEE 2.5.2 65-byte auth",
key: dehex("83c093b58de7ffe1c0da926ac43fb3609ac1c80fee1b624497ef942e2f79a82381c291b78fe5fde3c2d89068"),
nonce: dehex("7cfde9f9e33724c68932d612"),
aad: dehex("84c5d513d2aaf6e5bbd2727788e523008932d6127cfde9f9e33724c608000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f0005"),
plaintext: dehex(""),
ciphertext: dehex("b232cc1da5117bf15003734fa599d271"),
},
{
desc: "Derived from IEEE 2.6.1 61-byte crypt",
key: dehex("013fe00b5f11be7f866d0cbbc55a7a90003ee10a5e10bf7e876c0dbac45b7b91033de2095d13bc7d846f0eb9"),
nonce: dehex("7cfde9f9e33724c68932d612"),
aad: dehex("84c5d513d2aaf6e5bbd2727788e52f008932d6127cfde9f9e33724c6"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b0006"),
ciphertext: dehex("ff1910d35ad7e5657890c7c560146fd038707f204b66edbc3d161f8ace244b985921023c436e3a1c3532ecd5d09a056d70be583f0d10829d9387d07d33d872e490"),
},
{
desc: "Derived from IEEE 2.6.2 61-byte crypt",
key: dehex("83c093b58de7ffe1c0da926ac43fb3609ac1c80fee1b624497ef942e2f79a82381c291b78fe5fde3c2d89068"),
nonce: dehex("7cfde9f9e33724c68932d612"),
aad: dehex("84c5d513d2aaf6e5bbd2727788e52f008932d6127cfde9f9e33724c6"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b0006"),
ciphertext: dehex("0db4cf956b5f97eca4eab82a6955307f9ae02a32dd7d93f83d66ad04e1cfdc5182ad12abdea5bbb619a1bd5fb9a573590fba908e9c7a46c1f7ba0905d1b55ffda4"),
},
{
desc: "Derived from IEEE 2.7.1 79-byte crypt",
key: dehex("88ee087fd95da9fbf6725aa9d757b0cd89ef097ed85ca8faf7735ba8d656b1cc8aec0a7ddb5fabf9f47058ab"),
nonce: dehex("7ae8e2ca4ec500012e58495c"),
aad: dehex("68f2e77696ce7ae8e2ca4ec588e541002e58495c08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d0007"),
plaintext: dehex(""),
ciphertext: dehex("813f0e630f96fb2d030f58d83f5cdfd0"),
},
{
desc: "Derived from IEEE 2.7.2 79-byte crypt",
key: dehex("4c973dbc7364621674f8b5b89e5c15511fced9216490fb1c1a2caa0ffe0407e54e953fbe7166601476fab7ba"),
nonce: dehex("7ae8e2ca4ec500012e58495c"),
aad: dehex("68f2e77696ce7ae8e2ca4ec588e541002e58495c08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d0007"),
plaintext: dehex(""),
ciphertext: dehex("77e5a44c21eb07188aacbd74d1980e97"),
},
{
desc: "Derived from IEEE 2.8.1 61-byte crypt",
key: dehex("88ee087fd95da9fbf6725aa9d757b0cd89ef097ed85ca8faf7735ba8d656b1cc8aec0a7ddb5fabf9f47058ab"),
nonce: dehex("7ae8e2ca4ec500012e58495c"),
aad: dehex("68f2e77696ce7ae8e2ca4ec588e54d002e58495c"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748490008"),
ciphertext: dehex("958ec3f6d60afeda99efd888f175e5fcd4c87b9bcc5c2f5426253a8b506296c8c43309ab2adb5939462541d95e80811e04e706b1498f2c407c7fb234f8cc01a647550ee6b557b35a7e3945381821f4"),
},
{
desc: "Derived from IEEE 2.8.2 61-byte crypt",
key: dehex("4c973dbc7364621674f8b5b89e5c15511fced9216490fb1c1a2caa0ffe0407e54e953fbe7166601476fab7ba"),
nonce: dehex("7ae8e2ca4ec500012e58495c"),
aad: dehex("68f2e77696ce7ae8e2ca4ec588e54d002e58495c"),
plaintext: dehex("08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748490008"),
ciphertext: dehex("b44d072011cd36d272a9b7a98db9aa90cbc5c67b93ddce67c854503214e2e896ec7e9db649ed4bcf6f850aac0223d0cf92c83db80795c3a17ecc1248bb00591712b1ae71e268164196252162810b00"),
}} {
aead, err := newRekeyAEAD(test.key)
if err != nil {
t.Fatal("unexpected failure in newRekeyAEAD: ", err.Error())
}
if got := aead.Seal(nil, test.nonce, test.plaintext, test.aad); !bytes.Equal(got, test.ciphertext) {
t.Errorf("Unexpected ciphertext for test vector '%s':\nciphertext=%s\nwant= %s",
test.desc, hex.EncodeToString(got), hex.EncodeToString(test.ciphertext))
}
if got, err := aead.Open(nil, test.nonce, test.ciphertext, test.aad); err != nil || !bytes.Equal(got, test.plaintext) {
t.Errorf("Unexpected plaintext for test vector '%s':\nplaintext=%s (err=%v)\nwant= %s",
test.desc, hex.EncodeToString(got), err, hex.EncodeToString(test.plaintext))
}
}
}
func dehex(s string) []byte {
if len(s) == 0 {
return make([]byte, 0)
}
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

View File

@@ -0,0 +1,105 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"crypto/aes"
"crypto/cipher"
core "google.golang.org/grpc/credentials/alts/internal"
)
const (
// Overflow length n in bytes, never encrypt more than 2^(n*8) frames (in
// each direction).
overflowLenAES128GCM = 5
)
// aes128gcm is the struct that holds necessary information for ALTS record.
// The counter value is NOT included in the payload during the encryption and
// decryption operations.
type aes128gcm struct {
// inCounter is used in ALTS record to check that incoming counters are
// as expected, since ALTS record guarantees that messages are unwrapped
// in the same order that the peer wrapped them.
inCounter Counter
outCounter Counter
aead cipher.AEAD
}
// NewAES128GCM creates an instance that uses aes128gcm for ALTS record.
func NewAES128GCM(side core.Side, key []byte) (ALTSRecordCrypto, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
a, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
return &aes128gcm{
inCounter: NewInCounter(side, overflowLenAES128GCM),
outCounter: NewOutCounter(side, overflowLenAES128GCM),
aead: a,
}, nil
}
// Encrypt is the encryption function. dst can contain bytes at the beginning of
// the ciphertext that will not be encrypted but will be authenticated. If dst
// has enough capacity to hold these bytes, the ciphertext and the tag, no
// allocation and copy operations will be performed. dst and plaintext do not
// overlap.
func (s *aes128gcm) Encrypt(dst, plaintext []byte) ([]byte, error) {
// If we need to allocate an output buffer, we want to include space for
// GCM tag to avoid forcing ALTS record to reallocate as well.
dlen := len(dst)
dst, out := SliceForAppend(dst, len(plaintext)+GcmTagSize)
seq, err := s.outCounter.Value()
if err != nil {
return nil, err
}
data := out[:len(plaintext)]
copy(data, plaintext) // data may alias plaintext
// Seal appends the ciphertext and the tag to its first argument and
// returns the updated slice. However, SliceForAppend above ensures that
// dst has enough capacity to avoid a reallocation and copy due to the
// append.
dst = s.aead.Seal(dst[:dlen], seq, data, nil)
s.outCounter.Inc()
return dst, nil
}
func (s *aes128gcm) EncryptionOverhead() int {
return GcmTagSize
}
func (s *aes128gcm) Decrypt(dst, ciphertext []byte) ([]byte, error) {
seq, err := s.inCounter.Value()
if err != nil {
return nil, err
}
// If dst is equal to ciphertext[:0], ciphertext storage is reused.
plaintext, err := s.aead.Open(dst, seq, ciphertext, nil)
if err != nil {
return nil, ErrAuth
}
s.inCounter.Inc()
return plaintext, nil
}

View File

@@ -0,0 +1,223 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"testing"
core "google.golang.org/grpc/credentials/alts/internal"
)
// cryptoTestVector is struct for a GCM test vector
type cryptoTestVector struct {
key, counter, plaintext, ciphertext, tag []byte
allocateDst bool
}
// getGCMCryptoPair outputs a client/server pair on aes128gcm.
func getGCMCryptoPair(key []byte, counter []byte, t *testing.T) (ALTSRecordCrypto, ALTSRecordCrypto) {
client, err := NewAES128GCM(core.ClientSide, key)
if err != nil {
t.Fatalf("NewAES128GCM(ClientSide, key) = %v", err)
}
server, err := NewAES128GCM(core.ServerSide, key)
if err != nil {
t.Fatalf("NewAES128GCM(ServerSide, key) = %v", err)
}
// set counter if provided.
if counter != nil {
if CounterSide(counter) == core.ClientSide {
client.(*aes128gcm).outCounter = CounterFromValue(counter, overflowLenAES128GCM)
server.(*aes128gcm).inCounter = CounterFromValue(counter, overflowLenAES128GCM)
} else {
server.(*aes128gcm).outCounter = CounterFromValue(counter, overflowLenAES128GCM)
client.(*aes128gcm).inCounter = CounterFromValue(counter, overflowLenAES128GCM)
}
}
return client, server
}
func testGCMEncryptionDecryption(sender ALTSRecordCrypto, receiver ALTSRecordCrypto, test *cryptoTestVector, withCounter bool, t *testing.T) {
// Ciphertext is: counter + encrypted text + tag.
ciphertext := []byte(nil)
if withCounter {
ciphertext = append(ciphertext, test.counter...)
}
ciphertext = append(ciphertext, test.ciphertext...)
ciphertext = append(ciphertext, test.tag...)
// Decrypt.
if got, err := receiver.Decrypt(nil, ciphertext); err != nil || !bytes.Equal(got, test.plaintext) {
t.Errorf("key=%v\ncounter=%v\ntag=%v\nciphertext=%v\nDecrypt = %v, %v\nwant: %v",
test.key, test.counter, test.tag, test.ciphertext, got, err, test.plaintext)
}
// Encrypt.
var dst []byte
if test.allocateDst {
dst = make([]byte, len(test.plaintext)+sender.EncryptionOverhead())
}
if got, err := sender.Encrypt(dst[:0], test.plaintext); err != nil || !bytes.Equal(got, ciphertext) {
t.Errorf("key=%v\ncounter=%v\nplaintext=%v\nEncrypt = %v, %v\nwant: %v",
test.key, test.counter, test.plaintext, got, err, ciphertext)
}
}
// Test encrypt and decrypt using test vectors for aes128gcm.
func (s) TestAES128GCMEncrypt(t *testing.T) {
for _, test := range []cryptoTestVector{
{
key: dehex("11754cd72aec309bf52f7687212e8957"),
counter: dehex("3c819d9a9bed087615030b65"),
plaintext: nil,
ciphertext: nil,
tag: dehex("250327c674aaf477aef2675748cf6971"),
allocateDst: false,
},
{
key: dehex("ca47248ac0b6f8372a97ac43508308ed"),
counter: dehex("ffd2b598feabc9019262d2be"),
plaintext: nil,
ciphertext: nil,
tag: dehex("60d20404af527d248d893ae495707d1a"),
allocateDst: false,
},
{
key: dehex("7fddb57453c241d03efbed3ac44e371c"),
counter: dehex("ee283a3fc75575e33efd4887"),
plaintext: dehex("d5de42b461646c255c87bd2962d3b9a2"),
ciphertext: dehex("2ccda4a5415cb91e135c2a0f78c9b2fd"),
tag: dehex("b36d1df9b9d5e596f83e8b7f52971cb3"),
allocateDst: false,
},
{
key: dehex("ab72c77b97cb5fe9a382d9fe81ffdbed"),
counter: dehex("54cc7dc2c37ec006bcc6d1da"),
plaintext: dehex("007c5e5b3e59df24a7c355584fc1518d"),
ciphertext: dehex("0e1bde206a07a9c2c1b65300f8c64997"),
tag: dehex("2b4401346697138c7a4891ee59867d0c"),
allocateDst: false,
},
{
key: dehex("11754cd72aec309bf52f7687212e8957"),
counter: dehex("3c819d9a9bed087615030b65"),
plaintext: nil,
ciphertext: nil,
tag: dehex("250327c674aaf477aef2675748cf6971"),
allocateDst: true,
},
{
key: dehex("ca47248ac0b6f8372a97ac43508308ed"),
counter: dehex("ffd2b598feabc9019262d2be"),
plaintext: nil,
ciphertext: nil,
tag: dehex("60d20404af527d248d893ae495707d1a"),
allocateDst: true,
},
{
key: dehex("7fddb57453c241d03efbed3ac44e371c"),
counter: dehex("ee283a3fc75575e33efd4887"),
plaintext: dehex("d5de42b461646c255c87bd2962d3b9a2"),
ciphertext: dehex("2ccda4a5415cb91e135c2a0f78c9b2fd"),
tag: dehex("b36d1df9b9d5e596f83e8b7f52971cb3"),
allocateDst: true,
},
{
key: dehex("ab72c77b97cb5fe9a382d9fe81ffdbed"),
counter: dehex("54cc7dc2c37ec006bcc6d1da"),
plaintext: dehex("007c5e5b3e59df24a7c355584fc1518d"),
ciphertext: dehex("0e1bde206a07a9c2c1b65300f8c64997"),
tag: dehex("2b4401346697138c7a4891ee59867d0c"),
allocateDst: true,
},
} {
// Test encryption and decryption for aes128gcm.
client, server := getGCMCryptoPair(test.key, test.counter, t)
if CounterSide(test.counter) == core.ClientSide {
testGCMEncryptionDecryption(client, server, &test, false, t)
} else {
testGCMEncryptionDecryption(server, client, &test, false, t)
}
}
}
func testGCMEncryptRoundtrip(client ALTSRecordCrypto, server ALTSRecordCrypto, t *testing.T) {
// Encrypt.
const plaintext = "This is plaintext."
var err error
buf := []byte(plaintext)
buf, err = client.Encrypt(buf[:0], buf)
if err != nil {
t.Fatal("Encrypting with client-side context: unexpected error", err, "\n",
"Plaintext:", []byte(plaintext))
}
// Encrypt a second message.
const plaintext2 = "This is a second plaintext."
buf2 := []byte(plaintext2)
buf2, err = client.Encrypt(buf2[:0], buf2)
if err != nil {
t.Fatal("Encrypting with client-side context: unexpected error", err, "\n",
"Plaintext:", []byte(plaintext2))
}
// Decryption fails: cannot decrypt second message before first.
if got, err := server.Decrypt(nil, buf2); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want unexpected counter error:\n",
" Original plaintext:", []byte(plaintext2), "\n",
" Ciphertext:", buf2, "\n",
" Decrypted plaintext:", got)
}
// Decryption fails: wrong counter space.
if got, err := client.Decrypt(nil, buf); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want counter space error:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", buf, "\n",
" Decrypted plaintext:", got)
}
// Decrypt first message.
ciphertext := append([]byte(nil), buf...)
buf, err = server.Decrypt(buf[:0], buf)
if err != nil || string(buf) != plaintext {
t.Fatal("Decrypting client-side ciphertext with a server-side context did not produce original content:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", ciphertext, "\n",
" Decryption error:", err, "\n",
" Decrypted plaintext:", buf)
}
// Decryption fails: replay attack.
if got, err := server.Decrypt(nil, buf); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want unexpected counter error:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", buf, "\n",
" Decrypted plaintext:", got)
}
}
// Test encrypt and decrypt on roundtrip messages for aes128gcm.
func (s) TestAES128GCMEncryptRoundtrip(t *testing.T) {
// Test for aes128gcm.
key := make([]byte, 16)
client, server := getGCMCryptoPair(key, nil, t)
testGCMEncryptRoundtrip(client, server, t)
}

View File

@@ -0,0 +1,116 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"crypto/cipher"
core "google.golang.org/grpc/credentials/alts/internal"
)
const (
// Overflow length n in bytes, never encrypt more than 2^(n*8) frames (in
// each direction).
overflowLenAES128GCMRekey = 8
nonceLen = 12
aeadKeyLen = 16
kdfKeyLen = 32
kdfCounterOffset = 2
kdfCounterLen = 6
sizeUint64 = 8
)
// aes128gcmRekey is the struct that holds necessary information for ALTS record.
// The counter value is NOT included in the payload during the encryption and
// decryption operations.
type aes128gcmRekey struct {
// inCounter is used in ALTS record to check that incoming counters are
// as expected, since ALTS record guarantees that messages are unwrapped
// in the same order that the peer wrapped them.
inCounter Counter
outCounter Counter
inAEAD cipher.AEAD
outAEAD cipher.AEAD
}
// NewAES128GCMRekey creates an instance that uses aes128gcm with rekeying
// for ALTS record. The key argument should be 44 bytes, the first 32 bytes
// are used as a key for HKDF-expand and the remaining 12 bytes are used
// as a random mask for the counter.
func NewAES128GCMRekey(side core.Side, key []byte) (ALTSRecordCrypto, error) {
inCounter := NewInCounter(side, overflowLenAES128GCMRekey)
outCounter := NewOutCounter(side, overflowLenAES128GCMRekey)
inAEAD, err := newRekeyAEAD(key)
if err != nil {
return nil, err
}
outAEAD, err := newRekeyAEAD(key)
if err != nil {
return nil, err
}
return &aes128gcmRekey{
inCounter,
outCounter,
inAEAD,
outAEAD,
}, nil
}
// Encrypt is the encryption function. dst can contain bytes at the beginning of
// the ciphertext that will not be encrypted but will be authenticated. If dst
// has enough capacity to hold these bytes, the ciphertext and the tag, no
// allocation and copy operations will be performed. dst and plaintext do not
// overlap.
func (s *aes128gcmRekey) Encrypt(dst, plaintext []byte) ([]byte, error) {
// If we need to allocate an output buffer, we want to include space for
// GCM tag to avoid forcing ALTS record to reallocate as well.
dlen := len(dst)
dst, out := SliceForAppend(dst, len(plaintext)+GcmTagSize)
seq, err := s.outCounter.Value()
if err != nil {
return nil, err
}
data := out[:len(plaintext)]
copy(data, plaintext) // data may alias plaintext
// Seal appends the ciphertext and the tag to its first argument and
// returns the updated slice. However, SliceForAppend above ensures that
// dst has enough capacity to avoid a reallocation and copy due to the
// append.
dst = s.outAEAD.Seal(dst[:dlen], seq, data, nil)
s.outCounter.Inc()
return dst, nil
}
func (s *aes128gcmRekey) EncryptionOverhead() int {
return GcmTagSize
}
func (s *aes128gcmRekey) Decrypt(dst, ciphertext []byte) ([]byte, error) {
seq, err := s.inCounter.Value()
if err != nil {
return nil, err
}
plaintext, err := s.inAEAD.Open(dst, seq, ciphertext, nil)
if err != nil {
return nil, ErrAuth
}
s.inCounter.Inc()
return plaintext, nil
}

View File

@@ -0,0 +1,112 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"testing"
core "google.golang.org/grpc/credentials/alts/internal"
)
// getGCMCryptoPair outputs a client/server pair on aes128gcmRekey.
func getRekeyCryptoPair(key []byte, counter []byte, t *testing.T) (ALTSRecordCrypto, ALTSRecordCrypto) {
client, err := NewAES128GCMRekey(core.ClientSide, key)
if err != nil {
t.Fatalf("NewAES128GCMRekey(ClientSide, key) = %v", err)
}
server, err := NewAES128GCMRekey(core.ServerSide, key)
if err != nil {
t.Fatalf("NewAES128GCMRekey(ServerSide, key) = %v", err)
}
// set counter if provided.
if counter != nil {
if CounterSide(counter) == core.ClientSide {
client.(*aes128gcmRekey).outCounter = CounterFromValue(counter, overflowLenAES128GCMRekey)
server.(*aes128gcmRekey).inCounter = CounterFromValue(counter, overflowLenAES128GCMRekey)
} else {
server.(*aes128gcmRekey).outCounter = CounterFromValue(counter, overflowLenAES128GCMRekey)
client.(*aes128gcmRekey).inCounter = CounterFromValue(counter, overflowLenAES128GCMRekey)
}
}
return client, server
}
func testRekeyEncryptRoundtrip(client ALTSRecordCrypto, server ALTSRecordCrypto, t *testing.T) {
// Encrypt.
const plaintext = "This is plaintext."
var err error
buf := []byte(plaintext)
buf, err = client.Encrypt(buf[:0], buf)
if err != nil {
t.Fatal("Encrypting with client-side context: unexpected error", err, "\n",
"Plaintext:", []byte(plaintext))
}
// Encrypt a second message.
const plaintext2 = "This is a second plaintext."
buf2 := []byte(plaintext2)
buf2, err = client.Encrypt(buf2[:0], buf2)
if err != nil {
t.Fatal("Encrypting with client-side context: unexpected error", err, "\n",
"Plaintext:", []byte(plaintext2))
}
// Decryption fails: cannot decrypt second message before first.
if got, err := server.Decrypt(nil, buf2); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want unexpected counter error:\n",
" Original plaintext:", []byte(plaintext2), "\n",
" Ciphertext:", buf2, "\n",
" Decrypted plaintext:", got)
}
// Decryption fails: wrong counter space.
if got, err := client.Decrypt(nil, buf); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want counter space error:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", buf, "\n",
" Decrypted plaintext:", got)
}
// Decrypt first message.
ciphertext := append([]byte(nil), buf...)
buf, err = server.Decrypt(buf[:0], buf)
if err != nil || string(buf) != plaintext {
t.Fatal("Decrypting client-side ciphertext with a server-side context did not produce original content:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", ciphertext, "\n",
" Decryption error:", err, "\n",
" Decrypted plaintext:", buf)
}
// Decryption fails: replay attack.
if got, err := server.Decrypt(nil, buf); err == nil {
t.Error("Decrypting client-side ciphertext with a client-side context unexpectedly succeeded; want unexpected counter error:\n",
" Original plaintext:", []byte(plaintext), "\n",
" Ciphertext:", buf, "\n",
" Decrypted plaintext:", got)
}
}
// Test encrypt and decrypt on roundtrip messages for aes128gcmRekey.
func (s) TestAES128GCMRekeyEncryptRoundtrip(t *testing.T) {
// Test for aes128gcmRekey.
key := make([]byte, 44)
client, server := getRekeyCryptoPair(key, nil, t)
testRekeyEncryptRoundtrip(client, server, t)
}

View File

@@ -0,0 +1,80 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"encoding/binary"
"errors"
"fmt"
)
const (
// GcmTagSize is the GCM tag size is the difference in length between
// plaintext and ciphertext. From crypto/cipher/gcm.go in Go crypto
// library.
GcmTagSize = 16
)
// ErrAuth occurs on authentication failure.
var ErrAuth = errors.New("message authentication failed")
// SliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func SliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return head, tail
}
// ParseFramedMsg parse the provided buffer and returns a frame of the format
// msgLength+msg and any remaining bytes in that buffer.
func ParseFramedMsg(b []byte, maxLen uint32) ([]byte, []byte, error) {
// If the size field is not complete, return the provided buffer as
// remaining buffer.
length, sufficientBytes := parseMessageLength(b)
if !sufficientBytes {
return nil, b, nil
}
if length > maxLen {
return nil, nil, fmt.Errorf("received the frame length %d larger than the limit %d", length, maxLen)
}
if len(b) < int(length)+4 { // account for the first 4 msg length bytes.
// Frame is not complete yet.
return nil, b, nil
}
return b[:MsgLenFieldSize+length], b[MsgLenFieldSize+length:], nil
}
// parseMessageLength returns the message length based on frame header. It also
// returns a boolean indicating if the buffer contains sufficient bytes to parse
// the length header. If there are insufficient bytes, (0, false) is returned.
func parseMessageLength(b []byte) (uint32, bool) {
if len(b) < MsgLenFieldSize {
return 0, false
}
msgLenField := b[:MsgLenFieldSize]
return binary.LittleEndian.Uint32(msgLenField), true
}

View File

@@ -0,0 +1,62 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"errors"
)
const counterLen = 12
var (
errInvalidCounter = errors.New("invalid counter")
)
// Counter is a 96-bit, little-endian counter.
type Counter struct {
value [counterLen]byte
invalid bool
overflowLen int
}
// Value returns the current value of the counter as a byte slice.
func (c *Counter) Value() ([]byte, error) {
if c.invalid {
return nil, errInvalidCounter
}
return c.value[:], nil
}
// Inc increments the counter and checks for overflow.
func (c *Counter) Inc() {
// If the counter is already invalid, there is no need to increase it.
if c.invalid {
return
}
i := 0
for ; i < c.overflowLen; i++ {
c.value[i]++
if c.value[i] != 0 {
break
}
}
if i == c.overflowLen {
c.invalid = true
}
}

View File

@@ -0,0 +1,141 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"testing"
core "google.golang.org/grpc/credentials/alts/internal"
)
const (
testOverflowLen = 5
)
func (s) TestCounterSides(t *testing.T) {
for _, side := range []core.Side{core.ClientSide, core.ServerSide} {
outCounter := NewOutCounter(side, testOverflowLen)
inCounter := NewInCounter(side, testOverflowLen)
for i := 0; i < 1024; i++ {
value, _ := outCounter.Value()
if g, w := CounterSide(value), side; g != w {
t.Errorf("after %d iterations, CounterSide(outCounter.Value()) = %v, want %v", i, g, w)
break
}
value, _ = inCounter.Value()
if g, w := CounterSide(value), side; g == w {
t.Errorf("after %d iterations, CounterSide(inCounter.Value()) = %v, want %v", i, g, w)
break
}
outCounter.Inc()
inCounter.Inc()
}
}
}
func (s) TestCounterInc(t *testing.T) {
for _, test := range []struct {
counter []byte
want []byte
}{
{
counter: []byte{0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
want: []byte{0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
{
counter: []byte{0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80},
want: []byte{0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80},
},
{
counter: []byte{0xff, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
want: []byte{0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
{
counter: []byte{0x42, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
want: []byte{0x43, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
{
counter: []byte{0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
want: []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
counter: []byte{0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
want: []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
},
} {
c := CounterFromValue(test.counter, overflowLenAES128GCM)
c.Inc()
value, _ := c.Value()
if g, w := value, test.want; !bytes.Equal(g, w) || c.invalid {
t.Errorf("counter(%v).Inc() =\n%v, want\n%v", test.counter, g, w)
}
}
}
func (s) TestRolloverCounter(t *testing.T) {
for _, test := range []struct {
desc string
value []byte
overflowLen int
}{
{
desc: "testing overflow without rekeying 1",
value: []byte{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
overflowLen: 5,
},
{
desc: "testing overflow without rekeying 2",
value: []byte{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
overflowLen: 5,
},
{
desc: "testing overflow for rekeying mode 1",
value: []byte{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x80},
overflowLen: 8,
},
{
desc: "testing overflow for rekeying mode 2",
value: []byte{0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00},
overflowLen: 8,
},
} {
c := CounterFromValue(test.value, overflowLenAES128GCM)
// First Inc() + Value() should work.
c.Inc()
_, err := c.Value()
if err != nil {
t.Errorf("%v: first Inc() + Value() unexpectedly failed: %v, want <nil> error", test.desc, err)
}
// Second Inc() + Value() should fail.
c.Inc()
_, err = c.Value()
if err != errInvalidCounter {
t.Errorf("%v: second Inc() + Value() unexpectedly succeeded: want %v", test.desc, errInvalidCounter)
}
// Third Inc() + Value() should also fail because the counter is
// already in an invalid state.
c.Inc()
_, err = c.Value()
if err != errInvalidCounter {
t.Errorf("%v: Third Inc() + Value() unexpectedly succeeded: want %v", test.desc, errInvalidCounter)
}
}
}

View File

@@ -0,0 +1,380 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package conn contains an implementation of a secure channel created by gRPC
// handshakers.
package conn
import (
"encoding/binary"
"fmt"
"math"
"net"
core "google.golang.org/grpc/credentials/alts/internal"
imem "google.golang.org/grpc/internal/mem"
"google.golang.org/grpc/internal/transport/readyreader"
"google.golang.org/grpc/mem"
)
// ALTSRecordCrypto is the interface for gRPC ALTS record protocol.
type ALTSRecordCrypto interface {
// Encrypt encrypts the plaintext, computes the tag (if any) of dst and
// plaintext, and appends the result to dst, returning the updated slice.
// dst and plaintext may fully overlap or not at all.
Encrypt(dst, plaintext []byte) ([]byte, error)
// EncryptionOverhead returns the tag size (if any) in bytes.
EncryptionOverhead() int
// Decrypt decrypts ciphertext and verifies the tag (if any). If successful,
// this function appends the resulting plaintext to dst, returning the
// updated slice. dst and ciphertext may alias exactly or not at all. To
// reuse ciphertext's storage for the decrypted output, use ciphertext[:0]
// as dst. Even if the function fails, the contents of dst, up to its
// capacity, may be overwritten.
Decrypt(dst, ciphertext []byte) ([]byte, error)
}
// ALTSRecordFunc is a function type for factory functions that create
// ALTSRecordCrypto instances.
type ALTSRecordFunc func(s core.Side, keyData []byte) (ALTSRecordCrypto, error)
const (
// MsgLenFieldSize is the byte size of the frame length field of a
// framed message.
MsgLenFieldSize = 4
// The byte size of the message type field of a framed message.
msgTypeFieldSize = 4
// The bytes size limit for a ALTS record message.
altsRecordLengthLimit = 1024 * 1024 // 1 MiB
// The default bytes size of a ALTS record message.
altsRecordDefaultLength = 4 * 1024 // 4KiB
// Message type value included in ALTS record framing.
altsRecordMsgType = uint32(0x06)
// The maximum write buffer size, which can hold multiple ALTS frames.
altsWriteBufferMaxSize = 512 * 1024 // 512KiB
// The initial buffer used to read from the network.
// It includes an additional 512 Bytes to hold two 16KiB records plus
// small framing overheads.
altsReadBufferInitialSize = 32*1024 + 512 // 32.5KiB
)
var (
protocols = make(map[string]ALTSRecordFunc)
writeBufPool *imem.BinaryTieredBufferPool
// readBufPool pools buffers of at least `altsReadBufferInitialSize` size.
// Since the read buffer size is slightly larger than 32KB, using a regular
// BinaryTieredBufferPool results in allocating buffers of almost double the
// required length.
readBufPool = imem.NewDirtySimplePool()
// Compile-time check to ensure conn implements ReadyReader.
_ readyreader.Reader = &conn{}
)
func init() {
pool, err := imem.NewDirtyBinaryTieredBufferPool(
8,
12, // Go page size, 4KB
14, // 16KB (max HTTP/2 frame size used by gRPC)
15, // 32KB (default buffer size for gRPC)
16, // 64KB
17, // 128KB
19, // 512KB, max write buffer size
)
if err != nil {
panic(fmt.Sprintf("Failed to create write buffer pool: %v", err))
}
writeBufPool = pool
}
// RegisterProtocol register a ALTS record encryption protocol.
func RegisterProtocol(protocol string, f ALTSRecordFunc) error {
if _, ok := protocols[protocol]; ok {
return fmt.Errorf("protocol %v is already registered", protocol)
}
protocols[protocol] = f
return nil
}
// conn represents a secured connection. It implements the net.Conn interface.
type conn struct {
net.Conn
reader readyreader.Reader
crypto ALTSRecordCrypto
// buf holds data that has been read from the connection and decrypted,
// but has not yet been returned by Read. It is a sub-slice of protected.
buf []byte
payloadLengthLimit int
// protectedHandle buffer holds data read from the network but have not yet
// been decrypted. This data might not compose a complete frame.
//
// The buffer pointer to points to a buffer from the readBufPool. The handle
// should only be returned to the pool once nextFrame and buf are empty.
protectedHandle *[]byte
// nextFrame stores the next frame (in protected buffer) info.
nextFrame []byte
// overhead is the calculated overhead of each frame.
overhead int
constPool constBufferPool // stored as a field to avoid heap allocations.
}
// NewConn creates a new secure channel instance given the other party role and
// handshaking result.
func NewConn(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte) (net.Conn, error) {
return NewConnWithMaxFrameSize(c, side, recordProtocol, key, protected, 0)
}
// NewConnWithMaxFrameSize creates a new secure channel instance given the
// other party role, handshaking result, and negotiated maximum frame size.
func NewConnWithMaxFrameSize(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte, negotiatedMaxFrameSize int) (net.Conn, error) {
newCrypto := protocols[recordProtocol]
if newCrypto == nil {
return nil, fmt.Errorf("negotiated unknown next_protocol %q", recordProtocol)
}
crypto, err := newCrypto(side, key)
if err != nil {
return nil, fmt.Errorf("protocol %q: %v", recordProtocol, err)
}
overhead := MsgLenFieldSize + msgTypeFieldSize + crypto.EncryptionOverhead()
// Clamp maxRecordLen to be at least altsRecordDefaultLength.
maxRecordLen := max(altsRecordDefaultLength, negotiatedMaxFrameSize)
payloadLengthLimit := maxRecordLen - overhead
// We pre-allocate protected to be of size 32KB during initialization.
// We increase the size of the buffer by the required amount if it can't
// hold a complete encrypted record.
protectedHandle := readBufPool.Get(max(altsReadBufferInitialSize, len(protected)))
protectedBuf := *protectedHandle
// Copy additional data from hanshaker service.
copy(protectedBuf, protected)
protectedBuf = protectedBuf[:len(protected)]
altsConn := &conn{
Conn: c,
reader: readyreader.New(c),
crypto: crypto,
payloadLengthLimit: payloadLengthLimit,
protectedHandle: protectedHandle,
nextFrame: protectedBuf,
overhead: overhead,
}
return altsConn, nil
}
type constBufferPool struct {
buffer []byte
}
func (p *constBufferPool) Get(int) *[]byte {
return &p.buffer
}
func (p *constBufferPool) Put(*[]byte) {}
// Read reads and decrypts a frame from the underlying connection, and copies the
// decrypted payload into b. If the size of the payload is greater than len(b),
// Read retains the remaining bytes in an internal buffer, and subsequent calls
// to Read will read from this buffer until it is exhausted.
func (p *conn) Read(b []byte) (n int, err error) {
p.constPool.buffer = b
_, n, err = p.ReadOnReady(len(b), &p.constPool)
return n, err
}
func (p *conn) ReadOnReady(bufSize int, pool mem.BufferPool) (*[]byte, int, error) {
if len(p.buf) == 0 {
var framedMsg []byte
var protected []byte
if p.protectedHandle != nil {
protected = *p.protectedHandle
protected = protected[:cap(protected)]
}
var err error
framedMsg, p.nextFrame, err = ParseFramedMsg(p.nextFrame, altsRecordLengthLimit)
if err != nil {
return nil, 0, err
}
// Check whether the next frame to be decrypted has been
// completely received yet.
if len(framedMsg) == 0 {
copy(protected, p.nextFrame)
protected = protected[:len(p.nextFrame)]
// Always copy next incomplete frame to the beginning of
// the protected buffer and reset nextFrame to it.
p.nextFrame = protected
}
// Check whether a complete frame has been received yet.
for len(framedMsg) == 0 {
if p.protectedHandle != nil && len(protected) == cap(protected) {
// We can parse the length header to know exactly how large
// the buffer needs to be to hold the entire frame.
length, didParse := parseMessageLength(protected)
if !didParse {
// The protected buffer is initialized with a capacity of
// larger than 4B. It should always hold the message length
// header.
panic(fmt.Sprintf("protected buffer length shorter than expected: %d vs %d", len(protected), MsgLenFieldSize))
}
oldProtectedBuf := protected
oldBufHandle := p.protectedHandle
// The new buffer must be able to hold the message length header
// and the entire message.
requiredCapacity := int(length) + MsgLenFieldSize
p.protectedHandle = readBufPool.Get(requiredCapacity)
protected = *p.protectedHandle
// Copy the contents of the old buffer and set the length of the
// new buffer to the number of bytes already read.
copy(protected, oldProtectedBuf)
protected = protected[:len(oldProtectedBuf)]
readBufPool.Put(oldBufHandle)
}
if p.protectedHandle == nil {
// Connection was idle, need to re-allocate the read buffer.
newBuf, nRead, err := p.reader.ReadOnReady(altsReadBufferInitialSize, readBufPool)
if err != nil {
return nil, 0, err
}
p.protectedHandle = newBuf
protected = (*newBuf)[:nRead]
} else {
nRead, err := p.Conn.Read(protected[len(protected):cap(protected)])
if err != nil {
return nil, 0, err
}
protected = protected[:len(protected)+nRead]
}
framedMsg, p.nextFrame, err = ParseFramedMsg(protected, altsRecordLengthLimit)
if err != nil {
return nil, 0, err
}
}
// Now we have a complete frame, decrypted it.
msg := framedMsg[MsgLenFieldSize:]
if len(msg) < msgTypeFieldSize {
return nil, 0, fmt.Errorf("received frame with size %v which is shorter than message type field size %v", len(msg), msgTypeFieldSize)
}
msgType := binary.LittleEndian.Uint32(msg[:msgTypeFieldSize])
if msgType&0xff != altsRecordMsgType {
return nil, 0, fmt.Errorf("received frame with incorrect message type %v, expected lower byte %v",
msgType, altsRecordMsgType)
}
ciphertext := msg[msgTypeFieldSize:]
// Decrypt directly into the buffer, avoiding a copy from p.buf if
// possible.
if bufSize >= len(ciphertext) {
allocatedBuf := pool.Get(bufSize)
dec, err := p.crypto.Decrypt((*allocatedBuf)[:0], ciphertext)
if err != nil {
pool.Put(allocatedBuf)
return nil, 0, err
}
p.dropProtectedIfEmtpy()
return allocatedBuf, len(dec), nil
}
// Decrypt requires that if the dst and ciphertext alias, they
// must alias exactly. Code here used to use msg[:0], but msg
// starts MsgLenFieldSize+msgTypeFieldSize bytes earlier than
// ciphertext, so they alias inexactly. Using ciphertext[:0]
// arranges the appropriate aliasing without needing to copy
// ciphertext or use a separate destination buffer. For more info
// check: https://golang.org/pkg/crypto/cipher/#AEAD.
p.buf, err = p.crypto.Decrypt(ciphertext[:0], ciphertext)
if err != nil {
return nil, 0, err
}
}
allocatedBuf := pool.Get(bufSize)
n := copy(*allocatedBuf, p.buf)
p.buf = p.buf[n:]
p.dropProtectedIfEmtpy()
return allocatedBuf, n, nil
}
func (p *conn) dropProtectedIfEmtpy() {
if len(p.buf) > 0 || len(p.nextFrame) > 0 {
return
}
// Potentially idle connection, release the read buffer.
p.nextFrame = nil
p.buf = nil
if p.protectedHandle != nil {
readBufPool.Put(p.protectedHandle)
p.protectedHandle = nil
}
}
// Write encrypts, frames, and writes bytes from b to the underlying connection.
func (p *conn) Write(b []byte) (n int, err error) {
n = len(b)
// Calculate the output buffer size with framing and encryption overhead.
numOfFrames := int(math.Ceil(float64(len(b)) / float64(p.payloadLengthLimit)))
size := len(b) + numOfFrames*p.overhead
partialBSize := len(b)
if size > altsWriteBufferMaxSize {
size = altsWriteBufferMaxSize
frameLen := p.payloadLengthLimit + p.overhead
numOfFramesInMaxWriteBuf := altsWriteBufferMaxSize / frameLen
partialBSize = numOfFramesInMaxWriteBuf * p.payloadLengthLimit
}
// Get a writeBuf of the required length.
bufHandle := writeBufPool.Get(size)
defer writeBufPool.Put(bufHandle)
writeBuf := *bufHandle
for partialBStart := 0; partialBStart < len(b); partialBStart += partialBSize {
partialBEnd := min(partialBStart+partialBSize, len(b))
partialB := b[partialBStart:partialBEnd]
writeBufIndex := 0
for len(partialB) > 0 {
payloadLen := min(len(partialB), p.payloadLengthLimit)
buf := partialB[:payloadLen]
partialB = partialB[payloadLen:]
// Write buffer contains: length, type, payload, and tag
// if any.
// 1. Fill in type field.
msg := writeBuf[writeBufIndex+MsgLenFieldSize:]
binary.LittleEndian.PutUint32(msg, altsRecordMsgType)
// 2. Encrypt the payload and create a tag if any.
msg, err = p.crypto.Encrypt(msg[:msgTypeFieldSize], buf)
if err != nil {
return n, err
}
// 3. Fill in the size field.
binary.LittleEndian.PutUint32(writeBuf[writeBufIndex:], uint32(len(msg)))
// 4. Increase writeBufIndex.
writeBufIndex += len(buf) + p.overhead
}
nn, err := p.Conn.Write(writeBuf[:writeBufIndex])
if err != nil {
// We need to calculate the actual data size that was
// written. This means we need to remove header,
// encryption overheads, and any partially-written
// frame data.
numOfWrittenFrames := int(math.Floor(float64(nn) / float64(p.payloadLengthLimit+p.overhead)))
return partialBStart + numOfWrittenFrames*p.payloadLengthLimit, err
}
}
return n, nil
}

View File

@@ -0,0 +1,563 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
"net"
"reflect"
"strings"
"testing"
core "google.golang.org/grpc/credentials/alts/internal"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
const (
rekeyRecordProtocol = "ALTSRP_GCM_AES128_REKEY"
)
var (
recordProtocols = []string{rekeyRecordProtocol}
altsRecordFuncs = map[string]ALTSRecordFunc{
// ALTS handshaker protocols.
rekeyRecordProtocol: func(s core.Side, keyData []byte) (ALTSRecordCrypto, error) {
return NewAES128GCM(s, keyData)
},
}
)
func init() {
for protocol, f := range altsRecordFuncs {
if err := RegisterProtocol(protocol, f); err != nil {
panic(err)
}
}
}
// testConn mimics a net.Conn to the peer.
type testConn struct {
net.Conn
in *bytes.Buffer
out *bytes.Buffer
}
func (c *testConn) Read(b []byte) (n int, err error) {
return c.in.Read(b)
}
func (c *testConn) Write(b []byte) (n int, err error) {
return c.out.Write(b)
}
func (c *testConn) Close() error {
return nil
}
func newTestALTSRecordConn(in, out *bytes.Buffer, side core.Side, rp string, protected []byte) *conn {
key := []byte{
// 16 arbitrary bytes.
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
tc := testConn{
in: in,
out: out,
}
c, err := NewConn(&tc, side, rp, key, protected)
if err != nil {
panic(fmt.Sprintf("Unexpected error creating test ALTS record connection: %v", err))
}
return c.(*conn)
}
func newConnPair(rp string, clientProtected []byte, serverProtected []byte) (client, server *conn) {
clientBuf := new(bytes.Buffer)
serverBuf := new(bytes.Buffer)
clientConn := newTestALTSRecordConn(clientBuf, serverBuf, core.ClientSide, rp, clientProtected)
serverConn := newTestALTSRecordConn(serverBuf, clientBuf, core.ServerSide, rp, serverProtected)
return clientConn, serverConn
}
func testPingPong(t *testing.T, rp string) {
clientConn, serverConn := newConnPair(rp, nil, nil)
clientMsg := []byte("Client Message")
if n, err := clientConn.Write(clientMsg); n != len(clientMsg) || err != nil {
t.Fatalf("Client Write() = %v, %v; want %v, <nil>", n, err, len(clientMsg))
}
rcvClientMsg := make([]byte, len(clientMsg))
if n, err := serverConn.Read(rcvClientMsg); n != len(rcvClientMsg) || err != nil {
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(rcvClientMsg))
}
if !reflect.DeepEqual(clientMsg, rcvClientMsg) {
t.Fatalf("Client Write()/Server Read() = %v, want %v", rcvClientMsg, clientMsg)
}
serverMsg := []byte("Server Message")
if n, err := serverConn.Write(serverMsg); n != len(serverMsg) || err != nil {
t.Fatalf("Server Write() = %v, %v; want %v, <nil>", n, err, len(serverMsg))
}
rcvServerMsg := make([]byte, len(serverMsg))
if n, err := clientConn.Read(rcvServerMsg); n != len(rcvServerMsg) || err != nil {
t.Fatalf("Client Read() = %v, %v; want %v, <nil>", n, err, len(rcvServerMsg))
}
if !reflect.DeepEqual(serverMsg, rcvServerMsg) {
t.Fatalf("Server Write()/Client Read() = %v, want %v", rcvServerMsg, serverMsg)
}
}
func (s) TestPingPong(t *testing.T) {
for _, rp := range recordProtocols {
testPingPong(t, rp)
}
}
func testSmallReadBuffer(t *testing.T, rp string) {
clientConn, serverConn := newConnPair(rp, nil, nil)
msg := []byte("Very Important Message")
if n, err := clientConn.Write(msg); err != nil {
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
}
rcvMsg := make([]byte, len(msg))
n := 2 // Arbitrary index to break rcvMsg in two.
rcvMsg1 := rcvMsg[:n]
rcvMsg2 := rcvMsg[n:]
if n, err := serverConn.Read(rcvMsg1); n != len(rcvMsg1) || err != nil {
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg1))
}
if n, err := serverConn.Read(rcvMsg2); n != len(rcvMsg2) || err != nil {
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg2))
}
if !reflect.DeepEqual(msg, rcvMsg) {
t.Fatalf("Write()/Read() = %v, want %v", rcvMsg, msg)
}
}
func (s) TestSmallReadBuffer(t *testing.T) {
for _, rp := range recordProtocols {
testSmallReadBuffer(t, rp)
}
}
func testLargeMsg(t *testing.T, rp string) {
clientConn, serverConn := newConnPair(rp, nil, nil)
// msgLen is such that the length in the framing is larger than the
// default size of one frame.
msgLen := altsRecordDefaultLength - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1
msg := make([]byte, msgLen)
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
}
rcvMsg := make([]byte, len(msg))
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
}
if !reflect.DeepEqual(msg, rcvMsg) {
t.Fatalf("Write()/Server Read() = %v, want %v", rcvMsg, msg)
}
}
func (s) TestLargeMsg(t *testing.T) {
for _, rp := range recordProtocols {
testLargeMsg(t, rp)
}
}
// TestLargeRecord writes a very large ALTS record and verifies that the server
// receives it correctly. The large ALTS record should cause the reader to
// expand it's read buffer to hold the entire record and store the decrypted
// message until the receiver reads all of the bytes.
func (s) TestLargeRecord(t *testing.T) {
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
msg := []byte(strings.Repeat("a", 2*altsReadBufferInitialSize))
// Increase the size of ALTS records written by the client.
clientConn.payloadLengthLimit = math.MaxInt32
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
}
rcvMsg := make([]byte, len(msg))
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
}
if !reflect.DeepEqual(msg, rcvMsg) {
t.Fatalf("Write()/Server Read() = %v, want %v", rcvMsg, msg)
}
}
// BenchmarkLargeMessage measures the performance of ALTS conns for sending and
// receiving a large message.
func BenchmarkLargeMessage(b *testing.B) {
msgLen := 20 * 1024 * 1024 // 20 MiB
msg := make([]byte, msgLen)
rcvMsg := make([]byte, len(msg))
b.ResetTimer()
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
for range b.N {
// Write 20 MiB 5 times to transfer a total of 100 MiB.
for range 5 {
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
b.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
}
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
b.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
}
}
}
}
func testIncorrectMsgType(t *testing.T, rp string) {
// framedMsg is an empty ciphertext with correct framing but wrong
// message type.
framedMsg := make([]byte, MsgLenFieldSize+msgTypeFieldSize)
binary.LittleEndian.PutUint32(framedMsg[:MsgLenFieldSize], msgTypeFieldSize)
wrongMsgType := uint32(0x22)
binary.LittleEndian.PutUint32(framedMsg[MsgLenFieldSize:], wrongMsgType)
in := bytes.NewBuffer(framedMsg)
c := newTestALTSRecordConn(in, nil, core.ClientSide, rp, nil)
b := make([]byte, 1)
if n, err := c.Read(b); n != 0 || err == nil {
t.Fatalf("Read() = <nil>, want %v", fmt.Errorf("received frame with incorrect message type %v", wrongMsgType))
}
}
func (s) TestIncorrectMsgType(t *testing.T) {
for _, rp := range recordProtocols {
testIncorrectMsgType(t, rp)
}
}
func testFrameTooLarge(t *testing.T, rp string) {
buf := new(bytes.Buffer)
clientConn := newTestALTSRecordConn(nil, buf, core.ClientSide, rp, nil)
serverConn := newTestALTSRecordConn(buf, nil, core.ServerSide, rp, nil)
// payloadLen is such that the length in the framing is larger than
// allowed in one frame.
payloadLen := altsRecordLengthLimit - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1
payload := make([]byte, payloadLen)
c, err := clientConn.crypto.Encrypt(nil, payload)
if err != nil {
t.Fatalf("Error encrypting message: %v", err)
}
msgLen := msgTypeFieldSize + len(c)
framedMsg := make([]byte, MsgLenFieldSize+msgLen)
binary.LittleEndian.PutUint32(framedMsg[:MsgLenFieldSize], uint32(msgTypeFieldSize+len(c)))
msg := framedMsg[MsgLenFieldSize:]
binary.LittleEndian.PutUint32(msg[:msgTypeFieldSize], altsRecordMsgType)
copy(msg[msgTypeFieldSize:], c)
if _, err = buf.Write(framedMsg); err != nil {
t.Fatalf("Unexpected error writing to buffer: %v", err)
}
b := make([]byte, 1)
if n, err := serverConn.Read(b); n != 0 || err == nil {
t.Fatalf("Read() = <nil>, want %v", fmt.Errorf("received the frame length %d larger than the limit %d", altsRecordLengthLimit+1, altsRecordLengthLimit))
}
}
func (s) TestFrameTooLarge(t *testing.T) {
for _, rp := range recordProtocols {
testFrameTooLarge(t, rp)
}
}
func testWriteLargeData(t *testing.T, rp string) {
// Test sending and receiving messages larger than the maximum write
// buffer size.
clientConn, serverConn := newConnPair(rp, nil, nil)
// Message size is intentionally chosen to not be multiple of
// payloadLengthLimit.
msgSize := altsWriteBufferMaxSize + (100 * 1024)
clientMsg := make([]byte, msgSize)
for i := 0; i < msgSize; i++ {
clientMsg[i] = 0xAA
}
if n, err := clientConn.Write(clientMsg); n != len(clientMsg) || err != nil {
t.Fatalf("Client Write() = %v, %v; want %v, <nil>", n, err, len(clientMsg))
}
// We need to keep reading until the entire message is received. The
// reason we set all bytes of the message to a value other than zero is
// to avoid ambiguous zero-init value of rcvClientMsg buffer and the
// actual received data.
rcvClientMsg := make([]byte, 0, msgSize)
numberOfExpectedFrames := int(math.Ceil(float64(msgSize) / float64(serverConn.payloadLengthLimit)))
for i := 0; i < numberOfExpectedFrames; i++ {
expectedRcvSize := serverConn.payloadLengthLimit
if i == numberOfExpectedFrames-1 {
// Last frame might be smaller.
expectedRcvSize = msgSize % serverConn.payloadLengthLimit
}
tmpBuf := make([]byte, expectedRcvSize)
if n, err := serverConn.Read(tmpBuf); n != len(tmpBuf) || err != nil {
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(tmpBuf))
}
rcvClientMsg = append(rcvClientMsg, tmpBuf...)
}
if !reflect.DeepEqual(clientMsg, rcvClientMsg) {
t.Fatalf("Client Write()/Server Read() = %v, want %v", rcvClientMsg, clientMsg)
}
}
func (s) TestWriteLargeData(t *testing.T) {
for _, rp := range recordProtocols {
testWriteLargeData(t, rp)
}
}
func testProtectedBuffer(t *testing.T, rp string) {
key := []byte{
// 16 arbitrary bytes.
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
// Encrypt a message to be passed to NewConn as a client-side protected
// buffer.
newCrypto := protocols[rp]
if newCrypto == nil {
t.Fatalf("Unknown record protocol %q", rp)
}
crypto, err := newCrypto(core.ClientSide, key)
if err != nil {
t.Fatalf("Failed to create a crypter for protocol %q: %v", rp, err)
}
msg := []byte("Client Protected Message")
encryptedMsg, err := crypto.Encrypt(nil, msg)
if err != nil {
t.Fatalf("Failed to encrypt the client protected message: %v", err)
}
protectedMsg := make([]byte, 8) // 8 bytes = 4 length + 4 type
binary.LittleEndian.PutUint32(protectedMsg, uint32(len(encryptedMsg))+4) // 4 bytes for the type
binary.LittleEndian.PutUint32(protectedMsg[4:], altsRecordMsgType)
protectedMsg = append(protectedMsg, encryptedMsg...)
_, serverConn := newConnPair(rp, nil, protectedMsg)
rcvClientMsg := make([]byte, len(msg))
if n, err := serverConn.Read(rcvClientMsg); n != len(rcvClientMsg) || err != nil {
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(rcvClientMsg))
}
if !reflect.DeepEqual(msg, rcvClientMsg) {
t.Fatalf("Client protected/Server Read() = %v, want %v", rcvClientMsg, msg)
}
}
func (s) TestProtectedBuffer(t *testing.T) {
for _, rp := range recordProtocols {
testProtectedBuffer(t, rp)
}
}
// BenchmarkWriteMemoryUsage measures the allocations per ALTS connection.
// Run this with: go test -bench=BenchmarkWriteMemoryUsage -benchmem
func BenchmarkWriteMemoryUsage(b *testing.B) {
b.ReportAllocs()
data := []byte(strings.Repeat("d", 256))
key := []byte{
// 16 arbitrary bytes.
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
conn := &noopConn{}
for b.Loop() {
c, err := NewConn(conn, core.ClientSide, rekeyRecordProtocol, key, nil)
if err != nil {
b.Fatal(err)
}
if _, err := c.Write(data); err != nil {
b.Fatalf("Write failed: %v", err)
}
}
}
// BenchmarkReadMemoryUsage measures the allocations per ALTS connection.
// Run this with: go test -bench=BenchmarkReadMemoryUsage -benchmem
func BenchmarkReadMemoryUsage(b *testing.B) {
b.ReportAllocs()
data := []byte(strings.Repeat("d", 257))
readBuf := make([]byte, 1024)
for b.Loop() {
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
b.StopTimer()
clientConn.Write(data)
b.StartTimer()
if _, err := serverConn.Read(readBuf); err != nil {
b.Fatalf("Write failed: %v", err)
}
}
}
type noopConn struct {
net.Conn
}
func (c *noopConn) Write(b []byte) (n int, err error) {
return len(b), nil
}
func (c *noopConn) Close() error {
return nil
}
func (s) TestParseFramedMsgVulnerability(t *testing.T) {
key := []byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
tc := testConn{
in: new(bytes.Buffer),
out: new(bytes.Buffer),
}
// We pass a protected buffer representing a framed message length of 0,
// followed by bytes that will act as a dirty buffer capacity. If the capacity
// bytes happen to start with `0x06` (altsRecordMsgType), the vulnerable
// code will try to slice `msg[4:]` which panics because len(msg) is 0.
malformedProtected := []byte{0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}
c, err := NewConn(&tc, core.ServerSide, rekeyRecordProtocol, key, malformedProtected)
if err != nil {
t.Fatalf("NewConn failed: %v", err)
}
buf := make([]byte, 1024)
const wantErr = "shorter than message type field size"
if _, err := c.Read(buf); err == nil || !strings.Contains(err.Error(), wantErr) {
t.Fatalf("c.Read(buf) returned error: %v, want error containing %q", err, wantErr)
}
}
// countALTSFrames parses raw ALTS wire bytes and returns the number of complete frames.
func countALTSFrames(data []byte) int {
count := 0
for len(data) >= MsgLenFieldSize {
frameLen := int(binary.LittleEndian.Uint32(data[:MsgLenFieldSize]))
total := MsgLenFieldSize + frameLen
if total > len(data) {
break
}
data = data[total:]
count++
}
return count
}
func (s) TestNewConnNegotiatedMaxFrameSize(t *testing.T) {
key := make([]byte, 32)
for _, tc := range []struct {
name string
negotiatedMaxFrameSize int
wantMaxRecordLen int
}{
{"NotNegotiated", 0, altsRecordDefaultLength},
{"SmallerThanDefault_10B", 10, altsRecordDefaultLength},
{"SmallerThanDefault_1024B", 1024, altsRecordDefaultLength},
{"Negotiated_4KB", 4 * 1024, 4 * 1024},
{"Negotiated_16KB", 16 * 1024, 16 * 1024},
{"Negotiated_64KB", 64 * 1024, 64 * 1024},
} {
t.Run(tc.name, func(t *testing.T) {
tcConn := testConn{}
c, err := NewConnWithMaxFrameSize(&tcConn, core.ClientSide, rekeyRecordProtocol, key, nil, tc.negotiatedMaxFrameSize)
if err != nil {
t.Fatalf("NewConn() err = %v, want nil", err)
}
altsC := c.(*conn)
wantPayloadLimit := tc.wantMaxRecordLen - altsC.overhead
if altsC.payloadLengthLimit != wantPayloadLimit {
t.Errorf("payloadLengthLimit = %v, want %v", altsC.payloadLengthLimit, wantPayloadLimit)
}
})
}
}
// TestWriteHonorsNegotiatedMaxFrameSize verifies that Write uses the negotiated
// max_frame_size so we never send a frame larger than negotiated.
func (s) TestWriteHonorsNegotiatedMaxFrameSize(t *testing.T) {
key := make([]byte, 16)
const negotiatedMaxFrameSize = 16 * 1024
out := new(bytes.Buffer)
c, err := NewConnWithMaxFrameSize(&testConn{in: new(bytes.Buffer), out: out}, core.ClientSide, rekeyRecordProtocol, key, nil, negotiatedMaxFrameSize)
if err != nil {
t.Fatalf("NewConn failed: %v", err)
}
altsC := c.(*conn)
if got, want := altsC.payloadLengthLimit+altsC.overhead, negotiatedMaxFrameSize; got != want {
t.Fatalf("frame size = %v, want %v", got, want)
}
// Two negotiatedMaxFrameSize-worth of plaintext should split into exactly two frames.
payload := make([]byte, 2*altsC.payloadLengthLimit)
if _, err := c.Write(payload); err != nil {
t.Fatalf("Write failed: %v", err)
}
if got, want := countALTSFrames(out.Bytes()), 2; got != want {
t.Errorf("Write produced %d frames, want %d", got, want)
}
}
type partialWriteTestConn struct {
net.Conn
maxWriteBytes int
err error
out bytes.Buffer
}
func (c *partialWriteTestConn) Write(b []byte) (int, error) {
if len(b) > c.maxWriteBytes {
c.out.Write(b[:c.maxWriteBytes])
return c.maxWriteBytes, c.err
}
c.out.Write(b)
return len(b), nil
}
// TestWritePartialWriteErrorWithNegotiatedMaxFrameSize verifies that when a partial Write error occurs,
// the number of written bytes returned correctly accounts for the negotiated max_frame_size.
func (s) TestWritePartialWriteErrorWithNegotiatedMaxFrameSize(t *testing.T) {
key := make([]byte, 16)
const negotiatedMaxFrameSize = 16 * 1024
// Allow writing 1.5 frames (16384 + 8192 = 24576 bytes) before returning io.ErrUnexpectedEOF.
writeLimit := negotiatedMaxFrameSize + negotiatedMaxFrameSize/2
errConn := &partialWriteTestConn{
maxWriteBytes: writeLimit,
err: io.ErrUnexpectedEOF,
}
c, err := NewConnWithMaxFrameSize(errConn, core.ClientSide, rekeyRecordProtocol, key, nil, negotiatedMaxFrameSize)
if err != nil {
t.Fatalf("NewConn failed: %v", err)
}
altsC := c.(*conn)
// Send 3 frames worth of payload
payload := make([]byte, 3*altsC.payloadLengthLimit)
n, writeErr := c.Write(payload)
if writeErr != io.ErrUnexpectedEOF {
t.Fatalf("Write err = %v, want %v", writeErr, io.ErrUnexpectedEOF)
}
// Exactly 1 complete frame was written before the partial write error.
wantWrittenBytes := altsC.payloadLengthLimit
if n != wantWrittenBytes {
t.Errorf("Write() returned n = %d, want %d", n, wantWrittenBytes)
}
}

View File

@@ -0,0 +1,63 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package conn
import core "google.golang.org/grpc/credentials/alts/internal"
// NewOutCounter returns an outgoing counter initialized to the starting sequence
// number for the client/server side of a connection.
func NewOutCounter(s core.Side, overflowLen int) (c Counter) {
c.overflowLen = overflowLen
if s == core.ServerSide {
// Server counters in ALTS record have the little-endian high bit
// set.
c.value[counterLen-1] = 0x80
}
return
}
// NewInCounter returns an incoming counter initialized to the starting sequence
// number for the client/server side of a connection. This is used in ALTS record
// to check that incoming counters are as expected, since ALTS record guarantees
// that messages are unwrapped in the same order that the peer wrapped them.
func NewInCounter(s core.Side, overflowLen int) (c Counter) {
c.overflowLen = overflowLen
if s == core.ClientSide {
// Server counters in ALTS record have the little-endian high bit
// set.
c.value[counterLen-1] = 0x80
}
return
}
// CounterFromValue creates a new counter given an initial value.
func CounterFromValue(value []byte, overflowLen int) (c Counter) {
c.overflowLen = overflowLen
copy(c.value[:], value)
return
}
// CounterSide returns the connection side (client/server) a sequence counter is
// associated with.
func CounterSide(c []byte) core.Side {
if c[counterLen-1]&0x80 == 0x80 {
return core.ServerSide
}
return core.ClientSide
}

View File

@@ -0,0 +1,381 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package handshaker provides ALTS handshaking functionality for GCP.
package handshaker
import (
"context"
"errors"
"fmt"
"io"
"net"
"time"
"golang.org/x/sync/semaphore"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
core "google.golang.org/grpc/credentials/alts/internal"
"google.golang.org/grpc/credentials/alts/internal/authinfo"
"google.golang.org/grpc/credentials/alts/internal/conn"
altsgrpc "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
altspb "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
"google.golang.org/grpc/internal/envconfig"
)
const (
// The maximum byte size of receive frames.
frameLimit = 64 * 1024 // 64 KB
rekeyRecordProtocolName = "ALTSRP_GCM_AES128_REKEY"
)
var (
hsProtocol = altspb.HandshakeProtocol_ALTS
appProtocols = []string{"grpc"}
recordProtocols = []string{rekeyRecordProtocolName}
keyLength = map[string]int{
rekeyRecordProtocolName: 44,
}
altsRecordFuncs = map[string]conn.ALTSRecordFunc{
// ALTS handshaker protocols.
rekeyRecordProtocolName: func(s core.Side, keyData []byte) (conn.ALTSRecordCrypto, error) {
return conn.NewAES128GCMRekey(s, keyData)
},
}
// control number of concurrent created (but not closed) handshakes.
clientHandshakes = semaphore.NewWeighted(int64(envconfig.ALTSMaxConcurrentHandshakes))
serverHandshakes = semaphore.NewWeighted(int64(envconfig.ALTSMaxConcurrentHandshakes))
// errOutOfBound occurs when the handshake service returns a consumed
// bytes value larger than the buffer that was passed to it originally.
errOutOfBound = errors.New("handshaker service consumed bytes value is out-of-bound")
)
func init() {
for protocol, f := range altsRecordFuncs {
if err := conn.RegisterProtocol(protocol, f); err != nil {
panic(err)
}
}
}
// ClientHandshakerOptions contains the client handshaker options that can
// provided by the caller.
type ClientHandshakerOptions struct {
// ClientIdentity is the handshaker client local identity.
ClientIdentity *altspb.Identity
// TargetName is the server service account name for secure name
// checking.
TargetName string
// TargetServiceAccounts contains a list of expected target service
// accounts. One of these accounts should match one of the accounts in
// the handshaker results. Otherwise, the handshake fails.
TargetServiceAccounts []string
// RPCVersions specifies the gRPC versions accepted by the client.
RPCVersions *altspb.RpcProtocolVersions
// BoundAccessToken is a bound access token to be sent to the server for authentication.
BoundAccessToken string
}
// ServerHandshakerOptions contains the server handshaker options that can
// provided by the caller.
type ServerHandshakerOptions struct {
// RPCVersions specifies the gRPC versions accepted by the server.
RPCVersions *altspb.RpcProtocolVersions
}
// DefaultClientHandshakerOptions returns the default client handshaker options.
func DefaultClientHandshakerOptions() *ClientHandshakerOptions {
return &ClientHandshakerOptions{}
}
// DefaultServerHandshakerOptions returns the default client handshaker options.
func DefaultServerHandshakerOptions() *ServerHandshakerOptions {
return &ServerHandshakerOptions{}
}
// altsHandshaker is used to complete an ALTS handshake between client and
// server. This handshaker talks to the ALTS handshaker service in the metadata
// server.
type altsHandshaker struct {
// RPC stream used to access the ALTS Handshaker service.
stream altsgrpc.HandshakerService_DoHandshakeClient
// the connection to the peer.
conn net.Conn
// a virtual connection to the ALTS handshaker service.
clientConn *grpc.ClientConn
// client handshake options.
clientOpts *ClientHandshakerOptions
// server handshake options.
serverOpts *ServerHandshakerOptions
// defines the side doing the handshake, client or server.
side core.Side
}
// NewClientHandshaker creates a core.Handshaker that performs a client-side
// ALTS handshake by acting as a proxy between the peer and the ALTS handshaker
// service in the metadata server.
func NewClientHandshaker(_ context.Context, conn *grpc.ClientConn, c net.Conn, opts *ClientHandshakerOptions) (core.Handshaker, error) {
return &altsHandshaker{
stream: nil,
conn: c,
clientConn: conn,
clientOpts: opts,
side: core.ClientSide,
}, nil
}
// NewServerHandshaker creates a core.Handshaker that performs a server-side
// ALTS handshake by acting as a proxy between the peer and the ALTS handshaker
// service in the metadata server.
func NewServerHandshaker(_ context.Context, conn *grpc.ClientConn, c net.Conn, opts *ServerHandshakerOptions) (core.Handshaker, error) {
return &altsHandshaker{
stream: nil,
conn: c,
clientConn: conn,
serverOpts: opts,
side: core.ServerSide,
}, nil
}
// ClientHandshake starts and completes a client ALTS handshake for GCP. Once
// done, ClientHandshake returns a secure connection.
func (h *altsHandshaker) ClientHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error) {
if err := clientHandshakes.Acquire(ctx, 1); err != nil {
return nil, nil, err
}
defer clientHandshakes.Release(1)
if h.side != core.ClientSide {
return nil, nil, errors.New("only handshakers created using NewClientHandshaker can perform a client handshaker")
}
// TODO(matthewstevenson88): Change unit tests to use public APIs so
// that h.stream can unconditionally be set based on h.clientConn.
if h.stream == nil {
stream, err := altsgrpc.NewHandshakerServiceClient(h.clientConn).DoHandshake(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to establish stream to ALTS handshaker service: %v", err)
}
h.stream = stream
}
// Create target identities from service account list.
targetIdentities := make([]*altspb.Identity, 0, len(h.clientOpts.TargetServiceAccounts))
for _, account := range h.clientOpts.TargetServiceAccounts {
targetIdentities = append(targetIdentities, &altspb.Identity{
IdentityOneof: &altspb.Identity_ServiceAccount{
ServiceAccount: account,
},
})
}
req := &altspb.HandshakerReq{
ReqOneof: &altspb.HandshakerReq_ClientStart{
ClientStart: &altspb.StartClientHandshakeReq{
HandshakeSecurityProtocol: hsProtocol,
ApplicationProtocols: appProtocols,
RecordProtocols: recordProtocols,
TargetIdentities: targetIdentities,
LocalIdentity: h.clientOpts.ClientIdentity,
TargetName: h.clientOpts.TargetName,
RpcVersions: h.clientOpts.RPCVersions,
},
},
}
if h.clientOpts.BoundAccessToken != "" {
req.GetClientStart().AccessToken = h.clientOpts.BoundAccessToken
}
conn, result, err := h.doHandshake(req)
if err != nil {
return nil, nil, err
}
authInfo := authinfo.New(result)
return conn, authInfo, nil
}
// ServerHandshake starts and completes a server ALTS handshake for GCP. Once
// done, ServerHandshake returns a secure connection.
func (h *altsHandshaker) ServerHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error) {
if err := serverHandshakes.Acquire(ctx, 1); err != nil {
return nil, nil, err
}
defer serverHandshakes.Release(1)
if h.side != core.ServerSide {
return nil, nil, errors.New("only handshakers created using NewServerHandshaker can perform a server handshaker")
}
// TODO(matthewstevenson88): Change unit tests to use public APIs so
// that h.stream can unconditionally be set based on h.clientConn.
if h.stream == nil {
stream, err := altsgrpc.NewHandshakerServiceClient(h.clientConn).DoHandshake(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to establish stream to ALTS handshaker service: %v", err)
}
h.stream = stream
}
p := make([]byte, frameLimit)
n, err := h.conn.Read(p)
if err != nil {
return nil, nil, err
}
// Prepare server parameters.
params := make(map[int32]*altspb.ServerHandshakeParameters)
params[int32(altspb.HandshakeProtocol_ALTS)] = &altspb.ServerHandshakeParameters{
RecordProtocols: recordProtocols,
}
req := &altspb.HandshakerReq{
ReqOneof: &altspb.HandshakerReq_ServerStart{
ServerStart: &altspb.StartServerHandshakeReq{
ApplicationProtocols: appProtocols,
HandshakeParameters: params,
InBytes: p[:n],
RpcVersions: h.serverOpts.RPCVersions,
},
},
}
conn, result, err := h.doHandshake(req)
if err != nil {
return nil, nil, err
}
authInfo := authinfo.New(result)
return conn, authInfo, nil
}
func (h *altsHandshaker) doHandshake(req *altspb.HandshakerReq) (net.Conn, *altspb.HandshakerResult, error) {
resp, err := h.accessHandshakerService(req)
if err != nil {
return nil, nil, err
}
// Check of the returned status is an error.
if resp.GetStatus() != nil {
if got, want := resp.GetStatus().Code, uint32(codes.OK); got != want {
return nil, nil, fmt.Errorf("%v", resp.GetStatus().Details)
}
}
var extra []byte
if req.GetServerStart() != nil {
if resp.GetBytesConsumed() > uint32(len(req.GetServerStart().GetInBytes())) {
return nil, nil, errOutOfBound
}
extra = req.GetServerStart().GetInBytes()[resp.GetBytesConsumed():]
}
result, extra, err := h.processUntilDone(resp, extra)
if err != nil {
return nil, nil, err
}
// The handshaker returns a 128 bytes key. It should be truncated based
// on the returned record protocol.
keyLen, ok := keyLength[result.RecordProtocol]
if !ok {
return nil, nil, fmt.Errorf("unknown resulted record protocol %v", result.RecordProtocol)
}
maxFrameSize := int(envconfig.ALTSMaxFrameSize)
if peerMax := int(result.GetMaxFrameSize()); peerMax > 0 {
maxFrameSize = min(peerMax, maxFrameSize)
}
sc, err := conn.NewConnWithMaxFrameSize(h.conn, h.side, result.GetRecordProtocol(), result.KeyData[:keyLen], extra, maxFrameSize)
if err != nil {
return nil, nil, err
}
return sc, result, nil
}
func (h *altsHandshaker) accessHandshakerService(req *altspb.HandshakerReq) (*altspb.HandshakerResp, error) {
if err := h.stream.Send(req); err != nil {
return nil, fmt.Errorf("failed to send ALTS handshaker request: %w", err)
}
resp, err := h.stream.Recv()
if err != nil {
return nil, fmt.Errorf("failed to receive ALTS handshaker response: %w", err)
}
return resp, nil
}
// processUntilDone processes the handshake until the handshaker service returns
// the results. Handshaker service takes care of frame parsing, so we read
// whatever received from the network and send it to the handshaker service.
func (h *altsHandshaker) processUntilDone(resp *altspb.HandshakerResp, extra []byte) (*altspb.HandshakerResult, []byte, error) {
var lastWriteTime time.Time
buf := make([]byte, frameLimit)
for {
if len(resp.OutFrames) > 0 {
lastWriteTime = time.Now()
if _, err := h.conn.Write(resp.OutFrames); err != nil {
return nil, nil, err
}
}
if resp.Result != nil {
return resp.Result, extra, nil
}
n, err := h.conn.Read(buf)
if err != nil && err != io.EOF {
return nil, nil, err
}
// If there is nothing to send to the handshaker service, and
// nothing is received from the peer, then we are stuck.
// This covers the case when the peer is not responding. Note
// that handshaker service connection issues are caught in
// accessHandshakerService before we even get here.
if len(resp.OutFrames) == 0 && n == 0 {
return nil, nil, core.PeerNotRespondingError
}
// Append extra bytes from the previous interaction with the
// handshaker service with the current buffer read from conn.
p := append(extra, buf[:n]...)
// Compute the time elapsed since the last write to the peer.
timeElapsed := time.Since(lastWriteTime)
timeElapsedMs := uint32(timeElapsed.Milliseconds())
// From here on, p and extra point to the same slice.
resp, err = h.accessHandshakerService(&altspb.HandshakerReq{
ReqOneof: &altspb.HandshakerReq_Next{
Next: &altspb.NextHandshakeMessageReq{
InBytes: p,
NetworkLatencyMs: timeElapsedMs,
},
},
})
if err != nil {
return nil, nil, err
}
// Set extra based on handshaker service response.
if resp.GetBytesConsumed() > uint32(len(p)) {
return nil, nil, errOutOfBound
}
extra = p[resp.GetBytesConsumed():]
}
}
// Close terminates the Handshaker. It should be called when the caller obtains
// the secure connection.
func (h *altsHandshaker) Close() {
if h.stream != nil {
h.stream.CloseSend()
}
}
// ResetConcurrentHandshakeSemaphoreForTesting resets the handshake semaphores
// to allow numberOfAllowedHandshakes concurrent handshakes each.
func ResetConcurrentHandshakeSemaphoreForTesting(numberOfAllowedHandshakes int64) {
clientHandshakes = semaphore.NewWeighted(numberOfAllowedHandshakes)
serverHandshakes = semaphore.NewWeighted(numberOfAllowedHandshakes)
}

View File

@@ -0,0 +1,437 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package handshaker
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
grpc "google.golang.org/grpc"
core "google.golang.org/grpc/credentials/alts/internal"
altspb "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
"google.golang.org/grpc/credentials/alts/internal/testutil"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
var (
testRecordProtocol = rekeyRecordProtocolName
testKey = []byte{
// 44 arbitrary bytes.
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49,
0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49, 0x1f, 0x8b,
0xd2, 0x4c, 0xce, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2,
}
testServiceAccount = "test_service_account"
testTargetServiceAccounts = []string{testServiceAccount}
testClientIdentity = &altspb.Identity{
IdentityOneof: &altspb.Identity_Hostname{
Hostname: "i_am_a_client",
},
}
)
const defaultTestTimeout = 10 * time.Second
// testRPCStream mimics a altspb.HandshakerService_DoHandshakeClient object.
type testRPCStream struct {
grpc.ClientStream
t *testing.T
isClient bool
// The resp expected to be returned by Recv(). Make sure this is set to
// the content the test requires before Recv() is invoked.
recvBuf *altspb.HandshakerResp
// false if it is the first access to Handshaker service on Envelope.
first bool
// useful for testing concurrent calls.
delay time.Duration
// The minimum expected value of the network_latency_ms field in a
// NextHandshakeMessageReq.
minExpectedNetworkLatency time.Duration
// peerMaxFrameSize is the max_frame_size in the HandshakerResult.
peerMaxFrameSize uint32
}
func (t *testRPCStream) Recv() (*altspb.HandshakerResp, error) {
resp := t.recvBuf
t.recvBuf = nil
return resp, nil
}
func (t *testRPCStream) Send(req *altspb.HandshakerReq) error {
var resp *altspb.HandshakerResp
if !t.first {
// Generate the bytes to be returned by Recv() for the initial
// handshaking.
t.first = true
if t.isClient {
resp = &altspb.HandshakerResp{
OutFrames: testutil.MakeFrame("ClientInit"),
// Simulate consuming ServerInit.
BytesConsumed: 14,
}
} else {
resp = &altspb.HandshakerResp{
OutFrames: testutil.MakeFrame("ServerInit"),
// Simulate consuming ClientInit.
BytesConsumed: 14,
}
}
} else {
switch req := req.ReqOneof.(type) {
case *altspb.HandshakerReq_Next:
// Compare the network_latency_ms field to the minimum expected network
// latency.
if nl := time.Duration(req.Next.NetworkLatencyMs) * time.Millisecond; nl < t.minExpectedNetworkLatency {
return fmt.Errorf("networkLatency (%v) is smaller than expected min network latency (%v)", nl, t.minExpectedNetworkLatency)
}
default:
return fmt.Errorf("handshake request has unexpected type: %v", req)
}
// Add delay to test concurrent calls.
cleanup := stat.Update()
defer cleanup()
time.Sleep(t.delay)
// Generate the response to be returned by Recv() for the
// follow-up handshaking.
result := &altspb.HandshakerResult{
RecordProtocol: testRecordProtocol,
KeyData: testKey,
MaxFrameSize: t.peerMaxFrameSize,
}
resp = &altspb.HandshakerResp{
Result: result,
// Simulate consuming ClientFinished or ServerFinished.
BytesConsumed: 18,
}
}
t.recvBuf = resp
return nil
}
func (t *testRPCStream) CloseSend() error {
return nil
}
var stat testutil.Stats
func (s) TestClientHandshake(t *testing.T) {
for _, testCase := range []struct {
delay time.Duration
numberOfHandshakes int
readLatency time.Duration
}{
{0 * time.Millisecond, 1, time.Duration(0)},
{0 * time.Millisecond, 1, 2 * time.Millisecond},
{100 * time.Millisecond, 10 * int(envconfig.ALTSMaxConcurrentHandshakes), time.Duration(0)},
} {
errc := make(chan error)
stat.Reset()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
for i := 0; i < testCase.numberOfHandshakes; i++ {
stream := &testRPCStream{
t: t,
isClient: true,
minExpectedNetworkLatency: testCase.readLatency,
}
// Preload the inbound frames.
f1 := testutil.MakeFrame("ServerInit")
f2 := testutil.MakeFrame("ServerFinished")
in := bytes.NewBuffer(f1)
in.Write(f2)
out := new(bytes.Buffer)
tc := testutil.NewTestConnWithReadLatency(in, out, testCase.readLatency)
chs := &altsHandshaker{
stream: stream,
conn: tc,
clientOpts: &ClientHandshakerOptions{
TargetServiceAccounts: testTargetServiceAccounts,
ClientIdentity: testClientIdentity,
},
side: core.ClientSide,
}
go func() {
_, context, err := chs.ClientHandshake(ctx)
if err == nil && context == nil {
errc <- errors.New("expected non-nil ALTS context")
return
}
errc <- err
chs.Close()
}()
}
// Ensure that there are no errors.
for i := 0; i < testCase.numberOfHandshakes; i++ {
if err := <-errc; err != nil {
t.Errorf("ClientHandshake() = _, %v, want _, <nil>", err)
}
}
// Ensure that there are no concurrent calls more than the limit.
if stat.MaxConcurrentCalls > int(envconfig.ALTSMaxConcurrentHandshakes) {
t.Errorf("Observed %d concurrent handshakes; want <= %d", stat.MaxConcurrentCalls, envconfig.ALTSMaxConcurrentHandshakes)
}
}
}
func (s) TestServerHandshake(t *testing.T) {
for _, testCase := range []struct {
delay time.Duration
numberOfHandshakes int
}{
{0 * time.Millisecond, 1},
{100 * time.Millisecond, 10 * int(envconfig.ALTSMaxConcurrentHandshakes)},
} {
errc := make(chan error)
stat.Reset()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
for i := 0; i < testCase.numberOfHandshakes; i++ {
stream := &testRPCStream{
t: t,
isClient: false,
}
// Preload the inbound frames.
f1 := testutil.MakeFrame("ClientInit")
f2 := testutil.MakeFrame("ClientFinished")
in := bytes.NewBuffer(f1)
in.Write(f2)
out := new(bytes.Buffer)
tc := testutil.NewTestConn(in, out)
shs := &altsHandshaker{
stream: stream,
conn: tc,
serverOpts: DefaultServerHandshakerOptions(),
side: core.ServerSide,
}
go func() {
_, context, err := shs.ServerHandshake(ctx)
if err == nil && context == nil {
errc <- errors.New("expected non-nil ALTS context")
return
}
errc <- err
shs.Close()
}()
}
// Ensure that there are no errors.
for i := 0; i < testCase.numberOfHandshakes; i++ {
if err := <-errc; err != nil {
t.Errorf("ServerHandshake() = _, %v, want _, <nil>", err)
}
}
// Ensure that there are no concurrent calls more than the limit.
if stat.MaxConcurrentCalls > int(envconfig.ALTSMaxConcurrentHandshakes) {
t.Errorf("Observed %d concurrent handshakes; want <= %d", stat.MaxConcurrentCalls, envconfig.ALTSMaxConcurrentHandshakes)
}
}
}
// testUnresponsiveRPCStream is used for testing the PeerNotResponding case.
type testUnresponsiveRPCStream struct {
grpc.ClientStream
}
func (t *testUnresponsiveRPCStream) Recv() (*altspb.HandshakerResp, error) {
return &altspb.HandshakerResp{}, nil
}
func (t *testUnresponsiveRPCStream) Send(*altspb.HandshakerReq) error {
return nil
}
func (t *testUnresponsiveRPCStream) CloseSend() error {
return nil
}
func (s) TestPeerNotResponding(t *testing.T) {
stream := &testUnresponsiveRPCStream{}
chs := &altsHandshaker{
stream: stream,
conn: testutil.NewUnresponsiveTestConn(),
clientOpts: &ClientHandshakerOptions{
TargetServiceAccounts: testTargetServiceAccounts,
ClientIdentity: testClientIdentity,
},
side: core.ClientSide,
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
_, context, err := chs.ClientHandshake(ctx)
chs.Close()
if context != nil {
t.Error("expected non-nil ALTS context")
}
if got, want := err, core.PeerNotRespondingError; got != want {
t.Errorf("ClientHandshake() = %v, want %v", got, want)
}
}
func (s) TestNewClientHandshaker(t *testing.T) {
conn := testutil.NewTestConn(nil, nil)
clientConn := &grpc.ClientConn{}
opts := &ClientHandshakerOptions{}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
hs, err := NewClientHandshaker(ctx, clientConn, conn, opts)
if err != nil {
t.Errorf("NewClientHandshaker returned unexpected error: %v", err)
}
expectedHs := &altsHandshaker{
stream: nil,
conn: conn,
clientConn: clientConn,
clientOpts: opts,
serverOpts: nil,
side: core.ClientSide,
}
cmpOpts := []cmp.Option{
cmp.AllowUnexported(altsHandshaker{}),
cmpopts.IgnoreFields(altsHandshaker{}, "conn", "clientConn"),
}
if got, want := hs.(*altsHandshaker), expectedHs; !cmp.Equal(got, want, cmpOpts...) {
t.Errorf("NewClientHandshaker() returned unexpected handshaker: got: %v, want: %v", got, want)
}
if hs.(*altsHandshaker).stream != nil {
t.Errorf("NewClientHandshaker() returned handshaker with non-nil stream")
}
if hs.(*altsHandshaker).clientConn != clientConn {
t.Errorf("NewClientHandshaker() returned handshaker with unexpected clientConn")
}
hs.Close()
}
func (s) TestNewServerHandshaker(t *testing.T) {
conn := testutil.NewTestConn(nil, nil)
clientConn := &grpc.ClientConn{}
opts := &ServerHandshakerOptions{}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
hs, err := NewServerHandshaker(ctx, clientConn, conn, opts)
if err != nil {
t.Errorf("NewServerHandshaker returned unexpected error: %v", err)
}
expectedHs := &altsHandshaker{
stream: nil,
conn: conn,
clientConn: clientConn,
clientOpts: nil,
serverOpts: opts,
side: core.ServerSide,
}
cmpOpts := []cmp.Option{
cmp.AllowUnexported(altsHandshaker{}),
cmpopts.IgnoreFields(altsHandshaker{}, "conn", "clientConn"),
}
if got, want := hs.(*altsHandshaker), expectedHs; !cmp.Equal(got, want, cmpOpts...) {
t.Errorf("NewServerHandshaker() returned unexpected handshaker: got: %v, want: %v", got, want)
}
if hs.(*altsHandshaker).stream != nil {
t.Errorf("NewServerHandshaker() returned handshaker with non-nil stream")
}
if hs.(*altsHandshaker).clientConn != clientConn {
t.Errorf("NewServerHandshaker() returned handshaker with unexpected clientConn")
}
hs.Close()
}
func (s) TestHandshakeMaxFrameSizeNegotiation(t *testing.T) {
old := envconfig.ALTSMaxFrameSize
envconfig.ALTSMaxFrameSize = 64 * 1024
defer func() { envconfig.ALTSMaxFrameSize = old }()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
stream := &testRPCStream{
t: t,
isClient: true,
peerMaxFrameSize: 1024 * 1024,
}
f1 := testutil.MakeFrame("ServerInit")
f2 := testutil.MakeFrame("ServerFinished")
in := bytes.NewBuffer(f1)
in.Write(f2)
out := new(bytes.Buffer)
tc := testutil.NewTestConn(in, out)
chs := &altsHandshaker{
stream: stream,
conn: tc,
clientOpts: &ClientHandshakerOptions{
TargetServiceAccounts: testTargetServiceAccounts,
ClientIdentity: testClientIdentity,
},
side: core.ClientSide,
}
defer chs.Close()
sc, _, err := chs.ClientHandshake(ctx)
if err != nil {
t.Fatalf("ClientHandshake() failed: %v", err)
}
if sc == nil {
t.Fatal("ClientHandshake() returned nil conn, want non-nil")
}
// Clear any handshake frames written to out during ClientHandshake.
out.Reset()
// Write a payload large enough to produce a full ALTS frame.
payload := make([]byte, 128*1024)
if _, err := sc.Write(payload); err != nil {
t.Fatalf("Secure conn Write() failed: %v", err)
}
// In ALTS wire format, the first 4 bytes of a frame contain the little-endian
// frame length (frameLen). Writing a large payload splits it into frames of
// the maximum calculated size. We verify the connection's max frame size by
// asserting that the total wire frame length (4 + frameLen) equals 64 * 1024.
if len(out.Bytes()) < 4 {
t.Fatalf("Output buffer too small: %d bytes", len(out.Bytes()))
}
frameLen := int(binary.LittleEndian.Uint32(out.Bytes()[:4]))
if got, want := 4+frameLen, 64*1024; got != want {
t.Errorf("Wire frame size = %v, want %v", got, want)
}
}

View File

@@ -0,0 +1,91 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package service manages connections between the VM application and the ALTS
// handshaker service.
package service
import (
"sync"
"time"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/keepalive"
)
var (
// mu guards hsConnMap and hsDialer.
mu sync.Mutex
// hsConn represents a mapping from a hypervisor handshaker service address
// to a corresponding connection to a hypervisor handshaker service
// instance.
hsConnMap = make(map[string]*grpc.ClientConn)
)
// Dial dials the handshake service in the hypervisor. If a connection has
// already been established, this function returns it. Otherwise, a new
// connection is created.
func Dial(hsAddress string) (*grpc.ClientConn, error) {
mu.Lock()
defer mu.Unlock()
hsConn, ok := hsConnMap[hsAddress]
if !ok {
// Create a new connection to the handshaker service. Note that
// this connection stays open until the application is closed.
// Disable the service config to avoid unnecessary TXT record lookups that
// cause timeouts with some versions of systemd-resolved.
var err error
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDisableServiceConfig(),
}
if envconfig.ALTSHandshakerKeepaliveParams {
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
Timeout: 10 * time.Second,
Time: 10 * time.Minute,
}))
}
hsConn, err = grpc.NewClient(hsAddress, opts...)
if err != nil {
return nil, err
}
hsConnMap[hsAddress] = hsConn
}
return hsConn, nil
}
// CloseForTesting closes all open connections to the handshaker service.
//
// For testing purposes only.
func CloseForTesting() error {
for _, hsConn := range hsConnMap {
if hsConn == nil {
continue
}
if err := hsConn.Close(); err != nil {
return err
}
}
// Reset the connection map.
hsConnMap = make(map[string]*grpc.ClientConn)
return nil
}

View File

@@ -0,0 +1,80 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package service
import (
"testing"
"google.golang.org/grpc/internal/grpctest"
)
type s struct {
grpctest.Tester
}
func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}
const (
testAddress1 = "some_address_1"
testAddress2 = "some_address_2"
)
// TestDial verifies the behaviour of alts handshake when there are multiple Dials.
// If a connection has already been established, this function returns it.
// Otherwise, a new connection is created.
func (s) TestDial(t *testing.T) {
// First call to Dial, it should create a connection to the server running
// at the given address.
conn1, err := Dial(testAddress1)
if err != nil {
t.Fatalf("first call to Dial(%v) failed: %v", testAddress1, err)
}
defer conn1.Close()
if got, want := hsConnMap[testAddress1], conn1; got != want {
t.Fatalf("hsConnMap[%v]=%v, want %v", testAddress1, got, want)
}
// Second call to Dial should return conn1 above.
conn2, err := Dial(testAddress1)
if err != nil {
t.Fatalf("second call to Dial(%v) failed: %v", testAddress1, err)
}
defer conn2.Close()
if got, want := conn2, conn1; got != want {
t.Fatalf("second call to Dial(%v)=(%v, _), want (%v,. _)", testAddress1, got, want)
}
if got, want := hsConnMap[testAddress1], conn1; got != want {
t.Fatalf("hsConnMap[%v]=%v, want %v", testAddress1, got, want)
}
// Third call to Dial using a different address should create a new connection.
conn3, err := Dial(testAddress2)
if err != nil {
t.Fatalf("third call to Dial(%v) failed: %v", testAddress2, err)
}
defer conn3.Close()
if got, want := hsConnMap[testAddress2], conn3; got != want {
t.Fatalf("hsConnMap[%v]=%v, want %v", testAddress2, got, want)
}
if got, want := conn2 == conn3, false; got != want {
t.Fatalf("(conn2==conn3)=%v, want %v", got, want)
}
}

View File

@@ -0,0 +1,211 @@
// Copyright 2018 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The canonical version of this proto can be found at
// https://github.com/grpc/grpc-proto/blob/master/grpc/gcp/altscontext.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/gcp/altscontext.proto
package grpc_gcp
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type AltsContext struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The application protocol negotiated for this connection.
ApplicationProtocol string `protobuf:"bytes,1,opt,name=application_protocol,json=applicationProtocol,proto3" json:"application_protocol,omitempty"`
// The record protocol negotiated for this connection.
RecordProtocol string `protobuf:"bytes,2,opt,name=record_protocol,json=recordProtocol,proto3" json:"record_protocol,omitempty"`
// The security level of the created secure channel.
SecurityLevel SecurityLevel `protobuf:"varint,3,opt,name=security_level,json=securityLevel,proto3,enum=grpc.gcp.SecurityLevel" json:"security_level,omitempty"`
// The peer service account.
PeerServiceAccount string `protobuf:"bytes,4,opt,name=peer_service_account,json=peerServiceAccount,proto3" json:"peer_service_account,omitempty"`
// The local service account.
LocalServiceAccount string `protobuf:"bytes,5,opt,name=local_service_account,json=localServiceAccount,proto3" json:"local_service_account,omitempty"`
// The RPC protocol versions supported by the peer.
PeerRpcVersions *RpcProtocolVersions `protobuf:"bytes,6,opt,name=peer_rpc_versions,json=peerRpcVersions,proto3" json:"peer_rpc_versions,omitempty"`
// Additional attributes of the peer.
PeerAttributes map[string]string `protobuf:"bytes,7,rep,name=peer_attributes,json=peerAttributes,proto3" json:"peer_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AltsContext) Reset() {
*x = AltsContext{}
mi := &file_grpc_gcp_altscontext_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AltsContext) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AltsContext) ProtoMessage() {}
func (x *AltsContext) ProtoReflect() protoreflect.Message {
mi := &file_grpc_gcp_altscontext_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AltsContext.ProtoReflect.Descriptor instead.
func (*AltsContext) Descriptor() ([]byte, []int) {
return file_grpc_gcp_altscontext_proto_rawDescGZIP(), []int{0}
}
func (x *AltsContext) GetApplicationProtocol() string {
if x != nil {
return x.ApplicationProtocol
}
return ""
}
func (x *AltsContext) GetRecordProtocol() string {
if x != nil {
return x.RecordProtocol
}
return ""
}
func (x *AltsContext) GetSecurityLevel() SecurityLevel {
if x != nil {
return x.SecurityLevel
}
return SecurityLevel_SECURITY_NONE
}
func (x *AltsContext) GetPeerServiceAccount() string {
if x != nil {
return x.PeerServiceAccount
}
return ""
}
func (x *AltsContext) GetLocalServiceAccount() string {
if x != nil {
return x.LocalServiceAccount
}
return ""
}
func (x *AltsContext) GetPeerRpcVersions() *RpcProtocolVersions {
if x != nil {
return x.PeerRpcVersions
}
return nil
}
func (x *AltsContext) GetPeerAttributes() map[string]string {
if x != nil {
return x.PeerAttributes
}
return nil
}
var File_grpc_gcp_altscontext_proto protoreflect.FileDescriptor
const file_grpc_gcp_altscontext_proto_rawDesc = "" +
"\n" +
"\x1agrpc/gcp/altscontext.proto\x12\bgrpc.gcp\x1a(grpc/gcp/transport_security_common.proto\"\xf1\x03\n" +
"\vAltsContext\x121\n" +
"\x14application_protocol\x18\x01 \x01(\tR\x13applicationProtocol\x12'\n" +
"\x0frecord_protocol\x18\x02 \x01(\tR\x0erecordProtocol\x12>\n" +
"\x0esecurity_level\x18\x03 \x01(\x0e2\x17.grpc.gcp.SecurityLevelR\rsecurityLevel\x120\n" +
"\x14peer_service_account\x18\x04 \x01(\tR\x12peerServiceAccount\x122\n" +
"\x15local_service_account\x18\x05 \x01(\tR\x13localServiceAccount\x12I\n" +
"\x11peer_rpc_versions\x18\x06 \x01(\v2\x1d.grpc.gcp.RpcProtocolVersionsR\x0fpeerRpcVersions\x12R\n" +
"\x0fpeer_attributes\x18\a \x03(\v2).grpc.gcp.AltsContext.PeerAttributesEntryR\x0epeerAttributes\x1aA\n" +
"\x13PeerAttributesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01Bl\n" +
"\x15io.grpc.alts.internalB\x10AltsContextProtoP\x01Z?google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcpb\x06proto3"
var (
file_grpc_gcp_altscontext_proto_rawDescOnce sync.Once
file_grpc_gcp_altscontext_proto_rawDescData []byte
)
func file_grpc_gcp_altscontext_proto_rawDescGZIP() []byte {
file_grpc_gcp_altscontext_proto_rawDescOnce.Do(func() {
file_grpc_gcp_altscontext_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_gcp_altscontext_proto_rawDesc), len(file_grpc_gcp_altscontext_proto_rawDesc)))
})
return file_grpc_gcp_altscontext_proto_rawDescData
}
var file_grpc_gcp_altscontext_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_grpc_gcp_altscontext_proto_goTypes = []any{
(*AltsContext)(nil), // 0: grpc.gcp.AltsContext
nil, // 1: grpc.gcp.AltsContext.PeerAttributesEntry
(SecurityLevel)(0), // 2: grpc.gcp.SecurityLevel
(*RpcProtocolVersions)(nil), // 3: grpc.gcp.RpcProtocolVersions
}
var file_grpc_gcp_altscontext_proto_depIdxs = []int32{
2, // 0: grpc.gcp.AltsContext.security_level:type_name -> grpc.gcp.SecurityLevel
3, // 1: grpc.gcp.AltsContext.peer_rpc_versions:type_name -> grpc.gcp.RpcProtocolVersions
1, // 2: grpc.gcp.AltsContext.peer_attributes:type_name -> grpc.gcp.AltsContext.PeerAttributesEntry
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_grpc_gcp_altscontext_proto_init() }
func file_grpc_gcp_altscontext_proto_init() {
if File_grpc_gcp_altscontext_proto != nil {
return
}
file_grpc_gcp_transport_security_common_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_gcp_altscontext_proto_rawDesc), len(file_grpc_gcp_altscontext_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_gcp_altscontext_proto_goTypes,
DependencyIndexes: file_grpc_gcp_altscontext_proto_depIdxs,
MessageInfos: file_grpc_gcp_altscontext_proto_msgTypes,
}.Build()
File_grpc_gcp_altscontext_proto = out.File
file_grpc_gcp_altscontext_proto_goTypes = nil
file_grpc_gcp_altscontext_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
// Copyright 2018 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The canonical version of this proto can be found at
// https://github.com/grpc/grpc-proto/blob/master/grpc/gcp/handshaker.proto
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v5.27.1
// source: grpc/gcp/handshaker.proto
package grpc_gcp
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
HandshakerService_DoHandshake_FullMethodName = "/grpc.gcp.HandshakerService/DoHandshake"
)
// HandshakerServiceClient is the client API for HandshakerService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type HandshakerServiceClient interface {
// Handshaker service accepts a stream of handshaker request, returning a
// stream of handshaker response. Client is expected to send exactly one
// message with either client_start or server_start followed by one or more
// messages with next. Each time client sends a request, the handshaker
// service expects to respond. Client does not have to wait for service's
// response before sending next request.
DoHandshake(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[HandshakerReq, HandshakerResp], error)
}
type handshakerServiceClient struct {
cc grpc.ClientConnInterface
}
func NewHandshakerServiceClient(cc grpc.ClientConnInterface) HandshakerServiceClient {
return &handshakerServiceClient{cc}
}
func (c *handshakerServiceClient) DoHandshake(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[HandshakerReq, HandshakerResp], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &HandshakerService_ServiceDesc.Streams[0], HandshakerService_DoHandshake_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[HandshakerReq, HandshakerResp]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type HandshakerService_DoHandshakeClient = grpc.BidiStreamingClient[HandshakerReq, HandshakerResp]
// HandshakerServiceServer is the server API for HandshakerService service.
// All implementations must embed UnimplementedHandshakerServiceServer
// for forward compatibility.
type HandshakerServiceServer interface {
// Handshaker service accepts a stream of handshaker request, returning a
// stream of handshaker response. Client is expected to send exactly one
// message with either client_start or server_start followed by one or more
// messages with next. Each time client sends a request, the handshaker
// service expects to respond. Client does not have to wait for service's
// response before sending next request.
DoHandshake(grpc.BidiStreamingServer[HandshakerReq, HandshakerResp]) error
mustEmbedUnimplementedHandshakerServiceServer()
}
// UnimplementedHandshakerServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedHandshakerServiceServer struct{}
func (UnimplementedHandshakerServiceServer) DoHandshake(grpc.BidiStreamingServer[HandshakerReq, HandshakerResp]) error {
return status.Error(codes.Unimplemented, "method DoHandshake not implemented")
}
func (UnimplementedHandshakerServiceServer) mustEmbedUnimplementedHandshakerServiceServer() {}
func (UnimplementedHandshakerServiceServer) testEmbeddedByValue() {}
// UnsafeHandshakerServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to HandshakerServiceServer will
// result in compilation errors.
type UnsafeHandshakerServiceServer interface {
mustEmbedUnimplementedHandshakerServiceServer()
}
func RegisterHandshakerServiceServer(s grpc.ServiceRegistrar, srv HandshakerServiceServer) {
// If the following call panics, it indicates UnimplementedHandshakerServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&HandshakerService_ServiceDesc, srv)
}
func _HandshakerService_DoHandshake_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(HandshakerServiceServer).DoHandshake(&grpc.GenericServerStream[HandshakerReq, HandshakerResp]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type HandshakerService_DoHandshakeServer = grpc.BidiStreamingServer[HandshakerReq, HandshakerResp]
// HandshakerService_ServiceDesc is the grpc.ServiceDesc for HandshakerService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var HandshakerService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.gcp.HandshakerService",
HandlerType: (*HandshakerServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "DoHandshake",
Handler: _HandshakerService_DoHandshake_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "grpc/gcp/handshaker.proto",
}

View File

@@ -0,0 +1,366 @@
// Copyright 2018 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The canonical version of this proto can be found at
// https://github.com/grpc/grpc-proto/blob/master/grpc/gcp/transport_security_common.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.12
// protoc v5.27.1
// source: grpc/gcp/transport_security_common.proto
package grpc_gcp
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The security level of the created channel. The list is sorted in increasing
// level of security. This order must always be maintained.
type SecurityLevel int32
const (
SecurityLevel_SECURITY_NONE SecurityLevel = 0
SecurityLevel_INTEGRITY_ONLY SecurityLevel = 1
SecurityLevel_INTEGRITY_AND_PRIVACY SecurityLevel = 2
)
// Enum value maps for SecurityLevel.
var (
SecurityLevel_name = map[int32]string{
0: "SECURITY_NONE",
1: "INTEGRITY_ONLY",
2: "INTEGRITY_AND_PRIVACY",
}
SecurityLevel_value = map[string]int32{
"SECURITY_NONE": 0,
"INTEGRITY_ONLY": 1,
"INTEGRITY_AND_PRIVACY": 2,
}
)
func (x SecurityLevel) Enum() *SecurityLevel {
p := new(SecurityLevel)
*p = x
return p
}
func (x SecurityLevel) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (SecurityLevel) Descriptor() protoreflect.EnumDescriptor {
return file_grpc_gcp_transport_security_common_proto_enumTypes[0].Descriptor()
}
func (SecurityLevel) Type() protoreflect.EnumType {
return &file_grpc_gcp_transport_security_common_proto_enumTypes[0]
}
func (x SecurityLevel) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use SecurityLevel.Descriptor instead.
func (SecurityLevel) EnumDescriptor() ([]byte, []int) {
return file_grpc_gcp_transport_security_common_proto_rawDescGZIP(), []int{0}
}
// Max and min supported RPC protocol versions.
type RpcProtocolVersions struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Maximum supported RPC version.
MaxRpcVersion *RpcProtocolVersions_Version `protobuf:"bytes,1,opt,name=max_rpc_version,json=maxRpcVersion,proto3" json:"max_rpc_version,omitempty"`
// Minimum supported RPC version.
MinRpcVersion *RpcProtocolVersions_Version `protobuf:"bytes,2,opt,name=min_rpc_version,json=minRpcVersion,proto3" json:"min_rpc_version,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RpcProtocolVersions) Reset() {
*x = RpcProtocolVersions{}
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RpcProtocolVersions) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RpcProtocolVersions) ProtoMessage() {}
func (x *RpcProtocolVersions) ProtoReflect() protoreflect.Message {
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RpcProtocolVersions.ProtoReflect.Descriptor instead.
func (*RpcProtocolVersions) Descriptor() ([]byte, []int) {
return file_grpc_gcp_transport_security_common_proto_rawDescGZIP(), []int{0}
}
func (x *RpcProtocolVersions) GetMaxRpcVersion() *RpcProtocolVersions_Version {
if x != nil {
return x.MaxRpcVersion
}
return nil
}
func (x *RpcProtocolVersions) GetMinRpcVersion() *RpcProtocolVersions_Version {
if x != nil {
return x.MinRpcVersion
}
return nil
}
// The ordered list of protocols that the client wishes to use, or the set
// that the server supports.
type TransportProtocolPreferences struct {
state protoimpl.MessageState `protogen:"open.v1"`
TransportProtocol []string `protobuf:"bytes,1,rep,name=transport_protocol,json=transportProtocol,proto3" json:"transport_protocol,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TransportProtocolPreferences) Reset() {
*x = TransportProtocolPreferences{}
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TransportProtocolPreferences) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransportProtocolPreferences) ProtoMessage() {}
func (x *TransportProtocolPreferences) ProtoReflect() protoreflect.Message {
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransportProtocolPreferences.ProtoReflect.Descriptor instead.
func (*TransportProtocolPreferences) Descriptor() ([]byte, []int) {
return file_grpc_gcp_transport_security_common_proto_rawDescGZIP(), []int{1}
}
func (x *TransportProtocolPreferences) GetTransportProtocol() []string {
if x != nil {
return x.TransportProtocol
}
return nil
}
// The negotiated transport protocol.
type NegotiatedTransportProtocol struct {
state protoimpl.MessageState `protogen:"open.v1"`
TransportProtocol string `protobuf:"bytes,1,opt,name=transport_protocol,json=transportProtocol,proto3" json:"transport_protocol,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NegotiatedTransportProtocol) Reset() {
*x = NegotiatedTransportProtocol{}
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NegotiatedTransportProtocol) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NegotiatedTransportProtocol) ProtoMessage() {}
func (x *NegotiatedTransportProtocol) ProtoReflect() protoreflect.Message {
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NegotiatedTransportProtocol.ProtoReflect.Descriptor instead.
func (*NegotiatedTransportProtocol) Descriptor() ([]byte, []int) {
return file_grpc_gcp_transport_security_common_proto_rawDescGZIP(), []int{2}
}
func (x *NegotiatedTransportProtocol) GetTransportProtocol() string {
if x != nil {
return x.TransportProtocol
}
return ""
}
// RPC version contains a major version and a minor version.
type RpcProtocolVersions_Version struct {
state protoimpl.MessageState `protogen:"open.v1"`
Major uint32 `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"`
Minor uint32 `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RpcProtocolVersions_Version) Reset() {
*x = RpcProtocolVersions_Version{}
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RpcProtocolVersions_Version) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RpcProtocolVersions_Version) ProtoMessage() {}
func (x *RpcProtocolVersions_Version) ProtoReflect() protoreflect.Message {
mi := &file_grpc_gcp_transport_security_common_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RpcProtocolVersions_Version.ProtoReflect.Descriptor instead.
func (*RpcProtocolVersions_Version) Descriptor() ([]byte, []int) {
return file_grpc_gcp_transport_security_common_proto_rawDescGZIP(), []int{0, 0}
}
func (x *RpcProtocolVersions_Version) GetMajor() uint32 {
if x != nil {
return x.Major
}
return 0
}
func (x *RpcProtocolVersions_Version) GetMinor() uint32 {
if x != nil {
return x.Minor
}
return 0
}
var File_grpc_gcp_transport_security_common_proto protoreflect.FileDescriptor
const file_grpc_gcp_transport_security_common_proto_rawDesc = "" +
"\n" +
"(grpc/gcp/transport_security_common.proto\x12\bgrpc.gcp\"\xea\x01\n" +
"\x13RpcProtocolVersions\x12M\n" +
"\x0fmax_rpc_version\x18\x01 \x01(\v2%.grpc.gcp.RpcProtocolVersions.VersionR\rmaxRpcVersion\x12M\n" +
"\x0fmin_rpc_version\x18\x02 \x01(\v2%.grpc.gcp.RpcProtocolVersions.VersionR\rminRpcVersion\x1a5\n" +
"\aVersion\x12\x14\n" +
"\x05major\x18\x01 \x01(\rR\x05major\x12\x14\n" +
"\x05minor\x18\x02 \x01(\rR\x05minor\"M\n" +
"\x1cTransportProtocolPreferences\x12-\n" +
"\x12transport_protocol\x18\x01 \x03(\tR\x11transportProtocol\"L\n" +
"\x1bNegotiatedTransportProtocol\x12-\n" +
"\x12transport_protocol\x18\x01 \x01(\tR\x11transportProtocol*Q\n" +
"\rSecurityLevel\x12\x11\n" +
"\rSECURITY_NONE\x10\x00\x12\x12\n" +
"\x0eINTEGRITY_ONLY\x10\x01\x12\x19\n" +
"\x15INTEGRITY_AND_PRIVACY\x10\x02Bx\n" +
"\x15io.grpc.alts.internalB\x1cTransportSecurityCommonProtoP\x01Z?google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcpb\x06proto3"
var (
file_grpc_gcp_transport_security_common_proto_rawDescOnce sync.Once
file_grpc_gcp_transport_security_common_proto_rawDescData []byte
)
func file_grpc_gcp_transport_security_common_proto_rawDescGZIP() []byte {
file_grpc_gcp_transport_security_common_proto_rawDescOnce.Do(func() {
file_grpc_gcp_transport_security_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_grpc_gcp_transport_security_common_proto_rawDesc), len(file_grpc_gcp_transport_security_common_proto_rawDesc)))
})
return file_grpc_gcp_transport_security_common_proto_rawDescData
}
var file_grpc_gcp_transport_security_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_grpc_gcp_transport_security_common_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_grpc_gcp_transport_security_common_proto_goTypes = []any{
(SecurityLevel)(0), // 0: grpc.gcp.SecurityLevel
(*RpcProtocolVersions)(nil), // 1: grpc.gcp.RpcProtocolVersions
(*TransportProtocolPreferences)(nil), // 2: grpc.gcp.TransportProtocolPreferences
(*NegotiatedTransportProtocol)(nil), // 3: grpc.gcp.NegotiatedTransportProtocol
(*RpcProtocolVersions_Version)(nil), // 4: grpc.gcp.RpcProtocolVersions.Version
}
var file_grpc_gcp_transport_security_common_proto_depIdxs = []int32{
4, // 0: grpc.gcp.RpcProtocolVersions.max_rpc_version:type_name -> grpc.gcp.RpcProtocolVersions.Version
4, // 1: grpc.gcp.RpcProtocolVersions.min_rpc_version:type_name -> grpc.gcp.RpcProtocolVersions.Version
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_grpc_gcp_transport_security_common_proto_init() }
func file_grpc_gcp_transport_security_common_proto_init() {
if File_grpc_gcp_transport_security_common_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_grpc_gcp_transport_security_common_proto_rawDesc), len(file_grpc_gcp_transport_security_common_proto_rawDesc)),
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_grpc_gcp_transport_security_common_proto_goTypes,
DependencyIndexes: file_grpc_gcp_transport_security_common_proto_depIdxs,
EnumInfos: file_grpc_gcp_transport_security_common_proto_enumTypes,
MessageInfos: file_grpc_gcp_transport_security_common_proto_msgTypes,
}.Build()
File_grpc_gcp_transport_security_common_proto = out.File
file_grpc_gcp_transport_security_common_proto_goTypes = nil
file_grpc_gcp_transport_security_common_proto_depIdxs = nil
}

View File

@@ -0,0 +1,323 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package testutil include useful test utilities for the handshaker.
package testutil
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"sync"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/alts/internal/conn"
altsgrpc "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
altspb "google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp"
)
// Stats is used to collect statistics about concurrent handshake calls.
type Stats struct {
mu sync.Mutex
calls int
MaxConcurrentCalls int
}
// Update updates the statistics by adding one call.
func (s *Stats) Update() func() {
s.mu.Lock()
s.calls++
if s.calls > s.MaxConcurrentCalls {
s.MaxConcurrentCalls = s.calls
}
s.mu.Unlock()
return func() {
s.mu.Lock()
s.calls--
s.mu.Unlock()
}
}
// Reset resets the statistics.
func (s *Stats) Reset() {
s.mu.Lock()
defer s.mu.Unlock()
s.calls = 0
s.MaxConcurrentCalls = 0
}
// testConn mimics a net.Conn to the peer.
type testConn struct {
net.Conn
in *bytes.Buffer
out *bytes.Buffer
readLatency time.Duration
}
// NewTestConn creates a new instance of testConn object.
func NewTestConn(in *bytes.Buffer, out *bytes.Buffer) net.Conn {
return &testConn{
in: in,
out: out,
readLatency: time.Duration(0),
}
}
// NewTestConnWithReadLatency creates a new instance of testConn object that
// pauses for readLatency before any call to Read() returns.
func NewTestConnWithReadLatency(in *bytes.Buffer, out *bytes.Buffer, readLatency time.Duration) net.Conn {
return &testConn{
in: in,
out: out,
readLatency: readLatency,
}
}
// Read reads from the in buffer.
func (c *testConn) Read(b []byte) (n int, err error) {
time.Sleep(c.readLatency)
return c.in.Read(b)
}
// Write writes to the out buffer.
func (c *testConn) Write(b []byte) (n int, err error) {
return c.out.Write(b)
}
// Close closes the testConn object.
func (c *testConn) Close() error {
return nil
}
// unresponsiveTestConn mimics a net.Conn for an unresponsive peer. It is used
// for testing the PeerNotResponding case.
type unresponsiveTestConn struct {
net.Conn
}
// NewUnresponsiveTestConn creates a new instance of unresponsiveTestConn object.
func NewUnresponsiveTestConn() net.Conn {
return &unresponsiveTestConn{}
}
// Read reads from the in buffer.
func (c *unresponsiveTestConn) Read([]byte) (n int, err error) {
return 0, io.EOF
}
// Write writes to the out buffer.
func (c *unresponsiveTestConn) Write([]byte) (n int, err error) {
return 0, nil
}
// Close closes the TestConn object.
func (c *unresponsiveTestConn) Close() error {
return nil
}
// MakeFrame creates a handshake frame.
func MakeFrame(pl string) []byte {
f := make([]byte, len(pl)+conn.MsgLenFieldSize)
binary.LittleEndian.PutUint32(f, uint32(len(pl)))
copy(f[conn.MsgLenFieldSize:], []byte(pl))
return f
}
// FakeHandshaker is a fake implementation of the ALTS handshaker service.
type FakeHandshaker struct {
altsgrpc.HandshakerServiceServer
// ExpectedBoundAccessToken is the expected bound access token in the ClientStart request.
ExpectedBoundAccessToken string
}
// DoHandshake performs a fake ALTS handshake.
func (h *FakeHandshaker) DoHandshake(stream altsgrpc.HandshakerService_DoHandshakeServer) error {
var isAssistingClient bool
var handshakeFramesReceivedSoFar []byte
for {
req, err := stream.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return fmt.Errorf("stream recv failure: %v", err)
}
var resp *altspb.HandshakerResp
switch req := req.ReqOneof.(type) {
case *altspb.HandshakerReq_ClientStart:
isAssistingClient = true
resp, err = h.processStartClient(req.ClientStart)
if err != nil {
return fmt.Errorf("processStartClient failure: %v", err)
}
case *altspb.HandshakerReq_ServerStart:
// If we have received the full ClientInit, send the ServerInit and
// ServerFinished. Otherwise, wait for more bytes to arrive from the client.
isAssistingClient = false
handshakeFramesReceivedSoFar = append(handshakeFramesReceivedSoFar, req.ServerStart.InBytes...)
sendHandshakeFrame := bytes.Equal(handshakeFramesReceivedSoFar, []byte("ClientInit"))
resp, err = h.processServerStart(req.ServerStart, sendHandshakeFrame)
if err != nil {
return fmt.Errorf("processServerStart failure: %v", err)
}
case *altspb.HandshakerReq_Next:
// If we have received all handshake frames, send the handshake result.
// Otherwise, wait for more bytes to arrive from the peer.
oldHandshakesBytes := len(handshakeFramesReceivedSoFar)
handshakeFramesReceivedSoFar = append(handshakeFramesReceivedSoFar, req.Next.InBytes...)
isHandshakeComplete := false
if isAssistingClient {
isHandshakeComplete = bytes.HasPrefix(handshakeFramesReceivedSoFar, []byte("ServerInitServerFinished"))
} else {
isHandshakeComplete = bytes.HasPrefix(handshakeFramesReceivedSoFar, []byte("ClientInitClientFinished"))
}
if !isHandshakeComplete {
resp = &altspb.HandshakerResp{
BytesConsumed: uint32(len(handshakeFramesReceivedSoFar) - oldHandshakesBytes),
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}
break
}
resp, err = h.getHandshakeResult(isAssistingClient)
if err != nil {
return fmt.Errorf("getHandshakeResult failure: %v", err)
}
default:
return fmt.Errorf("handshake request has unexpected type: %v", req)
}
if err = stream.Send(resp); err != nil {
return fmt.Errorf("stream send failure: %v", err)
}
}
}
func (h *FakeHandshaker) processStartClient(req *altspb.StartClientHandshakeReq) (*altspb.HandshakerResp, error) {
if req.HandshakeSecurityProtocol != altspb.HandshakeProtocol_ALTS {
return nil, fmt.Errorf("unexpected handshake security protocol: %v", req.HandshakeSecurityProtocol)
}
if len(req.ApplicationProtocols) != 1 || req.ApplicationProtocols[0] != "grpc" {
return nil, fmt.Errorf("unexpected application protocols: %v", req.ApplicationProtocols)
}
if len(req.RecordProtocols) != 1 || req.RecordProtocols[0] != "ALTSRP_GCM_AES128_REKEY" {
return nil, fmt.Errorf("unexpected record protocols: %v", req.RecordProtocols)
}
if h.ExpectedBoundAccessToken != req.GetAccessToken() {
return nil, fmt.Errorf("unexpected access token: %v", req.GetAccessToken())
}
return &altspb.HandshakerResp{
OutFrames: []byte("ClientInit"),
BytesConsumed: 0,
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}, nil
}
func (h *FakeHandshaker) processServerStart(req *altspb.StartServerHandshakeReq, sendHandshakeFrame bool) (*altspb.HandshakerResp, error) {
if len(req.ApplicationProtocols) != 1 || req.ApplicationProtocols[0] != "grpc" {
return nil, fmt.Errorf("unexpected application protocols: %v", req.ApplicationProtocols)
}
parameters, ok := req.GetHandshakeParameters()[int32(altspb.HandshakeProtocol_ALTS)]
if !ok {
return nil, fmt.Errorf("missing ALTS handshake parameters")
}
if len(parameters.RecordProtocols) != 1 || parameters.RecordProtocols[0] != "ALTSRP_GCM_AES128_REKEY" {
return nil, fmt.Errorf("unexpected record protocols: %v", parameters.RecordProtocols)
}
if sendHandshakeFrame {
return &altspb.HandshakerResp{
OutFrames: []byte("ServerInitServerFinished"),
BytesConsumed: uint32(len(req.InBytes)),
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}, nil
}
return &altspb.HandshakerResp{
OutFrames: []byte("ServerInitServerFinished"),
BytesConsumed: 10,
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}, nil
}
func (h *FakeHandshaker) getHandshakeResult(isAssistingClient bool) (*altspb.HandshakerResp, error) {
if isAssistingClient {
return &altspb.HandshakerResp{
OutFrames: []byte("ClientFinished"),
BytesConsumed: 24,
Result: &altspb.HandshakerResult{
ApplicationProtocol: "grpc",
RecordProtocol: "ALTSRP_GCM_AES128_REKEY",
KeyData: []byte("negotiated-key-data-for-altsrp-gcm-aes128-rekey"),
PeerIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_ServiceAccount{
ServiceAccount: "server@bar.com",
},
},
PeerRpcVersions: &altspb.RpcProtocolVersions{
MaxRpcVersion: &altspb.RpcProtocolVersions_Version{
Minor: 1,
Major: 2,
},
MinRpcVersion: &altspb.RpcProtocolVersions_Version{
Minor: 1,
Major: 2,
},
},
},
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}, nil
}
return &altspb.HandshakerResp{
BytesConsumed: 14,
Result: &altspb.HandshakerResult{
ApplicationProtocol: "grpc",
RecordProtocol: "ALTSRP_GCM_AES128_REKEY",
KeyData: []byte("negotiated-key-data-for-altsrp-gcm-aes128-rekey"),
PeerIdentity: &altspb.Identity{
IdentityOneof: &altspb.Identity_ServiceAccount{
ServiceAccount: "client@baz.com",
},
},
PeerRpcVersions: &altspb.RpcProtocolVersions{
MaxRpcVersion: &altspb.RpcProtocolVersions_Version{
Minor: 1,
Major: 2,
},
MinRpcVersion: &altspb.RpcProtocolVersions_Version{
Minor: 1,
Major: 2,
},
},
},
Status: &altspb.HandshakerStatus{
Code: uint32(codes.OK),
},
}, nil
}