veyron/runtimes/google/ipc/stream/vc: channel binding authentication
This CL depends on a recent change in the environment repository:
https://veyron-review.googlesource.com/#/c/3832/
Change-Id: I17027bd77ff3af4096dcf933c18853173fdb9299
diff --git a/runtimes/google/ipc/stream/crypto/box.go b/runtimes/google/ipc/stream/crypto/box.go
index 3ab2c33..10a9cea 100644
--- a/runtimes/google/ipc/stream/crypto/box.go
+++ b/runtimes/google/ipc/stream/crypto/box.go
@@ -15,6 +15,7 @@
conn net.Conn
alloc *iobuf.Allocator
sharedKey [32]byte
+ sortedPubkeys []byte
writeNonce, readNonce uint64
}
@@ -48,8 +49,10 @@
// be using 1, 3, 5...
if bytes.Compare(pk[:], theirPK[:]) < 0 {
ret.writeNonce, ret.readNonce = 0, 1
+ ret.sortedPubkeys = append(pk[:], theirPK[:]...)
} else {
ret.writeNonce, ret.readNonce = 1, 0
+ ret.sortedPubkeys = append(theirPK[:], pk[:]...)
}
return ret, nil
}
@@ -82,6 +85,10 @@
return ret, nil
}
+func (c *boxcrypter) ChannelBinding() []byte {
+ return c.sortedPubkeys
+}
+
func (c *boxcrypter) String() string {
return fmt.Sprintf("%#v", *c)
}
diff --git a/runtimes/google/ipc/stream/crypto/crypto.go b/runtimes/google/ipc/stream/crypto/crypto.go
index 6f6a2c3..d881309 100644
--- a/runtimes/google/ipc/stream/crypto/crypto.go
+++ b/runtimes/google/ipc/stream/crypto/crypto.go
@@ -25,5 +25,11 @@
type Crypter interface {
Encrypter
Decrypter
+ // ChannelBinding Returns a byte slice that is unique for the the
+ // particular crypter (and the parties between which it is operationg).
+ // Having both parties assert out of the band that they are indeed
+ // participating in a connection with that channel binding value is
+ // sufficient to authenticate the data received through the crypter.
+ ChannelBinding() []byte
String() string
}
diff --git a/runtimes/google/ipc/stream/crypto/null.go b/runtimes/google/ipc/stream/crypto/null.go
index 7b07ce6..0401076 100644
--- a/runtimes/google/ipc/stream/crypto/null.go
+++ b/runtimes/google/ipc/stream/crypto/null.go
@@ -7,6 +7,7 @@
type null struct{}
-func (c null) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
-func (c null) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
-func (c null) String() string { return "Null" }
+func (null) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
+func (null) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
+func (null) String() string { return "Null" }
+func (null) ChannelBinding() []byte { return nil }
diff --git a/runtimes/google/ipc/stream/crypto/tls.go b/runtimes/google/ipc/stream/crypto/tls.go
index 8dfb09d..8c62afe 100644
--- a/runtimes/google/ipc/stream/crypto/tls.go
+++ b/runtimes/google/ipc/stream/crypto/tls.go
@@ -24,6 +24,8 @@
// handshaker was initiated by a client.
func NewTLSClient(handshaker net.Conn, sessionCache tls.ClientSessionCache, pool *iobuf.Pool) (Crypter, error) {
var config tls.Config
+ // TLS + resumption + channel bindings is broken: <https://secure-resumption.com/#channelbindings>.
+ config.SessionTicketsDisabled = true
config.InsecureSkipVerify = true
config.ClientSessionCache = sessionCache
return newTLSCrypter(handshaker, &config, pool, false)
@@ -192,9 +194,10 @@
panic(err)
}
return &tls.Config{
- Certificates: []tls.Certificate{c},
- InsecureSkipVerify: true,
- ClientAuth: tls.NoClientCert,
+ // TLS + resumption + channel bindings is broken: <https://secure-resumption.com/#channelbindings>.
+ SessionTicketsDisabled: true,
+ Certificates: []tls.Certificate{c},
+ InsecureSkipVerify: true,
// RC4_128_SHA is 4-5X faster compared to the other cipher suites
// and is what google.com seems to use.
// Allowing ECDHE_RSA for the key exchange since some older binaries
@@ -203,6 +206,10 @@
}
}
+func (c *tlsCrypter) ChannelBinding() []byte {
+ return c.tls.ConnectionState().TLSUnique
+}
+
// TODO(ashankar): Get rid of TLS certificates completely after implementing an
// anonymous key-exchange mechanism. See F.1.1.1 in RFC 5246.
//
diff --git a/runtimes/google/ipc/stream/manager/manager_test.go b/runtimes/google/ipc/stream/manager/manager_test.go
index 84a78ee..ff9a4dc 100644
--- a/runtimes/google/ipc/stream/manager/manager_test.go
+++ b/runtimes/google/ipc/stream/manager/manager_test.go
@@ -293,6 +293,7 @@
}
}
+/* TLS + resumption + channel bindings is broken: <https://secure-resumption.com/#channelbindings>.
func TestSessionTicketCache(t *testing.T) {
server := InternalNew(naming.FixedRoutingID(0x55555555))
_, ep, err := server.Listen("tcp", "localhost:0", vc.FixedLocalID(newID("server")))
@@ -309,6 +310,7 @@
t.Fatalf("SessionTicket from TLS handshake not cached")
}
}
+*/
func TestMultipleVCs(t *testing.T) {
server := InternalNew(naming.FixedRoutingID(0x55555555))
diff --git a/runtimes/google/ipc/stream/vc/auth.go b/runtimes/google/ipc/stream/vc/auth.go
index 76e7118..28dfa8c 100644
--- a/runtimes/google/ipc/stream/vc/auth.go
+++ b/runtimes/google/ipc/stream/vc/auth.go
@@ -21,6 +21,11 @@
serverChannelEnd = "server"
)
+var (
+ authServerContextTag = []byte("VCauthS\x00")
+ authClientContextTag = []byte("VCauthC\x00")
+)
+
// authenticateAsServer executes the authentication protocol at the server and
// returns the identity of the client and server.
func authenticateAsServer(conn io.ReadWriteCloser, localID LocalID, crypter crypto.Crypter, v version.IPCVersion) (clientID, serverID security.PublicID, err error) {
@@ -30,10 +35,10 @@
if serverID, err = localID.AsServer(); err != nil {
return
}
- if err = writeIdentity(conn, serverChannelEnd, crypter, localID, serverID, v); err != nil {
+ if err = writeIdentity(conn, serverChannelEnd, authServerContextTag, crypter, localID, serverID, v); err != nil {
return
}
- clientID, err = readIdentity(conn, clientChannelEnd, crypter, v)
+ clientID, err = readIdentity(conn, clientChannelEnd, authClientContextTag, crypter, v)
return
}
@@ -44,7 +49,7 @@
// successfull iff the server identity matches the provided regular expression.
func authenticateAsClient(conn io.ReadWriteCloser, localID LocalID, crypter crypto.Crypter, v version.IPCVersion) (serverID, clientID security.PublicID, err error) {
defer conn.Close()
- if serverID, err = readIdentity(conn, serverChannelEnd, crypter, v); err != nil {
+ if serverID, err = readIdentity(conn, serverChannelEnd, authServerContextTag, crypter, v); err != nil {
return
}
// TODO(ashankar,ataly): Have the ability to avoid talking to a server we do not want to.
@@ -52,7 +57,7 @@
if clientID, err = localID.AsClient(serverID); err != nil {
return
}
- err = writeIdentity(conn, clientChannelEnd, crypter, localID, clientID, v)
+ err = writeIdentity(conn, clientChannelEnd, authClientContextTag, crypter, localID, clientID, v)
return
}
@@ -76,125 +81,170 @@
errSingleCertificateRequired = errors.New("exactly one X.509 certificate chain with exactly one certificate is required")
)
-func writeIdentity(w io.Writer, chEnd string, enc crypto.Encrypter, id LocalID, pub security.PublicID, v version.IPCVersion) error {
- // Compute channel id - encrypted chEnd string
- chid, err := enc.Encrypt(iobuf.NewSlice([]byte(chEnd)))
+func writeIdentity(w io.Writer, chEnd string, contextTag []byte, crypter crypto.Crypter, id LocalID, pub security.PublicID, v version.IPCVersion) error {
+ // TODO(ashankar): Remove references to protocol V1 and V2 before release
+ if v == version.IPCVersion1 || v == version.IPCVersion2 {
+ // Compute channel id - encrypted chEnd string
+ chid, err := crypter.Encrypt(iobuf.NewSlice([]byte(chEnd)))
+ if err != nil {
+ return err
+ }
+ defer chid.Release()
+
+ // VOM-encode and encrypt the (public) identity.
+ var buf bytes.Buffer
+ if err := vom.NewEncoder(&buf).Encode(pub); err != nil {
+ return err
+ }
+ eid, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
+ if err != nil {
+ return err
+ }
+ defer eid.Release()
+
+ // Sign the channel ID
+ signature, err := id.Sign(chid.Contents)
+ if err != nil {
+ return err
+ }
+ msg := identityMessage{
+ ID: eid.Contents,
+ ChannelID: chid.Contents,
+ }
+ if v == version.IPCVersion1 {
+ er, err := crypter.Encrypt(iobuf.NewSlice(signature.R))
+ if err != nil {
+ return err
+ }
+ defer er.Release()
+ es, err := crypter.Encrypt(iobuf.NewSlice(signature.S))
+ if err != nil {
+ return err
+ }
+ defer es.Release()
+ msg.SignR, msg.SignS = er.Contents, es.Contents
+ } else { // v == version.IPCVersion2
+ buf = bytes.Buffer{}
+ if err := vom.NewEncoder(&buf).Encode(signature); err != nil {
+ return err
+ }
+ esig, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
+ if err != nil {
+ return err
+ }
+ defer esig.Release()
+ msg.Signature = esig.Contents
+ }
+ // Write the message out.
+ return vom.NewEncoder(w).Encode(msg)
+ }
+ // v == version.IPCVersion3
+ signature, err := id.Sign(append(contextTag, crypter.ChannelBinding()...))
if err != nil {
return err
}
- defer chid.Release()
-
- // VOM-encode and encrypt the (public) identity.
var buf bytes.Buffer
- if err := vom.NewEncoder(&buf).Encode(pub); err != nil {
+ ve := vom.NewEncoder(&buf)
+ if err := ve.Encode(pub); err != nil {
return err
}
- eid, err := enc.Encrypt(iobuf.NewSlice(buf.Bytes()))
+ if err := ve.Encode(signature); err != nil {
+ return err
+ }
+ msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
if err != nil {
return err
}
- defer eid.Release()
-
- // Sign the channel ID
- signature, err := id.Sign(chid.Contents)
- if err != nil {
- return err
- }
- msg := identityMessage{
- ID: eid.Contents,
- ChannelID: chid.Contents,
- }
- if v == version.IPCVersion1 {
- er, err := enc.Encrypt(iobuf.NewSlice(signature.R))
- if err != nil {
- return err
- }
- defer er.Release()
- es, err := enc.Encrypt(iobuf.NewSlice(signature.S))
- if err != nil {
- return err
- }
- defer es.Release()
- msg.SignR, msg.SignS = er.Contents, es.Contents
- } else {
- buf = bytes.Buffer{}
- if err := vom.NewEncoder(&buf).Encode(signature); err != nil {
- return err
- }
- esig, err := enc.Encrypt(iobuf.NewSlice(buf.Bytes()))
- if err != nil {
- return err
- }
- defer esig.Release()
- msg.Signature = esig.Contents
- }
-
- // Write the message out.
- return vom.NewEncoder(w).Encode(msg)
+ defer msg.Release()
+ return vom.NewEncoder(w).Encode(msg.Contents)
}
-func readIdentity(reader io.Reader, expectedChEnd string, dec crypto.Decrypter, v version.IPCVersion) (security.PublicID, error) {
- // Read the message.
- var msg identityMessage
- if err := vom.NewDecoder(reader).Decode(&msg); err != nil {
- return nil, fmt.Errorf("failed to decode identityMessage: %v", err)
- }
+func readIdentity(reader io.Reader, expectedChEnd string, contextTag []byte, crypter crypto.Crypter, v version.IPCVersion) (security.PublicID, error) {
+ if v == version.IPCVersion1 || v == version.IPCVersion2 {
+ // Read the message.
+ var msg identityMessage
+ if err := vom.NewDecoder(reader).Decode(&msg); err != nil {
+ return nil, fmt.Errorf("failed to decode identityMessage: %v", err)
+ }
- // Decrypt and authenticate the channel id
- chEnd, err := dec.Decrypt(iobuf.NewSlice(msg.ChannelID))
- if err != nil {
- return nil, fmt.Errorf("failed to decrypt ChannelID: %v", err)
- }
- defer chEnd.Release()
- if bytes.Compare([]byte(expectedChEnd), chEnd.Contents) != 0 {
- return nil, errChannelIDMismatch
- }
+ // Decrypt and authenticate the channel id
+ chEnd, err := crypter.Decrypt(iobuf.NewSlice(msg.ChannelID))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt ChannelID: %v", err)
+ }
+ defer chEnd.Release()
+ if bytes.Compare([]byte(expectedChEnd), chEnd.Contents) != 0 {
+ return nil, errChannelIDMismatch
+ }
- // Decrypt and VOM-decode the identity
- idbytes, err := dec.Decrypt(iobuf.NewSlice(msg.ID))
- if err != nil {
- return nil, fmt.Errorf("failed to decrypt security.PublicID: %v", err)
+ // Decrypt and VOM-decode the identity
+ idbytes, err := crypter.Decrypt(iobuf.NewSlice(msg.ID))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt security.PublicID: %v", err)
+ }
+ defer idbytes.Release()
+ var id security.PublicID
+ if err := vom.NewDecoder(bytes.NewBuffer(idbytes.Contents)).Decode(&id); err != nil {
+ return nil, fmt.Errorf("failed to decode security.PublicID: %v", err)
+ }
+ if id == nil || id.PublicKey() == nil {
+ return nil, errInvalidIdentityInMessage
+ }
+
+ // Decrypt the signature
+ if v == version.IPCVersion1 {
+ r, err := crypter.Decrypt(iobuf.NewSlice(msg.SignR))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt SignR: %v", err)
+ }
+ defer r.Release()
+ s, err := crypter.Decrypt(iobuf.NewSlice(msg.SignS))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt SignS: %v", err)
+ }
+ defer s.Release()
+ if !ecdsa.Verify(id.PublicKey(), msg.ChannelID, bigInt(r), bigInt(s)) {
+ return nil, errInvalidSignatureInMessage
+ }
+ } else {
+ sigbytes, err := crypter.Decrypt(iobuf.NewSlice(msg.Signature))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt Signature: %v", err)
+ }
+ defer sigbytes.Release()
+ var sig security.Signature
+ if err = vom.NewDecoder(bytes.NewBuffer(sigbytes.Contents)).Decode(&sig); err != nil {
+ return nil, fmt.Errorf("failed to decode security.Signature: %v", err)
+ }
+ // Validate the signature
+ if !sig.Verify(id.PublicKey(), msg.ChannelID) {
+ return nil, errInvalidSignatureInMessage
+ }
+ }
+ return id, nil
}
- defer idbytes.Release()
+ // v == version.IPCVersion3
+ var encryptedHandshake []byte
+ if err := vom.NewDecoder(reader).Decode(&encryptedHandshake); err != nil {
+ return nil, fmt.Errorf("failed to read handshake: %v", err)
+ }
+ handshake, err := crypter.Decrypt(iobuf.NewSlice(encryptedHandshake))
+ if err != nil {
+ return nil, fmt.Errorf("failed to decrypt handshake: %v", err)
+ }
+ defer handshake.Release()
+ vd := vom.NewDecoder(bytes.NewBuffer(handshake.Contents))
var id security.PublicID
- if err := vom.NewDecoder(bytes.NewBuffer(idbytes.Contents)).Decode(&id); err != nil {
+ if err := vd.Decode(&id); err != nil {
return nil, fmt.Errorf("failed to decode security.PublicID: %v", err)
}
- if id == nil || id.PublicKey() == nil {
- return nil, errInvalidIdentityInMessage
+ var signature security.Signature
+ if err := vd.Decode(&signature); err != nil {
+ return nil, fmt.Errorf("failed to decode security.Signature: %v", err)
}
-
- // Decrypt the signature
- if v == version.IPCVersion1 {
- r, err := dec.Decrypt(iobuf.NewSlice(msg.SignR))
- if err != nil {
- return nil, fmt.Errorf("failed to decrypt SignR: %v", err)
- }
- defer r.Release()
- s, err := dec.Decrypt(iobuf.NewSlice(msg.SignS))
- if err != nil {
- return nil, fmt.Errorf("failed to decrypt SignS: %v", err)
- }
- defer s.Release()
- if !ecdsa.Verify(id.PublicKey(), msg.ChannelID, bigInt(r), bigInt(s)) {
- return nil, errInvalidSignatureInMessage
- }
- } else {
- sigbytes, err := dec.Decrypt(iobuf.NewSlice(msg.Signature))
- if err != nil {
- return nil, fmt.Errorf("failed to decrypt Signature: %v", err)
- }
- defer sigbytes.Release()
- var sig security.Signature
- if err = vom.NewDecoder(bytes.NewBuffer(sigbytes.Contents)).Decode(&sig); err != nil {
- return nil, fmt.Errorf("failed to decode security.Signature: %v", err)
- }
- // Validate the signature
- if !sig.Verify(id.PublicKey(), msg.ChannelID) {
- return nil, errInvalidSignatureInMessage
- }
+ if !signature.Verify(id.PublicKey(), append(contextTag, crypter.ChannelBinding()...)) {
+ return nil, errInvalidSignatureInMessage
}
-
return id, nil
}