Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
etsi14Quantummodule.go 6.75 KiB
package peers

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"time"

	etsi14ClientGenerated "code.fbi.h-da.de/danet/quant/etsi014/go/rest/etsi/client"
	pbIC "code.fbi.h-da.de/danet/quant/goKMS/api/gen/proto/go/kmsintercom"
	"code.fbi.h-da.de/danet/quant/goKMS/config"
	etsi14ClientImpl "code.fbi.h-da.de/danet/quant/goKMS/kms/etsi/etsi14/client"
	"code.fbi.h-da.de/danet/quant/goKMS/kms/event"
	"code.fbi.h-da.de/danet/quant/goKMS/kms/store"
	kmstls "code.fbi.h-da.de/danet/quant/goKMS/kms/tls"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

type ETSI014HTTPQuantumModule struct {
	id               uuid.UUID
	kmsId            string
	addr             string
	keyStore         *store.KmsKeyStore
	kmsClient        *GRPCClient
	client           *etsi14ClientImpl.ClientImpl
	localSAEID       string
	targetSAEID      string
	master           bool
	keyFetchInterval int
	keyFetchAmount   int64
	maxKeyFillLevel  uint64
	stopFetch        context.CancelFunc
	active           bool
}

func NewETSI014HTTPQuantumModule(addr, kmsId, localSAEID, targetSAEID string, tlsConfig config.TLSConfig, master bool, keyFetchInterval int, keyFetchAmount int64, maxKeyFillLevel uint64) (*ETSI014HTTPQuantumModule, error) {
	parsedUrl, err := url.Parse(addr)
	if err != nil {
		return nil, err
	}

	restClientConf := &etsi14ClientGenerated.Configuration{
		Debug: true,
		Servers: etsi14ClientGenerated.ServerConfigurations{
			{
				URL:         parsedUrl.String(),
				Description: "QKD Module with ETSI14 implemented as API.",
			},
		},
		Scheme: parsedUrl.Scheme,
	}

	if tlsConfig.Active {
		tlsConf, err := kmstls.GenerateTLSLibraryConfig(tlsConfig)
		if err != nil {
			return nil, fmt.Errorf("unable to generate TLS config: %w", err)
		}

		restClientConf.HTTPClient = &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: tlsConf,
			},
		}
	}

	client, err := etsi14ClientImpl.NewClientImpl(restClientConf)
	if err != nil {
		return nil, err
	}

	// set defaults for key fetching if not defined in config
	if keyFetchInterval == 0 {
		keyFetchInterval = defaultKeyFetchInterval
	}

	if keyFetchAmount == 0 {
		keyFetchAmount = defaultKeyFetchAmount
	}

	if maxKeyFillLevel == 0 {
		maxKeyFillLevel = DefaultMaxKeyFillLevel
	}

	return &ETSI014HTTPQuantumModule{
		id:               uuid.New(),
		kmsId:            kmsId,
		addr:             addr,
		keyStore:         store.NewKmsKeyStore(256),
		kmsClient:        nil,
		client:           client,
		localSAEID:       localSAEID,
		targetSAEID:      targetSAEID,
		master:           master,
		keyFetchInterval: keyFetchInterval,
		keyFetchAmount:   keyFetchAmount,
		maxKeyFillLevel:  maxKeyFillLevel,
		active:           false,
	}, nil
}

func (qm *ETSI014HTTPQuantumModule) ID() uuid.UUID {
	return qm.id
}

func (qm *ETSI014HTTPQuantumModule) Client() *etsi14ClientImpl.ClientImpl {
	return qm.client
}

func (qm *ETSI014HTTPQuantumModule) Initialize() error {
	if !qm.active {
		var ctx context.Context
		ctx, qm.stopFetch = context.WithCancel(context.Background())

		qm.active = true

		// start polling keys
		if qm.master {
			go func() {
				restartWaitingTime := time.Duration(2) * time.Minute
				ticker := time.NewTicker(restartWaitingTime)
				defer ticker.Stop()

			RestartFetchLoop:
				for {
					// immediately start with the ticker instead of waiting the defined amount
					qm.doKeyFetching(ctx)
					select {
					case <-ticker.C:
						continue
					case <-ctx.Done():
						break RestartFetchLoop
					}
				}
			}()
		}
	}
	return nil
}

