Skip to content
Snippets Groups Projects
Commit 2a6a4bd7 authored by Malte Bauch's avatar Malte Bauch
Browse files

Merge quantumlayer repository into its own subfolder within quant

parents dc1eafd7 fd5ef306
No related branches found
No related tags found
1 merge request!8Create a monorepo for ekms and quantumlayer
Pipeline #180372 failed
artifacts
artifacts
ARG GOLANG_VERSION=1.21
ARG BUILDARGS
FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm as builder
ARG GITLAB_LOGIN
ARG GITLAB_TOKEN
WORKDIR /quantumlayer/
COPY . .
RUN echo "machine code.fbi.h-da.de login ${GITLAB_LOGIN} password ${GITLAB_TOKEN}" > ~/.netrc
RUN --mount=type=cache,target=/root/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
make build
FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm
WORKDIR /app/
COPY --from=builder /quantumlayer/artifacts/quantumlayer ./quantumlayer
ENTRYPOINT ["./quantumlayer"]
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
MAKEFILE_DIR := $(dir $(MAKEFILE_PATH))
GOPATH := $(~/go)
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
BUILD_ARTIFACTS_PATH=artifacts
all: build
pre:
mkdir -p $(BUILD_ARTIFACTS_PATH)
build: pre
$(GOBUILD) -o $(BUILD_ARTIFACTS_PATH)/quantumlayer ./example/main.go
container: build
docker buildx build --rm -t quantumlayer --load -f ./Dockerfile .
package main
import (
"flag"
"log"
"net"
"os"
"os/signal"
"syscall"
"code.fbi.h-da.de/danet/quantumlayer"
pb "code.fbi.h-da.de/danet/quipsec/gen/go/quipsec"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/yaml.v3"
)
type Config struct {
KMSAddr string `yaml:"KMSAddr"`
UDPAddr string `yaml:"UDPAddr"`
PeerUDPAddr string `yaml:"PeerUDPAddr"`
GenerateKeys bool `yaml:"GenerateKeys"`
}
func main() {
// TODO: flag validation
configPath := flag.String("config", "", "path to the config file")
flag.Parse()
// unmarshal config
config := &Config{}
file, err := os.ReadFile(*configPath)
if err != nil {
logrus.Fatal(err)
}
if err := yaml.Unmarshal(file, config); err != nil {
logrus.Fatal(err)
}
newPeerConn, err := grpc.Dial(config.KMSAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
return
}
kmsClient := pb.NewKmsQkdmCommunicationServiceClient(newPeerConn)
peerUDPAddr, err := net.ResolveUDPAddr("udp", config.PeerUDPAddr)
if err != nil {
log.Fatal(err)
return
}
stopChan := make(chan os.Signal)
signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)
ql := quantumlayer.NewQuantumlayerEmuPRNG(kmsClient, os.Stdout, logrus.GetLevel(), false)
ql.Configure(config.GenerateKeys, config.UDPAddr)
ql.PowerOn()
ql.AddPeer(peerUDPAddr)
<-stopChan
}
package quantumlayer
/*
*
* This packages "implements", actually it only emulates, the output of a
* quantum link, i.e., the transmitted random numbers from one quantum
* sender to a quantum receiver.
* This relies on crypto/rand to generate the random numbers that will be
* transmitted to the other end.
*
*/
import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"io"
"math/big"
"net"
"strconv"
"sync"
"time"
pb "code.fbi.h-da.de/danet/quipsec/gen/go/quipsec"
"github.com/sirupsen/logrus"
logi "github.com/sirupsen/logrus"
)
type QuantumPayloadElement struct {
BulkKeyId int64 `json:"bulk-key-id"` // the unique ID of this bulk of keys
BulkKeyLength int `json:"bulk-key-length"` // the length, counted in bytes, of bulkKey
BulkKey *[]byte `json:"bulk-key"` // the bulk key
}
type QuantumlayerEmuPRNG struct {
client pb.KmsQkdmCommunicationServiceClient
configured bool
poweron bool // set to yes if operation, i.e., generating keys
generateKeys bool // set to yes, if this qle should generate random number.
incomingRandNums chan QuantumPayloadElement
peerNumbers *NumberStore
myNumbers *NumberStore
localQLAddress string
udpSrvConn *net.UDPConn
qlPeer string
qlPeerCancel context.CancelFunc
qlLocalPort *net.UDPAddr
qlPeerMutex sync.Mutex
}
// We use our own logrus instance, as we would like to have different log levels for different parts.
var log = logrus.New()
func NewQuantumlayerEmuPRNG(client pb.KmsQkdmCommunicationServiceClient, logOutput io.Writer, logLevel logi.Level, logInJson bool) (newql *QuantumlayerEmuPRNG) {
/*
* 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(&logi.TextFormatter{})
} else {
log.SetFormatter(&logi.JSONFormatter{})
}
// print code function if level is set to Trace
if logLevel == logi.TraceLevel {
log.SetReportCaller(true)
} else {
log.SetReportCaller(false)
}
// Return PRNG Quantum Layer
return &QuantumlayerEmuPRNG{
client: client,
configured: false,
poweron: false,
generateKeys: false,
incomingRandNums: make(chan QuantumPayloadElement),
peerNumbers: NewNumberStore(40000),
myNumbers: NewNumberStore(40000),
qlPeer: "",
}
}
// Configure the quantum emulation, but do not start if yet
func (qlemuprng *QuantumlayerEmuPRNG) Configure(enableKeyGeneration bool, localQLAddress ...string) {
// Start receiving numberstores
go qlemuprng.peerNumbers.receiveNumbers(qlemuprng.incomingRandNums, qlemuprng.client)
// Determine if a local UDP address should be used or not
if len(localQLAddress) == 0 {
// No input
qlemuprng.localQLAddress = ":0"
} else {
qlemuprng.localQLAddress = localQLAddress[0]
}
qlemuprng.generateKeys = enableKeyGeneration
qlemuprng.configured = true
}
// Power on the quantum layer, i.e., open up the communication ports for the
// other quantum module
func (qlemuprng *QuantumlayerEmuPRNG) PowerOn() {
if !qlemuprng.configured {
// nothing do here move on
log.Errorf("QuantumlayerEmuPRNG: Sorry, the quantum layer is not configured for action. You've missed Configure()")
return
}
//qlemuprng.poweron = false
log.Infof("QuantumlayerEmuPRNG: is powering on...charging.")
if qlemuprng.generateKeys {
log.Infof("QuantumlayerEmuPRNG: will GENERATE random keys")
}
if qlemuprng.udpSrvConn == nil {
go func() {
// Get UDP server part going...
log.Debugf("QuantumlayerEmuPRNG: localQLAddress is %s", qlemuprng.localQLAddress)
// This reads random numbers from other Quantum end
udpSrvPort, err := net.ResolveUDPAddr("udp", qlemuprng.localQLAddress)
if err != nil {
log.Fatalf("QuantumlayerEmuPRNG: UDP failure: %s", err)
return
}
qlemuprng.udpSrvConn, err = net.ListenUDP("udp", udpSrvPort)
if err != nil {
log.Fatalf("QuantumlayerEmuPRNG: UDP failure: %s", err)
return
}
defer qlemuprng.udpSrvConn.Close()
// Retrieve local UDP address and store it for further actions.
qlemuprng.qlLocalPort = qlemuprng.udpSrvConn.LocalAddr().(*net.UDPAddr)
// TODO: This does not seem to be necessary if the gle is not generating rands
// serve UDP incoming
log.Infof("QuantumlayerEmuPRNG: started server, waiting for incoming rands on port %s \n", qlemuprng.udpSrvConn.LocalAddr().(*net.UDPAddr).String())
inBuffer := make([]byte, 1500)
for {
// Buffer for reading from "Quantum link"
n, addr, err := qlemuprng.udpSrvConn.ReadFromUDP(inBuffer)
if err != nil {
log.Errorf("QuantumlayerEmuPRNG: Could not read from UDP: %s", err)
} else {
log.Debugf("QuantumlayerEmuPRNG: read %d bytes from %s\n", n, addr)
// Check if sender of datagram is qlPeer
// Warning this is not checking the validity of the sender, i.e., spoofing is possible
if addr.String() == qlemuprng.qlPeer {
log.Debugf("QuantumlayerEmuPRNG: Peer %s listed", addr)
//dumb the received data into the channel and carry on
// TODO/XXX: no vetting for anything
// Unmarshall out of JSON
var inQBuffer QuantumPayloadElement
unmarshallErr := json.Unmarshal(inBuffer[0:n], &inQBuffer)
if unmarshallErr == nil {
qlemuprng.incomingRandNums <- inQBuffer
}
} else {
log.Infof("QuantumlayerEmuPRNG: Peer %s NOT listed", addr)
}
}
}
}()
}
// Wait for listening UDP socket in the above go-routine to get ready
for qlemuprng.udpSrvConn == nil {
}
// Ready, set, go!
qlemuprng.poweron = true
log.Infof("QuantumlayerEmuPRNG: is charged and powered on.")
}
// Power off the quantum layer, i.e., close the communication ports for the
// other quantum module
func (qlemuprng *QuantumlayerEmuPRNG) PowerOff() {
qlemuprng.poweron = false
log.Println("QuantumlayerEmuPRNG: is powered off...discharging.")
}
func (qlemuprng *QuantumlayerEmuPRNG) AddPeer(addr *net.UDPAddr) {
if !qlemuprng.poweron {
return
}
//TODO/XXX check the incoming addr
// Add peer to the ....
qlemuprng.qlPeerMutex.Lock()
qlemuprng.qlPeer = addr.String()
qlemuprng.qlPeerMutex.Unlock()
// generate only keys if requested to do so.
if qlemuprng.generateKeys {
ctx, cancel := context.WithCancel(context.Background())
qlemuprng.qlPeerCancel = cancel
// Start the generation and shipping of random numbers
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
if qlemuprng.poweron {
// retrieve a new back of random numbers
newNumberBatch := qlemuprng.GenerateRandomNumbers()
// TODO: Replace this by some generic encapsulation reader and not just JSON
//Get JSON for transmission ready
qpe := QuantumPayloadElement{time.Now().UnixNano(), len(newNumberBatch), &newNumberBatch}
// XXX/TODO: error must be handled
jsonPayload, err := json.Marshal(qpe)
if err != nil {
log.Errorf("QuantumlayerEmuPRNG: json.Marshal error %s", err)
}
_, _, err = qlemuprng.udpSrvConn.WriteMsgUDP(jsonPayload, nil, addr)
if err != nil {
log.Fatalf("QuantumlayerEmuPRNG: WriteMsgUDPAddrPort failed: %s", err)
}
qlemuprng.incomingRandNums <- qpe
}
// TODO: This sleep timer has to replaced by something for clever.
time.Sleep(5 * time.Second)
}
}
}(ctx)
}
}
func (qlemuprng *QuantumlayerEmuPRNG) RemovePeer() {
if !qlemuprng.poweron {
return
}
// Stop the generation and shipping of random numbers
qlemuprng.qlPeerCancel()
// delete peer
qlemuprng.qlPeerMutex.Lock()
qlemuprng.qlPeer = ""
qlemuprng.qlPeerMutex.Unlock()
}
func (qlemuprng *QuantumlayerEmuPRNG) GetLocalQLPort() (myAddr *net.UDPAddr) {
return qlemuprng.qlLocalPort
}
func (qlemuprng *QuantumlayerEmuPRNG) GenerateRandomNumbers() (randNums []byte) {
numRands, randError := rand.Int(rand.Reader, big.NewInt(1000))
if randError != nil {
log.Fatalf("QuantumlayerEmuPRNG: %s", randError)
return
}
b := make([]byte, numRands.Uint64())
_, randError = rand.Read(b)
if randError != nil {
log.Fatalf("QuantumlayerEmuPRNG: %s", randError)
return
}
return b
}
func (qlemuprng *QuantumlayerEmuPRNG) GetKeyBulkPeer() (QuantumLayerBulkKey, error) {
return qlemuprng.peerNumbers.GetBulk()
}
// GetStatus returns the current status of the QuantumLayerEmuPRNG. This
// includes the information if the QLE is powered, aswell as if the QLE is
// enabled for key generation.
func (qlemuprng *QuantumlayerEmuPRNG) GetStatus() (poweredOn, enabled bool) {
return qlemuprng.poweron, qlemuprng.generateKeys
}
type NumberStore struct {
mu sync.Mutex
maxBytes int
storage []byte
bulkKeyStorage []QuantumLayerBulkKey
topOfStorage int
}
// Generates a new store with given maximum number of bytes
func NewNumberStore(maxBytes int) (newNS *NumberStore) {
return &NumberStore{
maxBytes: maxBytes,
storage: make([]byte, maxBytes),
topOfStorage: 0,
}
}
func (store *NumberStore) GetBulk() (bulk QuantumLayerBulkKey, err error) {
store.mu.Lock()
defer store.mu.Unlock()
for nextID := range store.bulkKeyStorage {
next := store.bulkKeyStorage[nextID]
// Preprare to return
a := QuantumLayerBulkKey{
BulkKeyId: next.BulkKeyId,
BulkKeyLength: next.BulkKeyLength,
BulkKey: nil,
}
bulkKey := make([]byte, next.BulkKeyLength)
copy(bulkKey, *next.BulkKey)
a.BulkKey = &bulkKey
// Delete from key store
store.bulkKeyStorage = store.bulkKeyStorage[1:]
// and return
return a, nil
}
returnErr := errors.New("no bulk key to retrieve")
b := QuantumLayerBulkKey{}
return b, returnErr
}
func (store *NumberStore) GetBatch() (batch []byte) {
store.mu.Lock()
defer store.mu.Unlock()
if store.topOfStorage != 0 {
log.Debugf("QuantumlayerEmuPRNG: Have Storage in my belly")
}
// prepare to return full batch of numbers
batchReturn := make([]byte, store.topOfStorage)
copy(batchReturn, store.storage)
store.topOfStorage = 0
return batchReturn
}
func (store *NumberStore) receiveNumbers(incoming chan QuantumPayloadElement, client pb.KmsQkdmCommunicationServiceClient) {
for {
receivedNumbers := <-incoming
// add received to our buffer, if buffer still last
store.mu.Lock()
mem := QuantumLayerBulkKey{
BulkKeyId: receivedNumbers.BulkKeyId,
BulkKeyLength: receivedNumbers.BulkKeyLength,
BulkKey: receivedNumbers.BulkKey,
}
//store.bulkKeyStorage[receivedNumbers.BulkKeyId] = mem
store.bulkKeyStorage = append(store.bulkKeyStorage, mem)
store.mu.Unlock()
log.Info(mem.BulkKeyId)
_, err := client.PushKeys(context.Background(), &pb.PushKeysRequest{
Timestamp: time.Now().Unix(),
KeyBulk: &pb.KeyBulk{
KeyId: strconv.FormatInt(receivedNumbers.BulkKeyId, 10),
KeyLength: uint64(receivedNumbers.BulkKeyLength),
Keys: *receivedNumbers.BulkKey,
},
})
if err != nil {
logi.Error(err)
}
}
}
package quantumlayer
// Some tests
import (
"fmt"
"net"
"os"
"testing"
"time"
logrus "github.com/sirupsen/logrus"
)
func TestQuantumLayer(t *testing.T) {
// Generate UDPAddr for ql1 peer
udpQL2AddrString := fmt.Sprintf("127.0.0.1:%d", 5002)
udpQL2Addr, err := net.ResolveUDPAddr("udp", udpQL2AddrString)
if err != nil {
t.Fatalf("QuantumlayerEmuPRNG UDP failure: %s", err)
return
}
// Generate UDPAddr for ql2 peer
udpQL1AddrString := fmt.Sprintf("127.0.0.1:%d", 5001)
udpQL1Addr, err := net.ResolveUDPAddr("udp", udpQL1AddrString)
if err != nil {
t.Fatalf("QuantumlayerEmuPRNG UDP failure: %s", err)
return
}
ql1 := NewQuantumlayerEmuPRNG(nil, os.Stdout, logrus.DebugLevel, false)
ql1.Configure(true, udpQL1AddrString)
ql1.PowerOn() // this one generates keys
defer ql1.PowerOff()
ql2 := NewQuantumlayerEmuPRNG(nil, os.Stdout, logrus.DebugLevel, false)
ql2.Configure(false, udpQL2AddrString)
ql2.PowerOn() // this one does NOT generate keys
defer ql2.PowerOff()
ql1.AddPeer(udpQL2Addr)
ql2.AddPeer(udpQL1Addr)
// Wait for key gen to get up and running
time.Sleep(5 * time.Second)
for n := 0; n < 2; n++ {
resultQl1, err := ql1.GetKeyBulkPeer()
if err == nil {
t.Logf("run %d, *ql1* keyid %d \t keylen %d", n, resultQl1.BulkKeyId, resultQl1.BulkKeyLength)
} else {
t.Fatalf("Couldn't read local ql1 batch with error %s", err)
}
// TODO: Calculate checksum of BulkKey and double-check
time.Sleep(5 * time.Second)
}
}
// This package aims at emulating a quantum link and is extendable to different models
// One can use most of the sourc code of emu-prng and reuse it.
// To add a different quantum module one should only modify the GenerateRandomNumbers function
package quantumlayer
type QuantumLayerBulkKey struct {
BulkKeyId int64 // the unique ID of this bulk of keys
BulkKeyLength int // the length, counted in bytes, of bulkKey
// TODO: Pointer of slice should have a well thought reason;
// ask Martin if this is really necessary here
BulkKey *[]byte // the bulk key
}
type QuantumLayer interface {
Configure(...string) // configure the interface, e.g., used IP/Port config if emulated
PowerOn(enableKeyGeneration bool) // switch on the quantum layer element
PowerOff() // switch off the quantum layer element
GetStatus() (poweredOn bool) // returns true if quantum layer element is powered on
AddPeer() // Adds a Quantum Layer Peer to the peer list
RemovePeer() // Remmoves a Quantum Layer Peer to the peer list
GetLocalQLPort() // Returns the information about the local quantum layer IP and port
GetKeyBulkPeer() (QuantumLayerBulkKey, error) // retrieve the bulk key received from peer
GenerateRandomNumbers() []byte // generate a number of random numbers
}
type NumberLayer interface {
GetBatch() []byte // allows to retrieve the current available batch of numbers
GetBulk() (QuantumLayerBulkKey, error)
receiveNumbers(chan []byte)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment