Skip to content

Commit

Permalink
Add tests for client.go (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
andyrzhao committed Jun 13, 2022
1 parent 44aece8 commit 44c5943
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# MacOS
.DS_Store

# compiled binaries
build/bin/**
23 changes: 15 additions & 8 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// Client is a cross-platform client for the signer binary (a.k.a."EnterpriseCertSigner").
// The signer binary is OS-specific, but exposes a standard set of APIs for the client to use.
package client
Expand All @@ -20,16 +24,16 @@ const signAPI = "EnterpriseCertSigner.Sign"
const certificateChainAPI = "EnterpriseCertSigner.CertificateChain"
const publicKeyAPI = "EnterpriseCertSigner.Public"

// A Transport wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Transport struct {
// A Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}

// Close closes t's underlying ReadCloser and WriteCloser.
func (t *Transport) Close() error {
rerr := t.ReadCloser.Close()
werr := t.WriteCloser.Close()
// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
Expand Down Expand Up @@ -69,7 +73,10 @@ func (k *Key) Close() error {
if err := k.cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill signer process: %w", err)
}
return k.cmd.Wait()
if err := k.cmd.Wait(); err.Error() != "signal: killed" {
return fmt.Errorf("signer process was not killed: %w", err)
}
return nil
}

// Public returns the public key for this Key.
Expand Down Expand Up @@ -114,7 +121,7 @@ func Cred(configFilePath string) (*Key, error) {
if err != nil {
return nil, err
}
k.client = rpc.NewClient(&Transport{kout, kin})
k.client = rpc.NewClient(&Connection{kout, kin})

if err := k.cmd.Start(); err != nil {
return nil, fmt.Errorf("starting enterprise cert signer subprocess: %w", err)
Expand Down
72 changes: 72 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The tests in this file launches a mock signer binary "signer.go".
package client

import (
"bytes"
"errors"
"os"
"testing"
)

func TestClient_Cred_Success(t *testing.T) {
_, err := Cred("testdata/enterprise_certificate_config.json")
if err != nil {
t.Errorf("Cred: got %v, want nil err", err)
}
}

func TestClient_Cred_ConfigMissing(t *testing.T) {
_, err := Cred("missing.json")
if got, want := err, os.ErrNotExist; !errors.Is(got, want) {
t.Errorf("Cred: with missing config; got %v, want %v err", got, want)
}
}

func TestClient_Public(t *testing.T) {
key, err := Cred("testdata/enterprise_certificate_config.json")
if err != nil {
t.Fatal(err)
}
if key.Public() == nil {
t.Error("Public: got nil, want non-nil Public Key")
}
}

func TestClient_CertificateChain(t *testing.T) {
key, err := Cred("testdata/enterprise_certificate_config.json")
if err != nil {
t.Fatal(err)
}
if key.CertificateChain() == nil {
t.Error("CertificateChain: got nil, want non-nil Certificate Chain")
}
}

func TestClient_Sign(t *testing.T) {
key, err := Cred("testdata/enterprise_certificate_config.json")
if err != nil {
t.Fatal(err)
}
signed, err := key.Sign(nil, []byte("testDigest"), nil)
if err != nil {
t.Fatal(err)
}
if got, want := signed, []byte("testDigest"); !bytes.Equal(got, want) {
t.Errorf("Sign: got %c, want %c", got, want)
}
}

func TestClient_Close(t *testing.T) {
key, err := Cred("testdata/enterprise_certificate_config.json")
if err != nil {
t.Fatal(err)
}
err = key.Close()
if err != nil {
t.Errorf("Close: got %v, want nil err", err)
}
}
8 changes: 8 additions & 0 deletions client/testdata/enterprise_certificate_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cert_info": {
"issuer": "Test Issuer"
},
"libs": {
"signer_binary": "./testdata/signer.sh"
}
}
7 changes: 7 additions & 0 deletions client/testdata/signer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

# Copyright 2022 Google LLC.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

go run ../internal/signer/test/signer.go testdata/testcert.pem
21 changes: 21 additions & 0 deletions client/testdata/testcert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
-----END PRIVATE KEY-----
4 changes: 2 additions & 2 deletions cshared/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ func SignForPython(configFilePath *C.char, digest *byte, digestLen int, sigHolde
// Create a Go buffer around the output buffer and copy the signature into the buffer
outBytes := unsafe.Slice(sigHolder, sigHolderLen)
if sigHolderLen < len(signature) {
log.Printf("The sigHolder buffer size %d is smaller than the signature size %d", sigHolderLen, len(signature))
return 0
log.Printf("The sigHolder buffer size %d is smaller than the signature size %d", sigHolderLen, len(signature))
return 0
}
for i := 0; i < len(signature); i++ {
outBytes[i] = signature[i]
Expand Down
102 changes: 102 additions & 0 deletions internal/signer/test/signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2022 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// signer.go is a net/rpc server that listens on stdin/stdout, exposing
// mock methods for testing client.go.
package main

import (
"crypto"
"crypto/tls"
"crypto/x509"
"io"
"io/ioutil"
"log"
"net/rpc"
"os"
"time"
)

// SignArgs encapsulate the parameters for the Sign method.
type SignArgs struct {
Digest []byte
Opts crypto.SignerOpts
}

// EnterpriseCertSigner exports RPC methods for signing.
type EnterpriseCertSigner struct {
cert *tls.Certificate
}

// Connection wraps a pair of unidirectional streams as an io.ReadWriteCloser.
type Connection struct {
io.ReadCloser
io.WriteCloser
}

// Close closes c's underlying ReadCloser and WriteCloser.
func (c *Connection) Close() error {
rerr := c.ReadCloser.Close()
werr := c.WriteCloser.Close()
if rerr != nil {
return rerr
}
return werr
}

// CertificateChain returns the credential as a raw X509 cert chain. This
// contains the public key.
func (k *EnterpriseCertSigner) CertificateChain(ignored struct{}, certificateChain *[][]byte) error {
*certificateChain = k.cert.Certificate
return nil
}

// Public returns the first public key for this Key, in ASN.1 DER form.
func (k *EnterpriseCertSigner) Public(ignored struct{}, publicKey *[]byte) (err error) {
if len(k.cert.Certificate) == 0 {
return nil
}
cert, err := x509.ParseCertificate(k.cert.Certificate[0])
if err != nil {
return err
}
*publicKey, err = x509.MarshalPKIXPublicKey(cert.PublicKey)
return err
}

// Sign signs a message by encrypting a message digest.
func (k *EnterpriseCertSigner) Sign(args SignArgs, resp *[]byte) (err error) {
*resp = args.Digest
return nil
}

func main() {
enterpriseCertSigner := new(EnterpriseCertSigner)

data, err := ioutil.ReadFile(os.Args[1])
if err != nil {
log.Fatalf("Error reading certificate: %v", err)
}
cert, _ := tls.X509KeyPair(data, data)

enterpriseCertSigner.cert = &cert

if err := rpc.Register(enterpriseCertSigner); err != nil {
log.Fatalf("Error registering net/rpc: %v", err)
}

// If the parent process dies, we should exit.
// We can detect this by periodically checking if the PID of the parent
// process is 1 (https://stackoverflow.com/a/2035683).
go func() {
for {
if os.Getppid() == 1 {
log.Fatalln("Parent process died, exiting...")
}
time.Sleep(time.Second)
}
}()

rpc.ServeConn(&Connection{os.Stdin, os.Stdout})
}

0 comments on commit 44c5943

Please sign in to comment.