func (qm *ETSI014HTTPQuantumModule) Reset() {
	if qm.master && qm.stopFetch != nil {
		qm.stopFetch()
	}
	qm.active = false
	qm.KeyStore().Reset()
}

func (qm *ETSI014HTTPQuantumModule) MaxKeyFillLevel() uint64 {
	return qm.maxKeyFillLevel
}

func (qm *ETSI014HTTPQuantumModule) SetActive(active bool) {
	qm.active = active
}

func (qm *ETSI014HTTPQuantumModule) IsActive() bool {
	return qm.active
}

func (qm *ETSI014HTTPQuantumModule) SetKmsPeerInformation(kmsClient *GRPCClient, kmsEventBus *event.EventBus, kmsTcpSocketStr string) error {
	qm.kmsClient = kmsClient
	return nil
}

func (qm *ETSI014HTTPQuantumModule) Address() string {
	return qm.addr
}

func (qm *ETSI014HTTPQuantumModule) KeyStore() *store.KmsKeyStore {
	return qm.keyStore
}

func (qm *ETSI014HTTPQuantumModule) SetKeyStore(newKeyStore *store.KmsKeyStore) {
	qm.keyStore = newKeyStore
}

func (qm *ETSI014HTTPQuantumModule) Sync() error {
	return nil
}

func (qm *ETSI014HTTPQuantumModule) KmsGrpcClient() *GRPCClient {
	return nil
}

func (qm *ETSI014HTTPQuantumModule) SetKmsGrpcClient(peer *GRPCClient) {
}

func (qm *ETSI014HTTPQuantumModule) GetKeys(number int64, size int64, additionalTargetSAEIDs []string, extensionMandatory []map[string]string, extensionOptional []map[string]string) (*etsi14ClientGenerated.KeyContainer, error) {
	container, _, err := qm.client.GetKeyPost(qm.targetSAEID, number, size, additionalTargetSAEIDs, extensionMandatory, extensionOptional)
	if err != nil {
		return nil, err
	}

	if len(container.GetKeys()) == 0 {
		return nil, fmt.Errorf("no key received, length of key container was: %d", len(container.GetKeys()))
	}

	return container, nil
}

func (qm *ETSI014HTTPQuantumModule) GetKeyWithIds(keyIds []etsi14ClientGenerated.KeyIDsRequestKeyIDsInner) (*etsi14ClientGenerated.KeyContainer, error) {
	container, _, err := qm.client.GetKeyWithIdPost(qm.targetSAEID, keyIds)
	if err != nil {
		return nil, err
	}

	if len(container.GetKeys()) == 0 {
		return nil, fmt.Errorf("no key received, length of key container was: %d", len(container.GetKeys()))
	}

	return container, nil
}

func (qm *ETSI014HTTPQuantumModule) doKeyFetching(ctx context.Context) {
	ticker := time.NewTicker(time.Duration(qm.keyFetchInterval) * time.Second)
	defer ticker.Stop()

	failedAttemps := 0

FetchLoop:
	for {
		select {
		case <-ticker.C:
			if failedAttemps == maxFailedKeyRequestAttempts {
				log.Errorf("stopped trying to fetch keys from qkd module after %d tries", failedAttemps)
				break FetchLoop
			}

			if qm.keyStore.Length() < int(qm.maxKeyFillLevel) {
				container, err := qm.GetKeys(qm.keyFetchAmount, 256, nil, nil, nil)
				if err != nil {
					log.Error(err)
					failedAttemps++
					continue
				}

				keyIds := make([]string, len(container.GetKeys()))
				for i, keyItem := range container.GetKeys() {
					keyIds[i] = keyItem.GetKeyID()
				}

				_, err = qm.kmsClient.KeyIdNotification(context.Background(),
					&pbIC.KeyIdNotificationRequest{
						Timestamp: time.Now().Unix(),
						KmsId:     qm.kmsId,
						KeyIds:    keyIds,
					})
				if err != nil {
					log.Error(err)
					failedAttemps++
					continue
				}

				err = store.AddETSIKeysToKeystore(qm.keyStore, container.GetKeys())
				if err != nil {
					log.Error(err)
					failedAttemps++
					continue
				}

				failedAttemps = 0
			}
		case <-ctx.Done():
			break FetchLoop
		}
	}
}