Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg committed Sep 18, 2019
0 parents commit 122f260
Show file tree
Hide file tree
Showing 13 changed files with 679 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/.idea/
/*.iml

/*.pem

/dist/
/grpc-authenticated-greeter
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Geoff Bourne

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Building

```shell script
go build
```

## Example usage

Generate CA, server, and client certs
```shell script
./grpc-authenticated-greeter gencerts
```

Start the server on port 7676:
```shell script
./grpc-authenticated-greeter server \
--ca ca_cert.pem --cert server_cert.pem --key server_key.pem \
--binding :7676
```

In another terminal, run a client:
```shell script
./grpc-authenticated-greeter client \
--ca ca_cert.pem --cert client1_cert.pem --key client1_key.pem \
--serveraddress 127.0.0.1:7676 --servername server \
--message "Read me"
```

The client should log the response from the server, such as:
```
INFO[0000] got response response="Hello, client1. You said 'Read me'"
```
145 changes: 145 additions & 0 deletions certs/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package certs

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"github.com/sirupsen/logrus"
"math/big"
"os"
"time"
)

func Generate() {
serialNumber := big.NewInt(1)

caCert, caKey, err := generateCaCert(serialNumber)
if err != nil {
logrus.WithError(err).Fatal("generating CA key and cert")
}

serialNumber.Add(serialNumber, big.NewInt(1))
err = generateCert(caCert, caKey, serialNumber, x509.ExtKeyUsageServerAuth, "server")
if err != nil {
logrus.WithError(err).Fatal("generating server key and cert")
}

serialNumber.Add(serialNumber, big.NewInt(1))
err = generateCert(caCert, caKey, serialNumber, x509.ExtKeyUsageClientAuth, "client1")
if err != nil {
logrus.WithError(err).Fatal("generating client key and cert")
}

serialNumber.Add(serialNumber, big.NewInt(1))
err = generateCert(caCert, caKey, serialNumber, x509.ExtKeyUsageClientAuth, "client2")
if err != nil {
logrus.WithError(err).Fatal("generating client key and cert")
}
}

func generateCert(caCert *x509.Certificate, caKey interface{}, serialNumber *big.Int, usage x509.ExtKeyUsage, subjectCn string) error {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return fmt.Errorf("generating client key: %w", err)
}

certTemplate := &x509.Certificate{
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{usage},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
AuthorityKeyId: caCert.SubjectKeyId,
SignatureAlgorithm: x509.SHA512WithRSA,
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: subjectCn,
},
}

clientCertDer, err := x509.CreateCertificate(rand.Reader, certTemplate, caCert, key.Public(), caKey)
if err != nil {
return fmt.Errorf("creating client cert: %w", err)
}

err = writeDerToPemFile(clientCertDer, "CERTIFICATE", fmt.Sprintf("%s_cert.pem", subjectCn))
if err != nil {
return fmt.Errorf("writing client cert: %w", err)
}

keyDer, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return fmt.Errorf("marshaling private key: %w", err)
}
err = writeDerToPemFile(keyDer, "PRIVATE KEY", fmt.Sprintf("%s_key.pem", subjectCn))
if err != nil {
return fmt.Errorf("writing private key file: %w", err)
}

return nil
}

func generateCaCert(serialNumber *big.Int) (*x509.Certificate, interface{}, error) {
caKey, err := rsa.GenerateKey(rand.Reader, 2048)

if err != nil {
return nil, nil, fmt.Errorf("generating CA key: %w", err)
}
certTemplate := &x509.Certificate{
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
SignatureAlgorithm: x509.SHA512WithRSA,
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "ca",
},
}
caCertDer, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, caKey.Public(), caKey)
if err != nil {
return nil, nil, fmt.Errorf("creating CA cert: %w", err)
}
err = writeDerToPemFile(caCertDer, "CERTIFICATE", "ca_cert.pem")
if err != nil {
return nil, nil, fmt.Errorf("writing CA cert file: %w", err)
}
caKeyDer, err := x509.MarshalPKCS8PrivateKey(caKey)
if err != nil {
return nil, nil, fmt.Errorf("marshaling CA key: %w", err)
}
err = writeDerToPemFile(caKeyDer, "PRIVATE KEY", "ca_key.pem")
if err != nil {
return nil, nil, fmt.Errorf("writing CA key file: %w", err)
}

caCert, err := x509.ParseCertificate(caCertDer)
if err != nil {
return nil, nil, fmt.Errorf("parsing CA cert: %w", err)
}

return caCert, caKey, nil
}

func writeDerToPemFile(derBytes []byte, pemType string, filename string) error {
logrus.Infof("Writing to %s", filename)

file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("creating file: %w", err)
}
//noinspection GoUnhandledErrorResult
defer file.Close()
err = pem.Encode(file, &pem.Block{
Type: pemType,
Bytes: derBytes,
})
if err != nil {
return fmt.Errorf("encoding pem: %w", err)
}

return nil
}
46 changes: 46 additions & 0 deletions client/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package client

import (
"context"
"github.com/itzg/grpc-authenticated-greeter/common"
"github.com/itzg/grpc-authenticated-greeter/protocol"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"net"
"time"
)

func Run(caCert string, privateKey string, privateCert string, serverAddress string, serverName string, message string) {

if serverName == "" {
host, _, err := net.SplitHostPort(serverAddress)
if err != nil {
logrus.WithError(err).Fatal("Unable to split server address")
}

serverName = host
}

tlsConfig, err := common.LoadClientTlsConfig(caCert, privateKey, privateCert, serverName)
if err != nil {
logrus.WithError(err).Fatal("loading client tls config")
}

conn, err := grpc.Dial(serverAddress, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
logrus.WithError(err).Fatal("connecting")
}

ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)

client := protocol.NewHelloServiceClient(conn)
response, err := client.SayHello(ctx, &protocol.HelloRequest{
Greeting: message,
})
if err != nil {
logrus.WithError(err).Warn("hello failed")
} else {
logrus.WithField("response", response.Reply).Info("got response")
}
}
50 changes: 50 additions & 0 deletions common/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package common

import (
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
"os"
)

func LoadCertPool(caCert string) (*x509.CertPool, error) {
certPool := x509.NewCertPool()

file, err := os.Open(caCert)
if err != nil {
return nil, err
}
//noinspection GoUnhandledErrorResult
defer file.Close()

pemBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}

ok := certPool.AppendCertsFromPEM(pemBytes)
if !ok {
return nil, errors.New("unable to add CA certs")
}

return certPool, nil
}

func LoadClientTlsConfig(caCert string, privateKey string, privateCert string, serverNameOverride string) (*tls.Config, error) {
certPool, err := LoadCertPool(caCert)
if err != nil {
return nil, err
}

cert, err := tls.LoadX509KeyPair(privateCert, privateKey)
if err != nil {
return nil, err
}

return &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: certPool,
ServerName: serverNameOverride,
}, nil
}
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/itzg/grpc-authenticated-greeter

go 1.13

require (
github.com/alexflint/go-arg v1.1.0
github.com/golang/protobuf v1.3.2
github.com/sirupsen/logrus v1.4.2
golang.org/x/net v0.0.0-20190916140828-c8589233b77d // indirect
golang.org/x/sys v0.0.0-20190916141854-1a3b71a79e4a // indirect
golang.org/x/text v0.3.2 // indirect
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 // indirect
google.golang.org/grpc v1.23.1
)
57 changes: 57 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"github.com/alexflint/go-arg"
"github.com/itzg/grpc-authenticated-greeter/certs"
"github.com/itzg/grpc-authenticated-greeter/client"
"github.com/itzg/grpc-authenticated-greeter/server"
"os"
)

type ClientServerArgs struct {
Ca string `arg:"required" help:"PEM file containing the CA cert shared by server and clients"`
Key string `arg:"required" help:"PEM file containing private key"`
Cert string `arg:"required" help:"PEM file containing public certificate"`
}

type ServerCmd struct {
ClientServerArgs
Binding string `arg:"required" help:"host:port of server binding where host is optional"`
}

type ClientCmd struct {
ClientServerArgs
ServerAddress string `arg:"required" help:"host:port of the gRPC server"`
ServerName string `help:"SNI name to use when contacting the server. If not set, host from --serveraddress is used"`
Message string `arg:"required" help:"Any message you want to send to the server"`
}

type GenCerts struct {
// no args needed
}

var args struct {
Client *ClientCmd `arg:"subcommand:client" help:"Runs the gRPC client and sends authenticated hello request"`
Server *ServerCmd `arg:"subcommand:server" help:"Runs the gRPC server"`
GenCerts *GenCerts `arg:"subcommand:gencerts" help:"Generate CA, server, and client certs for testing"`
}

func main() {
parser := arg.MustParse(&args)

switch {
case args.Client != nil:
client.Run(args.Client.Ca, args.Client.Key, args.Client.Cert, args.Client.ServerAddress,
args.Client.ServerName, args.Client.Message)

case args.Server != nil:
server.Run(args.Server.Ca, args.Server.Key, args.Server.Cert, args.Server.Binding)

case args.GenCerts != nil:
certs.Generate()

default:
parser.WriteHelp(os.Stdout)
os.Exit(1)
}
}
Loading

0 comments on commit 122f260

Please sign in to comment.