Post-quantum signing utilities
A trio of utilities for generating SPHINCS⁺-SHAKE256-192f signatures. This is a post-quantum, hash-based signature scheme with 194-bit security. It is also a test for using the Nimble package manager for managing Nim target dependencies.
This commit is contained in:
committed by
Norman Feske
parent
affa36eb2d
commit
2ba33d38a9
31
mk/nimble.mk
Normal file
31
mk/nimble.mk
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
TARGET ?= $(lastword $(subst /, ,$(PRG_DIR)))
|
||||||
|
NIMBLE_PKG ?= $(TARGET)
|
||||||
|
LIBS = base libc
|
||||||
|
|
||||||
|
CC_CXX_WARN_STRICT =
|
||||||
|
|
||||||
|
$(TARGET): assemble.tag $(SHARED_LIBS)
|
||||||
|
libs=$(LIB_CACHE_DIR); $(LD_CMD) $(wildcard src/nimcache/*.o) -o $(TARGET)
|
||||||
|
ln -sf $(CURDIR)/$@ $(INSTALL_DIR)/$@
|
||||||
|
|
||||||
|
assemble.tag: copy.tag nim.cfg
|
||||||
|
nimble --verbose cpp src/$(TARGET)
|
||||||
|
@touch $@
|
||||||
|
|
||||||
|
nim.cfg:
|
||||||
|
rm -f $@
|
||||||
|
echo "-d:nimDebugDlOpen" >> $@
|
||||||
|
echo "--os:genode" >> $@
|
||||||
|
echo "--cpu:$(NIM_CPU)" >> $@
|
||||||
|
echo "--noCppExceptions" >> $@
|
||||||
|
echo "--noLinking" >> $@
|
||||||
|
echo "--passC:\"$(CXX_DEF) $(CC_CXX_OPT) $(INCLUDES) -fpermissive\"" >> $@
|
||||||
|
echo "$(NIM_OPT)" >> $@
|
||||||
|
|
||||||
|
NIMBLE_PATH := $(shell nimble path $(NIMBLE_PKG))
|
||||||
|
|
||||||
|
copy.tag:
|
||||||
|
nimble install -n https://github.com/ehmry/nim-genode
|
||||||
|
cp -avu $(PRG_DIR)/* .
|
||||||
|
|
||||||
|
.PHONY: nim.cfg
|
||||||
66
run/sphincs_verify.run
Normal file
66
run/sphincs_verify.run
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
installed_command nimble
|
||||||
|
|
||||||
|
build { nimble/sphincs_verify }
|
||||||
|
|
||||||
|
create_boot_directory
|
||||||
|
|
||||||
|
import_from_depot genodelabs/src/[base_src] \
|
||||||
|
genodelabs/src/init \
|
||||||
|
genodelabs/src/report_rom \
|
||||||
|
genodelabs/src/libc
|
||||||
|
|
||||||
|
install_config {
|
||||||
|
<config>
|
||||||
|
<parent-provides>
|
||||||
|
<service name="CPU"/>
|
||||||
|
<service name="LOG"/>
|
||||||
|
<service name="PD"/>
|
||||||
|
<service name="RAM"/>
|
||||||
|
<service name="ROM"/>
|
||||||
|
</parent-provides>
|
||||||
|
|
||||||
|
<default-route> <any-service> <parent/> <any-child/> </any-service> </default-route>
|
||||||
|
|
||||||
|
<start name="report_rom" caps="100">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<provides> <service name="Report"/> <service name="ROM"/> </provides>
|
||||||
|
<config verbose="yes"/>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="verify" caps="200">
|
||||||
|
<binary name="sphincs_verify"/>
|
||||||
|
<resource name="RAM" quantum="12M"/>
|
||||||
|
<config verbose="yes">
|
||||||
|
<libc stdout="/dev/log" stderr="/dev/null" rtc="/dev/null"/>
|
||||||
|
<vfs>
|
||||||
|
<tar name="test.tar"/>
|
||||||
|
<dir name="dev"> <log/> <null/> </dir>
|
||||||
|
</vfs>
|
||||||
|
<verify path="expect_valid.txt" pubkey="/nonexistent_pubkey"/>
|
||||||
|
<verify path="expect_valid.txt" pubkey="/dev/null"/>
|
||||||
|
<verify path="expect_valid.txt" pubkey="/pubkey"/>
|
||||||
|
<verify path="expect_invalid.txt" pubkey="/pubkey"/>
|
||||||
|
</config>
|
||||||
|
</start>
|
||||||
|
</config>
|
||||||
|
}
|
||||||
|
|
||||||
|
exec tar cf [run_dir]/genode/test.tar -C [genode_dir]/repos/world/src/nimble/sphincs_verify/test_data .
|
||||||
|
|
||||||
|
build_boot_image { sphincs_verify libc.lib.so vfs.lib.so pthread.lib.so }
|
||||||
|
|
||||||
|
append qemu_args " -nographic "
|
||||||
|
|
||||||
|
run_genode_until {</result>.*\n} 30
|
||||||
|
|
||||||
|
grep_output {\[init \-\> report_rom\]}
|
||||||
|
|
||||||
|
compare_output_to {
|
||||||
|
[init -> report_rom] report 'verify -> result'
|
||||||
|
[init -> report_rom] <result>
|
||||||
|
[init -> report_rom] <bad reason="public key unavailable" path="expect_valid.txt" />
|
||||||
|
[init -> report_rom] <bad reason="malformed public key" path="expect_valid.txt" />
|
||||||
|
[init -> report_rom] <good path="expect_valid.txt" />
|
||||||
|
[init -> report_rom] <bad reason="bad signature" path="expect_invalid.txt" />
|
||||||
|
[init -> report_rom] </result>
|
||||||
|
}
|
||||||
12
src/nimble/sphincs_keygen/sphincs_keygen.nimble
Normal file
12
src/nimble/sphincs_keygen/sphincs_keygen.nimble
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Emery Hemingway"
|
||||||
|
description = "A post-quantum drop-in-replacement for the Depot verify tool"
|
||||||
|
license = "GPLv3"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["sphincs_keygen"]
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 0.18.0", "sphincs"
|
||||||
17
src/nimble/sphincs_keygen/src/sphincs_keygen.nim
Normal file
17
src/nimble/sphincs_keygen/src/sphincs_keygen.nim
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import std/streams
|
||||||
|
import sphincs/shake256_192f
|
||||||
|
|
||||||
|
let rngStr = openFileStream("/dev/random")
|
||||||
|
proc readDevRand(p: pointer; size: int) =
|
||||||
|
let n = rngStr.readData(p, size)
|
||||||
|
doAssert(n == size, "short read from RNG")
|
||||||
|
|
||||||
|
proc writeToFile[T](path: string; x: var T) =
|
||||||
|
let fs = openFileStream(path, fmWrite)
|
||||||
|
fs.writeData(x.addr, sizeof(x))
|
||||||
|
close fs
|
||||||
|
|
||||||
|
var pair = generateKeypair(readDevRand)
|
||||||
|
writeToFile("secret", pair)
|
||||||
|
writeToFile("public", pair.pk)
|
||||||
|
echo "secret key written to ./secret, public key written to ./public"
|
||||||
3
src/nimble/sphincs_keygen/target.mk
Normal file
3
src/nimble/sphincs_keygen/target.mk
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include $(call select_from_repositories,mk/nimble.mk)
|
||||||
|
|
||||||
|
LIBS += base libc
|
||||||
12
src/nimble/sphincs_sign/sphincs_sign.nimble
Normal file
12
src/nimble/sphincs_sign/sphincs_sign.nimble
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Emery Hemingway"
|
||||||
|
description = "A post-quantum drop-in-replacement for the Depot verify tool"
|
||||||
|
license = "GPLv3"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["sphincs_sign"]
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 0.18.0", "sphincs", "nimcrypto"
|
||||||
16
src/nimble/sphincs_sign/src/sphincs_keygen.nim
Normal file
16
src/nimble/sphincs_sign/src/sphincs_keygen.nim
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import std/streams
|
||||||
|
import sphincs/shake256_256f
|
||||||
|
|
||||||
|
let rngStr = openFileStream("/dev/random")
|
||||||
|
proc readDevRand(p: pointer; size: int) =
|
||||||
|
let n = rngStr.readData(p, size)
|
||||||
|
doAssert(n == size, "short read from RNG")
|
||||||
|
|
||||||
|
proc writeToFile[T](path: string; x: var T) =
|
||||||
|
let fs = openFileStream(path, fmWrite)
|
||||||
|
fs.writeData(x.addr, sizeof(x))
|
||||||
|
close fs
|
||||||
|
|
||||||
|
var pair = shake256_256f.generateKeypair(readDevRand)
|
||||||
|
writeToFile("secret", pair)
|
||||||
|
writeToFile("public", pair.pk)
|
||||||
53
src/nimble/sphincs_sign/src/sphincs_sign.nim
Normal file
53
src/nimble/sphincs_sign/src/sphincs_sign.nim
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import std/parseopt, std/streams
|
||||||
|
import sphincs/shake256_192f
|
||||||
|
import nimcrypto.hash, nimcrypto/keccak
|
||||||
|
|
||||||
|
proc hashFile(path: string): string =
|
||||||
|
let fs = openFileStream(path, fmRead)
|
||||||
|
result = newString(32)
|
||||||
|
var
|
||||||
|
ctx: sha3_256
|
||||||
|
buf: array[512, byte]
|
||||||
|
let bp = addr buf[0]
|
||||||
|
init ctx
|
||||||
|
while not fs.atEnd:
|
||||||
|
let n = fs.readData(bp, buf.len)
|
||||||
|
ctx.update(bp, n.uint)
|
||||||
|
close fs
|
||||||
|
var d = finish ctx
|
||||||
|
copyMem(result[0].addr, d.data[0].addr, result.len)
|
||||||
|
|
||||||
|
proc readPair(path: string): KeyPair =
|
||||||
|
let fs = openFileStream path
|
||||||
|
defer: close fs
|
||||||
|
doAssert(fs.readData(result.addr, sizeof(result)) == sizeof(result))
|
||||||
|
|
||||||
|
let rngStr = openFileStream("/dev/random")
|
||||||
|
proc readDevRand(p: pointer; size: int) =
|
||||||
|
let n = rngStr.readData(p, size)
|
||||||
|
doAssert(n == size, "short read from RNG")
|
||||||
|
|
||||||
|
proc signPath(pair: KeyPair; path: string) =
|
||||||
|
let
|
||||||
|
digest = hashFile path
|
||||||
|
sig = pair.sign(digest, readDevRand)
|
||||||
|
writeFile(path & ".sphincs", sig)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
|
||||||
|
var
|
||||||
|
pair: KeyPair
|
||||||
|
argi = 0
|
||||||
|
for kind, key, val in getopt():
|
||||||
|
if kind != cmdArgument:
|
||||||
|
quit("invalid argument " & key & val)
|
||||||
|
if argi == 0:
|
||||||
|
pair = readPair(key)
|
||||||
|
else:
|
||||||
|
signPath(pair, key)
|
||||||
|
inc argi
|
||||||
|
|
||||||
|
if argi == 0:
|
||||||
|
echo "usage: sphincs_sign [SECRET_KEY] [FILE]"
|
||||||
|
|
||||||
|
main()
|
||||||
69
src/nimble/sphincs_sign/src/sphincs_verify.nim
Normal file
69
src/nimble/sphincs_sign/src/sphincs_verify.nim
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import std/xmltree, std/xmlparser, std/streams
|
||||||
|
import sphincs/shake256_256f
|
||||||
|
import nimcrypto.hash, nimcrypto/keccak
|
||||||
|
|
||||||
|
proc hashFile(path: string): string =
|
||||||
|
let fs = openFileStream(path, fmRead)
|
||||||
|
result = newString(32)
|
||||||
|
var
|
||||||
|
ctx: sha3_256
|
||||||
|
buf: array[512, byte]
|
||||||
|
let bp = addr buf[0]
|
||||||
|
init ctx
|
||||||
|
while not fs.atEnd:
|
||||||
|
let n = fs.readData(bp, buf.len)
|
||||||
|
ctx.update(bp, n.uint)
|
||||||
|
close fs
|
||||||
|
var d = finish ctx
|
||||||
|
copyMem(result[0].addr, d.data[0].addr, result.len)
|
||||||
|
|
||||||
|
proc readPk(path: string): Pk =
|
||||||
|
let fs = openFileStream path
|
||||||
|
defer: close fs
|
||||||
|
doAssert(fs.readData(result.addr, sizeof(result)) == sizeof(result))
|
||||||
|
|
||||||
|
proc verify(filePath, pkPath: string): XmlNode =
|
||||||
|
try:
|
||||||
|
let
|
||||||
|
pk = readPk(pkPath)
|
||||||
|
sig = readFile(filePath & ".spx")
|
||||||
|
let
|
||||||
|
(valid, msg) = pk.verify sig
|
||||||
|
if not valid:
|
||||||
|
result = <>bad(path=filePath, reason="invalid signature")
|
||||||
|
else:
|
||||||
|
let digest = hashFile(filePath)
|
||||||
|
if digest == msg:
|
||||||
|
result = <>good(path=filePath)
|
||||||
|
else:
|
||||||
|
result = <>bad(path=filePath, reason="invalid digest")
|
||||||
|
except:
|
||||||
|
let e = getCurrentException()
|
||||||
|
result = <>bad(path=filePath, reason=e.msg)
|
||||||
|
|
||||||
|
when defined(genode):
|
||||||
|
import genode/reports, genode/roms
|
||||||
|
|
||||||
|
proc xml(rom: RomClient): XmlNode =
|
||||||
|
let s = rom.newStream
|
||||||
|
result = s.parseXml
|
||||||
|
close s
|
||||||
|
|
||||||
|
componentConstructHook = proc (env: GenodeEnv) =
|
||||||
|
let report = env.newReportClient("result")
|
||||||
|
|
||||||
|
proc handleConfig(rom: RomClient) =
|
||||||
|
let nodes = rom.xml.findAll("verify")
|
||||||
|
var results = newSeq[XmlNode](nodes.len)
|
||||||
|
for i, x in nodes.pairs:
|
||||||
|
results[i] = verify(x.attr("path"), x.attr("pubkey"))
|
||||||
|
|
||||||
|
report.submit do (s: Stream):
|
||||||
|
s.writeLine("<result>")
|
||||||
|
for r in results.items:
|
||||||
|
s.writeLine(r)
|
||||||
|
s.writeLine("</result>")
|
||||||
|
let
|
||||||
|
configHandler = env.newRomHandler("config", handleConfig)
|
||||||
|
|
||||||
|
process configHandler
|
||||||
3
src/nimble/sphincs_sign/target.mk
Normal file
3
src/nimble/sphincs_sign/target.mk
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include $(call select_from_repositories,mk/nimble.mk)
|
||||||
|
|
||||||
|
LIBS += base libc
|
||||||
12
src/nimble/sphincs_verify/sphincs_verify.nimble
Normal file
12
src/nimble/sphincs_verify/sphincs_verify.nimble
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Emery Hemingway"
|
||||||
|
description = "A post-quantum drop-in-replacement for the Depot verify tool"
|
||||||
|
license = "GPLv3"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["sphincs_verify"]
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 0.18.0", "genode >= 18.7", "sphincs", "nimcrypto"
|
||||||
71
src/nimble/sphincs_verify/src/sphincs_verify.nim
Normal file
71
src/nimble/sphincs_verify/src/sphincs_verify.nim
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import std/xmltree, std/xmlparser, std/streams
|
||||||
|
import sphincs/shake256_192f
|
||||||
|
import nimcrypto.hash, nimcrypto/keccak
|
||||||
|
|
||||||
|
proc hashFile(path: string): string =
|
||||||
|
let fs = openFileStream(path, fmRead)
|
||||||
|
result = newString(32)
|
||||||
|
var
|
||||||
|
ctx: sha3_256
|
||||||
|
buf: array[512, byte]
|
||||||
|
let bp = addr buf[0]
|
||||||
|
init ctx
|
||||||
|
while not fs.atEnd:
|
||||||
|
let n = fs.readData(bp, buf.len)
|
||||||
|
ctx.update(bp, n.uint)
|
||||||
|
close fs
|
||||||
|
var d = finish ctx
|
||||||
|
copyMem(result[0].addr, d.data[0].addr, result.len)
|
||||||
|
|
||||||
|
proc readPk(path: string): Pk =
|
||||||
|
let fs = newFileStream path
|
||||||
|
if fs.isNil: raiseAssert("public key unavailable")
|
||||||
|
defer: close fs
|
||||||
|
if fs.readData(result.addr, sizeof(result)) != sizeof(result):
|
||||||
|
raiseAssert("malformed public key")
|
||||||
|
|
||||||
|
proc verify(filePath, pkPath: string): XmlNode =
|
||||||
|
try:
|
||||||
|
let
|
||||||
|
pk = readPk(pkPath)
|
||||||
|
sig = readFile(filePath & ".sphincs")
|
||||||
|
let
|
||||||
|
(valid, msg) = pk.verify sig
|
||||||
|
if not valid:
|
||||||
|
result = <>bad(path=filePath, reason="bad signature")
|
||||||
|
else:
|
||||||
|
let digest = hashFile(filePath)
|
||||||
|
if digest == msg:
|
||||||
|
result = <>good(path=filePath)
|
||||||
|
else:
|
||||||
|
result = <>bad(path=filePath, reason="file digest mismatch")
|
||||||
|
except:
|
||||||
|
let e = getCurrentException()
|
||||||
|
result = <>bad(path=filePath, reason=e.msg)
|
||||||
|
|
||||||
|
when defined(genode):
|
||||||
|
import genode/reports, genode/roms
|
||||||
|
|
||||||
|
proc xml(rom: RomClient): XmlNode =
|
||||||
|
let s = rom.newStream
|
||||||
|
result = s.parseXml
|
||||||
|
close s
|
||||||
|
|
||||||
|
componentConstructHook = proc (env: GenodeEnv) =
|
||||||
|
let report = env.newReportClient("result")
|
||||||
|
|
||||||
|
proc handleConfig(rom: RomClient) =
|
||||||
|
let nodes = rom.xml.findAll("verify")
|
||||||
|
var results = newSeq[XmlNode](nodes.len)
|
||||||
|
for i, x in nodes.pairs:
|
||||||
|
results[i] = verify(x.attr("path"), x.attr("pubkey"))
|
||||||
|
|
||||||
|
report.submit do (s: Stream):
|
||||||
|
let xml = <>result()
|
||||||
|
for r in results.items:
|
||||||
|
xml.add(r)
|
||||||
|
s.writeLine(xml)
|
||||||
|
let
|
||||||
|
configHandler = env.newRomHandler("config", handleConfig)
|
||||||
|
|
||||||
|
process configHandler
|
||||||
3
src/nimble/sphincs_verify/target.mk
Normal file
3
src/nimble/sphincs_verify/target.mk
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include $(call select_from_repositories,mk/nimble.mk)
|
||||||
|
|
||||||
|
LIBS += base libc
|
||||||
1
src/nimble/sphincs_verify/test_data/expect_invalid.txt
Normal file
1
src/nimble/sphincs_verify/test_data/expect_invalid.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Streifenhoernchen sind lecker
|
||||||
BIN
src/nimble/sphincs_verify/test_data/expect_invalid.txt.sphincs
Normal file
BIN
src/nimble/sphincs_verify/test_data/expect_invalid.txt.sphincs
Normal file
Binary file not shown.
1
src/nimble/sphincs_verify/test_data/expect_valid.txt
Normal file
1
src/nimble/sphincs_verify/test_data/expect_valid.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Kinder moegen Suessigkeiten
|
||||||
BIN
src/nimble/sphincs_verify/test_data/expect_valid.txt.sphincs
Normal file
BIN
src/nimble/sphincs_verify/test_data/expect_valid.txt.sphincs
Normal file
Binary file not shown.
1
src/nimble/sphincs_verify/test_data/pubkey
Normal file
1
src/nimble/sphincs_verify/test_data/pubkey
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rOÛâ:áI³£<C2B3>ÈaþÎSFzw—cw}ÙB)i¨V–_ž—ÿ]žŒ÷TÃç
|
||||||
1
src/nimble/sphincs_verify/test_data/secret
Normal file
1
src/nimble/sphincs_verify/test_data/secret
Normal file
@@ -0,0 +1 @@
|
|||||||
|
>Q¢”C¶HµD‰¸v6‡ÉöûºÝàŒ½Þ´R¯ñã›ÀüZR#ÜîÓŠ¯øxâ‹ÑìrOÛâ:áI³£<C2B3>ÈaþÎSFzw—cw}ÙB)i¨V–_ž—ÿ]žŒ÷TÃç
|
||||||
Reference in New Issue
Block a user