Newer
Older
// This package kms implements a simplistic key management 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 (
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/metadata"
pbIC "code.fbi.h-da.de/danet/quant/goKMS/api/gen/proto/go/kmsintercom"
"code.fbi.h-da.de/danet/quant/goKMS/config"
akmsInterfaceClient "code.fbi.h-da.de/danet/quant/goKMS/kms/akmsInterface/client"
akmsInterfaceServer "code.fbi.h-da.de/danet/quant/goKMS/kms/akmsInterface/server"
"code.fbi.h-da.de/danet/quant/goKMS/kms/crypto"
etsi14Server "code.fbi.h-da.de/danet/quant/goKMS/kms/etsi/etsi14/server"
"code.fbi.h-da.de/danet/quant/goKMS/kms/event"
"code.fbi.h-da.de/danet/quant/goKMS/kms/peers"
"code.fbi.h-da.de/danet/quant/goKMS/kms/receiver"
"code.fbi.h-da.de/danet/quant/goKMS/kms/store"
kmstls "code.fbi.h-da.de/danet/quant/goKMS/kms/tls"
"code.fbi.h-da.de/danet/quant/goKMS/kms/util"
pbQS "code.fbi.h-da.de/danet/quipsec/gen/go/quipsec"
"github.com/google/uuid"
)
PathId uuid.UUID
Previous *peers.KmsPeer
Next *peers.KmsPeer
type BitKeyLength string
const (
BitKeyLen128 BitKeyLength = "128"
BitKeyLen256 BitKeyLength = "256"
BitKeyLen512 BitKeyLength = "512"
)
type PlatformKey struct {
Id uuid.UUID
Value []byte
ProcessId string
}
kmsName string
kmsUUID uuid.UUID
interComAddr string
quantumAddress string
tlsConfig config.TLSConfig
gRPCTimeout time.Duration
// TODO create a mapping between ids and address
remoteKMSMapping map[string]*util.RemoteKMS
remoteKMSMappingMutex sync.RWMutex
quantumModules map[string]peers.QuantumModule
quantumModulesMutex sync.RWMutex
kmsPeersMutex sync.Mutex
// NOTE: There is probably a better way to handle this
PKStore map[string]map[uuid.UUID]*PlatformKey
// TODO(maba): find a better name for this
routingTable map[uuid.UUID]*Route
routingTableMutex sync.RWMutex
KmsPeers map[string]*peers.KmsPeer
pbIC.UnimplementedKmsTalkerServer
supportedKeyLengths map[BitKeyLength]bool
eventBus *event.EventBus
receiver *receiver.Receiver
// Akms things
ckmsAkmsClient *akmsInterfaceClient.CkmsAkmsClient
ckmsAkmsServer *akmsInterfaceServer.AKMSReceiverServer
etsi14Server *etsi14Server.ETSI14RESTService
keyStoreChannel chan []crypto.KSAKey
// 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
}*/
func NewKMS(kmsUUID uuid.UUID, logOutput io.Writer, logLevel log.Level, logInJson bool, config *config.Config, receiver *receiver.Receiver) (newKMS *KMS) {
/*
* Setup logging
*/
//What level
log.SetLevel(logLevel)
// Where to send log out put
log.SetOutput(logOutput)
// and plain-text (standard) or json
if !logInJson {
log.SetFormatter(&log.TextFormatter{})
} else {
log.SetFormatter(&log.JSONFormatter{})
}
// print code function if level is set to Trace
if logLevel == log.TraceLevel {
log.SetReportCaller(true)
} else {
log.SetReportCaller(false)
}
var ckmsAkmsClient *akmsInterfaceClient.CkmsAkmsClient
var err error
if config.AKMS.RemoteAddress != "" {
ckmsAkmsClient, err = akmsInterfaceClient.NewCkmsAkmsClient(config.AKMS.RemoteAddress, config.AKMS.ClientTLS)
if err != nil {
log.Fatalf("Failed to setup CkmsAkmsClient: %s", err)
}
gRPCTimeoutInSecondsDuration := time.Duration(config.GRPCTimeoutInSeconds) * time.Second
kmsUUID: kmsUUID,
interComAddr: config.InterComAddr,
quantumAddress: config.QuantumAddr,
tlsConfig: config.KmsTLS,
gRPCTimeout: gRPCTimeoutInSecondsDuration,
remoteKMSMapping: make(map[string]*util.RemoteKMS),
quantumModules: make(map[string]peers.QuantumModule),
routingTable: make(map[uuid.UUID]*Route),
PKStore: make(map[string]map[uuid.UUID]*PlatformKey),
KmsPeers: make(map[string]*peers.KmsPeer),
supportedKeyLengths: make(map[BitKeyLength]bool),
eventBus: event.NewEventBus(),
ckmsAkmsClient: ckmsAkmsClient,
createdKMS.supportedKeyLengths[BitKeyLen256] = true
// start the inter communication gRPC server
go createdKMS.startGRPC()
err = createdKMS.initializePeers(config)
if err != nil {
log.Fatalf("Failed to initialize peers: %s", err)
}
// Start the akmsCkmsReceiverServer
if config.AKMS.ServerPort != "" {
createdKMS.ckmsAkmsServer, err = akmsInterfaceServer.NewAKMSReceiver(config.AKMS.ServerPort, createdKMS.eventBus, receiver, createdKMS.GenerateAndSendKSAKey, config.AKMS.ServerTLS)
if err != nil {
log.Fatalf("Failed to initialize CkmsAkmsServer: %s", err)
}
log.Infof("Starting AKMS receiver server on port: %s", config.AKMS.ServerPort)
go createdKMS.ckmsAkmsServer.Serve()
}
// Setup ETSI14 Server if info is in config
if config.ETSI14Server != nil {
remoteCKMSID, err := uuid.Parse(config.ETSI14Server.RemoteCKMSID)
if err != nil {
log.Error(err)
}
createdKMS.etsi14Server = etsi14Server.NewETSI14RESTService(config.ETSI14Server.Address, remoteCKMSID, createdKMS.exchangeKeyAfterETSI14GetKeyRequest)
go createdKMS.etsi14Server.Run()
createdKMS.keyStoreChannel = createdKMS.etsi14Server.KeyStoreChannel
} else {
log.Info("No ETSI14 server running.")
}
func (kms *KMS) initializePeers(config *config.Config) error {
var qm peers.QuantumModule
var err error
err = resolveHostnameToIPForQuantumModule(&peer)
if err != nil {
log.Error(err)
continue
}
pqm := peer.QuantumModule
switch qmt := peer.QuantumModule.QmType; qmt {
case "emulated":
qm = peers.NewDanetQuantumModule(pqm.Address, config.Id)
Fabian Seidl
committed
qm, err = peers.NewETSI014HTTPQuantumModule(pqm.Address, config.Id, pqm.LocalSAEID, pqm.TargetSAEID,
peer.QuantumModule.TLS, pqm.MasterMode,
peer.QuantumModule.KeyFetchInterval, int64(peer.QuantumModule.KeyFetchAmount), uint64(peer.QuantumModule.MaxKeyFillLevel))
if err != nil {
log.Fatalf("Failed to create ETSI QKD module: %s", err)
return nil
}
default:
log.Fatalf("Unknown type: %s for quantum module", qmt)
return nil
}
if err != nil {
log.Fatalf("Failed to add quantum element: %s", err)
return nil
}
gRPCTransportCreds, err := kmstls.GenerateGRPCClientTransportCredsBasedOnTLSFlag(kms.tlsConfig)
if err != nil {
return fmt.Errorf("unable to generate gRPC transport creds: %w", err)
newPeerConn, err := grpc.NewClient(peer.PeerInterComAddr, grpc.WithTransportCredentials(gRPCTransportCreds))
if err != nil {
return nil
}
client := &peers.GRPCClient{}
switch pt := peer.Type; pt {
case "danet":
client.KmsTalkerClient = pbIC.NewKmsTalkerClient(newPeerConn)
}
_, err = kms.AddPeer(peer.PeerId, peer.PeerInterComAddr, qm, client)
if err != nil {
log.Fatalf("Failed to create a peer: %s", err)
return nil
}
}
return nil
}
func (kms *KMS) startGRPC() {
interKMSLis, err := net.Listen("tcp", kms.interComAddr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
gRPCTransportCreds, err := kmstls.GenerateGRPCServerTransportCredsBasedOnTLSFlag(kms.tlsConfig)
if err != nil {
log.Fatalf("unable to generate gRPC server: %v", err)
}
interKMSServer := grpc.NewServer(grpc.Creds(gRPCTransportCreds))
healthCheck := health.NewServer()
healthpb.RegisterHealthServer(interKMSServer, healthCheck)
pbIC.RegisterKmsTalkerServer(interKMSServer, &kmsTalkerServer{
keyNegotiationMap: make(map[uuid.UUID]*store.KmsKSElement),
if kms.quantumAddress != "" {
quantumLis, err := net.Listen("tcp", kms.quantumAddress)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
quantumServ := grpc.NewServer()
pbQS.RegisterKmsQkdmCommunicationServiceServer(quantumServ, &quipSecServer{
KMS: kms,
})
log.Infof("quantum server listening at %v", quantumLis.Addr())
go quantumServ.Serve(quantumLis) //nolint:errcheck
go func() {
// set status to serving
healthCheck.SetServingStatus(pbIC.KmsTalker_ServiceDesc.ServiceName, healthpb.HealthCheckResponse_SERVING)
// TODO: add logic for adjusting health status based on operating status of
// the services
// for{}
}()
log.Infof("inter KMS server listening at %v", interKMSLis.Addr())
if err := interKMSServer.Serve(interKMSLis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func (kms *KMS) AddQuantumElement(qm peers.QuantumModule) error {
kms.quantumModulesMutex.Lock()
defer kms.quantumModulesMutex.Unlock()
log.Infof("quantum module address: %s ", qm.Address())
kms.quantumModules[qm.Address()] = qm
func (kms *KMS) AddPeer(peerKmsId string, kmsPeerSocket string, servingQLE peers.QuantumModule, client *peers.GRPCClient) (*peers.KmsPeer, error) {
if _, there := kms.KmsPeers[peerKmsId]; there {
log.Errorf("Trying to add existing peer %s, with KMS ID %s", kmsPeerSocket, peerKmsId)
return nil, fmt.Errorf("trying to add existing peer %s, with KMS ID %s", kmsPeerSocket, peerKmsId)
peer, err := peers.NewKmsPeer(peerKmsId, servingQLE, kmsPeerSocket, client, kms.eventBus, kms.gRPCTimeout)
kms.kmsPeersMutex.Lock()
defer kms.kmsPeersMutex.Unlock()
kms.KmsPeers[peerKmsId] = peer
func (kms *KMS) AssignForwardingRoute(pId, pHop, nHop string, remoteKMS *util.RemoteKMS) error {
pathId, err := uuid.Parse(pId)
if err != nil {
return fmt.Errorf("the given path id %s is no uuid; err = %w", pathId, err)
var previousHop *peers.KmsPeer
var nextHop *peers.KmsPeer
var ok bool
if pHop != "" {
previousHop, ok = kms.KmsPeers[pHop]
if !ok {
return fmt.Errorf("no peer found for %s", pHop)
}
}
if nHop != "" {
nextHop, ok = kms.KmsPeers[nHop]
return fmt.Errorf("no peer found for %s", nHop)
PathId: pathId,
Previous: previousHop,
Next: nextHop,
RemoteKMS: remoteKMS,
kms.routingTableMutex.Lock()
// set the route within routing table
kms.routingTableMutex.Unlock()
if tmpRoute.RemoteKMS != nil {
kms.remoteKMSMappingMutex.Lock()
if _, ok := kms.remoteKMSMapping[tmpRoute.RemoteKMS.Id]; !ok {
kms.remoteKMSMapping[tmpRoute.RemoteKMS.Id] = tmpRoute.RemoteKMS
}
kms.remoteKMSMappingMutex.Unlock()
}
if tmpRoute.Previous == nil && tmpRoute.Next != nil && tmpRoute.RemoteKMS != nil {
// generate pk key
pk, err := crypto.Random256BitKey()
if err != nil {
return err
}
// generate process id
processId := uuid.New()
kms.AddSpecificPlatformKey(tmpRoute.RemoteKMS.Id, pathId, processId, pk)
err = tmpRoute.Next.SendInitialPayloadBasedOnGRPCClient(pk, tmpRoute.PathId, processId, kms.kmsUUID.String(), remoteKMS)
if err != nil {
log.Error(err)
return err
}
}
func (kms *KMS) AddSpecificPlatformKey(remoteKMSId string, pathId uuid.UUID, processId uuid.UUID, key *crypto.Key) {
kms.PKStoreMutex.Lock()
defer kms.PKStoreMutex.Unlock()
keys, ok := kms.PKStore[remoteKMSId]
if !ok {
kms.PKStore[remoteKMSId] = map[uuid.UUID]*PlatformKey{
pathId: {
Id: key.ID,
Value: key.Key,
ProcessId: processId.String(),
},
}
} else {
keys[pathId] = &PlatformKey{
Id: key.ID,
Value: key.Key,
ProcessId: processId.String(),
}
}
log.Debug("Current store of platform keys: ", kms.PKStore)
}
func (kms *KMS) GetSpecificPlatformKey(remoteKMSId string, keyId uuid.UUID) (*PlatformKey, error) {
kms.PKStoreMutex.Lock()
defer kms.PKStoreMutex.Unlock()
keyIDs, ok := kms.PKStore[remoteKMSId]
if !ok {
return nil, fmt.Errorf("no entry for given KMS id: %s", remoteKMSId)
pk, ok := keyIDs[keyId]
if !ok {
return nil, fmt.Errorf("no key ready to use for given KMS id: %s", remoteKMSId)
}
delete(keyIDs, keyId)
return pk, nil
}
func (kms *KMS) GetRemoteKMS(remoteKMSId string) (*util.RemoteKMS, error) {
kms.remoteKMSMappingMutex.RLock()
defer kms.remoteKMSMappingMutex.RUnlock()
remoteKMS, ok := kms.remoteKMSMapping[remoteKMSId]
if !ok {
return nil, fmt.Errorf("address for remoteKMS with id %s not found", remoteKMSId)
}
return remoteKMS, nil
}
// NOTE: address/remoteid still have to decide.
func (kms *KMS) GenerateAndSendKSAKey(remoteKMSId string, pathId uuid.UUID, requestID string, number int) error {
if number < 1 {
log.Errorf("number must be positive and at least 1, provided: %d\n", number)
return fmt.Errorf("number must be positive and at least 1, provided: %d", number)
}
log.Infof("KMS store: %v", kms.remoteKMSMapping)
remoteKMS, err := kms.GetRemoteKMS(remoteKMSId)
if err != nil {
log.Error(err)
return err
}
platformKey, err := kms.GetSpecificPlatformKey(remoteKMSId, pathId)
if err != nil {
log.Error(err)
return err
}
akmsKSAKeys := make([]crypto.KSAKey, number)
cryptoAlgo := crypto.NewAES()
for i := 0; i < number; i++ {
ksaKey, akmsKSAKey, err := generateNewKSAKey(cryptoAlgo, platformKey.Value)
if err != nil {
log.Error(err)
return err
}
ksaKeys[i] = ksaKey
akmsKSAKeys[i] = *akmsKSAKey
remoteKMSAdrress := fmt.Sprintf("%s:%d", remoteKMS.Address, remoteKMS.Port)
err = kms.sendKSAKeysToPlatformKmsPeer(remoteKMSAdrress, platformKey.Id.String(), requestID, ksaKeys)
if err != nil {
log.Error(err)
return err
}
// Use the real processID when we know what it is
err = kms.ckmsAkmsClient.SendKSAKeysToRequestingInstances(requestID, platformKey.ProcessId, akmsKSAKeys)
if err != nil {
log.Error(err)
return err
}
func (kms *KMS) EventBus() *event.EventBus {
return kms.eventBus
}
func (kms *KMS) RemovePeer(kmsPeerSocket string) {
log.Errorf("%s: Can not find a peer with socket: %s", kms.kmsName, kmsPeerSocket)
func (kms *KMS) FindPeerUuid(lookup uuid.UUID) (peer *peers.KmsPeer) {
if kms.KmsPeers != nil {
for _, peer = range kms.KmsPeers {
return peer
}
}
}
return nil
}
func (kms *KMS) RoutingTableDeepCopy() map[uuid.UUID]*Route {
routingTableCopy := make(map[uuid.UUID]*Route, len(kms.KmsPeers))
kms.routingTableMutex.Lock()
defer kms.routingTableMutex.Unlock()
for k, v := range kms.routingTable {
func (kms *KMS) PeersDeepCopy() map[string]*peers.KmsPeer {
peersCopy := make(map[string]*peers.KmsPeer, len(kms.KmsPeers))
kms.kmsPeersMutex.Lock()
defer kms.kmsPeersMutex.Unlock()
for k, v := range kms.KmsPeers {
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
func resolveHostnameToIPForQuantumModule(peer *config.Peer) error {
const connectionRetries = 60
var ipAddr []net.IP
var err error
// If the address is not set, try to resolve the hostname.
if peer.QuantumModule.Address == "" && peer.QuantumModule.Hostname != "" {
log.Info("Trying to get IP from hostname for quantum module: ", peer.QuantumModule.Hostname)
for j := 0; j < connectionRetries; j++ {
ipAddr, err = net.LookupIP(peer.QuantumModule.Hostname)
if err == nil {
break
}
log.Errorf("Failed to get IP from hostname %s, retrying in 2 seconds (attempt %d/%d)", peer.QuantumModule.Hostname, j+1, connectionRetries)
time.Sleep(2 * time.Second)
}
if err != nil {
return fmt.Errorf("IP address not set and failed to resolve hostname for two minutes for quantum module: %s. Error: %s", peer.QuantumModule.Hostname, err.Error())
}
// Just use the first valid IP for now.
ipAdrrString := ipAddr[0].String()
log.Infof("Resolved hostname to IP address for quantum module. Hostname: %s, IP: %s", peer.QuantumModule.Hostname, ipAdrrString)
peer.QuantumModule.Address = ipAdrrString
} else if peer.QuantumModule.Address == "" && peer.QuantumModule.Hostname == "" {
return fmt.Errorf("IP address and hostname not set for quantum module. Erros may occur and the module might not work properly.")
}
return nil
}
func generateNewKSAKey(cryptoAlgo crypto.CryptoAlgorithm, platformKeyValue []byte) (*pbIC.Key, *crypto.KSAKey, error) {
// generate ksa key
ksaKeyId := uuid.New()
ksaKey, err := crypto.Random256BitKey()
if err != nil {
log.Error(err)
return nil, nil, err
}
// encrypt the key
nonce, encryptedKSAKey, err := cryptoAlgo.Encrypt(ksaKey.Key, platformKeyValue)
if err != nil {
log.Error(err)
return nil, nil, err
}
ksaKeyAsString := base64.StdEncoding.EncodeToString(ksaKey.Key)
encryptedKSAKeyAsString := base64.StdEncoding.EncodeToString(encryptedKSAKey)
nonceAsString := base64.StdEncoding.EncodeToString(nonce)
ksaKeyToSend := &pbIC.Key{
Id: ksaKeyId.String(),
Nonce: nonceAsString,
Key: encryptedKSAKeyAsString,
}
akmsKSAKey := &crypto.KSAKey{
KeyID: ksaKeyId.String(),
Key: ksaKeyAsString,
}
return ksaKeyToSend, akmsKSAKey, nil
}
func (kms *KMS) sendKSAKeysToPlatformKmsPeer(kmsPeerAddress, platformKeyID, requestID string, ksaKeys []*pbIC.Key) error {
gRPCTransportCreds, err := kmstls.GenerateGRPCClientTransportCredsBasedOnTLSFlag(kms.tlsConfig)
if err != nil {
return fmt.Errorf("unable to generate gRPC transport creds: %w", err)
}
remoteConn, err := grpc.NewClient(kmsPeerAddress, grpc.WithTransportCredentials(gRPCTransportCreds))
if err != nil {
log.Error(err)
return err
}
remoteClient := pbIC.NewKmsTalkerClient(remoteConn)
ctx, cancel := context.WithTimeout(context.Background(), kms.gRPCTimeout)
// create a new context with some metadata
md := metadata.Pairs("hostname", kms.kmsName)
ctx = metadata.NewOutgoingContext(ctx, md)
defer cancel()
_, err = remoteClient.KeyDelivery(ctx, &pbIC.KeyDeliveryRequest{
KeyId: platformKeyID,
RequestId: requestID,
KmsId: kms.kmsUUID.String(),
Keys: ksaKeys,
})
if err != nil {
log.Error(err)
return err
}
return nil
}
func (kms *KMS) exchangeKeyAfterETSI14GetKeyRequest(receivingCKMSID uuid.UUID, number int64, requestID string) ([]crypto.KSAKey, error) {
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
// CreateRouteRequest
pathID := uuid.New()
log.Debugf("(ETSI14) created new path id: %s, for incoming ETSI14 key request", pathID)
receiverChan, err := kms.receiver.RequestReceiverChannel(pathID)
if err != nil {
log.Error(err)
return nil, err
}
err = kms.eventBus.Publish(event.NewCreateRouteEvent(pathID.String(), receivingCKMSID.String()))
if err != nil {
log.Errorf("(ETSI14) Failed sending a create route request: %s", err)
return nil, err
}
select {
case <-receiverChan:
case <-time.After(20 * time.Second):
if err := kms.receiver.RemoveReceiver(pathID); err != nil {
log.Errorf("Failed removing receiver for pathId: %s ; err: %v", pathID, err)
}
log.Errorf("(ETSI14) Platform Key exchange failed for PathID: %s", pathID)
return nil, fmt.Errorf("Platform Key exchange failed for PathID: %s", pathID)
}
// GenerateAndSend
return kms.generateAndReturnKsaKey(receivingCKMSID, pathID, number, requestID)
func (kms *KMS) generateAndReturnKsaKey(receivingCKMSID, pathID uuid.UUID, number int64, requestID string) ([]crypto.KSAKey, error) {
if number < 1 {
log.Errorf("number must be positive and at least 1, provided: %d\n", number)
return nil, fmt.Errorf("number must be positive and at least 1, provided: %d", number)
}
log.Infof("KMS store: %v", kms.remoteKMSMapping)
remoteKMS, err := kms.GetRemoteKMS(receivingCKMSID.String())
if err != nil {
log.Error(err)
return nil, err
}
platformKey, err := kms.GetSpecificPlatformKey(receivingCKMSID.String(), pathID)
if err != nil {
log.Error(err)
return nil, err
}
ksaKeysToSendToRemoteKMS := make([]*pbIC.Key, number)
ksaKeysToReturn := make([]crypto.KSAKey, number)
cryptoAlgo := crypto.NewAES()
for i := int64(0); i < number; i++ {
remoteKSAKey, localKSAKey, err := generateNewKSAKey(cryptoAlgo, platformKey.Value)
if err != nil {
log.Error(err)
return nil, err
}
ksaKeysToReturn[i] = *localKSAKey
ksaKeysToSendToRemoteKMS[i] = remoteKSAKey
}
remoteKMSAdrress := fmt.Sprintf("%s:%d", remoteKMS.Address, remoteKMS.Port)
err = kms.sendKSAKeysToPlatformKmsPeer(remoteKMSAdrress, platformKey.Id.String(), requestID, ksaKeysToSendToRemoteKMS)
if err != nil {
log.Error(err)
return nil, err
}
return ksaKeysToReturn, nil
}
func (kms *KMS) GetID() uuid.UUID {
return kms.kmsUUID
}