Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
kms.go 5.79 KiB
// This package kms implements a simplistic key managment system (kms) for
// Quantum Key Distribution Networks (QKDN) which is a simple emulated KMS. x
// It relies on the emulated quantum link out of the quantumlayer package

package kms

import (
	"crypto/rand"
	"encoding/binary"
	"log"
	"sync"
	"time"

	pbETSI "code.fbi.h-da.de/m.stiemerling/proto-kms/kmsetsiproto"
	pbIC "code.fbi.h-da.de/m.stiemerling/proto-kms/kmsintercomproto"
	"code.fbi.h-da.de/m.stiemerling/proto-kms/quantumlayer"
	"github.com/google/uuid"
)

type Qkdnkms interface {
	//AddExternalNotifierGeneral(chan bool)   // used to indicate unspecific changes
	AddExternalNotifierQLE(chan uint32)     // used to indicate changes to specific Quantum Link Element (QLE)
	AddExternalNotifierKMSPeer(chan string) // used to indicate changes to specific KMSPeer
	AddQuantumElement() *QuantumElement
	GlobalKeyHandler(time.Duration) error
	AddPeer(kmsPeerSocket string, servingQLE *QuantumElement)
	RemovePeer(kmsPeerSocket string)
}

type qlElementLinkID int

// The general emulated KMS
type EKMS struct {
	kmsName                 string
	kmsUUID                 uuid.UUID
	qleMapMutex             sync.Mutex
	QuantumElements         map[uint32]*QuantumElement
	externalNotifierQLE     chan uint32
	kmsPeersMutex           sync.Mutex
	KmsPeers                map[string]*kmsPeer
	externalNotifierKMSPeer chan string
	pbETSI.UnimplementedKmsETSIServer
	pbIC.UnimplementedKmsTalkerServer
}

// Will keep information about the quantum elements that this EKMS is talking to
// This actually constitutes a quantum element with only a single link

/*
type QuantumElementInterface interface {
	GetQlID() qlElementId
}*/

type QuantumElement struct {
	QlID               uint32
	QuantumElementLink *quantumlayer.QuantumlayerEmuPRNG // contains information about the quantum links
	//key stores go here
	keyStoreLocal  *kmsKeyStore // the keys this local entity has produced and are ready to use
	keyStoreRemote *kmsKeyStore // the keys th remote entity (peer) has produced and are ready to use
}

func NewEKMS(kmsName string, kmsUUID uuid.UUID) (newEKMS *EKMS) {

	return &EKMS{
		kmsName:                 kmsName,
		kmsUUID:                 kmsUUID,
		QuantumElements:         make(map[uint32]*QuantumElement),
		KmsPeers:                make(map[string]*kmsPeer),
		externalNotifierQLE:     nil, // just be surely set to nil!
		externalNotifierKMSPeer: nil, // just be surely set to nil!
	}
}

func (kms *EKMS) AddQuantumElement(kmsUDPAddrr string) *QuantumElement {

	//Get an emulated Quantumlayer
	ql := quantumlayer.NewQuantumlayerEmuPRNG()
	ql.Configure(kmsUDPAddrr)
	ql.PowerOn()

	ksl := kmsKeyStore{
		keyStore: make(map[string]kmsKSElement),
	}
	ksr := kmsKeyStore{
		keyStore: make(map[string]kmsKSElement),
	}

	qle := QuantumElement{
		QuantumElementLink: ql,
		keyStoreLocal:      &ksl,
		keyStoreRemote:     &ksr,
	}

	// generate a ID for this quantum element that is unique locally
	var randError error
	qle.QlID, randError = kms.GenerateNewQleID()
	if randError != nil {
		log.Fatalf("GenerateNewQleID: %s", randError)
		return nil
	}

	kms.QuantumElements[qle.QlID] = &qle

	return &qle
}

func (kms *EKMS) GlobalKeyHandler(waitTime time.Duration) error {

	// periodically walk through QuantumElements and retrieve their
	// - local key bulk buffer
	// - remote key bulk buffer
	// feed this into the corresponding key buffers of the kmss
	for {
		for currentQE := range kms.QuantumElements {
			log.Printf("%s GlobalKeyHandler reading...\n", kms.kmsName)

			bulkKeysLocal, err := kms.QuantumElements[currentQE].QuantumElementLink.GetKeyBatchLocal()
			if err != nil {
				log.Printf("%s failed to retrieve local bulkkeys with error %s", kms.kmsName, err)
			} else {
				// process bulkKeysLocal
				log.Printf("%s produced %d bytes of key locally", kms.kmsName, bulkKeysLocal.BulkKeyLength)
				kms.QuantumElements[currentQE].keyStoreLocal.KeyChopper256Bit(&bulkKeysLocal)

			}

			bulkKeysRemote, err := kms.QuantumElements[currentQE].QuantumElementLink.GetKeyBatchPeer()
			if err != nil {
				log.Printf("%s failed to retrieve remote bulkkeys with error %s", kms.kmsName, err)
			} else {
				// process bulkKeysRemote
				log.Printf("%s received %d bytes of key from remote peer", kms.kmsName, bulkKeysRemote.BulkKeyLength)
				kms.QuantumElements[currentQE].keyStoreRemote.KeyChopper256Bit(&bulkKeysRemote)
			}
		}
		time.Sleep(waitTime)
	}
}

// This has a design flaw, as the generated ID is returned to the calling function and used there.
// However, when being used a potential other caller might received the same qlElementId
// TODO/XXX: This would be collision and must be eventually avoided
func (kms *EKMS) GenerateNewQleID() (uint32, error) {
	for { // this needs a condiction to stop!
		// create buffer for uint32, so reserve 4 bytes
		buf := make([]byte, 4)

		// fill the buffer from rand
		_, err := rand.Read(buf)
		if err != nil {
			return 0, err
		}

		propopsedQlElementID := binary.BigEndian.Uint32(buf)

		// check if ID is already taken
		if kms.QuantumElements[propopsedQlElementID] == nil {
			return propopsedQlElementID, nil
		}
		//keep going....
	}
}

// TODO/XXX error handling
func (kms *EKMS) AddPeer(kmsPeerSocket string, servingQLE *QuantumElement) {
	//check if peer exists
	if _, there := kms.KmsPeers[kmsPeerSocket]; there {
		log.Printf("Trying to add existing peer %s", kmsPeerSocket)
		return
	}
	peer := NewKmsPeer(servingQLE, kms.externalNotifierKMSPeer)
	peer.tcpSocketStr = kmsPeerSocket

	kms.kmsPeersMutex.Lock()
	kms.KmsPeers[kmsPeerSocket] = &peer
	kms.kmsPeersMutex.Unlock()

	go peer.PeerHandler(kms.kmsName)
}

// TODO/XXX error handling
func (kms *EKMS) RemovePeer(kmsPeerSocket string) {

}

func (kms *EKMS) AddExternalNotifierQLE(in chan uint32) {
	kms.externalNotifierQLE = in
}

func (kms *EKMS) AddExternalNotifierKMSPeer(in chan string) {
	kms.externalNotifierKMSPeer = in
}