"crypto/sha3"
"errors"
"flag"
- "fmt"
"hash"
"io"
"log"
"go.cypherpunks.su/balloon/v3"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
- "golang.org/x/term"
"go.cypherpunks.su/keks"
"go.cypherpunks.su/keks/cm"
return h
}
-func readPasswd(prompt string) (passwd []byte) {
- if raw := os.Getenv("CMENCTOOL_PASSPHRASE"); raw != "" {
- return []byte(raw)
- }
- tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
- if err != nil {
- log.Fatal(err)
- }
- defer tty.Close()
- tty.WriteString(prompt)
- passwd, err = term.ReadPassword(int(tty.Fd()))
+func parsePrv(data []byte) (av cm.AV, tail []byte, err error) {
+ data, err = cmballoon.PossibleInteractiveDecrypt(data)
if err != nil {
- log.Fatalln(err)
+ return
}
- // taken from age/cmd/age/tui.go:clearLine
- const (
- CUI = "\033[" // Control Sequence Introducer
- CPL = CUI + "F" // Cursor Previous Line
- EL = CUI + "K" // Erase in Line
- )
- fmt.Fprintf(tty, "\r\n"+CPL+EL)
- return
-}
-
-func parsePrv(data []byte) (av cm.AV, tail []byte, err error) {
- var magic keks.Magic
- magic, data = keks.StripMagic(data)
- switch magic {
- case sign.PrvMagic:
- case cmenc.Magic:
- var encrypted cmenc.Encrypted
- var v any
- {
- d := keks.NewDecoderFromBytes(data, nil)
- v, err = d.Decode()
- if err != nil {
- return
- }
- err = schema.Check("encrypted", cmenc.EncryptedSchemas, v)
- if err != nil {
- return
- }
- err = d.UnmarshalStruct(&encrypted)
- if err != nil {
- return
- }
- }
- if encrypted.DEM.A != chapoly.DEMAlgo {
- err = errors.New("unsupported prv encryption DEM")
- return
- }
- if len(encrypted.KEM) != 1 ||
- encrypted.KEM[0].A != cmballoon.BalloonBLAKE2bHKDF ||
- len(encrypted.Payload) == 0 {
- err = errors.New("wrong prv encryption KEM")
- return
- }
- v = v.(map[string]any)["kem"].([]any)[0]
- err = schema.Check("kem-balloon-blake2b-hkdf", cmenc.EncryptedSchemas, v)
- if err != nil {
+ {
+ var magic keks.Magic
+ magic, data = keks.StripMagic(data)
+ if magic != sign.PrvMagic {
+ err = errors.New("wrong magic")
return
}
-
- passwd := readPasswd("Passphrase for private key:")
- var cek []byte
- cek, err = cmballoon.Decapsulate(encrypted.KEM[0], encrypted.Id[:], passwd)
- if err != nil {
+ }
+ sign.PrvParse(data)
+ d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
+ {
+ var v any
+ if v, err = d.Decode(); err != nil {
return
}
- var buf bytes.Buffer
- _, err = chapoly.Open(&buf, bytes.NewReader(encrypted.Payload), cek, 1)
- if err != nil {
+ if err = schema.Check("av", sign.PubSchemas, v); err != nil {
return
}
- data = buf.Bytes()
- magic, data = keks.StripMagic(data)
- if magic == sign.PrvMagic {
- break
- }
- fallthrough
- default:
- err = errors.New("wrong magic")
+ }
+ if err = d.UnmarshalStruct(&av); err != nil {
return
}
- sign.PrvParse(data)
- d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
- err = d.DecodeStruct(&av)
tail = d.B
return
}
log.Println(kemIdx, kem.A, "skipping because no -passwd")
continue
}
- passwd := readPasswd("Passphrase for " + strconv.Itoa(kemIdx) + " KEM:")
+ var passwd []byte
+ passwd, err = cmenc.ReadPasswd("Passphrase for " + strconv.Itoa(kemIdx) + " KEM:")
+ if err != nil {
+ log.Fatal(err)
+ }
cek, err = cmballoon.Decapsulate(kem, encrypted.Id[:], passwd)
if err != nil {
log.Print(err)
cek = make([]byte, chapoly.CEKLen)
rand.Read(cek)
if *passphrase {
- passwd := readPasswd("Passphrase:")
+ var passwd []byte
+ passwd, err = cmenc.ReadPasswd("Passphrase:")
+ if err != nil {
+ log.Fatal(err)
+ }
{
- confirm := readPasswd("Confirm:")
+ var confirm []byte
+ confirm, err = cmenc.ReadPasswd("Confirm:")
+ if err != nil {
+ log.Fatal(err)
+ }
if !bytes.Equal(passwd, confirm) {
log.Fatal("passphrases do not match")
}
TMPDIR=${TMPDIR:-/tmp}
dd if=/dev/urandom of=$TMPDIR/enc.data bs=300K count=1 2>/dev/null
-export CMENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
+export CM_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
balloonparams="-balloon-s 123 -balloon-t 2"
test_expect_success "encrypting" "cmenctool $balloonparams -p \
<$TMPDIR/enc.data >$TMPDIR/enc.enc"
cmkeytool -algo sntrup761-x25519 -ku kem -sub A=KEY 5>$TMPDIR/enc.pub 9>$TMPDIR/enc.prv
dd if=/dev/urandom of=$TMPDIR/enc.data bs=12K count=1 2>/dev/null
-export CMENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
+export CM_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
balloonparams="-balloon-s 123 -balloon-t 2"
test_expect_success "key encrypting" "cmenctool -p -embed $balloonparams \
<$TMPDIR/enc.prv >$TMPDIR/enc.prv.enc"
test_expect_success "$algo1: comparing" \
"test_cmp $TMPDIR/enc.data $TMPDIR/enc.data.got"
-export CMENCTOOL_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
+export CM_PASSPHRASE=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | xxd -p)
test_expect_success "encrypting also with passphrase" "
cat $TMPDIR/enc.$algo0.pub $TMPDIR/enc.$algo1.pub |
cmenctool $balloonparams -p 4<&0 <$TMPDIR/enc.data >$TMPDIR/enc.enc"
"time"
"go.cypherpunks.su/keks"
+ cmballoon "go.cypherpunks.su/keks/cm/enc/balloon"
cmhash "go.cypherpunks.su/keks/cm/hash"
"go.cypherpunks.su/keks/cm/sign"
"go.cypherpunks.su/keks/cm/sign/mode"
}
} else {
var signer sign.Iface
- signer, _, err = sign.PrvParse(mustReadAll(fdPrvR))
- fdPrvR.Close()
+ {
+ 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)
}
--- /dev/null
+// GoKEKS/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 balloon
+
+import (
+ "bytes"
+ "errors"
+
+ "go.cypherpunks.su/keks"
+ cmenc "go.cypherpunks.su/keks/cm/enc"
+ "go.cypherpunks.su/keks/cm/enc/chapoly"
+ "go.cypherpunks.su/keks/schema"
+)
+
+// Possibly interactively decrypt passphrase-encrypted data.
+func PossibleInteractiveDecrypt(in []byte) (out []byte, err error) {
+ var magic keks.Magic
+ magic, out = keks.StripMagic(in)
+ if magic != cmenc.Magic {
+ return in, nil
+ }
+ var encrypted cmenc.Encrypted
+ var v any
+ {
+ d := keks.NewDecoderFromBytes(out, nil)
+ v, err = d.Decode()
+ if err != nil {
+ return
+ }
+ err = schema.Check("encrypted", cmenc.EncryptedSchemas, v)
+ if err != nil {
+ return
+ }
+ err = d.UnmarshalStruct(&encrypted)
+ if err != nil {
+ return
+ }
+ }
+ if encrypted.DEM.A != chapoly.DEMAlgo {
+ err = errors.New("unsupported encryption DEM")
+ return
+ }
+ if len(encrypted.KEM) != 1 ||
+ encrypted.KEM[0].A != BalloonBLAKE2bHKDF ||
+ len(encrypted.Payload) == 0 {
+ err = errors.New("wrong encryption KEM")
+ return
+ }
+ v = v.(map[string]any)["kem"].([]any)[0]
+ err = schema.Check("kem-balloon-blake2b-hkdf", cmenc.EncryptedSchemas, v)
+ if err != nil {
+ return
+ }
+ var passwd []byte
+ passwd, err = cmenc.ReadPasswd("Passphrase:")
+ if err != nil {
+ return
+ }
+ var cek []byte
+ cek, err = Decapsulate(encrypted.KEM[0], encrypted.Id[:], passwd)
+ if err != nil {
+ return
+ }
+ var buf bytes.Buffer
+ _, err = chapoly.Open(&buf, bytes.NewReader(encrypted.Payload), cek, 1)
+ out = buf.Bytes()
+ return
+}
--- /dev/null
+// GoKEKS/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 encrypted
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/term"
+)
+
+func ReadPasswd(prompt string) (passwd []byte, err error) {
+ if raw := os.Getenv("CM_PASSPHRASE"); raw != "" {
+ return []byte(raw), nil
+ }
+ var tty *os.File
+ tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
+ if err != nil {
+ return
+ }
+ defer tty.Close()
+ tty.WriteString(prompt)
+ passwd, err = term.ReadPassword(int(tty.Fd()))
+ if err != nil {
+ return
+ }
+ // taken from age/cmd/age/tui.go:clearLine
+ const (
+ CUI = "\033[" // Control Sequence Introducer
+ CPL = CUI + "F" // Cursor Previous Line
+ EL = CUI + "K" // Erase in Line
+ )
+ fmt.Fprintf(tty, "\r\n"+CPL+EL)
+ return
+}