-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 122f260
Showing
13 changed files
with
679 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/.idea/ | ||
/*.iml | ||
|
||
/*.pem | ||
|
||
/dist/ | ||
/grpc-authenticated-greeter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.