1
0
Fork 0
mirror of https://github.com/postmannen/ctrl.git synced 2024-12-14 12:37:31 +00:00
ctrl/node_auth.go
2022-05-12 11:32:12 +02:00

308 lines
7.8 KiB
Go

package steward
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"sync"
)
type signature string
// allowedSignatures is the structure for reading and writing from
// the signatures map. It holds a mutex to use when interacting with
// the map.
type nodeAuth struct {
// All the allowed signatures a node is allowed to recive from.
allowedSignatures *allowedSignatures
// All the public keys for nodes a node is allowed to receive from.
publicKeys *publicKeys
// Full path to the signing keys folder
SignKeyFolder string
// Full path to private signing key.
SignKeyPrivateKeyPath string
// Full path to public signing key.
SignKeyPublicKeyPath string
// private key for ed25519 signing.
SignPrivateKey []byte
// public key for ed25519 signing.
SignPublicKey []byte
configuration *Configuration
errorKernel *errorKernel
}
func newNodeAuth(configuration *Configuration, errorKernel *errorKernel) *nodeAuth {
n := nodeAuth{
allowedSignatures: newAllowedSignatures(),
publicKeys: newPublicKeys(configuration),
configuration: configuration,
errorKernel: errorKernel,
}
// Set the signing key paths.
n.SignKeyFolder = filepath.Join(configuration.ConfigFolder, "signing")
n.SignKeyPrivateKeyPath = filepath.Join(n.SignKeyFolder, "private.key")
n.SignKeyPublicKeyPath = filepath.Join(n.SignKeyFolder, "public.key")
err := n.loadSigningKeys()
if err != nil {
log.Printf("%v\n", err)
os.Exit(1)
}
return &n
}
type allowedSignatures struct {
// allowed is a map for holding all the allowed signatures.
allowed map[signature]Node
mu sync.Mutex
}
func newAllowedSignatures() *allowedSignatures {
a := allowedSignatures{
allowed: make(map[signature]Node),
}
return &a
}
type publicKeys struct {
// nodesKey is a map who holds all the public keys for nodes.
NodeKeys map[Node][]byte
Hash [32]byte
mu sync.Mutex
filePath string
}
func newPublicKeys(c *Configuration) *publicKeys {
p := publicKeys{
NodeKeys: make(map[Node][]byte),
filePath: filepath.Join(c.DatabaseFolder, "publickeys.txt"),
}
err := p.loadFromFile()
if err != nil {
log.Printf("error: loading public keys from file: %v\n", err)
// os.Exit(1)
}
return &p
}
// loadFromFile will try to load all the currently stored public keys from file,
// and return the error if it fails.
// If no file is found a nil error is returned.
func (p *publicKeys) loadFromFile() error {
if _, err := os.Stat(p.filePath); os.IsNotExist(err) {
// Just logging the error since it is not crucial that a key file is missing,
// since a new one will be created on the next update.
log.Printf("no public keys file found at %v\n", p.filePath)
return nil
}
fh, err := os.OpenFile(p.filePath, os.O_RDONLY, 0600)
if err != nil {
return fmt.Errorf("error: failed to open public keys file: %v", err)
}
defer fh.Close()
b, err := io.ReadAll(fh)
if err != nil {
return err
}
p.mu.Lock()
defer p.mu.Unlock()
err = json.Unmarshal(b, &p.NodeKeys)
if err != nil {
return err
}
fmt.Printf("\n ***** DEBUG: Loaded existing keys from file: %v\n\n", p.NodeKeys)
return nil
}
// saveToFile will save all the public kets to file for persistent storage.
// An error is returned if it fails.
func (p *publicKeys) saveToFile() error {
fh, err := os.OpenFile(p.filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("error: failed to open public keys file: %v", err)
}
defer fh.Close()
p.mu.Lock()
defer p.mu.Unlock()
b, err := json.Marshal(p.NodeKeys)
if err != nil {
return err
}
_, err = fh.Write(b)
if err != nil {
return err
}
return nil
}
// loadSigningKeys will try to load the ed25519 signing keys. If the
// files are not found new keys will be generated and written to disk.
func (n *nodeAuth) loadSigningKeys() error {
// Check if folder structure exist, if not create it.
if _, err := os.Stat(n.SignKeyFolder); os.IsNotExist(err) {
err := os.MkdirAll(n.SignKeyFolder, 0700)
if err != nil {
er := fmt.Errorf("error: failed to create directory for signing keys : %v", err)
return er
}
}
// Check if there already are any keys in the etc folder.
foundKey := false
if _, err := os.Stat(n.SignKeyPublicKeyPath); !os.IsNotExist(err) {
foundKey = true
}
if _, err := os.Stat(n.SignKeyPrivateKeyPath); !os.IsNotExist(err) {
foundKey = true
}
// If no keys where found generete a new pair, load them into the
// processes struct fields, and write them to disk.
if !foundKey {
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
er := fmt.Errorf("error: failed to generate ed25519 keys for signing: %v", err)
return er
}
pubB64string := base64.RawStdEncoding.EncodeToString(pub)
privB64string := base64.RawStdEncoding.EncodeToString(priv)
// Write public key to file.
err = n.writeSigningKey(n.SignKeyPublicKeyPath, pubB64string)
if err != nil {
return err
}
// Write private key to file.
err = n.writeSigningKey(n.SignKeyPrivateKeyPath, privB64string)
if err != nil {
return err
}
// Also store the keys in the processes structure so we can
// reference them from there when we need them.
n.SignPublicKey = pub
n.SignPrivateKey = priv
er := fmt.Errorf("info: no signing keys found, generating new keys")
log.Printf("%v\n", er)
// We got the new generated keys now, so we can return.
return nil
}
// Key files found, load them into the processes struct fields.
pubKey, _, err := n.readKeyFile(n.SignKeyPublicKeyPath)
if err != nil {
return err
}
n.SignPublicKey = pubKey
privKey, _, err := n.readKeyFile(n.SignKeyPrivateKeyPath)
if err != nil {
return err
}
n.SignPublicKey = pubKey
n.SignPrivateKey = privKey
return nil
}
// writeSigningKey will write the base64 encoded signing key to file.
func (n *nodeAuth) writeSigningKey(realPath string, keyB64 string) error {
fh, err := os.OpenFile(realPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
er := fmt.Errorf("error: failed to open key file for writing: %v", err)
return er
}
defer fh.Close()
_, err = fh.Write([]byte(keyB64))
if err != nil {
er := fmt.Errorf("error: failed to write key to file: %v", err)
return er
}
return nil
}
// readKeyFile will take the path of a key file as input, read the base64
// encoded data, decode the data. It will return the raw data as []byte,
// the base64 encoded data, and any eventual error.
func (n *nodeAuth) readKeyFile(keyFile string) (ed2519key []byte, b64Key []byte, err error) {
fh, err := os.Open(keyFile)
if err != nil {
er := fmt.Errorf("error: failed to open key file: %v", err)
return nil, nil, er
}
defer fh.Close()
b, err := ioutil.ReadAll(fh)
if err != nil {
er := fmt.Errorf("error: failed to read key file: %v", err)
return nil, nil, er
}
key, err := base64.RawStdEncoding.DecodeString(string(b))
if err != nil {
er := fmt.Errorf("error: failed to base64 decode key data: %v", err)
return nil, nil, er
}
return key, b, nil
}
// verifySignature
func (n *nodeAuth) verifySignature(m Message) bool {
// fmt.Printf(" * DEBUG: verifySignature, method: %v\n", m.Method)
if !n.configuration.EnableSignatureCheck {
// fmt.Printf(" * DEBUG: verifySignature: AllowEmptySignature set to TRUE\n")
return true
}
// TODO: Only enable signature checking for REQCliCommand for now.
if m.Method != REQCliCommand {
// fmt.Printf(" * DEBUG: verifySignature: WAS OTHER THAN CLI COMMAND\n")
return true
}
// Verify if the signature matches.
argsStringified := argsToString(m.MethodArgs)
ok := ed25519.Verify(n.SignPublicKey, []byte(argsStringified), m.ArgSignature)
// fmt.Printf(" * DEBUG: verifySignature, result: %v, fromNode: %v, method: %v\n", ok, m.FromNode, m.Method)
return ok
}
// argsToString takes args in the format of []string and returns a string.
func argsToString(args []string) string {
return strings.Join(args, " ")
}