"go.cypherpunks.su/keks/types"
)
-const (
- FdPubR = 4
- FdPrvR = 8
-
- X25519KeyLen = 32
-)
+const X25519KeyLen = 32
var (
- PrvDir = flag.String("prvs", "prvs", "Path to directory with private keys")
- PubDir = flag.String("pubs", "pubs", "Path to directory with public keys")
+ PrvDir = flag.String("prvs", os.Getenv("CM_PRVS"),
+ "Path to directory with private keys")
+ PubDirPth = flag.String("pubs", os.Getenv("CM_PUBS"),
+ "Path to directory with public keys")
+ PubDir sign.PubStorageDir
)
func blake2bHash() hash.Hash {
return h
}
-func pubParse(pth string) (pubData *sign.PubData, err error) {
- var data []byte
- data, err = os.ReadFile(pth)
- if err != nil {
- return
- }
- var signed *sign.Signed
- signed, _, err = sign.PubParse(data)
- if err != nil {
- return
- }
+func pubDataFromSigned(signed *sign.Signed) (pubData *sign.PubData, err error) {
pubData = signed.PubData()
if !pubData.Can(sign.KUKEM) {
err = fmt.Errorf("does not have %s key usage", sign.KUKEM)
return
}
-func findPub(id []byte) (pub *sign.PubData, err error) {
- pth := path.Join(
- *PubDir, strings.ToUpper(hex.EncodeToString(id)),
- )
- if _, err = os.Stat(pth); err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- err = nil
- }
+func pubParse(pth string) (pubData *sign.PubData, err error) {
+ var data []byte
+ data, err = os.ReadFile(pth)
+ if err != nil {
+ return
+ }
+ var signed *sign.Signed
+ signed, _, err = sign.PubParse(data)
+ if err != nil {
return
}
- return pubParse(pth)
+ return pubDataFromSigned(signed)
}
func findPrv(id []byte) (av *cm.AV, err error) {
if v, err = d.Decode(); err != nil {
return
}
- if err = schema.Check("av", sign.PubSchemas, v); err != nil {
+ if err = schema.Check("prv", sign.PrvSchemas, v); err != nil {
return
}
}
`Assume that hexadecimal "from" value if missing`)
noFrom := flag.Bool("no-from", false, `Do not include "from" field in KEMs`)
noTo := flag.Bool("no-to", false, `Do not include "to" field in KEMs`)
+ noId := flag.Bool("no-id", false, `Do not include "id" field`)
passphrase := flag.Bool("p", false, "Use passphrase")
balloonS := flag.Int("balloon-s", 1<<17, "Balloon's space cost")
balloonT := flag.Int("balloon-t", 4, "Balloon's time cost")
parallel := flag.Int("parallel", cmhash.DefaultNumCPU, "Parallel cryptors")
noblob := flag.Bool("embed", false, "Include payload into container")
fromPth := flag.String("from", "", "Path to sender's public key for authentication")
+ flag.Parse()
+
+ PubDir.Dir = *PubDirPth
var pubs []cm.AV
var pubIds [][]byte
- flag.Func("to", "Path to recipient's public key", func(v string) error {
- pubData, err := pubParse(v)
+ for _, pth := range flag.Args() {
+ pubData, err := pubParse(pth)
if err != nil {
- log.Fatalln(v, ":", err)
+ log.Fatalln(pth, ":", err)
}
pubs = append(pubs, pubData.Pub[0])
pubIds = append(pubIds, pubData.Id)
- return nil
- })
- flag.Parse()
+ }
var err error
var assumeTo []byte
if len(encrypted.KEM) == 0 {
log.Fatalln("no KEMs")
}
+ var id uuid.UUID
+ if encrypted.Id != nil {
+ id = *encrypted.Id
+ }
for kemIdx, kem := range encrypted.KEM {
switch kem.A {
case cmballoon.BalloonBLAKE2bHKDF:
if err != nil {
log.Fatal(err)
}
- cek, err = cmballoon.Decapsulate(kem, encrypted.Id[:], passwd)
+ cek, err = cmballoon.Decapsulate(kem, id[:], passwd)
if err != nil {
log.Print(err)
continue
if bytes.Equal(kem.From, bytes.Repeat([]byte{0}, 32)) {
kem.From = assumeFrom
}
- from, err = findPub(kem.From)
+ var signed *sign.Signed
+ signed, err = PubDir.Get(kem.From)
if err != nil {
log.Fatalln("from:", err)
}
- if from == nil {
+ if signed == nil {
log.Fatalln(kemIdx, kem.A, "can not find public key")
}
+ from, err = pubDataFromSigned(signed)
+ if err != nil {
+ log.Fatalln("from:", err)
+ }
}
var ourSNTRUP sntrup761kem.PrivateKey
ourSNTRUP, err = scheme.UnmarshalBinaryPrivateKey(
kek, err = hkdf.Expand(
blake2bHash,
prk,
- string(append(
- []byte(cmenc.SNTRUP761X25519Info),
- encrypted.Id[:]...)),
+ string(append([]byte(cmenc.SNTRUP761X25519Info), id[:]...)),
chacha20poly1305.KeySize)
if err != nil {
log.Fatal(err)
if bytes.Equal(kem.From, bytes.Repeat([]byte{0}, 32)) {
kem.From = assumeFrom
}
- from, err = findPub(kem.From)
+ var signed *sign.Signed
+ signed, err = PubDir.Get(kem.From)
if err != nil {
log.Fatalln("from:", err)
}
- if from == nil {
+ if signed == nil {
log.Fatalln(kemIdx, kem.A, "can not find public key")
}
+ from, err = pubDataFromSigned(signed)
+ if err != nil {
+ log.Fatalln("from:", err)
+ }
}
var ourMcEliece *mceliece6960119.PrivateKey
ourMcEliece, err = mceliece6960119.UnmarshalBinaryPrivateKey(
keyMcEliece,
string(append(
[]byte(cmenc.ClassicMcEliece6960119X25519DecapInfo),
- encrypted.Id[:]...)),
+ id[:]...)),
chacha20poly1305.KeySize+chacha20poly1305.NonceSizeX,
)
if err != nil {
prk,
string(append(
[]byte(cmenc.ClassicMcEliece6960119X25519Info),
- encrypted.Id[:]...)),
+ id[:]...)),
chacha20poly1305.KeySize,
)
if err != nil {
}
} else {
var id uuid.UUID
- if *setId == "" {
- id, err = uuid.NewRandom()
- } else {
- id, err = uuid.Parse(*setId)
+ if !*noId {
+ if *setId == "" {
+ id, err = uuid.NewRandom()
+ } else {
+ id, err = uuid.Parse(*setId)
+ }
}
if err != nil {
log.Fatal(err)
log.Fatal(err)
}
enc := cmenc.Encrypted{
- Id: id,
KEM: kems,
DEM: cmenc.DEM{A: chapoly.DEMAlgo},
}
+ if !*noId {
+ enc.Id = &id
+ }
if *noblob {
var buf bytes.Buffer
if _, err = chapoly.Seal(&buf, os.Stdin, cek, *parallel); err != nil {
-algo sntrup761-x25519 -ku kem -sub N=1 5>enc.1.pub 9>enc.1.prv"
test_expect_success "encrypting" "
- cmenctool -to enc.0.pub -to enc.1.pub <enc.data >enc.enc"
+ cmenctool enc.0.pub enc.1.pub <enc.data >enc.enc"
pubId=$(kekspp -v -p /data/id <enc.0.pub)
ln -s enc.0.prv $pubId
ln -fs ../s.pub pubs/$senderId
ln -fs ../r.pub pubs/$(kekspp -v -p /data/id <r.pub)
test_expect_success "$algo: encrypting" "cmenctool \
- -from s.pub -prvs prvs -no-from -to r.pub <enc.data >enc.enc"
+ -from s.pub -prvs prvs -no-from r.pub <enc.data >enc.enc"
test_expect_success "$algo: decrypting fails" "
! cmenctool -d -prvs prvs -pubs pubs <enc.enc >enc.data.got"
test_expect_success "$algo: decrypting" "
--- /dev/null
+#!/bin/sh
+
+test_description="Check workability with missing \"id\""
+. $SHARNESS_TEST_SRCDIR/sharness.sh
+
+dd if=/dev/urandom of=enc.data bs=300K count=1 2>/dev/null
+
+for algo in sntrup761-x25519 mceliece6960119-x25519 ; do
+
+test_expect_success "$algo: pub generation" "cmkeytool \
+ -algo $algo -ku kem -sub N=0 5>enc.pub 9>enc.prv"
+test_expect_success "$algo: encrypting" "cmenctool -no-id enc.pub <enc.data >enc.enc"
+pubId=$(kekspp -v -p /data/id <enc.pub)
+ln -s enc.prv $pubId
+test_expect_success "$algo: decrypting" "
+ cmenctool -d -prvs . <enc.enc >enc.data.got"
+test_expect_success "$algo: comparing" "test_cmp enc.data enc.data.got"
+
+done
+
+test_done
for algo in sntrup761-x25519 mceliece6960119-x25519 ; do
test_expect_success "$algo: pub generation" "cmkeytool \
- -algo sntrup761-x25519 -ku kem -sub N=0 5>enc.pub 9>enc.prv"
-test_expect_success "$algo: encrypting" "cmenctool -no-to -to enc.pub <enc.data >enc.enc"
+ -algo $algo -ku kem -sub N=0 5>enc.pub 9>enc.prv"
+test_expect_success "$algo: encrypting" "cmenctool -no-to enc.pub <enc.data >enc.enc"
pubId=$(kekspp -v -p /data/id <enc.pub)
ln -s enc.prv $pubId
test_expect_success "$algo: decrypting fails" "
test_expect_success "$algo: key encrypting" "
cmenctool -p -embed $balloonparams <enc.prv >enc.prv.enc"
test_expect_success "$algo: data encrypting" "
- cmenctool -to enc.pub <enc.data >enc.enc"
+ cmenctool enc.pub <enc.data >enc.enc"
ln -s enc.prv.enc $(kekspp -v -p /data/id <enc.pub)
test_expect_success "$algo: decrypting" "
cmenctool -d -prvs . <enc.enc >enc.data.got"
id1=$(kekspp -v -p /data/id <enc.$algo.pub)
test_expect_success "encrypting" "
- cmenctool -to enc.$algo0.pub -to enc.$algo1.pub <enc.data >enc.enc"
+ cmenctool enc.$algo0.pub enc.$algo1.pub <enc.data >enc.enc"
ln -s enc.$algo0.prv $id0
ln -s enc.$algo1.prv $id1
test_expect_success "comparing" "test_cmp enc.data enc.data.got"
rm $id1
-test_expect_success "$algo0: decrypting" "
- cmenctool -d -prvs . <enc.enc >enc.data.got"
+CM_PRVS=$(realpath .) test_expect_success "$algo0: decrypting" "
+ cmenctool -d <enc.enc >enc.data.got"
test_expect_success "$algo0: comparing" "test_cmp enc.data enc.data.got"
rm $id0
export CM_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
test_expect_success "encrypting also with passphrase" "
- cmenctool $balloonparams -p -to enc.$algo0.pub -to enc.$algo1.pub <enc.data >enc.enc"
+ cmenctool $balloonparams -p enc.$algo0.pub enc.$algo1.pub <enc.data >enc.enc"
test_expect_success "any: decrypting" "
cmenctool -d -prvs . <enc.enc >enc.data.got"
test_expect_success "comparing" "test_cmp enc.data enc.data.got"
ln -fs ../s.pub pubs/$(kekspp -v -p /data/id <s.pub)
ln -fs ../r.pub pubs/$(kekspp -v -p /data/id <r.pub)
test_expect_success "$algo: encrypting" "cmenctool \
- -from s.pub -prvs prvs -to r.pub <enc.data >enc.enc"
+ -from s.pub -prvs prvs r.pub <enc.data >enc.enc"
+export CM_PRVS=$(realpath prvs) CM_PUBS=$(realpath pubs)
test_expect_success "$algo: decrypting" "
- cmenctool -d -prvs prvs -pubs pubs <enc.enc >enc.data.got"
+ cmenctool -d <enc.enc >enc.data.got"
test_expect_success "$algo: comparing" "test_cmp enc.data enc.data.got"
ln -fs ../r.pub pubs/$(kekspp -v -p /data/id <s.pub)
test_expect_success "$algo: bad auth" "
- ! cmenctool -d -prvs prvs -pubs pubs <enc.enc >enc.data.got"
+ ! cmenctool -d <enc.enc >enc.data.got"
done
func usage() {
fmt.Fprintf(os.Stderr, `Usage:
Encrypt to recipient(s):
- cmenctool [-no-to] -to PUB0 [-to PUB1 ...] <DATA >DATA.encrypted
+ cmenctool [-no-to] [-no-from] [-from PUB] [-prvs dir] \
+ PUB0 [PUB1 ...] <DATA >DATA.encrypted
Encrypt on passphrase:
cmenctool -p [-balloon-s X] [-balloon-t X] [-balloon-p X] <DATA >DATA.encrypted
Decrypt by providing possible KEMs and/or passphrase(s):
- cmenctool -d [-p] -prvs prvs/dir <DATA.encrypted >DATA
- With sender authentication:
- cmenctool [...] -from PUB -prvs prvs/dir [...]
- cmenctool [...] -d -pubs pubs/dir [...]
+ cmenctool -d [-p] [-prvs dir] [-pubs dir] <DATA.encrypted >DATA
+
+PUB is a path to public key files.
+-prvs/-pubs directory must contain key files with uppercase hexadecimal
+corresponding public key's id used as the filename. They can be set by
+CM_PRVS and CM_PUBS environment variables.
+
+If -from is specified, then sender authentication will be enabled and
+-prvs must be set to find corresponding private key. -pubs must be set
+on recipient side in that case.
-{prvs,pubs}/dir must contain key files with uppercase hexadecimal
-corresponding public key's id used as the filename.
`)
flag.PrintDefaults()
}
test_description="Check certification"
. $SHARNESS_TEST_SRCDIR/sharness.sh
+mkdir prvs pubs
+
echo "gost3410-512C gost3410-256A
ed25519-blake2b ed25519-blake2b
slh-dsa-shake-256s slh-dsa-shake-256s" | while read caAlgo eeAlgo ; do
+unset CM_PRVS CM_PUBS
sub="-sub CN=CA -sub C=RU"
test_expect_success "$caAlgo: CA load generation" "cmkeytool \
-algo $caAlgo -ku sig $sub \
5>ca.$caAlgo.pub 9>ca.$caAlgo.prv"
-test_expect_success "$caAlgo: CA generation" "cmkeytool \
- 4<ca.$caAlgo.pub \
- 8<ca.$caAlgo.prv \
- <ca.$caAlgo.pub \
- 5>ca.$caAlgo.pub.certified"
+ln -fs ../ca.$caAlgo.prv prvs/$(kekspp -v -p /pub-id <ca.$caAlgo.prv)
+export CM_PRVS=$(realpath prvs)
+test_expect_success "$caAlgo: CA generation" "cmkeytool -c \
+ ca.$caAlgo.pub ca.$caAlgo.pub >ca.$caAlgo.pub.certified"
mv ca.$caAlgo.pub.certified ca.$caAlgo.pub
-test_expect_success "$caAlgo: CA regeneration" "cmkeytool \
- 4<ca.$caAlgo.pub \
- 8<ca.$caAlgo.prv \
- <ca.$caAlgo.pub \
- 5>ca.$caAlgo.pub.certified"
+test_expect_success "$caAlgo: CA regeneration" "cmkeytool -c \
+ ca.$caAlgo.pub ca.$caAlgo.pub >ca.$caAlgo.pub.certified"
mv ca.$caAlgo.pub.certified ca.$caAlgo.pub
-test_expect_success "$caAlgo: CA self-signature" "
- cmkeytool -verify 4<ca.$caAlgo.pub <ca.$caAlgo.pub"
+test_expect_success "$caAlgo: CA self-signature" "cmkeytool -v ca.$caAlgo.pub"
sub="-sub CN=SubCA -sub C=RU"
test_expect_success "$eeAlgo: SubCA load generation" "cmkeytool \
-algo $eeAlgo -ku sig $sub \
5>subca.$eeAlgo.pub 9>subca.$eeAlgo.prv"
-test_expect_success "$eeAlgo: SubCA generation" "cmkeytool \
- 4<ca.$caAlgo.pub \
- 8<ca.$caAlgo.prv \
- <subca.$eeAlgo.pub \
- 5>subca.$eeAlgo.pub.certified"
+test_expect_success "$eeAlgo: SubCA generation" "cmkeytool -c \
+ subca.$eeAlgo.pub ca.$caAlgo.pub >subca.$eeAlgo.pub.certified"
mv subca.$eeAlgo.pub.certified subca.$eeAlgo.pub
-test_expect_success "$eeAlgo: SubCA signature" "
- cmkeytool -verify 4<ca.$caAlgo.pub <subca.$eeAlgo.pub"
+test_expect_success "$eeAlgo: SubCA signature fails" "
+ ! cmkeytool -v subca.$eeAlgo.pub"
+ln -fs ../ca.$caAlgo.pub pubs/$(kekspp -v -p /data/id <ca.$caAlgo.pub)
+export CM_PUBS=$(realpath pubs)
+test_expect_success "$eeAlgo: SubCA signature" "cmkeytool -v subca.$eeAlgo.pub"
sub="-sub CN=EE -sub C=RU"
+ln -fs ../subca.$eeAlgo.prv prvs/$(kekspp -v -p /data/id <subca.$eeAlgo.pub)
test_expect_success "$eeAlgo: EE load generation" "cmkeytool \
-algo $eeAlgo $sub \
5>ee.$eeAlgo.pub 9>ee.$eeAlgo.prv"
-test_expect_success "$eeAlgo: EE generation" "cmkeytool \
- 4<subca.$eeAlgo.pub \
- 8<subca.$eeAlgo.prv \
- <ee.$eeAlgo.pub \
- 5>ee.$eeAlgo.pub.certified"
+test_expect_success "$eeAlgo: EE generation" "cmkeytool -c \
+ ee.$eeAlgo.pub subca.$eeAlgo.pub >ee.$eeAlgo.pub.certified"
mv ee.$eeAlgo.pub.certified ee.$eeAlgo.pub
-test_expect_success "$eeAlgo: EE chain" "
- cat ca.$caAlgo.pub subca.$eeAlgo.pub |
- cmkeytool -verify 4<&0 <ee.$eeAlgo.pub"
+ln -fs ../subca.$eeAlgo.pub pubs/$(kekspp -v -p /data/id <subca.$eeAlgo.pub)
+test_expect_success "$eeAlgo: EE chain" "cmkeytool -v ee.$eeAlgo.pub"
done
test_description="Check KEM certificates generation"
. $SHARNESS_TEST_SRCDIR/sharness.sh
-echo "mceliece6960119-x25519
-sntrup761-x25519" | while read algo ; do
+for algo in mceliece6960119-x25519 sntrup761-x25519 ; do
test_expect_success "$algo: generation" "
cmkeytool -algo $algo -ku kem -sub CN=DH 5>kem.$algo.pub 9>kem.$algo.prv"
import (
"bytes"
+ "encoding/hex"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
+ "path"
"sort"
"strings"
"time"
)
const (
- FdPubR = 4
FdPubW = 5
- FdPrvR = 8
FdPrvW = 9
)
-func mustReadAll(r io.ReadCloser) []byte {
- data, err := io.ReadAll(r)
- if err != nil {
- log.Fatal(err)
- }
- r.Close()
- return data
-}
-
func main() {
flag.Usage = usage
ku := make(map[string]*struct{})
"Lifetime of the certification, days")
algo := flag.String("algo", ed25519blake2b.Ed25519BLAKE2b,
"Public key algorithm")
- verify := flag.Bool("verify", false, "Verify provided -pub with -ca-pub")
+ certify := flag.Bool("c", false, "Certify public key")
+ verify := flag.Bool("v", false, "Verify provided -pub with -ca-pub")
+ prvsDir := flag.String("prvs", os.Getenv("CM_PRVS"),
+ "Path to directory with private keys")
+ pubsDirPth := flag.String("pubs", os.Getenv("CM_PUBS"),
+ "Path to directory with public keys")
doList := flag.Bool("list", false, "List available algorithms")
-
flag.Parse()
log.SetFlags(log.Lshortfile)
return
}
- fdPubR := os.NewFile(FdPubR, "pub-in")
fdPubW := os.NewFile(FdPubW, "pub-out")
- fdPrvR := os.NewFile(FdPrvR, "prv-in")
fdPrvW := os.NewFile(FdPrvW, "prv-out")
- var doCertify bool
- if len(sub) == 0 && !*verify {
- doCertify = true
- }
-
+ pubsDir := sign.PubStorageDir{Dir: *pubsDirPth}
+ var caPrv sign.Iface
+ var caPub *sign.Signed
var err error
- var since time.Time
- if *sinceRaw == "" {
- since = time.Now().UTC().Truncate(time.Second)
- } else {
- since, err = time.Parse(time.DateTime, *sinceRaw)
+ if *certify {
+ if (len(sub) > 0) || *verify {
+ log.Fatal("-c can not be used with -sub or -v")
+ }
+ if flag.NArg() != 2 {
+ log.Fatal("must specify PUB and CA.PUB")
+ }
+ var data []byte
+ data, err = os.ReadFile(flag.Arg(1))
if err != nil {
- log.Fatalln("while parsing -since:", err)
+ log.Fatal(err)
+ }
+ caPub, _, err = sign.PubParse(data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ data, err = os.ReadFile(path.Join(
+ *prvsDir, strings.ToUpper(hex.EncodeToString(caPub.PubData().Id)),
+ ))
+ if err != nil {
+ log.Fatal(err)
+ }
+ caPrv, _, err = sign.PrvParse(data)
+ if err != nil {
+ log.Fatal(err)
}
}
- till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
-
- var caPrv sign.Iface
- var caPubs []*sign.Signed
- if doCertify || *verify {
- data := mustReadAll(fdPubR)
- for len(data) > 0 {
- var signed *sign.Signed
- signed, data, err = sign.PubParse(data)
- if err != nil {
- log.Fatal(err)
- }
- caPubs = append(caPubs, signed)
+ if *verify {
+ if (len(sub) > 0) || *certify {
+ log.Fatal("-v can not be used with -sub or -c")
+ }
+ if flag.NArg() != 1 {
+ log.Fatal("must specify PUB")
}
}
- if doCertify {
- caPrv, _, err = sign.PrvParse(mustReadAll(fdPrvR))
+ var signed *sign.Signed
+ if *certify || *verify {
+ var data []byte
+ data, err = os.ReadFile(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+ signed, _, err = sign.PubParse(data)
if err != nil {
log.Fatal(err)
}
}
+ if !*certify && !*verify && len(sub) == 0 {
+ log.Fatal("must specify one of -v, -c, -sub")
+ }
- var signed *sign.Signed
- if doCertify || *verify {
- signed, _, err = sign.PubParse(mustReadAll(os.Stdin))
+ var since time.Time
+ if *sinceRaw == "" {
+ since = time.Now().UTC().Truncate(time.Second)
+ } else {
+ since, err = time.Parse(time.DateTime, *sinceRaw)
if err != nil {
- log.Fatal(err)
+ log.Fatalln("while parsing -since:", err)
}
}
+ till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
if *verify {
- err = signed.CertificationVerify(caPubs, time.Now().UTC())
+ err = signed.CertificationVerify(&pubsDir, time.Now().UTC())
if err != nil {
log.Fatal(err)
}
var prvRaw []byte
var pubData map[string]any
- if doCertify {
+ if *certify {
pubData = (*signed.Data).(map[string]any)
} else {
var pub []byte
if err != nil {
log.Fatal(err)
}
- {
- var buf bytes.Buffer
- if _, err = keks.Encode(&buf, sign.PrvMagic, nil); err != nil {
- log.Fatal(err)
- }
- if _, err = keks.Encode(&buf, cm.AV{A: *algo, V: prvRaw}, nil); err != nil {
- log.Fatal(err)
- }
- if _, err = io.Copy(fdPrvW, &buf); err != nil {
- log.Fatal(err)
- }
- }
+ var pubId []byte
{
pubData = map[string]any{
"sub": sub,
if err != nil {
log.Fatal(err)
}
- pubData["id"] = hasher.Sum(nil)
+ pubId = hasher.Sum(nil)
+ pubData["id"] = pubId
if err != nil {
log.Fatal(err)
}
if len(ku) > 0 {
pubData["ku"] = ku
}
+ {
+ var buf bytes.Buffer
+ if _, err = keks.Encode(&buf, sign.PrvMagic, nil); err != nil {
+ log.Fatal(err)
+ }
+ if _, err = keks.Encode(
+ &buf,
+ sign.Prv{A: *algo, V: prvRaw, PubId: pubId},
+ nil,
+ ); err != nil {
+ log.Fatal(err)
+ }
+ if _, err = io.Copy(fdPrvW, &buf); err != nil {
+ log.Fatal(err)
+ }
+ }
}
{
signed = &sign.Signed{TBS: sign.SDTBS{T: "pub"}, Data: &pubLoadAny}
}
- if doCertify {
- if err = signed.CertifyWith(
- caPubs[0].PubData(), caPrv, since, till,
- ); err != nil {
+ if *certify {
+ if err = signed.CertifyWith(caPub.PubData(), caPrv, since, till); err != nil {
log.Fatal(err)
}
}
if _, err = keks.Encode(&buf, signed, nil); err != nil {
log.Fatal(err)
}
- if _, err = io.Copy(fdPubW, &buf); err != nil {
+ var dst io.Writer
+ if *certify {
+ dst = os.Stdout
+ } else {
+ dst = fdPubW
+ }
+ if _, err = io.Copy(dst, &buf); err != nil {
log.Fatal(err)
}
}
Generate public key load:
cmkeytool -sub K=V [-sub K=V ...] [-algo ALGO] [-ku KU ...] 5>PUB 9>PRV
Certify public key:
- cmkeytool [-lifetime DAYS] [-since DATE] 4<CA-PUB 8<CA-PRV <PUB 5>PUB.certified
+ cmkeytool -c [-lifetime DAYS] [-since DATE] [-prvs dir] PUB CA.PUB >PUB.cer
Verify certification:
- cmkeytool -verify 4<CA-PUB0[,CA-PUB1,...] <PUB
+ cmkeytool -v [-pubs dir] PUB
+
+PUB is a path to public key files.
+-prvs/-pubs directory must contain key files with uppercase hexadecimal
+corresponding public key's id used as the filename. They can be set by
+CM_PRVS and CM_PUBS environment variables.
`)
flag.PrintDefaults()
test_description="Check signing"
. $SHARNESS_TEST_SRCDIR/sharness.sh
+mkdir prvs pubs
echo "gost3410-512C
gost3410-256A
ed25519-blake2b
slh-dsa-shake-256s" | while read keyalgo ; do
+unset CM_PRVS CM_PUBS
sub="-sub what=ever"
typ="some-different-type"
test_expect_success "$keyalgo: pub generation" "cmkeytool \
-algo $keyalgo -ku sig $sub \
5>sign.$keyalgo.pub 9>sign.$keyalgo.prv"
+ln -s ../sign.$keyalgo.prv prvs/$(kekspp -v -p /pub-id <sign.$keyalgo.prv)
+export CM_PRVS=$(realpath prvs)
dd if=/dev/urandom of=sign.$keyalgo.data bs=300K count=1 2>/dev/null
encTo=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -c 0 -p)
badEncTo=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -c 0 -p)
for merkle in "" "-merkle" ; do
+unset CM_PUBS
algo=${keyalgo}${merkle}
test_expect_success "$algo: signing" "
- cmsigtool $merkle -t $typ $encTo \
- 4<sign.$keyalgo.pub 8<sign.$keyalgo.prv \
+ cmsigtool $merkle -t $typ $encTo sign.$keyalgo.pub \
<sign.$keyalgo.data >sign.$algo.sig"
+test_expect_success "$algo: verifying, no key" "
+ ! cmsigtool -v -t $typ <sign.$algo.sig >sign.data.got"
+ln -fs ../sign.$keyalgo.pub pubs/$(kekspp -v -p /pub-id <sign.$keyalgo.prv)
+export CM_PUBS=$(realpath pubs)
test_expect_success "$algo: verifying" "
- cmsigtool -v -t $typ 4<sign.$keyalgo.pub \
- <sign.$algo.sig >sign.data.got"
+ cmsigtool -v -t $typ <sign.$algo.sig >sign.data.got"
test_expect_success "$algo: comparing" \
"test_cmp sign.$keyalgo.data sign.data.got"
test_expect_success "$algo: differing type" "
- ! cmsigtool -v 4<sign.$keyalgo.pub <sign.$algo.sig >/dev/null"
+ ! cmsigtool -v <sign.$algo.sig >/dev/null"
test_expect_success "$algo: good encTo" "
- ! cmsigtool -v $encTo 4<sign.$keyalgo.pub <sign.$algo.sig >/dev/null"
+ ! cmsigtool -v $encTo <sign.$algo.sig >/dev/null"
test_expect_success "$algo: bad encTo" "
- ! cmsigtool -v $badEncTo 4<sign.$keyalgo.pub <sign.$algo.sig >/dev/null"
+ ! cmsigtool -v $badEncTo <sign.$algo.sig >/dev/null"
test_expect_success "$algo: detached signing" "
cmsigtool -d $merkle -t $typ \
- 4<sign.$keyalgo.pub 8<sign.$keyalgo.prv \
- <sign.$keyalgo.data >sign.$algo.detached.sig"
+ sign.$keyalgo.pub <sign.$keyalgo.data >sign.$algo.detached.sig"
test_expect_success "$algo: detached verifying" "
- cat sign.$algo.detached.sig sign.$keyalgo.data |
- cmsigtool -d -v -t $typ 4<sign.$keyalgo.pub"
+ cat sign.$algo.detached.sig sign.$keyalgo.data | cmsigtool -d -v -t $typ"
test_expect_success "$algo: differing type" "
- ! cmsigtool -d -v 4<sign.$keyalgo.pub <sign.$algo.detached.sig >/dev/null"
+ ! cmsigtool -d -v <sign.$algo.detached.sig >/dev/null"
test_expect_success "$algo: good encTo" "! cmsigtool -d \
- -v $encTo 4<sign.$keyalgo.pub <sign.$algo.detached.sig >/dev/null"
+ -v $encTo <sign.$algo.detached.sig >/dev/null"
test_expect_success "$algo: bad encTo" "! cmsigtool -d \
- -v $badEncTo 4<sign.$keyalgo.pub <sign.$algo.detached.sig >/dev/null"
+ -v $badEncTo <sign.$algo.detached.sig >/dev/null"
done
"io"
"log"
"os"
+ "path"
+ "strings"
"time"
"go.cypherpunks.su/keks"
"go.cypherpunks.su/keks/types"
)
-const (
- FdPubR = 4
- FdPrvR = 8
-)
-
const BlobChunkLen = 128 * 1024
-func mustReadAll(r io.Reader) []byte {
- data, err := io.ReadAll(r)
- if err != nil {
- log.Fatal(err)
- }
- return data
-}
-
func main() {
log.SetFlags(log.Lshortfile)
flag.Usage = usage
typ := flag.String("t", "data", "Set/check the load type")
verify := flag.Bool("v", false, "Do verification")
+ prvDir := flag.String("prvs", os.Getenv("CM_PRVS"),
+ "Path to directory with private keys")
+ pubDirPth := flag.String("pubs", os.Getenv("CM_PUBS"),
+ "Path to directory with public keys")
var encryptedTo [][]byte
flag.Func("encrypted-to", "Set/check encrypted-to, hex", func(v string) error {
to, err := hex.DecodeString(v)
doMerkle := flag.Bool("merkle", false, "Use Merkle-tree based hasher")
flag.Parse()
- fdPubR := os.NewFile(FdPubR, "pub-in")
- fdPrvR := os.NewFile(FdPrvR, "prv-in")
-
- var pubs []*sign.PubData
+ pubsDir := sign.PubStorageDir{Dir: *pubDirPth}
+ var pub *sign.PubData
+ var signer sign.Iface
var err error
- {
- data := mustReadAll(fdPubR)
- for len(data) > 0 {
- var signed *sign.Signed
- signed, data, err = sign.PubParse(data)
- if err != nil {
- log.Fatal(err)
- }
- pubs = append(pubs, signed.PubData())
+ if !*verify {
+ if flag.NArg() != 1 {
+ log.Fatal("no PUB specified")
+ }
+ var data []byte
+ data, err = os.ReadFile(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+ var signed *sign.Signed
+ signed, _, err = sign.PubParse(data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ pub = signed.PubData()
+ data, err = os.ReadFile(path.Join(
+ *prvDir, strings.ToUpper(hex.EncodeToString(pub.Id)),
+ ))
+ if err != nil {
+ log.Fatal(err)
+ }
+ data, err = cmballoon.PossibleInteractiveDecrypt(data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ signer, _, err = sign.PrvParse(data)
+ if err != nil {
+ log.Fatal(err)
}
- fdPubR.Close()
}
stdin := bufio.NewReaderSize(os.Stdin, BlobChunkLen)
}
for _, sig := range signed.Sigs {
sid := sig.TBS["sid"].([]byte)
- var signerFound bool
- for _, pub := range pubs {
- if !bytes.Equal(sid, pub.Id) {
- continue
- }
- signerFound = true
- var sigTBS *sign.SigTBS
- sigTBS, err = sig.TBSGet()
+ {
+ var p *sign.Signed
+ p, err = pubsDir.Get(sid)
if err != nil {
log.Fatal(err)
}
- if len(encryptedTo) > 0 {
- if len(sigTBS.EncryptedTo) == 0 {
- log.Fatalln(hex.EncodeToString(sid), "missing encrypted-to")
- }
- found := false
- for _, their := range sigTBS.EncryptedTo {
- for _, our := range encryptedTo {
- if bytes.Equal(our, their) {
- found = true
- break
- }
+ if p == nil {
+ log.Fatalln(hex.EncodeToString(sid), "can not find signer")
+ }
+ pub = p.PubData()
+ }
+ var sigTBS *sign.SigTBS
+ sigTBS, err = sig.TBSGet()
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(encryptedTo) > 0 {
+ if len(sigTBS.EncryptedTo) == 0 {
+ log.Fatalln(hex.EncodeToString(sid), "missing encrypted-to")
+ }
+ found := false
+ for _, their := range sigTBS.EncryptedTo {
+ for _, our := range encryptedTo {
+ if bytes.Equal(our, their) {
+ found = true
+ break
}
}
- if !found {
- log.Fatalln(
- hex.EncodeToString(sid),
- "corresponding encrypted-to not found")
- }
}
- var hasher hash.Hash
- if prehash.T == "" {
- hasher = cmhash.ByName(sig.Sign.A)
- if _, err = io.Copy(hasher, stdin); err != nil {
- log.Fatal(hex.EncodeToString(sid), err)
- }
- } else {
- var found bool
- hasher, found = hashers[sig.Sign.A]
- if !found {
- log.Fatalln(hex.EncodeToString(sid), "no hasher in prehash")
- }
+ if !found {
+ log.Fatalln(
+ hex.EncodeToString(sid),
+ "corresponding encrypted-to not found")
+ }
+ }
+ var hasher hash.Hash
+ if prehash.T == "" {
+ hasher = cmhash.ByName(sig.Sign.A)
+ if _, err = io.Copy(hasher, stdin); err != nil {
+ log.Fatal(hex.EncodeToString(sid), err)
}
- if err = signed.CertificationCheckSignatureFrom(
- pub, &hasher,
- ); err != nil {
- log.Fatalln(hex.EncodeToString(sid), err)
+ } else {
+ var found bool
+ hasher, found = hashers[sig.Sign.A]
+ if !found {
+ log.Fatalln(hex.EncodeToString(sid), "no hasher in prehash")
}
- break
}
- if !signerFound {
- log.Fatalln(hex.EncodeToString(sid), "can not find signer")
+ if err = signed.CertificationCheckSignatureFrom(pub, &hasher); err != nil {
+ log.Fatalln(hex.EncodeToString(sid), err)
}
}
} else {
- var signer sign.Iface
- {
- prvRaw := mustReadAll(fdPrvR)
- fdPrvR.Close()
- prvRaw, err = cmballoon.PossibleInteractiveDecrypt(prvRaw)
- if err != nil {
- log.Fatal(err)
- }
- signer, _, err = sign.PrvParse(prvRaw)
- }
- if err != nil {
- log.Fatal(err)
- }
if *doMerkle {
err = signer.SetMode(mode.Merkle)
} else {
if err != nil {
log.Fatal(err)
}
-
if _, err = keks.Encode(os.Stdout, sign.SignedMagic, nil); err != nil {
log.Fatal(err)
}
if len(encryptedTo) > 0 {
tbs["encrypted-to"] = encryptedTo
}
- if err = signed.SignWith(pubs[0], signer, tbs); err != nil {
+ if err = signed.SignWith(pub, signer, tbs); err != nil {
log.Fatal(err)
}
if _, err = keks.Encode(os.Stdout, signed, nil); err != nil {
func usage() {
fmt.Fprintf(os.Stderr, `Usage:
- cmsigtool [-t TYPE] 4<PUB 8<PRV <DATA >DATA.signed
- cmsigtool -v [-t TYPE] 4<PUB0[,PUB1...] <DATA.signed >DATA
- cmsigtool -d [-t TYPE] 4<PUB 8<PRV <DATA >DATA.signature
- cmsigtool -d -v [-t TYPE] 4<PUB0[,PUB1...] <<(cat DATA.signature DATA)
+ cmsigtool [-t TYPE] [-prvs dir] PUB <DATA >DATA.signed
+ cmsigtool -d [-t TYPE] [-prvs dir] PUB <DATA >DATA.signature
+ cmsigtool -v [-t TYPE] [-pubs dir] <DATA.signed >DATA
+ cmsigtool -d -v [-t TYPE] [-pubs dir] <<(cat DATA.signature DATA)
DATA.signed holds completely encapsulated DATA.
-d(etached) mode keeps DATA completely separate.
+PUB is a path to public key files.
+-prvs/-pubs directory must contain key files with uppercase hexadecimal
+corresponding public key's id used as the filename. They can be set by
+CM_PRVS and CM_PUBS environment variables.
+
`)
flag.PrintDefaults()
}
import "github.com/google/uuid"
type Encrypted struct {
- DEM DEM `keks:"dem"`
- KEM []KEM `keks:"kem"`
- Payload []byte `keks:"payload,omitempty"`
- Id uuid.UUID `keks:"id"`
+ DEM DEM `keks:"dem"`
+ KEM []KEM `keks:"kem"`
+ Payload []byte `keks:"payload,omitempty"`
+ Id *uuid.UUID `keks:"id,omitempty"`
}
+/prv.schema.keks
/pub.schema.keks
/signed.schema.keks
const PrvMagic = keks.Magic("cm/prv")
-// Parse private key contained in AV KEKS-encoded structure.
+type Prv struct {
+ A string `keks:"a"`
+ V []byte `keks:"v"`
+ PubId []byte `keks:"pub-id,omitempty"`
+}
+
+// Parse private key contained in KEKS-encoded structure.
func PrvParse(data []byte) (prv Iface, pub []byte, err error) {
{
var magic keks.Magic
if err != nil {
return
}
- err = schema.Check("av", PubSchemas, v)
+ err = schema.Check("prv", PrvSchemas, v)
if err != nil {
return
}
import (
"bytes"
+ "encoding/hex"
"errors"
"fmt"
"hash"
return &pubData
}
-// Verify signed Signed PubData certification against pubs chain of
+// Verify signed Signed PubData certification against pubs storage of
// public keys at specified point of time t.
-func (signed *Signed) CertificationVerify(pubs []*Signed, t time.Time) (err error) {
+func (signed *Signed) CertificationVerify(pubs PubStorage, t time.Time) (err error) {
if len(signed.Sigs) == 0 {
return errors.New("no sigs")
}
if bytes.Equal(sid, signed.PubData().Id) {
return signed.CertificationCheckSignatureFrom(signed.PubData(), nil)
}
- type FPR [FPRLen]byte
- idToPub := make(map[FPR]*Signed, len(pubs))
- for _, cer := range pubs {
- pubData := cer.PubData()
- if !pubData.Can(KUSig) || len(pubData.Pub) != 1 {
- err = errors.New("pub can not sign")
- return
- }
- idToPub[FPR(pubData.Id)] = cer
+ var signer *Signed
+ signer, err = pubs.Get(sid)
+ if err != nil {
+ return
}
- signer := idToPub[FPR(sid)]
if signer == nil {
- err = fmt.Errorf("no pub found for sid: %v", sigTBS.SID)
+ err = fmt.Errorf("no pub found for sid: %s", hex.EncodeToString(sid))
+ return
+ }
+ pubData := signer.PubData()
+ if !pubData.Can(KUSig) || len(pubData.Pub) != 1 {
+ err = errors.New("pub can not sign")
return
}
- err = signed.CertificationCheckSignatureFrom(signer.PubData(), nil)
+ err = signed.CertificationCheckSignatureFrom(pubData, nil)
if err != nil {
return
}
//go:embed signed.schema.keks
var SignedSchemasRaw []byte
+//go:embed prv.schema.keks
+var PrvSchemasRaw []byte
+
//go:embed pub.schema.keks
var PubSchemasRaw []byte
var (
SignedSchemas map[string][][]any
+ PrvSchemas map[string][][]any
PubSchemas map[string][][]any
)
panic(err)
}
+ magic, PrvSchemasRaw = keks.StripMagic(PrvSchemasRaw)
+ if magic != schema.Magic {
+ panic("wrong magic in prv.schema.keks")
+ }
+ if err := keks.NewDecoderFromBytes(
+ PrvSchemasRaw, nil,
+ ).DecodeStruct(&PrvSchemas); err != nil {
+ panic(err)
+ }
+
magic, PubSchemasRaw = keks.StripMagic(PubSchemasRaw)
if magic != schema.Magic {
panic("wrong magic in pub.schema.keks")
const SignedMagic = keks.Magic("cm/signed")
type SDTBS struct {
- T string `keks:"t"`
+ T string `keks:"t"`
+ Id *uuid.UUID `keks:"id,omitempty"`
}
type SigTBS struct {
}
type Signed struct {
- TBS SDTBS `keks:"tbs"`
+ TBS SDTBS `keks:"tbs"`
Data *any `keks:"data,omitempty"`
Pubs *[]*Signed `keks:"pubs,omitempty"`
Sigs []*Sig `keks:"sigs,omitempty"`
--- /dev/null
+// KEKS/CM -- KEKS-encoded cryptographic messages
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package sign
+
+import (
+ "encoding/hex"
+ "errors"
+ "io/fs"
+ "os"
+ "path"
+ "strings"
+)
+
+type PubStorage interface {
+ Get(id []byte) (*Signed, error)
+}
+
+type PubStorageDir struct {
+ Dir string
+}
+
+func (s *PubStorageDir) Get(id []byte) (signed *Signed, err error) {
+ var data []byte
+ data, err = os.ReadFile(path.Join(
+ s.Dir, strings.ToUpper(hex.EncodeToString(id)),
+ ))
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ signed, _, err = PubParse(data)
+ return signed, err
+}
[ -d vendor ] && mod_vendor="-mod=vendor" || redo-ifchange \
enc/encrypted.schema.keks \
hash/prehash.schema.keks \
+ sign/prv.schema.keks \
sign/pub.schema.keks \
sign/signed.schema.keks
mkdir -p bin
=> PUBKEY-CM.pub\r
=> PUBKEY-CM.pub.asc\r
+ $ ln -s PUBKEY-CM.pub $(kekspp -v -p /data/id <PUBKEY-CM.pub)
$ cat keks-$version.tar.zst.sig keks-$version.tar.zst |
- cmsigtool -v -d 4<PUBKEY-CM.pub
+ cmsigtool -v -d -pubs .
do-backs\r
Private key container.
-<< [schemas/av.tcl]\r
+<< [schemas/prv.tcl]\r
Stored in a file, it should begin with "cm/prv" [encoding/MAGIC].
+
+Optional /pub-id contains corresponding public key's /data/id to ease
+their searching.
--- /dev/null
+prv {
+ {field . {map}}
+ {field a {str} >0} {# algorithm identifier}
+ {field v {bin}}
+ {field pub-id {with fpr} optional}
+}
+
+schema-include fpr.tcl
tbs {
{field . {map}}
{field t {str} >0} {# type of the data we sign}
+ {field id {hexlet} optional}
}
sig {