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