203 lines
4.5 KiB
Go
203 lines
4.5 KiB
Go
|
// Copyright (C) 2018 Storj Labs, Inc.
|
||
|
// See LICENSE for copying information.
|
||
|
|
||
|
package peertls
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/zeebo/errs"
|
||
|
"google.golang.org/grpc"
|
||
|
"google.golang.org/grpc/credentials"
|
||
|
)
|
||
|
|
||
|
type fileRole int
|
||
|
|
||
|
const (
|
||
|
rootCert fileRole = iota
|
||
|
rootKey
|
||
|
leafCert
|
||
|
leafKey
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
fileLabels = map[fileRole]string{
|
||
|
rootCert: "root certificate",
|
||
|
rootKey: "root key",
|
||
|
leafCert: "leaf certificate",
|
||
|
leafKey: "leaf key",
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// EnsureAbsPath ensures that the absolute path fields are not empty, deriving them from the relative paths if not
|
||
|
func (t *TLSFileOptions) EnsureAbsPaths() error {
|
||
|
for _, role := range t.requiredFiles() {
|
||
|
for absPtr, relPath := range t.pathMap() {
|
||
|
if t.pathRoleMap()[absPtr] == role {
|
||
|
if *absPtr == "" {
|
||
|
if relPath == "" {
|
||
|
return ErrTLSOptions.New("no relative %s path provided", fileLabels[t.pathRoleMap()[absPtr]])
|
||
|
}
|
||
|
|
||
|
absPath, err := filepath.Abs(relPath)
|
||
|
if err != nil {
|
||
|
return ErrTLSOptions.Wrap(err)
|
||
|
}
|
||
|
|
||
|
*absPtr = absPath
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// EnsureExists checks whether the cert/key exists and whether to create/overwrite them given `t`s field values.
|
||
|
func (t *TLSFileOptions) EnsureExists() error {
|
||
|
if err := t.EnsureAbsPaths(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
hasRequiredFiles, err := t.hasRequiredFiles()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if t.Create && !t.Overwrite && hasRequiredFiles {
|
||
|
return ErrNoOverwrite.New("certificates and keys exist; refusing to create without overwrite")
|
||
|
}
|
||
|
|
||
|
// NB: even when `overwrite` is false, this WILL overwrite
|
||
|
// a key if the cert is missing (vice versa)
|
||
|
if t.Create && (t.Overwrite || !hasRequiredFiles) {
|
||
|
if err := t.generateTLS(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if !hasRequiredFiles {
|
||
|
missing, _ := t.missingFiles()
|
||
|
|
||
|
return ErrNotExist.New(fmt.Sprintf(strings.Join(missing, ", ")))
|
||
|
}
|
||
|
|
||
|
// NB: ensure `t.Certificate` is not nil when create is false
|
||
|
if !t.Create {
|
||
|
t.loadTLS()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) NewTLSConfig(c *tls.Config) *tls.Config {
|
||
|
config := cloneTLSConfig(c)
|
||
|
|
||
|
config.Certificates = []tls.Certificate{*t.LeafCertificate}
|
||
|
// Skip normal verification
|
||
|
config.InsecureSkipVerify = true
|
||
|
// Required client certificate
|
||
|
config.ClientAuth = tls.RequireAnyClientCert
|
||
|
// Custom verification logic for *both* client and server
|
||
|
config.VerifyPeerCertificate = VerifyPeerCertificate
|
||
|
|
||
|
return config
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) NewPeerTLS(config *tls.Config) credentials.TransportCredentials {
|
||
|
return credentials.NewTLS(t.NewTLSConfig(config))
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) DialOption() grpc.DialOption {
|
||
|
return grpc.WithTransportCredentials(t.NewPeerTLS(nil))
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) ServerOption() grpc.ServerOption {
|
||
|
return grpc.Creds(t.NewPeerTLS(nil))
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) loadTLS() (_ error) {
|
||
|
leafC, err := LoadCert(t.LeafCertAbsPath, t.LeafKeyAbsPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
t.LeafCertificate = leafC
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) missingFiles() ([]string, error) {
|
||
|
missingFiles := []string{}
|
||
|
|
||
|
paths := map[fileRole]string{
|
||
|
rootCert: t.RootCertAbsPath,
|
||
|
rootKey: t.RootKeyAbsPath,
|
||
|
leafCert: t.LeafCertAbsPath,
|
||
|
leafKey: t.LeafKeyAbsPath,
|
||
|
}
|
||
|
|
||
|
requiredFiles := t.requiredFiles()
|
||
|
|
||
|
for _, requiredRole := range requiredFiles {
|
||
|
for role, path := range paths {
|
||
|
if role == requiredRole {
|
||
|
if _, err := os.Stat(path); err != nil {
|
||
|
if !IsNotExist(err) {
|
||
|
return nil, errs.Wrap(err)
|
||
|
}
|
||
|
|
||
|
missingFiles = append(missingFiles, fileLabels[role])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return missingFiles, nil
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) requiredFiles() []fileRole {
|
||
|
var roles = []fileRole{}
|
||
|
|
||
|
// rootCert is always required
|
||
|
roles = append(roles, rootCert, leafCert, leafKey)
|
||
|
|
||
|
if t.Create {
|
||
|
// required for writing rootKey when create is true
|
||
|
roles = append(roles, rootKey)
|
||
|
}
|
||
|
return roles
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) hasRequiredFiles() (bool, error) {
|
||
|
missingFiles, err := t.missingFiles()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
return len(missingFiles) == 0, nil
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) pathMap() map[*string]string {
|
||
|
return map[*string]string{
|
||
|
&t.RootCertAbsPath: t.RootCertRelPath,
|
||
|
&t.RootKeyAbsPath: t.RootKeyRelPath,
|
||
|
&t.LeafCertAbsPath: t.LeafCertRelPath,
|
||
|
&t.LeafKeyAbsPath: t.LeafKeyRelPath,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *TLSFileOptions) pathRoleMap() map[*string]fileRole {
|
||
|
return map[*string]fileRole{
|
||
|
&t.RootCertAbsPath: rootCert,
|
||
|
&t.RootKeyAbsPath: rootKey,
|
||
|
&t.LeafCertAbsPath: leafCert,
|
||
|
&t.LeafKeyAbsPath: leafKey,
|
||
|
}
|
||
|
}
|