Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions core/client/gobindclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 gobindclient contains a gobind friendly implementation of a KeyTransparency Client able to make
// GetEntry requests to a KT server and verify the soundness of the responses.
package gobindclient

import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"net"
"os"
"time"

"github.com/google/keytransparency/cmd/keytransparency-client/grpcc"
"github.com/google/keytransparency/core/client/kt"
"github.com/google/keytransparency/core/client/multiWriter"

"github.com/benlaurie/objecthash/go/objecthash"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

tpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types"
spb "github.com/google/keytransparency/impl/proto/keytransparency_v1_service"
_ "github.com/google/trillian/merkle/coniks" // Register coniks
_ "github.com/google/trillian/merkle/objhasher" // Used to init the package so that the hasher gets registered
)

var (
clients = make(map[string]*grpcc.Client)

timeout = 500 * time.Millisecond

multiLogWriter = multiWriter.New(os.Stderr)

// Vlog is the verbose logger. By default it outputs to stderr (logcat on Android), but other destination can be
// added through the AddVerboseLogsDestination method.
Vlog = log.New(multiLogWriter, "", log.LstdFlags)
)

func init() {
kt.Vlog = log.New(multiLogWriter, "", log.LstdFlags)
}

// AddVerboseLogsDestination instructs the logger of the gobindclient package to also write all log statements to the provided writer.
func AddVerboseLogsDestination(writer LogWriter) {
multiLogWriter.AddWriter(writer)
}

// LogWriter is a local copy of the io.Writer interface which can be implemented in Java. Used to redirect logs.
type LogWriter interface {
Write(p []byte) (n int, err error)
}

// SetTimeout sets the timeout (in milliseconds) used for all rpc network requests.
func SetTimeout(ms int32) {
timeout = time.Duration(ms) * time.Millisecond
}

// AddKtServer creates a new grpc client to handle connections to the ktURL server and adds it to the global map of clients.
func AddKtServer(ktURL string, insecureTLS bool, ktTLSCertPEM []byte, domainInfoHash []byte) error {
if _, exists := clients[ktURL]; exists {
return fmt.Errorf("The KtServer connection for %v already exists", ktURL)
}

// TODO Add URL validation here.

cc, err := dial(ktURL, insecureTLS, ktTLSCertPEM)
if err != nil {
return fmt.Errorf("Error Dialing %v: %v", ktURL, err)
}

ktClient := spb.NewKeyTransparencyServiceClient(cc)

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
config, err := ktClient.GetDomainInfo(ctx, &tpb.GetDomainInfoRequest{})
if err != nil {
return fmt.Errorf("Error getting config: %v", err)
}

if len(domainInfoHash) == 0 {
Vlog.Print("Warning: no domainInfoHash provided. Key material from the server will be trusted.")
} else {
if got := objecthash.ObjectHash(config); !bytes.Equal(got[:], domainInfoHash) {
return fmt.Errorf("The KtServer %v returned a domainInfoResponse inconsistent with the provided domainInfoHash", ktURL)
}
}

client, err := grpcc.NewFromConfig(cc, config)
if err != nil {
return fmt.Errorf("Error adding the KtServer: %v", err)
}

clients[ktURL] = client
return nil
}

// GetEntry retrieves an entry from the ktURL server and verifies the soundness of the corresponding proofs.
func GetEntry(ktURL, userID, appID string) ([]byte, error) {
client, exists := clients[ktURL]
if !exists {
return nil, fmt.Errorf("A connection to %v does not exists. Please call BAddKtServer first", ktURL)
}

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
entry, smr, err := client.GetEntry(ctx, userID, appID)
if err != nil {
return nil, fmt.Errorf("GetEntry failed: %v", err)
}
// TODO(amarcedone): Consider returning or persisting smr it to verify consistency over time
_ = smr
//encodedSmr, err := proto.Marshal(smr)
//if err != nil {
// return nil, fmt.Errorf("GetEntry failed: error serializing smr: %v", err)
//}

return entry, nil
}

func dial(ktURL string, insecureTLS bool, ktTLSCertPEM []byte) (*grpc.ClientConn, error) {

creds, err := transportCreds(ktURL, insecureTLS, ktTLSCertPEM)
if err != nil {
return nil, err
}

cc, err := grpc.Dial(ktURL, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, err
}
return cc, nil
}

func transportCreds(ktURL string, insecure bool, ktTLSCertPEM []byte) (credentials.TransportCredentials, error) {

host, _, err := net.SplitHostPort(ktURL)
if err != nil {
return nil, err
}

switch {
case insecure: // Impatient insecure.
Vlog.Printf("Warning: Skipping verification of KT Server's TLS certificate.")
return credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
}), nil

case len(ktTLSCertPEM) != 0: // Custom CA Cert.
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(ktTLSCertPEM) {
return nil, fmt.Errorf("Failed to append certificates")
}
creds := credentials.NewTLS(&tls.Config{ServerName: host, RootCAs: cp})
return creds, nil

default: // Use the local set of root certs.
return credentials.NewClientTLSFromCert(nil, host), nil
}
}
65 changes: 65 additions & 0 deletions core/client/multiWriter/multiWriter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 multiWriter

import (
"errors"
"fmt"
"io"
)

// MultiWriter contains a list of io.Writer objects. Its Write method tries to write to all of them, and aggregates
// the errors (if any of the write call fails).
type MultiWriter interface {
AddWriter(w io.Writer)
Write(p []byte) (n int, err error)
}

// New returns an implementation of the MultiWriter interface
func New(w io.Writer) MultiWriter {
return &multiIoWriter{[]io.Writer{w}}
}

type multiIoWriter struct {
writers []io.Writer
}

func (m *multiIoWriter) Write(p []byte) (n int, err error) {
if len(m.writers) == 0 {
return 0, fmt.Errorf("Tried to use a MultiIoWriter which does not contain any writers")
}
multiError := ""
minBytesWritten := len(p)
for _, w := range m.writers {
n, err = w.Write(p)
if err != nil {
multiError = multiError + fmt.Sprintf("%v bytes written to %v: %v", n, w, err)
}
minBytesWritten = min(n, minBytesWritten)
}

return minBytesWritten, errors.New(multiError)
}

func (m *multiIoWriter) AddWriter(w io.Writer) {
m.writers = append(m.writers, w)
}

func min(a, b int) int {
if a <= b {
return a
}
return b
}