package keks
import (
+ "errors"
"fmt"
"io"
"strings"
+ "unsafe"
+
+ "go.cypherpunks.su/keks/types"
)
type BlobChunked struct {
func (blob *BlobReader) String() string {
return fmt.Sprintf("BLOB(%d, ?)", blob.ChunkLen)
}
+
+// BlobDecoder decodes a BLOB from the reader.
+type BlobDecoder struct {
+ d *Decoder
+ ChunkLen int64
+ eof bool
+}
+
+func NewBlobDecoder(r io.Reader, maxChunkLen int64) (*BlobDecoder, error) {
+ d := NewDecoderFromReader(r, &DecodeOpts{MaxStrLen: maxChunkLen})
+ t, err := d.DecodeAtom()
+ if err != nil {
+ return nil, err
+ }
+ if t != types.Blob {
+ return nil, errors.New("not a BLOB")
+ }
+ chunkLen := d.blobChunkLens[0]
+ if chunkLen > maxChunkLen {
+ return nil, ErrLenTooBig
+ }
+ d.readBuf = make([]byte, max(chunkLen, 8))
+ return &BlobDecoder{d: d, ChunkLen: chunkLen}, nil
+}
+
+// Get the next chunk of the BLOB. Last one may be smaller size than the
+// ChunkLen. When BLOB is finished, io.EOF is returned with nil chunk.
+func (d *BlobDecoder) Next() (chunk []byte, err error) {
+ if d.eof {
+ return nil, io.EOF
+ }
+ var t types.Type
+ t, err = d.d.DecodeAtom()
+ if err != nil {
+ return
+ }
+ if t != types.Bin {
+ err = ErrBlobBadAtom
+ return
+ }
+ s := d.d.strs[len(d.d.strs)-1]
+ d.d.deTail()
+ d.d.strs = d.d.strs[:len(d.d.strs)-1]
+ chunk = unsafe.Slice(unsafe.StringData(s), len(s))
+ if int64(len(s)) == d.ChunkLen {
+ return chunk, nil
+ }
+ if int64(len(s)) > d.ChunkLen {
+ return nil, ErrBlobBadChunkLen
+ }
+ d.eof = true
+ if len(s) == 0 {
+ return nil, io.EOF
+ }
+ return chunk, nil
+}
blobChunkLens []int64
blobChunkses [][]string
+
+ readBuf []byte // used only by BlobDecoder
}
// Initialise decoder that will read from b bytes. After the parse,
func (ctx *Decoder) getBytes(n int) (s string, err error) {
var read int
if ctx.B == nil {
- buf := make([]byte, n)
+ var buf []byte
+ if ctx.readBuf == nil {
+ buf = make([]byte, n)
+ } else {
+ buf = ctx.readBuf[:n]
+ if len(ctx.readBuf) < n {
+ panic("readBuf is smaller")
+ }
+ }
read, err = io.ReadFull(ctx.R, buf)
ctx.Read += int64(read)
if err != nil {
package pki
import (
+ "go.cypherpunks.su/keks"
ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
"go.cypherpunks.su/keks/pki/gost"
sntrup4591761x25519 "go.cypherpunks.su/keks/pki/sntrup4591761-x25519"
BalloonBLAKE2bHKDF = "balloon-blake2b-hkdf"
ChaCha20Poly1305 = "chacha20poly1305"
- EncryptedMagic = "pki/encryptd"
- HashedMagic = "pki/hashed"
- PrvKeyMagic = "pki/prvkey"
+ EncryptedMagic = keks.Magic("pki/encryptd")
+ HashedMagic = keks.Magic("pki/hashed")
+ PrvKeyMagic = keks.Magic("pki/prvkey")
)
"crypto"
"errors"
"fmt"
+ "hash"
"time"
"github.com/google/uuid"
KUCA = "ca" // CA-capable key usage
KUSig = "sig" // Signing-capable key usage
KUKEM = "kem" // Key-encapsulation-mechanism key usage
- CerMagic = "pki/cer"
+ CerMagic = keks.Magic("pki/cer")
)
// Public key.
}
// Parse Signed contents as CerLoad (certificate) and check its
-// signatures necessary structure. sd.Load.V will hold the CerLoad in
+// signatures necessary structure. signed.Load.V will hold the CerLoad in
// case of success.
-func (sd *Signed) CerParse() error {
- if sd.Load.T != "cer" {
+func (signed *Signed) CerParse() error {
+ if signed.Load.T != "cer" {
return errors.New("CerParse: wrong load type")
}
- for _, sig := range sd.Sigs {
- if sig.TBS.Hashes != nil {
- return errors.New("CerParse: prehashed Signed")
- }
+ for _, sig := range signed.Sigs {
if sig.TBS.CID == nil {
return errors.New("CerParse: missing cid")
}
return errors.New("CerParse: missing exp")
}
}
+ if signed.Load.V == nil {
+ return errors.New("CerParse: missing /load/v")
+ }
var load CerLoad
var err error
- if v, ok := sd.Load.V.(map[string]any); ok {
+ if v, ok := (*signed.Load.V).(map[string]any); ok {
err = keks.Map2Struct(&load, v)
} else {
err = errors.New("CerParse: wrong /load/v")
if err != nil {
return err
}
- sd.Load.V = load
+ {
+ loadAny := any(load)
+ signed.Load.V = &loadAny
+ }
if load.KU != nil {
if len(*load.KU) == 0 {
return errors.New("CerParse: empty ku")
}
// Parse KEKS-encoded data as Signed with the CerLoad (certificate) contents.
-func CerParse(data []byte) (sd *Signed, err error) {
+func CerParse(data []byte) (signed *Signed, err error) {
{
var magic keks.Magic
magic, data = keks.StripMagic(data)
return
}
}
- sd, err = SignedParse(data)
+ signed, err = SignedParse(data)
if err != nil {
return
}
- err = sd.CerParse()
+ err = signed.CerParse()
return
}
// Sign the current Signed, having CerLoad payload with the provided
// parent's CerLoad and prv key. Certificate's CID will be automatically
// generated UUIDv7. since and till times must not have nanoseconds part.
-func (sd *Signed) CerIssueWith(
+func (signed *Signed) CerIssueWith(
parent *CerLoad,
prv crypto.Signer,
since, till time.Time,
if err != nil {
return err
}
- return sd.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
+ return signed.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
}
var ErrSigInvalid = errors.New("signature is invalid")
return
}
+// Verify signature of signed data, by providing prehashed data.
+// ErrSigInvalid will be returned in case of invalid signature.
+func (cer *CerLoad) CheckSignatureDigest(digest, signature []byte) (err error) {
+ if !cer.Can(KUSig) || len(cer.Pub) != 1 {
+ err = errors.New("cer can not sign")
+ return
+ }
+ pub := cer.Pub[0]
+ var valid bool
+ switch pub.A {
+ case Ed25519BLAKE2b:
+ valid, err = ed25519blake2b.VerifyDigest(pub.V, digest, signature)
+ if !valid {
+ err = ErrSigInvalid
+ }
+ case GOST3410256A, GOST3410512C:
+ valid, err = gost.VerifyDigest(pub.A, pub.V, digest, signature)
+ if !valid {
+ err = ErrSigInvalid
+ }
+ default:
+ err = errors.New("unsupported signature algorithm")
+ }
+ return
+}
+
// Verify Signed CerLoad certificate's signature with provided parent.
+// If hasher is specified, then prehashed signature mode is used.
// Currently only single signature can be verified.
-func (sd *Signed) CerCheckSignatureFrom(parent *CerLoad) (err error) {
- if len(sd.Sigs) != 1 {
+func (signed *Signed) CerCheckSignatureFrom(
+ parent *CerLoad,
+ hasher *hash.Hash,
+) (err error) {
+ if len(signed.Sigs) != 1 {
err = errors.New("can verify only single signature")
return
}
err = errors.New("parent can not sign")
return
}
- sig := sd.Sigs[0]
+ sig := signed.Sigs[0]
if sig.TBS.SID != parent.Pub[0].Id {
err = errors.New("sid != parent pub id")
return
}
- tbs := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS}
- buf, err := keks.EncodeBuf(tbs, nil)
- if err != nil {
- return
+ var tbs SignedTBS
+ if hasher == nil {
+ tbs = SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sig.TBS}
+ var buf []byte
+ buf, err = keks.EncodeBuf(tbs, nil)
+ if err != nil {
+ return
+ }
+ return parent.CheckSignature(buf, sig.Sign.V)
+ } else {
+ tbs = SignedTBS{T: signed.Load.T, TBS: sig.TBS}
+ _, err = keks.Encode(*hasher, tbs, nil)
+ if err != nil {
+ return
+ }
+ return parent.CheckSignatureDigest((*hasher).Sum(nil), sig.Sign.V)
}
- return parent.CheckSignature(buf, sig.Sign.V)
}
// Get CerLoad from Signed.
// Returns nil if Signed does not hold it (or it is not yet parsed).
-func (sd *Signed) CerLoad() *CerLoad {
- if sd.Load.T != "cer" {
+func (signed *Signed) CerLoad() *CerLoad {
+ if signed.Load.T != "cer" || signed.Load.V == nil {
return nil
}
- l, ok := sd.Load.V.(CerLoad)
+ l, ok := (*signed.Load.V).(CerLoad)
if ok {
return &l
}
return nil
}
-// Verify sd Signed CerLoad certificate against cers chain of
+// Verify signed Signed CerLoad certificate against cers chain of
// certificate authority ones at specified point of time t.
-func (sd *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
+func (signed *Signed) CerVerify(cers []*Signed, t time.Time) (err error) {
{
- exp := *(sd.Sigs[0].TBS.Exp)
+ exp := *(signed.Sigs[0].TBS.Exp)
if t.Before(exp[0]) || t.Equal(exp[0]) {
err = errors.New("cer is not active")
return
return
}
}
- sid := sd.Sigs[0].TBS.SID
- if sid == sd.CerLoad().Pub[0].Id {
- return sd.CerCheckSignatureFrom(sd.CerLoad())
+ sid := signed.Sigs[0].TBS.SID
+ if sid == signed.CerLoad().Pub[0].Id {
+ return signed.CerCheckSignatureFrom(signed.CerLoad(), nil)
}
idToCer := make(map[uuid.UUID]*Signed, len(cers))
for _, cer := range cers {
}
signer := idToCer[sid]
if signer == nil {
- err = fmt.Errorf("no cer found for sid: %v", sd.Sigs[0].TBS.SID)
+ err = fmt.Errorf("no cer found for sid: %v", signed.Sigs[0].TBS.SID)
return
}
- err = sd.CerCheckSignatureFrom(signer.CerLoad())
+ err = signed.CerCheckSignatureFrom(signer.CerLoad(), nil)
if err != nil {
return
}
-ku ca -ku sig $subj \
-prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer"
test_expect_success "$caAlgo: CA generation" "certool \
- -algo $caAlgo \
- -ku ca -ku sig $subj \
- -reuse-key \
- -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
+ -cer $TMPDIR/ca.cer \
-ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer"
test_expect_success "$caAlgo: CA regeneration" "certool \
- -algo $caAlgo \
- -ku ca -ku sig $subj \
- -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
- -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer \
- -reuse-key"
+ -cer $TMPDIR/ca.cer \
+ -ca-prv $TMPDIR/ca.prv -ca-cer $TMPDIR/ca.cer"
test_expect_success "$caAlgo: CA self-signature" "certool \
-ca-cer $TMPDIR/ca.cer \
-cer $TMPDIR/ca.cer \
-verify"
subj="-subj CN=SubCA -subj C=RU"
-test_expect_success "$eeAlgo: SubCA generation" "certool \
+test_expect_success "$eeAlgo: SubCA load generation" "certool \
-algo $eeAlgo \
-ku ca -ku sig $subj \
- -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer \
+ -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer"
+test_expect_success "$eeAlgo: SubCA generation" "certool \
+ -cer $TMPDIR/subca.cer \
-ca-cer $TMPDIR/ca.cer -ca-prv $TMPDIR/ca.prv"
test_expect_success "$eeAlgo: SubCA signature" "certool \
-ca-cer $TMPDIR/ca.cer \
-verify"
subj="-subj CN=EE -subj C=RU"
-test_expect_success "$eeAlgo: EE generation" "certool \
+test_expect_success "$eeAlgo: EE load generation" "certool \
-algo $eeAlgo $subj \
- -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
-prv $TMPDIR/ee.prv -cer $TMPDIR/ee.cer"
+test_expect_success "$eeAlgo: EE generation" "certool \
+ -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
+ -cer $TMPDIR/ee.cer"
test_expect_success "$eeAlgo: EE chain" "certool \
-ca-cer $TMPDIR/ca.cer \
-ca-cer $TMPDIR/subca.cer \
"crypto"
"errors"
"flag"
+ "fmt"
"log"
"os"
+ "sort"
"strings"
"time"
)
func main() {
+ flag.Usage = usage
ku := make(map[string]*struct{})
subj := make(map[string]string)
flag.Func(
"Optional notBefore, \"2006-01-02 15:04:05\" format")
lifetime := flag.Uint("lifetime", 365,
"Lifetime of the certificate, days")
- algo := flag.String("algo", pki.GOST3410256A, "Public key algorithm")
+ algo := flag.String("algo", pki.Ed25519BLAKE2b, "Public key algorithm")
issuingPrv := flag.String("ca-prv", "",
"Path to private key file for issuing with")
- reuseKey := flag.Bool("reuse-key", false,
- "Reuse the key, do not generate new one")
prvPath := flag.String("prv", "", "Path to private key file")
cerPath := flag.String("cer", "", "Path to certificate file")
verify := flag.Bool("verify", false, "Verify provided -cer with -ca-cer")
+ doList := flag.Bool("list-algo", false, "List available algorithms")
flag.Parse()
log.SetFlags(log.Lshortfile)
- if *cerPath == "" {
- log.Fatal("no -cer is set")
+ if *doList {
+ algos := []string{
+ pki.Ed25519BLAKE2b,
+ pki.GOST3410256A,
+ pki.GOST3410512C,
+ pki.SNTRUP4591761X25519,
+ }
+ sort.Strings(algos)
+ for _, s := range algos {
+ fmt.Println(s)
+ }
+ return
}
- if !*verify && len(subj) == 0 {
- log.Fatal("no -subj is set")
+ if *cerPath == "" {
+ log.Fatal("no -cer is set")
}
var err error
var caPrv crypto.Signer
var caCers []*pki.Signed
for _, issuingCer := range issuingCers {
- var sd *pki.Signed
- sd, err = pki.CerParse(utils.MustReadFile(issuingCer))
+ var signed *pki.Signed
+ signed, err = pki.CerParse(utils.MustReadFile(issuingCer))
if err != nil {
log.Fatal(err)
}
- caCers = append(caCers, sd)
+ caCers = append(caCers, signed)
}
if len(caCers) > 0 && !*verify {
if *issuingPrv == "" {
- log.Fatal("no -ca-key is set")
+ log.Fatal("no -ca-prv is set")
}
caPrv, _, err = pki.PrvParse(utils.MustReadFile(*issuingPrv))
if err != nil {
}
if *verify {
- var sd *pki.Signed
- sd, err = pki.CerParse(utils.MustReadFile(*cerPath))
+ var signed *pki.Signed
+ signed, err = pki.CerParse(utils.MustReadFile(*cerPath))
if err != nil {
log.Fatal(err)
}
- err = sd.CerVerify(caCers, time.Now().UTC())
+ err = signed.CerVerify(caCers, time.Now().UTC())
if err != nil {
log.Fatal(err)
}
return
}
- if *prvPath == "" {
- log.Fatal("no -prv is set")
- }
-
var prvRaw []byte
- var pub []byte
- if *reuseKey {
- _, pub, err = pki.PrvParse(utils.MustReadFile(*prvPath))
+ var cerLoad *pki.CerLoad
+ var signed *pki.Signed
+ if caPrv != nil {
+ signed, err = pki.CerParse(utils.MustReadFile(*cerPath))
if err != nil {
log.Fatal(err)
}
+ cerLoad = signed.CerLoad()
} else {
+ if len(subj) == 0 {
+ log.Fatal("no -subj is set")
+ }
+ var pub []byte
+ if *prvPath == "" {
+ log.Fatal("no -prv is set")
+ }
switch *algo {
case pki.Ed25519BLAKE2b:
_, prvRaw, pub, err = ed25519blake2b.NewKeypair()
}
{
var buf bytes.Buffer
- if _, err = keks.Encode(&buf, keks.Magic(pki.PrvKeyMagic), nil); err != nil {
+ if _, err = keks.Encode(&buf, pki.PrvKeyMagic, nil); err != nil {
log.Fatal(err)
}
if _, err = keks.Encode(&buf, pki.AV{A: *algo, V: prvRaw}, nil); err != nil {
log.Fatal(err)
}
}
+ {
+ pubMap := pki.Pub{A: *algo, V: pub}
+ {
+ av := pki.AV{A: *algo, V: pub}
+ pubMap.Id = av.Id()
+ }
+ cerLoad = &pki.CerLoad{Subj: subj, Pub: []pki.Pub{pubMap}}
+ }
+ if len(ku) > 0 {
+ cerLoad.KU = &ku
+ }
}
- pubMap := pki.Pub{A: *algo, V: pub}
{
- av := pki.AV{A: *algo, V: pub}
- pubMap.Id = av.Id()
- }
- cerLoad := pki.CerLoad{Subj: subj, Pub: []pki.Pub{pubMap}}
- if len(ku) > 0 {
- cerLoad.KU = &ku
+ cerLoadAny := any(cerLoad)
+ signed = &pki.Signed{Load: pki.SignedLoad{T: "cer", V: &cerLoadAny}}
}
- var caCerLoad *pki.CerLoad
- if caPrv == nil {
- caCerLoad = &cerLoad
- } else {
- caCerLoad = caCers[0].CerLoad()
- }
- sd := pki.Signed{Load: pki.SignedLoad{T: "cer", V: cerLoad}}
+
if caPrv != nil {
- if err = sd.CerIssueWith(caCerLoad, caPrv, since, till); err != nil {
+ if err = signed.CerIssueWith(
+ caCers[0].CerLoad(), caPrv, since, till,
+ ); err != nil {
log.Fatal(err)
}
}
{
var buf bytes.Buffer
- if _, err = keks.Encode(&buf, keks.Magic(pki.CerMagic), nil); err != nil {
+ if _, err = keks.Encode(&buf, pki.CerMagic, nil); err != nil {
log.Fatal(err)
}
- if _, err = keks.Encode(&buf, sd, nil); err != nil {
+ if _, err = keks.Encode(&buf, signed, nil); err != nil {
log.Fatal(err)
}
if err = os.WriteFile(*cerPath, buf.Bytes(), 0o666); err != nil {
--- /dev/null
+// certool -- dealing with KEKS-encoded certificates utility
+// 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 main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage:
+ Generate certificate load:
+ certool -prv PRV -cer CER [-algo ALGO] [-ku KU ...] \
+ -subj K=V [-subj K=V ...]
+ Sign certificate:
+ certool -cer CER -ca-prv CA-PRV -ca-cer CA-CER \
+ [-lifetime DAYS] [-since DATE]
+ Verify certificate:
+ certool -verify -cer CER CA-PRV -ca-cer CA-CER0 [-ca-cer CA-CER1 ...]
+
+`)
+ flag.PrintDefaults()
+}
func main() {
log.SetFlags(log.Lshortfile)
+ flag.Usage = usage
setBind := flag.String("bind", "", "Set that /bind instead of autogeneration")
includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`)
passwd := flag.String("passwd", "", "Passphrase")
balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
doDecrypt := flag.Bool("d", false, "Decrypt")
var pubs []*pki.Pub
- flag.Func("pub", "Encrypt to, path to .cer", func(v string) error {
- sd, err := pki.CerParse(utils.MustReadFile(v))
+ flag.Func("cer", "Path to certificate to encrypt to", func(v string) error {
+ signed, err := pki.CerParse(utils.MustReadFile(v))
if err != nil {
return err
}
- load := sd.CerLoad()
+ load := signed.CerLoad()
if load.KU == nil {
log.Println(v, "does not have key usages")
} else {
})
var prvs []*pki.AV
flag.Func("prv", "Our private keys for decryption", func(v string) (err error) {
+ magic, data := keks.StripMagic(utils.MustReadFile(v))
+ if magic == "" || magic != pki.PrvKeyMagic {
+ return errors.New("wrong magic")
+ }
var av pki.AV
- d := keks.NewDecoderFromBytes(
- utils.MustReadFile(v),
- &keks.DecodeOpts{MaxStrLen: 1 << 16},
- )
+ d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
if err = d.DecodeStruct(&av); err != nil {
return err
}
if len(*kem.Encap) != sntrup4591761.CiphertextSize+32 {
log.Fatalln("invalid encap len")
}
- for prvIdx, prv := range prvs {
+ for _, prv := range prvs {
if len(prv.V) != sntrup4591761.PrivateKeySize+32 {
log.Fatalln("invalid private keys len")
}
log.Fatal(err)
}
{
+ pub := append(
+ ourSNTRUP[382:],
+ ourX25519.PublicKey().Bytes()...,
+ )
ikm := bytes.Join([][]byte{
encrypted.Bind[:],
- *kem.Encap, pubs[prvIdx].V,
+ *kem.Encap, pub,
keySNTRUP[:], keyX25519,
}, []byte{})
kek := hkdf.Extract(blake2b256,
}
{
var hdr bytes.Buffer
- if _, err = keks.Encode(&hdr, keks.Magic(pki.EncryptedMagic), nil); err != nil {
+ if _, err = keks.Encode(&hdr, pki.EncryptedMagic, nil); err != nil {
log.Fatal(err)
}
if _, err = keks.Encode(&hdr, &Encrypted{
--- /dev/null
+// enctool -- dealing with KEKS-encoded pki-encrypted utility
+// 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 main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage:
+ Encrypt to recipient:
+ enctool -cer CER [-include-to] [-bind UUID] <DATA >DATA.encrypted
+ Encrypt on passphrase:
+ enctool -passwd PASSPHRASE [-bind UUID] <DATA >DATA.encrypted
+ [-balloon-s X] [-balloon-t X] [-balloon-p X]
+ Decrypt by providing possible KEMs:
+ enctool -d [-passwd ...] [-prv PRV ...] <DATA.encrypted >DATA
+
+`)
+ flag.PrintDefaults()
+}
import (
"bufio"
"bytes"
- "crypto"
"flag"
+ "hash"
"io"
"log"
"os"
"go.cypherpunks.su/keks/pki"
pkihash "go.cypherpunks.su/keks/pki/hash"
"go.cypherpunks.su/keks/pki/utils"
+ "go.cypherpunks.su/keks/types"
)
+const BlobChunkLen = 128 * 1024
+
func main() {
+ flag.Usage = usage
prvPath := flag.String("prv", "", "Path to private key file")
cerPath := flag.String("cer", "", "Path to certificate file")
- sdPath := flag.String("sd", "", "Path to pki-signed file")
- typ := flag.String("type", "data", "Type of the content, /load/t value")
- hashAlgo := flag.String("hash", "", "Algorithm identifier of the hash to use")
- verify := flag.Bool("verify", false, "Verify with provided -cer")
- encryptedBindingHex := flag.String("encrypted-binding", "", "Set encrypted-binding")
+ typ := flag.String("type", "data", "Set/check the load type")
+ verify := flag.Bool("verify", false, "Do verification")
+ encryptedBindingHex := flag.String("encrypted-binding", "",
+ "Set/check encrypted-binding, UUID")
+ detached := flag.Bool("detached", false, "Detached data mode")
flag.Parse()
log.SetFlags(log.Lshortfile)
log.Fatal(err)
}
- var signer crypto.Signer
+ var signer pki.Signer
+ var hasher hash.Hash
if !*verify {
if *prvPath == "" {
log.Fatal("no -prv is set")
if err != nil {
log.Fatal(err)
}
+ hasher = signer.NewHasher()
}
- hasher := pkihash.ByName(*hashAlgo)
- if hasher == nil {
- log.Fatal("unknown -hash specified")
- }
- _, err = io.Copy(hasher, bufio.NewReader(os.Stdin))
- if err != nil {
- log.Fatal(err)
- }
+ stdin := bufio.NewReaderSize(os.Stdin, BlobChunkLen)
if *verify {
- var sd *pki.Signed
- sd, err = pki.SignedParse(utils.MustReadFile(*sdPath))
+ decoder := keks.NewDecoderFromReader(stdin, nil)
+ var t types.Type
+ t, err = decoder.Parse()
if err != nil {
log.Fatal(err)
}
- if len(sd.Sigs) == 0 {
- log.Fatal("no sigs")
+ if t != types.Magic || decoder.Iter().Magic() != pki.SignedMagic {
+ log.Fatal("wrong magic")
}
- sig := sd.Sigs[0]
- if sd.Hashes == nil || sig.TBS.Hashes == nil {
- log.Fatal("no hashes")
+ decoder = keks.NewDecoderFromReader(stdin, nil)
+ if _, err = decoder.Parse(); err != nil {
+ log.Fatal(err)
}
- hashes := *sig.TBS.Hashes
- hashExpect := hashes[*hashAlgo]
- hashGot := hasher.Sum(nil)
- if hashExpect == nil || !bytes.Equal(hashExpect, hashGot) {
- log.Fatal("hash mismatch")
+ var prehash pki.SignedPrehash
+ var signed pki.Signed
+ err = decoder.UnmarshalStruct(&prehash)
+ if err == nil && prehash.T == pki.SignedPrehashT {
+ if len(prehash.Sigs) == 0 {
+ log.Fatal("prehash: no sigs")
+ }
+ if len(prehash.Sigs) > 1 {
+ log.Fatal("prehash: currently only single signature support")
+ }
+ for algo := range prehash.Sigs {
+ hasher = pkihash.ByName(algo)
+ }
+ var blob *keks.BlobDecoder
+ blob, err = keks.NewBlobDecoder(stdin, 1<<32)
+ if err != nil {
+ log.Fatal(err)
+ }
+ var chunk []byte
+ mw := io.MultiWriter(hasher, os.Stdout)
+ for {
+ chunk, err = blob.Next()
+ if err != nil {
+ if err != io.EOF {
+ log.Fatal(err)
+ }
+ break
+ }
+ if _, err = io.Copy(mw, bytes.NewReader(chunk)); err != nil {
+ log.Fatal(err)
+ }
+ }
+ decoder = keks.NewDecoderFromReader(stdin, nil)
+ err = decoder.DecodeStruct(&signed)
+ } else {
+ err = decoder.UnmarshalStruct(&signed)
}
- signer := cer.CerLoad()
- if !signer.Can(pki.KUSig) || len(signer.Pub) != 1 {
- log.Fatal("cer can not sign")
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err = pki.SignedValidate(&signed); err != nil {
+ log.Fatal(err)
+ }
+ if len(signed.Sigs) == 0 {
+ log.Fatal("no sigs")
+ }
+ if len(signed.Sigs) > 1 {
+ log.Fatal("prehash: currently only single signature support")
}
+ sig := signed.Sigs[0]
+ signer := cer.CerLoad()
if sig.Sign.A != signer.Pub[0].A {
log.Fatal("differing signature algorithms")
}
- err = sd.CerCheckSignatureFrom(signer)
- if err != nil {
+ if signed.Load.T != *typ {
+ log.Fatalln("differing load type:", signed.Load.T)
+ }
+ if encryptedBinding != uuid.Nil {
+ if sig.TBS.EncryptedBinding == nil {
+ log.Fatal("missing encrypted-binding")
+ }
+ if *sig.TBS.EncryptedBinding != encryptedBinding {
+ log.Fatalln("differing encrypted-binding:", *sig.TBS.EncryptedBinding)
+ }
+ }
+ if prehash.T == "" {
+ hasher = pkihash.ByName(sig.Sign.A)
+ if _, err = io.Copy(hasher, stdin); err != nil {
+ log.Fatal(err)
+ }
+ }
+ if err = signed.CerCheckSignatureFrom(signer, &hasher); err != nil {
log.Fatal(err)
}
} else {
- var sd pki.Signed
- sd.Load.T = *typ
- sdHashes := map[string]*struct{}{*hashAlgo: nil}
- sd.Hashes = &sdHashes
- sigHashes := map[string][]byte{*hashAlgo: hasher.Sum(nil)}
- when := time.Now().UTC().Truncate(1000 * time.Microsecond)
- sigTbs := pki.SigTBS{
- Hashes: &sigHashes,
- When: &when,
+ if _, err = keks.Encode(os.Stdout, pki.SignedMagic, nil); err != nil {
+ log.Fatal(err)
}
+ if *detached {
+ if _, err = io.Copy(hasher, stdin); err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ if _, err = keks.Encode(os.Stdout, pki.SignedPrehash{
+ T: pki.SignedPrehashT,
+ Sigs: map[string]*struct{}{cer.CerLoad().Pub[0].A: nil},
+ }, nil); err != nil {
+ log.Fatal(err)
+ }
+ pr, pw := io.Pipe()
+ br := keks.BlobReader{R: pr, ChunkLen: BlobChunkLen}
+ copyErr := make(chan error)
+ go func() {
+ _, e := io.Copy(io.MultiWriter(pw, hasher), stdin)
+ pw.Close()
+ copyErr <- e
+ }()
+ _, err = keks.Encode(os.Stdout, br, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err = <-copyErr; err != nil {
+ log.Fatal(err)
+ }
+ }
+ var signed pki.Signed
+ signed.Load.T = *typ
+ when := time.Now().UTC().Truncate(1000 * time.Microsecond)
+ sigTbs := pki.SigTBS{When: &when}
if encryptedBinding != uuid.Nil {
sigTbs.EncryptedBinding = &encryptedBinding
}
- err = sd.SignWith(cer.CerLoad(), signer, sigTbs)
- if err != nil {
+ if err = signed.SignPrehashedWith(
+ hasher, cer.CerLoad(), signer, sigTbs,
+ ); err != nil {
log.Fatal(err)
}
- var data []byte
- data, err = keks.EncodeBuf(sd, nil)
- if err != nil {
- log.Fatal(err)
- }
- err = os.WriteFile(*sdPath, data, 0o666)
- if err != nil {
+ if _, err = keks.Encode(os.Stdout, signed, nil); err != nil {
log.Fatal(err)
}
}
--- /dev/null
+// sigtool -- dealing with KEKS-encoded pki-signed utility
+// 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 main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage:
+ sigtool -prv PRV -cer CER [-type TYPE] [bind] <DATA >DATA.signed
+ sigtool -verify -cer CER [-type TYPE] [bind] <DATA.signed >DATA
+ sigtool -detached -prv PRV -cer CER [-type TYPE] [bind] <DATA >DATA.signature
+ sigtool -detached -verify -cer CER [-type TYPE] [bind] <(cat DATA.signature DATA)
+
+"bind" is optional -encrypted-binding flag.
+DATA.signed holds completely encapsulated DATA.
+-detached mode keeps DATA completely separate.
+
+`)
+ flag.PrintDefaults()
+}
+++ ed25519/ed25519.go 2024-12-03 11:07:51.892841000 +0300
@@ -20,11 +20,12 @@
"crypto"
- "go.cypherpunks.su/gokeks/pki/ed25519-blake2b/edwards25519"
+ "go.cypherpunks.su/keks/pki/ed25519-blake2b/edwards25519"
cryptorand "crypto/rand"
- "crypto/sha512"
"crypto/subtle"
package ed25519blake2b
import (
- "crypto"
"crypto/rand"
"go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
)
-func NewKeypair() (signer crypto.Signer, prv, pub []byte, err error) {
+func NewKeypair() (signer *Signer, prv, pub []byte, err error) {
var prvEd ed25519.PrivateKey
var pubEd ed25519.PublicKey
pubEd, prvEd, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return
}
- signer = prvEd
+ signer = &Signer{prvEd}
prv = prvEd.Seed()
pub = pubEd[:]
return
# That script copies the library (tested on 1.23.3) and patches it to
# use BLAKE2b hash.
-modname=go.cypherpunks.su/gokeks/pki/ed25519-blake2b
+modname=go.cypherpunks.su/keks/pki/ed25519-blake2b
go mod init $modname
dst=$PWD
cd $(go env GOROOT)/src
import (
"crypto"
"errors"
+ "hash"
+ "io"
"go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
+ "golang.org/x/crypto/blake2b"
)
-func NewSigner(v []byte) (prv crypto.Signer, pub []byte, err error) {
+type Signer struct {
+ Prv ed25519.PrivateKey
+}
+
+func (s *Signer) Public() crypto.PublicKey {
+ return s.Prv.Public()
+}
+
+func (s *Signer) Sign(
+ rand io.Reader,
+ msg []byte,
+ opts crypto.SignerOpts,
+) (signature []byte, err error) {
+ return s.Prv.Sign(rand, msg, opts)
+}
+
+func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
+ return s.Prv.Sign(rand, dgst, &ed25519.Options{Hash: crypto.BLAKE2b_512})
+}
+
+func (s *Signer) NewHasher() hash.Hash {
+ h, err := blake2b.New512(nil)
+ if err != nil {
+ panic(err)
+ }
+ return h
+}
+
+func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
if len(v) != ed25519.SeedSize {
err = errors.New("wrong ed25519 private key size")
return
}
p := ed25519.NewKeyFromSeed(v)
pub = p[ed25519.SeedSize:]
- prv = p
+ prv = &Signer{p}
return
}
package ed25519blake2b
import (
+ "crypto"
"errors"
"go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
valid = ed25519.Verify(ed25519.PublicKey(pub), signed, signature)
return
}
+
+func VerifyDigest(pub, digest, signature []byte) (valid bool, err error) {
+ if len(pub) != ed25519.PublicKeySize {
+ err = errors.New("invalid ed25519 public key size")
+ return
+ }
+ err = ed25519.VerifyWithOptions(
+ ed25519.PublicKey(pub),
+ digest,
+ signature,
+ &ed25519.Options{Hash: crypto.BLAKE2b_512},
+ )
+ if err == nil {
+ valid = true
+ } else {
+ if err.Error() == "ed25519: invalid signature" {
+ valid = false
+ err = nil
+ }
+ }
+ return
+}
package gost
import (
- "crypto"
"crypto/rand"
"errors"
"io"
"go.cypherpunks.su/gogost/v6/gost3410"
)
-func NewKeypair(algo string) (signer crypto.Signer, prv, pub []byte, err error) {
+func NewKeypair(algo string) (signer *Signer, prv, pub []byte, err error) {
curve := CurveByName(algo)
if curve == nil {
err = errors.New("unknown curve")
return
}
-func NewSigner(a string, v []byte) (prv crypto.Signer, pub []byte, err error) {
+func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
+ signature, err = s.Prv.SignDigest(dgst, rand)
+ if err != nil {
+ return
+ }
+ signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...)
+ return
+}
+
+func (s *Signer) NewHasher() hash.Hash {
+ return s.Hasher()
+}
+
+func NewSigner(a string, v []byte) (prv *Signer, pub []byte, err error) {
signer := Signer{}
switch a {
case GOST3410256A:
append(signature[len(signature)/2:], signature[:len(signature)/2]...))
return
}
+
+func VerifyDigest(algo string, pub, digest, signature []byte) (valid bool, err error) {
+ var pk *gost3410.PublicKey
+ pk, err = gost3410.NewPublicKeyBE(CurveByName(algo), pub)
+ if err != nil {
+ return
+ }
+ valid, err = pk.VerifyDigest(digest,
+ append(signature[len(signature)/2:], signature[:len(signature)/2]...))
+ return
+}
"go.cypherpunks.su/gogost/v6/gost34112012256"
"go.cypherpunks.su/gogost/v6/gost34112012512"
"golang.org/x/crypto/blake2b"
+
+ ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
+ "go.cypherpunks.su/keks/pki/gost"
)
const (
func ByName(name string) hash.Hash {
switch name {
- case Streebog256:
+ case Streebog256, gost.GOST3410256A:
return gost34112012256.New()
- case Streebog512:
+ case Streebog512, gost.GOST3410512C:
return gost34112012512.New()
- case BLAKE2b:
+ case BLAKE2b, ed25519blake2b.Ed25519BLAKE2b:
h, err := blake2b.New512(nil)
if err != nil {
panic(err)
package pki
import (
- "crypto"
"errors"
"fmt"
)
// Parse private key contained in AV KEKS-encoded structure.
-func PrvParse(data []byte) (prv crypto.Signer, pub []byte, err error) {
+func PrvParse(data []byte) (prv Signer, pub []byte, err error) {
{
var magic keks.Magic
magic, data = keks.StripMagic(data)
"crypto"
"crypto/rand"
"errors"
+ "hash"
+ "io"
"time"
"github.com/google/uuid"
"go.cypherpunks.su/keks"
)
-const SignedMagic = "pki/signed"
+const SignedMagic = keks.Magic("pki/signed")
+
+type Signer interface {
+ crypto.Signer
+ SignDigest(io.Reader, []byte) ([]byte, error)
+ NewHasher() hash.Hash
+}
+
+const SignedPrehashT = "prehash"
+
+type SignedPrehash struct {
+ T string `keks:"t"`
+ Sigs map[string]*struct{} `keks:"sigs"`
+}
type SignedLoad struct {
- V any `keks:"v,omitempty"`
+ V *any `keks:"v,omitempty"`
T string `keks:"t"`
}
type SigTBS struct {
- Hashes *map[string][]byte `keks:"hash,omitempty"`
- CID *uuid.UUID `keks:"cid,omitempty"`
- Exp *[]time.Time `keks:"exp,omitempty"`
- When *time.Time `keks:"when,omitempty"`
- SID uuid.UUID `keks:"sid"`
+ CID *uuid.UUID `keks:"cid,omitempty"`
+ Exp *[]time.Time `keks:"exp,omitempty"`
+ When *time.Time `keks:"when,omitempty"`
+ SID uuid.UUID `keks:"sid"`
EncryptedBinding *uuid.UUID `keks:"encrypted-binding,omitempty"`
}
}
type SignedTBS struct {
- V any `keks:"v"`
+ V any `keks:"v,omitempty"`
T string `keks:"t"`
TBS SigTBS `keks:"tbs"`
}
type Signed struct {
- Hashes *map[string]*struct{} `keks:"hash,omitempty"`
- Cers *[]*Signed `keks:"certs,omitempty"`
- Load SignedLoad `keks:"load"`
- Sigs []*Sig `keks:"sigs"`
+ Cers *[]*Signed `keks:"certs,omitempty"`
+ Load SignedLoad `keks:"load"`
+ Sigs []*Sig `keks:"sigs"`
}
// Validate parsed pki-signed structure.
-func SignedValidate(sd *Signed) (err error) {
- if sd.Hashes != nil && len(*sd.Hashes) == 0 {
- err = errors.New("SignedParse: empty /hash")
- return
- }
- if sd.Cers != nil {
- if len(*sd.Cers) == 0 {
+func SignedValidate(signed *Signed) (err error) {
+ if signed.Cers != nil {
+ if len(*signed.Cers) == 0 {
err = errors.New("SignedParse: empty /certs")
return
}
- for _, cer := range *sd.Cers {
+ for _, cer := range *signed.Cers {
err = cer.CerParse()
if err != nil {
return
}
}
}
- for _, sig := range sd.Sigs {
+ for _, sig := range signed.Sigs {
if sig.CerLoc != nil && len(*sig.CerLoc) == 0 {
err = errors.New("SignedParse: empty cer-loc")
return
}
- if sig.TBS.Hashes != nil && len(*sig.TBS.Hashes) == 0 {
- err = errors.New("SignedParse: empty hash")
- return
- }
if sig.TBS.Exp != nil {
if len(*sig.TBS.Exp) != 2 {
err = errors.New("SignedParse: wrong exp len")
}
}
}
- if sd.Hashes != nil {
- if sig.TBS.Hashes == nil {
- err = errors.New("SignedParse: /sigs: no hash")
- return
- }
- var exists bool
- for ai := range *sd.Hashes {
- if _, ok := (*sig.TBS.Hashes)[ai]; ok {
- exists = true
- break
- }
- }
- if !exists {
- err = errors.New("SignedParse: /sigs: no hash")
- return
- }
- }
}
return
}
}
}
d := keks.NewDecoderFromBytes(data, nil)
- var sd Signed
- err := d.DecodeStruct(&sd)
+ var signed Signed
+ err := d.DecodeStruct(&signed)
if err != nil {
return nil, err
}
- err = SignedValidate(&sd)
- return &sd, err
+ err = SignedValidate(&signed)
+ return &signed, err
}
// Sign Signed's contents and sigTBS corresponding data with the
// provided prv signer, having parent certificate. Signature is appended
-// to the sd.Sigs. parent certificate must have "sig" key-usage.
-func (sd *Signed) SignWith(
+// to the signed.Sigs. parent certificate must have "sig" key-usage.
+func (signed *Signed) SignWith(
parent *CerLoad,
prv crypto.Signer,
sigTBS SigTBS,
return errors.New("parent can not sign")
}
sigTBS.SID = parent.Pub[0].Id
- sdTBS := SignedTBS{T: sd.Load.T, V: sd.Load.V, TBS: sigTBS}
+ signedTBS := SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sigTBS}
sig := Sig{TBS: sigTBS}
sig.Sign.A = parent.Pub[0].A
var buf []byte
- buf, err = keks.EncodeBuf(sdTBS, nil)
+ buf, err = keks.EncodeBuf(signedTBS, nil)
if err != nil {
return
}
if err != nil {
return
}
- sd.Sigs = append(sd.Sigs, &sig)
+ signed.Sigs = append(signed.Sigs, &sig)
+ return nil
+}
+
+// Same as SignWith, but use prehashed mode.
+func (signed *Signed) SignPrehashedWith(
+ hasher hash.Hash,
+ parent *CerLoad,
+ prv Signer,
+ sigTBS SigTBS,
+) (err error) {
+ if !parent.Can(KUSig) || len(parent.Pub) != 1 {
+ return errors.New("parent can not sign")
+ }
+ sigTBS.SID = parent.Pub[0].Id
+ signedTBS := SignedTBS{T: signed.Load.T, TBS: sigTBS}
+ sig := Sig{TBS: sigTBS}
+ sig.Sign.A = parent.Pub[0].A
+ _, err = keks.Encode(hasher, signedTBS, nil)
+ if err != nil {
+ return
+ }
+ sig.Sign.V, err = prv.SignDigest(rand.Reader, hasher.Sum(nil))
+ if err != nil {
+ return
+ }
+ signed.Sigs = append(signed.Sigs, &sig)
return nil
}
* text => any
}
-ku = "ca" / "sig" / "app-name" / text
+ku = "ca" / "sig" / "kem" / "app-name" / text
crit-ext-type = text
Its @code{/load/t} equals to @code{cer}.
@code{/load/v} contains @code{cer-load}:
+@verbatim
+cer = pki-signed ; with /load/t = "cer", /load/v = cer-load
+@end verbatim
+
@verbatiminclude format/cer-load.cddl
@table @code
* text => any
}
-kem-balloon-blake2b = {
+kem-balloon-blake2b-hkdf = {
a: "balloon-blake2b-hkdf",
cek: bytes,
cost: {
@cindex private-key
@section private-key format
-Private key is stored in trivial map:
-
@verbatiminclude format/private-key.cddl
Stored in a file, it should begin with "pki/prvkey" @ref{Magic, magic}.
Big-endian private key representation must be used.
-Following algorithm identifiers are acceptable:
+Following algorithm identifiers are used:
@code{gost3410-256A}, @code{gost3410-512C}.
@node private-key-ed25519-blake2b
@item skein512
@code{@ref{pki-hashed-skein512}}
@item streebog256, streebog512
- @code{@ref{cer-gost3410}},
- @code{@ref{pki-hashed-gost3411}},
- @code{@ref{pki-signed-gost3410}}
+ @code{@ref{pki-hashed-gost3411}}
@item xxh3-128
@code{@ref{pki-hashed-xxh3-128}}
@end table
--- /dev/null
+pki-signed-prehash = {
+ t: "prehash",
+ sigs: set, ; set of signature algorithm identifiers (/sigs/*/sign/a)
+}
pki-signed-tbs = {
t: text, ; = /load/t
- v: nil / any,
+ ? v: any, ; /load/v if it is supplied
tbs: map, ; = /sigs/?/tbs
}
ai = text ; algorithm identifier
-av = {a: ai, v: bytes}
pki-signed = {
- ? hash: set, ; when using prehashing
load: {
t: text,
- ? v: bytes / text / blob / map / list,
+ ? v: bytes / text / map / list,
},
sigs: [+ sig],
? certs: [+ cer],
}
-cer = pki-signed ; with /load/t = "cer", /load/v = cer-load
-
sig = {
tbs: sig-tbs,
- sign: av,
+ sign: {a: ai, v: bytes},
? cer-loc: [+ url],
* text => any
}
sig-tbs = {
sid: uuid, ; signer's public key id
- ? hash: {ai => bytes}, ; when using prehashing
- ? envelope-binding: uuid,
+ ? encrypted-binding: uuid,
? when: tai64 / tai64n,
* text => any
}
(PKCS#7) ASN.1-based format.
Stored in a file, it should begin with "pki/signed" @ref{Magic, magic},
-unless this is a @ref{cer, certificate}.
+unless it is a @ref{cer, certificate}.
@verbatiminclude format/signed.cddl
@verbatiminclude format/signed-tbs.cddl
-If prehashing is used, then @code{/hash} tells what algorithms will be
-used to hash the data of @code{/load/v}. If @code{/load/v} is either a
-MAP or LIST, then its encoded binary representation is hashed. If it is
-BIN/STR or BLOB, then its binary contents are hashed. So signature will
-stay the same even if data is converted from BIN to BLOB.
+If no @code{/load/v} is provided, then the data is detached from the
+@code{pki-signed} structure itself and it is fed into hasher before that
+structure. You can provide it any way you wish, but for keeping that
+detached data closely to the @code{pki-signed}, you should use the
+following approach:
-If @code{/load/v} is absent, then it is detached and must be provided as
-a binary data from outside.
+@verbatim
+pki-signed-prehash || BLOB || pki-signed
+@end verbatim
+
+@verbatiminclude format/signed-prehash.cddl
+
+With @code{pki-signed-prehash} you initialise your hashers used during
+signing process and fed BLOB's contents (not the encoded BLOB itself!)
+into the them. Finally you add @code{pki-signed-tbs} without @code{v}
+field to them.
@code{/sigs/*/tbs/when} is optional signing time.
If signed data is also intended to be @ref{pki-encrypted, encrypted},
then @code{/sigs/*/tbs/encrypted-binding} should be set to
-envelope's @code{/bind} value.
+@ref{pki-encrypted, encrypted}'s @code{/bind} value.
@node pki-signed-gost3410
@subsection pki-signed with GOST R 34.10-2012
in @code{BE(X)||BE(Y)} format. Signature is in @code{BE(R)||BE(S)}
format.
-Following algorithm identifiers should be used for the hash:
-@code{streebog256}, @code{streebog512}.
-
Following algorithm identifiers are acceptable for the public key and
signature: @code{gost3410-256A}, @code{gost3410-512C}.
Strict @url{https://zips.z.cash/zip-0215, ZIP-0215} validation rules
should be used while verifying the signature.
+PureEdDSA @strong{must} be used when no detached data exists.
+HashEdDSA @strong{must} be used otherwise, using BLAKE2b-512 as a hash.
+
@code{ed25519-blake2b} algorithm identifier is used.