
Framework for controlling QKD devices and managing symmetric keys. See the [project page here](https://qcomms.gitlab.io/cqptoolkit/)
The system provides various components for integrating QKD into a security system. It's written in C++11 but uses GRPC interfaces so can be integrated with lots of different languages.
To run the software natively, ether:
To clone the source including submodules:
git clone --recurse-submodules [email protected]:QComms/cqptoolkit.git
If you cloned without using
--recurse-submodules, submodules can be updated by runninggit submodule update --initfrom within the source folder.
Here is a list of dependencies you need to compile the project (please read further down below for more details on the installation):
sudo apt install pkg-config ca-certificates file build-essential cmake ninja-build libusb-1.0-0-dev libcurl4-openssl-dev \
libcrypto++-dev libcap-dev uuid-dev libssl-dev libsqlite3-dev libprotobuf-dev libgrpc++-dev \
libssl-dev protobuf-compiler protobuf-compiler-grpc checkinstall
mkdir build-cqptoolkit
cd build-cqptoolkit
cmake -G Ninja ../cqptoolkit && ninja
Quick test
From the build folder, to run two sites (on the same local computer) each with a QKD device , first start site "A" by starting a site agent and connecting an Alice "dummy driver" to it: (If the binaries have been installed, omit the paths to the commands from the instructions.)
./src/Tools/SiteAgentRunner/SiteAgentRunner -p 8000 &
./src/Drivers/DummyQKDDriver/DummyQKDDriver -r localhost:8000 -a
./src/Tools/SiteAgentRunner/SiteAgentRunner -p 8001 &
./src/Drivers/DummyQKDDriver/DummyQKDDriver -r localhost:8001 -b
This will not start producing key immediately as this system is designed to be controlled by a management system, the connection needs to be established with the command SiteAgentCtl.
./src/Tools/SiteAgentCtl/SiteAgentCtl -d -c localhost:8000
which should produce something similar to this by default, if SiteAgentRunner and DummyQKDDriver were lauched without specifying a configuration JSON string file argument:
{
"url": "<hostname>:8000",
"devices": [
{
"config": {
"id": "dummyqkd__0__16_alice",
"kind": "dummyqkd"
},
"controlAddress": "<hostname>:34219"
}
]
}
and port 8001 should produce something similar to
{
"url": "<hostname>:8001",
"devices": [
{
"config": {
"id": "dummyqkd__0__16_bob",
"side": "Bob",
"kind": "dummyqkd"
},
"controlAddress": "<hostname>:38367"
}
]
}
./src/Tools/SiteAgentCtl/SiteAgentCtl -c localhost:8000 -j localhost:8001
This will create a single hop from one site to the next, again more complex routes can be defined by using the -a option with a JSON string specifying the path.
After a few seconds there should be key available which can be tested by requesting a key.
NOTE: The -k parameter must be the url shown in the details of the second site, not "localhost:8001"
./src/Tools/SiteAgentCtl/SiteAgentCtl -c localhost:8000 -k `hostname`:8001
The link can be stopped with the unjoin command:
./src/Tools/SiteAgentCtl/SiteAgentCtl -c localhost:8000 -u localhost:8001
Not that key is still available even though generation has stopped, as long as the site agents are running. It can be requested with the same key request command above.
Encryption example
With the launched site agents and drivers on the same local computer as described above and after having started the link for key exchange, one can also test the encryption features.
./src/Tools/QTunnelServer/QTunnelServer -p 9010 --keystore-url=`hostname`:8001
./src/Tools/QTunnelServer/QTunnelServer --keystore-url=`hostname`:8000 --remote=localhost:9010 --start-node=tcpsrv://0.0.0.0:9000 --end-node=tcpsrv://0.0.0.0:9001
Anything which uses tcp communications can then use this port, netcat is a simple program which will send data over the ports, start one on one side:
nc localhost 9000
and one on the other:
nc localhost 9001
Anything typed into one side will appear on the other when enter is pressed. Inspecting the packets travelling through ports 9000 and 9001
with a tool such as wireshark will show the data being encrypted and the key ID used.
Other forms of connection can be created, instead of tcpserv:
| Example | Description | | ============================= | ===================================================== | | tcpserv://0.0.0.0:1234 | A listening port is created on port 1234 | | tcp://127.0.01:1234 | A connection to tcp port 1234 on localhost is made | | udp://0.0.0.0:1234 | UDP packets are sent from this port | | tun://192.168.101.1/?netmask=255.255.255.0 | An IP level tunnel device is created with an IP address | | tap://192.168.101.1/?netmask=255.255.255.0 | An ethernet level tap device is created | | eth://eth0/?level=tcp | Create a raw socket, level can be tcp, ip or eth. |
Planed and completed features
It is hoped that this project can prove useful for both scientific research work and large projects. More details on the project can be found in this paper.
To contribute to this project please see the Contribution file.
The system currently works on Linux - Windows is planned for the future. The easiest is to build from source at the moment
There is a ready to run docker image in the gitlab registry. You can run with
sudo docker run -it --rm registry.gitlab.com/qcomms/cqptoolkit/runtime. Add a command on the end to run something directly, for example to run a simulation of the QKD key generation with QKDSim:
sudo docker run -it --rm registry.gitlab.com/qcomms/cqptoolkit/runtime AlignmentTests
You can install binary packages from Gitlab. Extract the zip file and install the tools with dpkg, it will complain about missing dependencies but don't worry, the second line will fix them.
sudo dpkg -i setup/*.deb build/gcc/CQP-*-Linux-{Algorithms,Networking,CQPToolkit,KeyManagement,QKDInterfaces,CQPUI,Simulate,Tools,UI,Drivers,IDQDevices}.deb
sudo apt install -fy
To install the development files (headers and static libraries) run sudo dpkg -i build/gcc/CQP-*-Linux-*-dev.deb ; sudo apt install -fy instead.
Of course, if you don't want to change your system libraries and dependencies versions, you can build inside a docker container which already has all the dependencies installed with:
sudo docker run -it registry.gitlab.com/qcomms/cqptoolkit/buildenv
Otherwise, building from source requires that you install dependencies listed in the setup/setupbuild.sh, currently it works for Ubuntu and Arch Linux.
Clone the source from gitlab with git and build with CMake and gnu make:
The --recurse-submodules adds the optional extras - access to some of these is restricted to UoB and it's partners, the build will work without them.
git clone --recurse-submodules https://gitlab.com/QComms/cqptoolkit.git
If you want to get all submodules, and have the appropreate logins run:
git submodule update --checkout
Now, you can go in you local repository and install the dependencies with the script (maybe need to change file permission):
cd cqptoolkit/setup
./setupbuild.sh
Then, you can build the project into a new folder:
mkdir build-cqptoolkit
cd build-cqptoolkit
cmake ../cqptoolkit && nice make -s -j
# this can be installed with
sudo make install
The build uses CMake to produce makefiles/solutions/etc for many different platforms and is invoked from an empty build folder which will contain all the output files. Debug builds with produce packages post fixed with a "D".
The build can be controlled by passing options to cmake, Eg -DBUILD_TESTING=OFF. Run cmake with the -LH options to list available switches.
To make changes and develop the library it's recommended to install QT Creator and open the project by selecting the CMakeLists.txt file. It is advisable to use parallel builds by going to Projects->Build Steps-> Details and adding -j<number> to the tool parameters, see so.
Once built the files, by default, are at the same level as the project folder called build-<project name>-<platform>-<target>.
Note about protobuf + QT on Ubuntu The library
qt5-gtk-platformthemeis linked against an old version of protobuf which will prevent our QT programs from running. This optional dependency can be removed withapt-get remove qt5-gtk-platformtheme
@startuml
title Applicaiton Overview
component "QKD Device Drivers" as drv
interface IDevice as idev
drv - idev
component "Device contol and\n Key storage" as sa
interface IKey as ikey
sa - ikey
sa ..> idev
package "Key consumers" as kc {
component "Custom VPN" as tun
component "Custom\nWeb Server" as nginx
component "HSM bridge" as hsm
}
tun ..> ikey
nginx ..> ikey
hsm ..> ikey
package Utilities {
component "Config/Control GUI" as gui
component "Simulators/Testing" as sim
component "Data extraction" as stats
gui -[hidden]down- sim
sim -[hidden]down- stats
}
@enduml
Below is a flow chart to help find the area relevant to you as the project covers many different aspects of QKD and key management - contributors are welcome to drive this project to be more specialised. QKD requires some form of non-cloning communication, usually by using single photons over a fibre optic cable. They can operate point-to-point or as one-to-many but they inherently have a physical location (where the fibre terminates) - they cannot be virtualised! The point at which the photon is transmitted or detected is the boundary of the secure system - almost like the firewall to a network. Once the in-divisible photons have been turned into a string of bits to form a symmetric key the standard rules of computer security like authentication, access control, etc, apply. The difference is that once those keys have been produced, each of the QKD devices have a number which no one else knows proven by science.
The nature of this "firewall" effect is that the systems controlling the QKD devices need to be secure and considered trusted - also called "trusted node" - where and how you draw the line can range from armed guards to just locking the server room door.
If you can't see the diagram below, please go to the online documentation, it can also be built by the doc target.
@startuml
title Where to start \n
skinparam activity {
StartColor #EF476F
BarColor #FFD166
EndColor #EF476F
BackgroundColor #06D6A0
BorderColor #118AB2
}
start
if (Do you have a QKD Device?) then (Yes)
if (Does your QKD device have a driver?) then (No)
:You will need to [[./index.html#CreatingDrivers create a driver]] which implements the
<b>IDriver</b> and <b>IReporting</b> interfaces.;
if (Use the library?) then (Yes)
:See the [[./index.html#RunningDummyQKDDriver DummyQKDDriver]] for an example.;
else (No)
endif
else (Yes)
endif
else (No)
:Check out how to run the
[[./index.html#RunningDummyQKDDriver DummyQKDDriver]];
endif
if (Do you want keys for multiple
locations in a network?) then (Yes: Sites)
:[[./index.html#Registering Register your driver]] with the site agent
using the <b>ISiteAgent</b> Interface
Keys can be obtained by using the [[./index.html#IKeyInterface IKey]] interface;
if(Do you want keys to be stored/persist between restarts?) then (Yes)
if (Do the keys need to be secured?) then (Yes)
:Use the [[./index.html#HSMs HSM storage]] options;
if (Is your HSM supported?) then (No)
:Create a driver that implements
the <b>IBackingStore</b> interface.
Add the driver creation to the
<b>BackingStoreFactory</b>;
else (Yes)
endif
else (No)
: When running SiteAgentRunner,
use the option ""-b file"" or
set ""backingStoreUrl"" to
""file:///filename.db"";
endif
else (No)
endif
else (No: Point-to-Point)
:Use the [[./index.html#IDeviceInterface IDevice interface]] to
control the driver for your system.
As keys become available they will
be sent via the call to <b>WaitForSession</b>.;
endif
if (Do you want to use the key for anything?) then (Yes)
:See the [[./index.html#Encryption encryption section]]
for an example of using the [[./index.html#IKeyInterface IKey interface]];
else (No)
endif
stop
@enduml
This section is just a comment on the useful functionality of DummyQKDDriver to simulate the output of a QKD device. Please refer to the next section for a guided example to set up a simple link with them.
As with most programs, passing -h to it will display the options available. The DummyQKDDriver runs a standard set of post processing steps on simulated photon detections using the cqp::DummyQKD class. There needs to be two instances of the program running, one for Alice and one for Bob.
Run Bob first on port 8000 by calling:
DummyQKDDriver -b -k 0.0.0.0:8000
Now run Alice, telling her to connect to Bob and start exchanging key (in manual mode):
DummyQKDDriver -a -m localhost:8000
If it worked you'll get a flood of error message like this: ERROR: OnKeyGeneration No listener for generated key. This is because while running the system like this is nice to see it doing something, it's not very useful, there's nowhere to put the key that is generated. the drivers are designed to be used by something.
The project has a system for managing the keys called Site Agents or you can talk to the drivers directly using the IDevice interface
Site agents, drivers, and VPN tunnels need to be parametrized for each node of the network with JSON string files as the default command without arguments don't specify all the necessary fields by default and only works for test on the same local machine.
Site agents can be run with the SiteAgentRunner, we can run two sites with:
SiteAgentRunner -c site-a.json
and:
SiteAgentRunner -c site-b.json
with each JSON file appropriately indicating a different port for Alice and Bob (e.g. 9000/9001). The JSON string looks like this:
{
"name":"",
"id":"",
"netManUri":"",
"bindAddress":"0.0.0.0",
"listenPort":9000,
"connectionAddress":"",
"credentials": {},
"useAutoDiscover":false,
"backingStoreUrl":"",
"fallbackKey":""
}
Now we can attach our DummyQKDDriver's, one to each site:
DummyQKDDriver -c driver_config-a.json # run as Alice, register with site agent
and:
DummyQKDDriver -c driver_config-b.json # run as Bob, register with site agent
with each JSON file appropriately indicating a different port for Alice and Bob (e.g. 9000/9001). The JSON string looks like this:
{
"controlParams": {
"config": {
"id": "dummyqkd__0__16_alice",
"side": "Alice",
"switchName": "",
"switchPort": "",
"kind": "dummyqkd",
"bytesPerKey": 0
},
"controlAddress": "'hostnameIP':4423",
"siteAgentAddress": "127.0.0.1:9000"
},
}
More complex setups can be achieved by implementing the cqp::remote::INetworkManager interface yourself to issue the commands. Feedback from the devices comes over the cqp::remote::IReporting interface on the same socket so that you can react to changes in the system. Here you can see how to extract things from the reporting interface with the StatsDump tool.
The driver application is a bridge between the internal device interfaces (cqp::IQKDDevice) and the external cqp::remote::IDevice interface. The cqp::RemoteQKDDevice class handles most of the work for you, the application must handle the configuration and creation of the device.
The real work is in creating a driver to setup the device and read the key. If your device just produces raw detections then you will need to configure a processing pipeline like cqp::DummyQKD or cqp::PhotonDetectorMk1 and cqp::LEDAliceMk1. If your device generates ready to use key like the cqp::Clavis3Device you need to read it and publish it over the cqp::IKeyCallback interface (use cqp::KeyPublisher).
Both these approaches require a form of session management, provided by cqp::session::SessionController and cqp::session::AliceSessionController, these implement the cqp::ISessionController and cqp::remote::ISession interface and are used by cqp::RemoteQKDDevice to start and stop the device and it's peer. These are usually all that's needed but in some situations they need to be specialised to cope with device requirements
@startuml Readme_Drivers
title Anatomy of a driver
package Application {
namespace cqp #DDDDDD {
class RemoteQKD
interface IQKDDevice {
GetSessionController()
}
class "SessionController" as session
interface "IDetector::Service" as detServ {
StartDetecting()
StopDetecting()
}
class "Provider<IDetectionEventCallback>" as provider {
Attach()
Dettach()
Emit()
}
RemoteQKD .r.> IQKDDevice : uses
IQKDDevice -r[hidden]-> session
}
class Main {
main()
}
class MyDriver
class Detector
MyDriver .u.|> cqp.IQKDDevice
MyDriver o-u-> cqp.session
Detector .u.|> cqp.detServ
Detector -u-|> cqp.provider
Main o-> MyDriver
MyDriver o-> Detector
Main o-u-> cqp.RemoteQKD
note bottom of MyDriver
In this case the driver is a simple detector
which produces detection. Post processing detail not shown.
MyDriver pull together all the parts to run the driver.
end note
note bottom of Detector
The detector controls the device
and outputs the data using the Provider
end note
}
@enduml
At the time of writing, all the drivers can be registered with a site agent by using the -r switch. This causes the cqp::RemoteQKDDevice to call cqp::remote::ISiteAgent::RegisterDevice on the cqp::SiteAgent, it will then use the cqp::remote::ISession interface to start/stop the device.
This interface allows a more direct access to the device than going through the site agents. Key generated by the device are immediately fed back to the caller. First call cqp::remote::IDevice::WaitForSession, then cqp::remote::IDevice::RunSession. Call cqp::remote::IDevice::EndSession to stop generating key.
HSMs are storage devices which are physically secure digital vaults. There is a standard interface for them called PKCS#11. Each manufacturer has their own interfaces and support for PKCS#11 is sketchy, however there is a software implementation which we use as a reference called SoftHSM2. The cqp::keygen::HSMStore class proves an implementation that links the cqp::SiteAgent with the cqp::IBackingStore interface.
Sites provide keys more many endpoints (the available key stores can be retrieved by calling cqp::remote::IKey::GetKeyStores), they can be requested by using the cqp::remote::IKey interface. Once a new key has been requested, the other side can only retrieve it as an existing key - this prevents clashing and misuse of keys.
More details on specific implementations can be found in:
Changes to values in the system are published externally via the cqp::remote::IReporting interface and internally with the cqp::stats::Stat class.
You can register for all stats by calling cqp::remote::IReporting::GetStatistics with the cqp::remote::ReportingFilter::listIsExclude field set to true or specific filters can be specified.
Standard BB84 QKD protocol requires post processing steps to turn the raw detections into usable keys.
An example of a processing pipeline can be seen in cqp::DummyQKD::ProcessingChain.
The builds are managed by the gitlab continuous integration system defined in the .gitlab-ci.yml file.
The docker images can by built manually by running setup/makeDocker.sh.
This is work in progress. This setup has be tested on Window 10 and Windows Server 2016 with Visual studio 15 (2016). The following configurations are supported
| OS | VS 2017 | QT Creator | Codeblocks |
|---|---|---|---|
| Linux | gcc | gcc | |
| Windows | MSVC | MSVC / MSYS2 | MSYS2-Mingw |
CMakeLists.txt file at the base of the source tree.mingw32- prefix.If your project file has deep folder structure (this is a bug), it can be improved by right clicking on the workspace and:
@Manual{,
title = {CQPToolkit: A QKD toolkit library},
author = {{Richard Collins, University of Bristol, UK}},
organization = {University of Bristol},
address = {Bristol, UK},
year = 2018,
url = {https://gitlab.com/QComms}
}
For more details on writing code for the tool kit see Coding Guide Questions and issues see the FAQ
The control address field can be 0.0.0.0:0 if there qre no firewall to deal with. If so, one need to indicate the actual IP of the host and chose an appropriate port the firewall will not block.
Nothing will happen yet as the site agents don't know what to do with these devices. We can instruct them to start making key (-b as begin) by sending the cqp::ISiteAgent::StartNode command:
SiteAgentCtl -c localhost:9000 -b '{"hops":[{"first":{"site":"'hostname':9000","deviceId":"dummyqkd__0__16_alice"},"second":{"site":"'hostname':9001","deviceId":"dummyqkd__0__16_bob"}}]}'
The computers have to be able to resolve hostnames and this should be used here instead of the hard IP address. So one should modify /etc/hostnames files on both machine if it's not the case.
This is a long winded way of saying connect A to B but it is very powerful, allowing multiple hops to be specified to produce an end-to-end secure key from a chain of devices. This JSON string can be specified in the site agents "staticHops" config field to let this happen automatically once all the devices are available. The link can then be stopped with:
SiteAgentCtl -c localhost:9000 -e '{"hops":[{"first":{"site":"'hostname':9000","deviceId":"dummyqkd__0__16_alice"},"second":{"site":"'hostname':9001","deviceId":"dummyqkd__0__16_bob"}}]}'
Note the
-einstead of-bto end the link instead of beginning it.
mingw32-make.exe -jSetupCodeBlocks-MSYS2.bat file