diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9c4f4e0..82a44cb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,9 +26,12 @@ jobs: run: | git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: tests + run: go test ./... + - name: make run: make - + reuse-compliance-check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 9b0c0a1..f9ee87e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,18 +15,18 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 with: - go-version: '1.21' + go-version: stable cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 with: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.55.2 + version: v2.4.0 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/.golangci.yml b/.golangci.yml index bc2bc66..98e619e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,34 +1,90 @@ +version: "2" linters: - presets: - # found in: golangci-lint help linters - - bugs - - comment - - complexity - - error - - format - - import - - metalinter - - module - - performance - - sql - # - style # turned off, can be too much - - test - - unused + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - dupword + - durationcheck + - errchkjson + - errorlint + - exhaustive + - fatcontext + - gocheckcompilerdirectives + - gochecksumtype + - gocritic + - gocyclo + - godox + - gomoddirectives + - gomodguard + - gosec + - gosmopolitan + - loggercheck + - maintidx + - makezero + - misspell + - musttag + - nilerr + - nilnesserr + - noctx + - paralleltest + - perfsprint + - prealloc + - protogetter + - reassign + - recvcheck + - revive + - rowserrcheck + - spancheck + - sqlclosecheck + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - unparam + - usetesting + - wrapcheck + - zerologlint disable: - cyclop + - depguard + - err113 + - exhaustruct - funlen - gocognit - - nestif - - exhaustruct # TODO? annoying for now - - goerr113 # TODO enable later - godot - - depguard - + - nestif + - testpackage + - perfsprint + settings: + govet: + enable: + - shadow + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 - -linters-settings: - govet: - enable: - - shadow +formatters: + enable: + - gci + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 9500e55..2e34193 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,10 @@ reload-rules: udevadm control --reload udevadm trigger +.PHONY: test +test: + go test ./... + podman: podman run --arch=amd64 --rm --mount type=bind,source=$(CURDIR),target=/src -w /src -it ghcr.io/tillitis/tkey-builder:4 make -j diff --git a/README.md b/README.md index 7f2fa0a..cee310a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ See [Release notes](RELEASE.md). ## Usage +You can specify the algorithm to use on the file before signing and +verifying by specifying `-a/--alg algorithm`. The choices are "ed" +(default, uses SHA-512) and "b2s" (BLAKE2s). + Get a public key, possibly modifying the key pair by using a User Supplied Secret, and storing the public key in file `-p pubkey`. @@ -29,7 +33,7 @@ to supply the public key file as well which `tkey-sign` will automatically verify that it's the expected public key. ``` -tkey-sign -S/--sign [-d/--port device] [-s speed] -m message +tkey-sign -S/--sign [-d/--port device] [-s speed] -m message [-a/--alg algorithm] [--uss] [--uss-file secret-file] -p/--public pubkey [-x sig-file] ``` @@ -38,7 +42,7 @@ Signature is by default in `message.sig` but can be specified with `-x sigfile`. Doesn't need a connected TKey. ``` -tkey-sign -V/--verify -m message -p/--public pubkey [-x sigfile] +tkey-sign -V/--verify -m message -p/--public pubkey [-x sigfile] [-a/--alg algorithm] ``` Alternatively you can use OpenBSD's *signify(1)* to verify the @@ -60,6 +64,7 @@ All examples either load the device app automatically or works with an already loaded device app. Store the public key in a file. + ``` $ tkey-sign -G -p key.pub ``` diff --git a/RELEASE.md b/RELEASE.md index 788f4a2..000e1db 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,16 @@ # Release notes +## Upcoming release + +- Change to BSD2 license. + +- Follow REUSE specification. + +- Introduce package signify. Export Signify types to import and export + them to buffers and files. + +- Add support for BLAKE2s hashing. + ## v1.0.1 - Normalize line endings of user input when asked to overwrite a file. diff --git a/cmd/tkey-sign/file.go b/cmd/tkey-sign/file.go deleted file mode 100644 index 00ca14c..0000000 --- a/cmd/tkey-sign/file.go +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Tillitis AB -// SPDX-License-Identifier: BSD-2-Clause - -package main - -import ( - "bufio" - "bytes" - "encoding/base64" - "encoding/binary" - "errors" - "fmt" - "os" - "strings" -) - -// readBase64 reads the file in filename with base64, decodes it and -// returns a binary representation -func readBase64(filename string) ([]byte, error) { - input, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - lines := strings.Split(string(input), "\n") - if len(lines) < 2 { - return nil, fmt.Errorf("Too few lines in file %s", filename) - } - - data, err := base64.StdEncoding.DecodeString(lines[1]) - if err != nil { - return nil, fmt.Errorf("could not decode: %w", err) - } - - return data, nil -} - -func readKey(filename string) (*pubKey, error) { - var pub pubKey - - buf, err := readBase64(filename) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - r := bytes.NewReader(buf) - err = binary.Read(r, binary.BigEndian, &pub) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - return &pub, nil -} - -func readSig(filename string) (*signature, error) { - var sig signature - - buf, err := readBase64(filename) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - r := bytes.NewReader(buf) - err = binary.Read(r, binary.BigEndian, &sig) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - return &sig, nil -} - -// writeBase64 encodes data in base64 and writes it the file given in -// filename. If overwrite is true it overwrites any existing file, -// otherwise it returns an error. -func writeBase64(filename string, data any, comment string, overwrite bool) error { - var buf bytes.Buffer - - err := binary.Write(&buf, binary.BigEndian, data) - if err != nil { - return fmt.Errorf("%w", err) - } - - b64 := base64.StdEncoding.EncodeToString(buf.Bytes()) - b64 += "\n" - - var f *os.File - - f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) - if err != nil { - if os.IsExist(err) && overwrite { - f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o666) - if err != nil { - return fmt.Errorf("%w", err) - } - } else { - return fmt.Errorf("%w", err) - } - } - - defer f.Close() - - _, err = f.Write([]byte(fmt.Sprintf("untrusted comment: %s\n", comment))) - if err != nil { - return fmt.Errorf("%w", err) - } - _, err = f.Write([]byte(b64)) - if err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// writeRetry writes the data in the file given in filename as base64. -// If a file already exists it prompts interactively for permission to -// overwrite the file. -func writeRetry(filename string, data any, comment string) error { - err := writeBase64(filename, data, comment, false) - if os.IsExist(errors.Unwrap(err)) { - le.Printf("File %v exists. Overwrite [y/n]?", filename) - reader := bufio.NewReader(os.Stdin) - overWriteP, _ := reader.ReadString('\n') - - // Trim space to normalize Windows line endings - overWriteP = strings.TrimSpace(overWriteP) - - if overWriteP == "y" { - err = writeBase64(filename, data, comment, true) - } else { - le.Printf("Aborted\n") - os.Exit(1) - } - } - - if !os.IsExist(errors.Unwrap(err)) && err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} diff --git a/cmd/tkey-sign/main.go b/cmd/tkey-sign/main.go index 0c7cc89..0a18745 100644 --- a/cmd/tkey-sign/main.go +++ b/cmd/tkey-sign/main.go @@ -18,9 +18,11 @@ import ( "syscall" "github.com/spf13/pflag" + "github.com/tillitis/tkey-sign-cli/signify" "github.com/tillitis/tkeyclient" "github.com/tillitis/tkeysign" "github.com/tillitis/tkeyutil" + "golang.org/x/crypto/blake2s" ) type command int @@ -30,8 +32,19 @@ const ( cmdGetKey cmdSign cmdVerify + cmdDump ) +type devArgs struct { + Path string + Speed int +} + +type USSArgs struct { + FileName string + Request bool +} + // nolint:typecheck // Avoid lint error when the embedding file is missing. // Build copies the built signer here // @@ -48,18 +61,6 @@ var ( verbose = false ) -type pubKey struct { - Alg [2]byte - KeyNum [8]byte - Key [32]byte -} - -type signature struct { - Alg [2]byte - KeyNum [8]byte - Sig [64]byte -} - // May be set to non-empty at build time to indicate that the signer // app has been compiled with touch requirement removed. var signerAppNoTouch string @@ -76,89 +77,57 @@ func GetEmbeddedAppDigest() string { return hex.EncodeToString(digest[:]) } -// signFile uses the connection to the signer and produces an Ed25519 +// signMessage uses the connection to the signer and produces an Ed25519 // signature over the file in fileName. It automatically verifies the // signature against the provided pubkey. // // It returns the Ed25519 signature on success or an error. -func signFile(signer tkeysign.Signer, pubkey []byte, fileName string) (*signature, error) { - message, err := os.ReadFile(fileName) - if err != nil { - return nil, fmt.Errorf("ReadFile: %w", err) - } - - fileDigest := sha512.Sum512(message) - fileDigestHex := fmt.Sprintf("%x %s\n", fileDigest, fileName) - if verbose { - le.Printf("SHA512 hash: %x", fileDigest) - le.Printf("SHA512 hash: %v", fileDigest) - } - +func signMessage(signer tkeysign.Signer, pubkey []byte, message string) (*signify.Signature, error) { if signerAppNoTouch != "" { le.Printf("WARNING! This tkey-sign and signer app is built with the touch requirement removed") } - sig, err := signer.Sign([]byte(fileDigestHex)) + sig, err := signer.Sign([]byte(message)) if err != nil { return nil, fmt.Errorf("signing failed: %w", err) } + s, err := signify.NewSignature(signify.Ed, sig) + if err != nil { + return nil, fmt.Errorf("couldn't convert to signify signature") + } + if verbose { le.Printf("signature: %x", sig) } - if !ed25519.Verify(pubkey, []byte(fileDigestHex), sig) { + if !ed25519.Verify(pubkey, []byte(message), sig) { return nil, fmt.Errorf("signature FAILED verification") } - s := signature{ - Alg: [2]byte{'E', 'd'}, - KeyNum: [8]byte{1, 7}, - Sig: [64]byte{}, - } - - copy(s.Sig[:], sig) - return &s, nil } // verifySignature verifies a Ed25519 signature stored in sigFile over // messageFile with public key in pubkeyFile -func verifySignature(messageFile string, sigFile string, pubkeyFile string) error { - signature, err := readSig(sigFile) - if err != nil { +func verifySignature(message string, sigFile string, pubkeyFile string) error { + var signature signify.Signature + + if err := signature.FromFile(sigFile); err != nil { if errors.Is(errors.Unwrap(err), fs.ErrNotExist) { - return fmt.Errorf("Signature file %v not found, specify with '-x sigfile'", sigFile) + return fmt.Errorf("signature file %v not found, specify with '-x sigfile'", sigFile) } return fmt.Errorf("%w", err) } - if len(signature.Sig) != 64 { - return fmt.Errorf("invalid length of signature. Expected 64 bytes, got %d bytes", len(signature.Sig)) - } + var pubKey signify.PubKey - pubkey, err := readKey(pubkeyFile) - if err != nil { + if err := pubKey.FromFile(pubkeyFile); err != nil { return fmt.Errorf("%w", err) } - if len(pubkey.Key) != 32 { - return fmt.Errorf("invalid length of public key. Expected 32 bytes, got %d bytes", len(pubkey.Key)) - } - - message, err := os.ReadFile(messageFile) - if err != nil { - return fmt.Errorf("could not read %s: %w", messageFile, err) - } - - digest := sha512.Sum512(message) - digestHex := fmt.Sprintf("%x %s\n", digest, messageFile) - if verbose { - le.Printf("SHA512 hash: %x", digest) - } - - if !ed25519.Verify(pubkey.Key[:], []byte(digestHex), signature.Sig[:]) { + if !ed25519.Verify(pubKey[:], []byte(message), signature.Sig[:]) { return fmt.Errorf("signature not valid") } @@ -246,16 +215,189 @@ func loadSigner(devPath string, speed int, fileUSS string, enterUSS bool) (*tkey return &signer, pubkey, nil } +func dumpFiles(sigFn string, keyFn string) error { + var sig signify.Signature + var key signify.PubKey + + if err := sig.FromFile(sigFn); err != nil { + return fmt.Errorf("%w", err) + } + + fmt.Printf("Signature\n Alg: ") + switch sig.Alg { + case signify.Ed: + fmt.Printf("Ed\n") + + case signify.B2sEd: + fmt.Printf("B2sEd\n") + + default: + fmt.Printf(" : %v\n", sig.Alg) + } + + fmt.Printf(" Sig: %x\n", sig.Sig) + + if err := key.FromFile(keyFn); err != nil { + return fmt.Errorf("%w", err) + } + + fmt.Printf("Key: %x\n", key) + + return nil +} + +func GetKey(keyFn string, overwrite bool, dev devArgs, uss USSArgs) error { + if keyFn == "" { + return errors.New("please provide -p pubkey") + } + + signer, pub, err := loadSigner(dev.Path, dev.Speed, uss.FileName, uss.Request) + if err != nil { + return fmt.Errorf("%w", err) + } + + defer signer.Close() + pubkey, err := signify.NewPubKey(pub) + if err != nil { + le.Printf("Couldn't convert public key from signer to Signify key\n") + } + + comment := "tkey public key" + if overwrite { + err = pubkey.ToFile(keyFn, comment, true) + } else { + err = writeRetry(keyFn, &pubkey, comment) + } + + if err != nil { + signer.Close() + return fmt.Errorf("%w", err) + } + + return nil +} + +func Sign(msg string, keyFn string, sigFn string, overwrite bool, dev devArgs, uss USSArgs) error { + var pubKey signify.PubKey + + if keyFn == "" { + return errors.New("provide -p pubkey") + } + + if err := pubKey.FromFile(keyFn); err != nil { + return fmt.Errorf("couldn't read pubkey file: %w", err) + } + + signer, pub, err := loadSigner(dev.Path, dev.Speed, uss.FileName, uss.Request) + if err != nil { + return fmt.Errorf("couldn't load signer: %w", err) + } + + defer signer.Close() + + if !bytes.Equal(pub, pubKey[:]) { + return fmt.Errorf("key from file %v not equal to loaded app's", keyFn) + } + + sig, err := signMessage(*signer, pub, msg) + if err != nil { + return fmt.Errorf("signing failed: %w", err) + } + + comment := fmt.Sprintf("verify with %v", keyFn) + if overwrite { + err = sig.ToFile(sigFn, comment, true) + } else { + err = writeRetry(sigFn, sig, comment) + } + + if err != nil { + return fmt.Errorf("couldn't store signature: %w", err) + } + + return nil +} + +func Verify(msg string, keyFn string, sigFn string) error { + if keyFn == "" { + return errors.New("provide public key file path with -p pubkey") + } + + if err := verifySignature(msg, sigFn, keyFn); err != nil { + return fmt.Errorf("verifying failed: %w", err) + } + + return nil +} + +func Dump(keyFn string, sigFn string) error { + if keyFn == "" { + return errors.New("provide public key file path with -p pubkey") + } + + err := dumpFiles(sigFn, keyFn) + if err != nil { + return fmt.Errorf("error dumping data: %w", err) + } + + return nil +} + +// getMessage returns the message to sign or verify. +func getMessage(msgFn string, alg string) (string, error) { + if msgFn == "" { + return "", errors.New("provide -m messagefile") + } + + file, err := os.ReadFile(msgFn) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + var message string + + switch alg { + case "ed": + // default + + fileDigest := sha512.Sum512(file) + // The actual message is compatible with output from + // sha512sum, including the filename, to work with + // scripts using signify. + // + // XXX Do we really want to include the filename + // here? Keeping it for backwards compatibility now. + message = fmt.Sprintf("%x %s\n", fileDigest, msgFn) + if verbose { + le.Printf("SHA512 digest: %x", fileDigest) + } + + case "b2s": + digest := blake2s.Sum256(file) + message = string(digest[:]) + + default: + return "", errors.New("unknown algorithm") + } + + if verbose { + le.Printf("message to be signed: %v", message) + } + + return message, nil +} + func usage() { desc := fmt.Sprintf(`Usage: %[1]s -h/--help +%[1]s -D/--dump -p/--public pubkey -m message [-x sigfile] %[1]s -G/--getkey -p/--public pubkey [-d/--port device] [-f/--force] [-s/--speed speed] [--uss] [--uss-file ussfile] [--verbose] -%[1]s -S/--sign -m message -p/--public pubkey [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] +%[1]s -S/--sign -m message -p/--public pubkey [-a/--alg algorithm] [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] -%[1]s -V/--verify -m message -p/--public pubkey [-x sigfile] +%[1]s -V/--verify -m message -p/--public pubkey [-a/--alg algorithm] [-x sigfile] %[1]s --version @@ -267,8 +409,10 @@ algorithm is Ed25519. Exit status code is 0 if everything went well or non-zero if unsuccessful. Alternatively, -G/--getkey can be used to receive the public key of -the signer app on the TKey. Specify where to store it with -p key.pub`, - os.Args[0]) +the signer app on the TKey. Specify where to store it with -p key.pub. + +Use -D/--dump to get more information about signature and public key +files.`, os.Args[0]) le.Printf("%s\n\n%s", desc, pflag.CommandLine.FlagUsagesWrapped(86)) @@ -277,6 +421,8 @@ the signer app on the TKey. Specify where to store it with -p key.pub`, func main() { var cmd command var cmdArgs int + alg := pflag.StringP("alg", "a", "ed", "Hash message file before ops (ed: SHA-512 output, b2s: BLAKE2s") + dump := pflag.BoolP("dump", "D", false, "Dump data about files.") getKey := pflag.BoolP("getkey", "G", false, "Get public key.") sign := pflag.BoolP("sign", "S", false, "Sign the message.") verify := pflag.BoolP("verify", "V", false, "Verify signature of the message.") @@ -321,6 +467,11 @@ func main() { } + if *dump { + cmd = cmdDump + cmdArgs++ + } + if *getKey { cmd = cmdGetKey cmdArgs++ @@ -341,117 +492,58 @@ func main() { os.Exit(1) } - switch cmd { - case cmdGetKey: - if *keyFile == "" { - le.Printf("Provide public key file with -p pubkey") - os.Exit(1) - } - - signer, pub, err := loadSigner(*devPath, *speed, *ussFile, *enterUss) - if err != nil { - le.Printf("Couldn't load signer: %v", err) - os.Exit(1) - } - - pubkey := pubKey{ - Alg: [2]byte{'E', 'd'}, - KeyNum: [8]byte{1, 7}, - Key: [32]byte{}, - } - - copy(pubkey.Key[:], pub) - - comment := "tkey public key" - if *force { - err = writeBase64(*keyFile, pubkey, comment, true) - } else { - err = writeRetry(*keyFile, pubkey, comment) - } - - if err != nil { - le.Printf("%v", err) - signer.Close() - os.Exit(1) - } - - signer.Close() - - case cmdSign: - if *messageFile == "" { - le.Printf("Provide message file with -m message") - os.Exit(1) - } + dev := devArgs{ + Path: *devPath, + Speed: *speed, + } - if *keyFile == "" { - le.Printf("Provide public key file with -p pubkey") - os.Exit(1) - } + uss := USSArgs{ + FileName: *ussFile, + Request: *enterUss, + } - if *sigFile == "" { - *sigFile = *messageFile + ".sig" - } + var msg string - pubkey, err := readKey(*keyFile) - if err != nil { - le.Printf("Couldn't read pubkey file: %v", err) - os.Exit(1) - } + if cmd == cmdSign || cmd == cmdVerify { + var err error - signer, pub, err := loadSigner(*devPath, *speed, *ussFile, *enterUss) + msg, err = getMessage(*messageFile, *alg) if err != nil { - le.Printf("Couldn't load signer: %v", err) + fmt.Printf("%v\n", err) os.Exit(1) } + } - if !bytes.Equal(pub, pubkey.Key[:]) { - le.Printf("Public key from file %v not equal to loaded app's", *keyFile) - os.Exit(1) - } + if *sigFile == "" { + *sigFile = *messageFile + ".sig" + } - sig, err := signFile(*signer, pub, *messageFile) - if err != nil { - le.Printf("signing failed: %v", err) - signer.Close() + switch cmd { + case cmdGetKey: + if err := GetKey(*keyFile, *force, dev, uss); err != nil { + fmt.Printf("%v\n", err) os.Exit(1) } - comment := fmt.Sprintf("verify with %v", *keyFile) - if *force { - err = writeBase64(*sigFile, sig, comment, true) - } else { - err = writeRetry(*sigFile, sig, comment) - } - - if err != nil { - le.Printf("Couldn't store signature: %v", err) - signer.Close() + case cmdSign: + if err := Sign(msg, *keyFile, *sigFile, *force, dev, uss); err != nil { + fmt.Printf("%v\n", err) os.Exit(1) } - signer.Close() - case cmdVerify: - if *messageFile == "" { - le.Printf("Provide message file with -m message") + if err := Verify(msg, *keyFile, *sigFile); err != nil { + fmt.Printf("%v\n", err) os.Exit(1) } - if *keyFile == "" { - le.Printf("Provide public key file path with -p pubkey") - os.Exit(1) - } - - if *sigFile == "" { - *sigFile = *messageFile + ".sig" - } + le.Printf("Signature verified") - err := verifySignature(*messageFile, *sigFile, *keyFile) - if err != nil { - le.Printf("Error verifying: %v", err) + case cmdDump: + if err := Dump(*keyFile, *sigFile); err != nil { + fmt.Printf("%v\n", err) os.Exit(1) } - le.Printf("Signature verified") default: pflag.Usage() diff --git a/cmd/tkey-sign/util.go b/cmd/tkey-sign/util.go index 2ac8cc4..261cf30 100644 --- a/cmd/tkey-sign/util.go +++ b/cmd/tkey-sign/util.go @@ -4,6 +4,7 @@ package main import ( + "bufio" "errors" "fmt" "io" @@ -12,6 +13,7 @@ import ( "runtime/debug" "strings" + "github.com/tillitis/tkey-sign-cli/signify" "github.com/tillitis/tkeyclient" "github.com/tillitis/tkeysign" ) @@ -74,3 +76,31 @@ func readBuildInfo() string { } return v } + +// writeRetry writes the data in the file given in filename as base64. +// If a file already exists it prompts interactively for permission to +// overwrite the file. +func writeRetry(filename string, data signify.Data, comment string) error { + err := data.ToFile(filename, comment, false) + if os.IsExist(errors.Unwrap(err)) { + le.Printf("File %v exists. Overwrite [y/n]?", filename) + reader := bufio.NewReader(os.Stdin) + overWriteP, _ := reader.ReadString('\n') + + // Trim space to normalize Windows line endings + overWriteP = strings.TrimSpace(overWriteP) + + if overWriteP == "y" { + err = data.ToFile(filename, comment, true) + } else { + le.Printf("Aborted\n") + os.Exit(1) + } + } + + if !os.IsExist(errors.Unwrap(err)) && err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/doc/tkey-sign.1 b/doc/tkey-sign.1 index 7e5b15f..d0177d6 100644 --- a/doc/tkey-sign.1 +++ b/doc/tkey-sign.1 @@ -1,45 +1,54 @@ -.\" Generated by scdoc 1.11.2 +.\" Generated by scdoc 1.11.4 .\" Complete documentation for this program is not available as a GNU info page .ie \n(.g .ds Aq \(aq .el .ds Aq ' .nh .ad l .\" Begin generated content: -.TH "tkey-sign" "1" "2023-11-16" -.P +.TH "tkey-sign" "1" "2026-02-04" +.PP .SH NAME -.P +.PP tkey-sign - Create or verify digital signatures with the Tillitis TKey -.P +.PP .SH SYNOPSIS -.P +.PP \fBtkey-sign\fR -h/--help -.P +.PP +\fBtkey-sign\fR -D/--dump -p/--public pubkey -m message [-x sigfile] +.PP \fBtkey-sign\fR -G/--getkey -p/--public pubkey [-d/--port device] [-f/--force] [-s/--speed speed] [--uss] [--uss-file ussfile] [--verbose] -.P -\fBtkey-sign\fR -S/--sign -m message -p/--public pubkey [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] -.P -\fBtkey-sign\fR -V/--verify -m message -p/--public pubkey [--verbose] [-x sigfile] -.P +.PP +\fBtkey-sign\fR -S/--sign -m message -p/--public pubkey [-a/--alg algorithm] [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] +.PP +\fBtkey-sign\fR -V/--verify -m message -p/--public [-a/--alg algorithm] pubkey [--verbose] [-x sigfile] +.PP \fBtkey-sign\fR --version -.P +.PP .SH DESCRIPTION -.P +.PP \fBtkey-sign\fR creates (-S) and verifies (-V) cryptographic signatures of files.\& The signature is created by the \fBsigner\fR device app running on the Tillitis TKey.\& The signer is automatically loaded into the TKey when signing or extracting the public key.\& -.P +.PP +\fB-D, --dump\fR +.PP +.RS 4 +Print helpful information about the contents of decoded signature and +public key files.\& +.PP +.RE \fB-G, --getkey\fR -.P +.PP .RS 4 Load device app (if not already loaded) and output its public key into a file.\& Pass the filename to store the public key in with \fB-p pubkey\fR.\& Combine with \fB--uss\fR or \fB--uss-file ussfile\fR to change the key pair.\& -.P +.PP .RE \fB-S, --sign\fR -.P +.PP .RS 4 Load device app (if not already loaded) and sign the specified message file (\fB-m message\fR).\& Store a signature in a file.\& Default signature @@ -47,222 +56,170 @@ file is message file + ".\&sig", specify another filename with \fB-x sigfile\fR.\& Also needs \fB-p pubkey\fR which is used for the comment in the sigfile and to verify that you really have the same public key as the loaded device app.\& -.P +.PP Combine with \fB--uss\fR or \fB--uss-file ussfile\fR to change the key pair.\& -.P +.PP .RE \fB-V, --verify\fR -.P +.PP .RS 4 Verify the signature of message.\& Does not need a connected TKey.\& Needs \fB-m message\fR and \fB-p pubkey\fR.\& Default signature file is message.\&sig.\& Specify with \fB-x sigfile\fR.\& -.P +.PP +.RE +\fB-a, --alg algorithm\fR +.PP +.RS 4 +Specify the hash algorithm to use on the message file before +signing or verifying.\& Options are "ed" (SHA-512) and "b2s" +(BLAKE2s).\& Default is "ed" +.PP .RE \fB-d, --port device\fR -.P +.PP .RS 4 Specify the device path for the TKey, typically something like \fB/dev/ttyACM0\fR.\& By default the device is auto-detected.\& -.P +.PP .RE \fB-f, --force\fR -.P +.PP .RS 4 Force writing signature and pubkey file, overwriting existing files.\& -.P +.PP .RE \fB-h, --help\fR -.P +.PP .RS 4 Output short help text.\& -.P +.PP .RE \fB-m message\fR -.P +.PP .RS 4 When signing, the file containing the message to sign.\& When verifying, the file containing the message to verify.\& -.P +.PP .RE \fB-p, --public pubkey\fR -.P +.PP .RS 4 Specify path to public key file.\& -.P +.PP .RE \fB-s, --speed speed\fR -.P +.PP .RS 4 Specify the speed in bits/second for the TKey device.\& Default is 62500 b/s.\& -.P +.PP .RE \fB--uss\fR -.P +.PP .RS 4 Interactively enter a User Supplied Secret to be used when loading a device app to the TKey.\& This is used to create different key pairs.\& A warning is printed if the app is already loaded and this USS is not used.\& -.P +.PP .RE \fB--uss-file secret\fR -.P +.PP .RS 4 Use a User Supplied Secret in a file to be used when loading a device app to the TKey.\& This is used to create different key pairs.\& A warning is printed if the app is already loaded and this USS is not used.\& Use \fB-\fR to input from stdin.\& -.P +.PP .RE \fB--verbose\fR -.P +.PP .RS 4 Be very verbose about what is happening, including hexdumps of traffic to and from the TKey.\& -.P +.PP .RE \fB--version\fR -.P +.PP .RS 4 Output version information and quit.\& -.P +.PP .RE \fB-x sigfile\fR -.P +.PP .RS 4 The signature file to create or use with verification.\& The default when not using \fB-x\fR is \fBmessage.\&sig\fR.\& -.P +.PP .RE .SH FORMATS -.P +.PP Signature files are expected to contain a one-line comment beginning with "untrusted comment: ", then encoded in BASE64: -.P -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ +.PP +.PD 0 .IP \(bu 4 -.\} -2 byte algorithm identifier, so far only "Ed" for Ed25519.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ +2 byte algorithm identifier, "Ed" for Ed25519 hashed with SHA-512, +and "Eb", Ed25519 hashed with BLAKE2s.\& .IP \(bu 4 -.\} 8 byte key number (unused by tkey-sign).\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} 64 byte Ed25519 signature.\& - -.RE -.P -Pubkey files are expected to contain, a one-line comment beginning +.PD +.PP +Pubkey files are expected to contain a one-line comment beginning with "untrusted comment: ", then encoded in BASE64: -.P -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ +.PP +.PD 0 .IP \(bu 4 -.\} 2 byte algorithm identifier, so far only "Ed" for Ed25519.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} 8 byte key number (unused by tkey-sign).\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} 32 byte Ed25519 public key.\& - +.PD +.PP +NOTE WELL: The original `signify` command does Ed25519 signing on the +complete file content, not a digest.\& To be compatible we still use the +"Ed" identifier in our signature files, but you need to do: +.PP +.nf +.RS 4 +sha512 file > digest +signify -V -m digest -x file\&.sig -p key\&.pub +.fi .RE -.P +.PP +to actually verify the right message.\& See the signify-verify script.\& +.PP .SH EXIT STATUS -.P +.PP The exit code is 0 on success and >0 if an error occurs.\& -.P +.PP It may fail because of one of the following reasons: -.P -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ +.PP +.PD 0 .IP \(bu 4 -.\} Connection with or uploading of the app to the TKey failed.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} Some necessary files do not exist.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} File parse error.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} Public key of device app doesn'\&t match public key file.\& -.RE -.RS 4 -.ie n \{\ -\h'-04'\(bu\h'+03'\c -.\} -.el \{\ .IP \(bu 4 -.\} The computed message file signature does not match the signature file.\& - -.RE -.P +.PD +.PP .SH CONFIGURATION -.P +.PP You must have read and write access to the USB serial port TKey provides.\& On platforms like macOS and Windows this happens automatically when you approve the TKey device.\& Under Linux one way to get access as your ordinary user is by installing a udev rule like this: -.P +.PP .nf .RS 4 # Mark Tillitis TKey as a security token\&. /usr/lib/udev/rules\&.d/70-uaccess\&.rules @@ -272,16 +229,16 @@ ATTRS{idVendor}=="1207", ATTRS{idProduct}=="8887", ENV{ID_SECURITY_TOKEN}="1" .fi .RE -.P +.PP Put this in \fB/etc/udev/rules.\&d/60-tkey.\&rules\fR and run \fBudevadm control --reload\fR which should make the TKey device (typically \fB/dev/ttyACM0\fR) availabe to anyone logged in on the console (see \fBloginctl\fR).\& -.P +.PP Another way to get access is by becoming a member of the group that owns serial ports on some systems with default udev rules for USB CDC ACM devices that come and go.\& On Ubuntu that group is \fBdialout\fR.\& You can do it like this: -.P +.PP .nf .RS 4 $ id -un @@ -291,20 +248,20 @@ crw-rw---- 1 root dialout 166, 0 Sep 16 08:20 /dev/ttyACM0 $ sudo usermod -a -G dialout exampleuser .fi .RE -.P +.PP For the change to take effect, you need to either log out and login again or run the command \fBnewgrp dialout\fR in the terminal that you are working in.\& -.P +.PP .SH EXAMPLES -.P +.PP All examples either load the device app automatically or works with an already loaded device app.\& -.P +.PP Store the public key in a file.\& When loading the device app modify the measured identity with a User Supplied Secret.\& The public key file `key.\&pub` already exists so `tkey-sign` asks if it should replace it.\& -.P +.PP .nf .RS 4 $ tkey-sign -G -p key\&.pub --uss @@ -315,10 +272,10 @@ y $ .fi .RE -.P +.PP Sign a file.\& The program warns that the \fBsigner\fR device app is already loaded, probably from the above command.\& -.P +.PP .nf .RS 4 \&./tkey-sign -S -m message -p key\&.pub @@ -326,10 +283,10 @@ WARNING: App already loaded\&. $ .fi .RE -.P +.PP Verify a signature over a message file with the signature in the default "message.\&sig" file: -.P +.PP .nf .RS 4 $ tkey-sign -V -m message -p key\&.pub @@ -337,7 +294,7 @@ Signature verified $ .fi .RE -.P +.PP .SH SEE ALSO -.P +.PP https://tillitis.\&se/ diff --git a/doc/tkey-sign.scd b/doc/tkey-sign.scd index 370e175..aedf461 100644 --- a/doc/tkey-sign.scd +++ b/doc/tkey-sign.scd @@ -8,11 +8,13 @@ tkey-sign - Create or verify digital signatures with the Tillitis TKey *tkey-sign* -h/--help +*tkey-sign* -D/--dump -p/--public pubkey -m message [-x sigfile] + *tkey-sign* -G/--getkey -p/--public pubkey [-d/--port device] [-f/--force] [-s/--speed speed] [--uss] [--uss-file ussfile] [--verbose] -*tkey-sign* -S/--sign -m message -p/--public pubkey [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] +*tkey-sign* -S/--sign -m message -p/--public pubkey [-a/--alg algorithm] [-d/--port device] [-f/--force] [-s speed] [--uss] [--uss-file ussfile] [--verbose] [-x sigfile] -*tkey-sign* -V/--verify -m message -p/--public pubkey [--verbose] [-x sigfile] +*tkey-sign* -V/--verify -m message -p/--public [-a/--alg algorithm] pubkey [--verbose] [-x sigfile] *tkey-sign* --version @@ -23,6 +25,11 @@ files. The signature is created by the *signer* device app running on the Tillitis TKey. The signer is automatically loaded into the TKey when signing or extracting the public key. +*-D, --dump* + + Print helpful information about the contents of decoded signature and + public key files. + *-G, --getkey* Load device app (if not already loaded) and output its public key into @@ -46,6 +53,12 @@ when signing or extracting the public key. Needs *-m message* and *-p pubkey*. Default signature file is message.sig. Specify with *-x sigfile*. +*-a, --alg algorithm* + + Specify the hash algorithm to use on the message file before + signing or verifying. Options are "ed" (SHA-512) and "b2s" + (BLAKE2s). Default is "ed" + *-d, --port device* Specify the device path for the TKey, typically something like @@ -106,17 +119,29 @@ when signing or extracting the public key. Signature files are expected to contain a one-line comment beginning with "untrusted comment: ", then encoded in BASE64: -- 2 byte algorithm identifier, so far only "Ed" for Ed25519. +- 2 byte algorithm identifier, "Ed" for Ed25519 hashed with SHA-512, + and "Eb", Ed25519 hashed with BLAKE2s. - 8 byte key number (unused by tkey-sign). - 64 byte Ed25519 signature. -Pubkey files are expected to contain, a one-line comment beginning +Pubkey files are expected to contain a one-line comment beginning with "untrusted comment: ", then encoded in BASE64: - 2 byte algorithm identifier, so far only "Ed" for Ed25519. - 8 byte key number (unused by tkey-sign). - 32 byte Ed25519 public key. +NOTE WELL: The original `signify` command does Ed25519 signing on the +complete file content, not a digest. To be compatible we still use the +"Ed" identifier in our signature files, but you need to do: + +``` +sha512 file > digest +signify -V -m digest -x file.sig -p key.pub +``` + +to actually verify the right message. See the signify-verify script. + # EXIT STATUS The exit code is 0 on success and >0 if an error occurs. diff --git a/signify/signify.go b/signify/signify.go new file mode 100644 index 0000000..367631a --- /dev/null +++ b/signify/signify.go @@ -0,0 +1,277 @@ +// SPDX-FileCopyrightText: 2026 Tillitis AB +// SPDX-License-Identifier: BSD-2-Clause + +// Package signify implements types and methods to interact with data +// compatible with the files the signify command use. +// +// NB! We only support Ed25519 signatures and public keys. +package signify + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "encoding/binary" + "fmt" + "os" + "strings" +) + +// The PubKey and Signature types adher to this interface. +// +// FromFile parses a public key or signature from the Signify file +// format. +// +// ToFile exports a public key or signature to the Signify file format. +// +// FromBuffer parses from a Signify buffer. +// +// ToBuffer export to a Signify buffer. +type Data interface { + FromFile(fileName string) error + ToFile(fileName string, comment string, overwrite bool) error + FromBuffer(buf []byte) error + ToBuffer(comment string) ([]byte, error) +} + +type AlgType int + +const ( + Ed AlgType = iota + B2sEd +) + +// A signify-compatible Ed25519 public key. Instantiate directly or +// using NewPubKey if you have a slice. +type PubKey [ed25519.PublicKeySize]byte + +type signifyPubKey struct { + Alg [2]byte + KeyNum [8]byte + Key [ed25519.PublicKeySize]byte +} + +// A signify-compatible Ed25519 signature. Instantiate directly or +// using NewSignature if you have a slice. +type Signature struct { + Alg AlgType + Sig [ed25519.SignatureSize]byte +} + +type signifySignature struct { + Alg [2]byte + KeyNum [8]byte + Sig [ed25519.SignatureSize]byte +} + +// NewPubKey instantiates a signify PubKey from a byte slice. +func NewPubKey(srcKey []byte) (PubKey, error) { + var key PubKey + + if len(srcKey) != ed25519.PublicKeySize { + return key, fmt.Errorf("key too large") + } + + copy(key[:], srcKey) + + return key, nil +} + +func (p *PubKey) FromFile(fileName string) error { + input, err := os.ReadFile(fileName) + if err != nil { + return fmt.Errorf("%w", err) + } + + return p.FromBuffer(input) +} + +func (p *PubKey) FromBuffer(b []byte) error { + var pubKey signifyPubKey + + buf, err := fromSlice(b) + if err != nil { + return fmt.Errorf("could not decode: %w", err) + } + + r := bytes.NewReader(buf) + if err := binary.Read(r, binary.BigEndian, &pubKey); err != nil { + return fmt.Errorf("%w", err) + } + + if pubKey.Alg != [2]byte{'E', 'd'} || pubKey.KeyNum != [8]byte{1, 7} { + return fmt.Errorf("incompatible key") + } + + copy(p[:], pubKey.Key[:]) + + return nil +} + +func (p *PubKey) ToBuffer(comment string) ([]byte, error) { + signifyKey := signifyPubKey{ + Alg: [2]uint8{'E', 'd'}, + KeyNum: [8]uint8{0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Key: *p, + } + + return toSlice(signifyKey, comment) +} + +func (p *PubKey) ToFile(fileName string, comment string, overwrite bool) error { + buf, err := p.ToBuffer(comment) + if err != nil { + return err + } + + return writeFile(fileName, buf, overwrite) +} + +// NewSignature instantiates a signify Signature from a byte slice. +func NewSignature(t AlgType, srcSig []byte) (Signature, error) { + var sig Signature + + if t != Ed && t != B2sEd { + return sig, fmt.Errorf("unknown algorithm") + } + + if len(srcSig) != ed25519.SignatureSize { + return sig, fmt.Errorf("signature too large") + } + + sig.Alg = t + copy(sig.Sig[:], srcSig) + + return sig, nil +} + +func (s *Signature) FromFile(fileName string) error { + input, err := os.ReadFile(fileName) + if err != nil { + return fmt.Errorf("%w", err) + } + + return s.FromBuffer(input) +} + +func (s *Signature) FromBuffer(b []byte) error { + var sig signifySignature + + buf, err := fromSlice(b) + if err != nil { + return fmt.Errorf("could not decode: %w", err) + } + + r := bytes.NewReader(buf) + if err := binary.Read(r, binary.BigEndian, &sig); err != nil { + return fmt.Errorf("binary read: %w", err) + } + + switch sig.Alg { + case [2]byte{'E', 'd'}: + s.Alg = Ed + + case [2]byte{'E', 'b'}: + s.Alg = B2sEd + + default: + return fmt.Errorf("unknown signature algorithm") + } + + if sig.KeyNum != [8]byte{1, 7} { + return fmt.Errorf("incompatible signature") + } + + copy(s.Sig[:], sig.Sig[:]) + + return nil +} + +func (s *Signature) ToBuffer(comment string) ([]byte, error) { + signifySig := signifySignature{ + KeyNum: [8]uint8{1, 7}, + Sig: s.Sig, + } + + switch s.Alg { + case Ed: + signifySig.Alg = [2]uint8{'E', 'd'} + + case B2sEd: + signifySig.Alg = [2]uint8{'E', 'b'} + + default: + return nil, fmt.Errorf("unknown sig algorithm") + + } + + return toSlice(signifySig, comment) +} + +func (s *Signature) ToFile(fileName string, comment string, overwrite bool) error { + buf, err := s.ToBuffer(comment) + if err != nil { + return err + } + + return writeFile(fileName, buf, overwrite) +} + +func fromSlice(input []byte) ([]byte, error) { + lines := strings.Split(string(input), "\n") + if len(lines) < 2 { + return nil, fmt.Errorf("too few lines") + } + + data, err := base64.StdEncoding.DecodeString(lines[1]) + if err != nil { + return nil, fmt.Errorf("could not decode: %w", err) + } + + return data, nil +} + +func toSlice(data any, comment string) ([]byte, error) { + var binBuf bytes.Buffer + + err := binary.Write(&binBuf, binary.BigEndian, data) + if err != nil { + return []byte{}, fmt.Errorf("%w", err) + } + + b64 := base64.StdEncoding.EncodeToString(binBuf.Bytes()) + b64 += "\n" + + var buf bytes.Buffer + if _, err := buf.WriteString(fmt.Sprintf("untrusted comment: %s\n%s", comment, b64)); err != nil { + return []byte{}, fmt.Errorf("%w", err) + } + + return buf.Bytes(), nil +} + +// writeFile writes buf into file filename. If overwrite is true it +// overwrites any existing file, otherwise it returns an error. +func writeFile(filename string, buf []byte, overwrite bool) error { + var f *os.File + + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + if err != nil { + if os.IsExist(err) && overwrite { + f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o666) + if err != nil { + return fmt.Errorf("%w", err) + } + } else { + return fmt.Errorf("%w", err) + } + } + + defer f.Close() + + if _, err := f.Write(buf); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/signify/signify_test.go b/signify/signify_test.go new file mode 100644 index 0000000..c8d9413 --- /dev/null +++ b/signify/signify_test.go @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2026 Tillitis AB +// SPDX-License-Identifier: BSD-2-Clause + +package signify + +import ( + "crypto/ed25519" + "fmt" + "testing" +) + +// Full signature file +const WorkingSigString = `untrusted comment: app_a.bin +RWQBBwAAAAAAALE/DLVQ8RU5OA11qzhxDZ5nDOgbVGhxNwWlylI2YdPHIBVH/Q+HnhWfO5CxgPUb6EOCxG8ZzPVy+lQt4atUHAE= +` + +// Fully parsed +var WorkingSig = Signature{ + Alg: Ed, + Sig: [ed25519.SignatureSize]byte{0xb1, 0x3f, 0xc, 0xb5, 0x50, 0xf1, 0x15, 0x39, 0x38, 0xd, 0x75, 0xab, 0x38, 0x71, 0xd, 0x9e, 0x67, 0xc, 0xe8, 0x1b, 0x54, 0x68, 0x71, 0x37, 0x5, 0xa5, 0xca, 0x52, 0x36, 0x61, 0xd3, 0xc7, 0x20, 0x15, 0x47, 0xfd, 0xf, 0x87, 0x9e, 0x15, 0x9f, 0x3b, 0x90, 0xb1, 0x80, 0xf5, 0x1b, 0xe8, 0x43, 0x82, 0xc4, 0x6f, 0x19, 0xcc, 0xf5, 0x72, 0xfa, 0x54, 0x2d, 0xe1, 0xab, 0x54, 0x1c, 0x1}, +} + +// Full public key file +const WorkingKeyString = `untrusted comment: +RWQBBwAAAAAAAJtidzMj70GhGDSCQZTlUWTTJeuc3MEN3afRCt5PvY9t +` + +var WorkingKey = PubKey{0x9b, 0x62, 0x77, 0x33, 0x23, 0xef, 0x41, 0xa1, 0x18, 0x34, 0x82, 0x41, 0x94, 0xe5, 0x51, 0x64, 0xd3, 0x25, 0xeb, 0x9c, 0xdc, 0xc1, 0xd, 0xdd, 0xa7, 0xd1, 0xa, 0xde, 0x4f, 0xbd, 0x8f, 0x6d} + +// TestSig generates a Signify format buffer, then parses the same +// buffer back into a signature. +func TestSig(t *testing.T) { + var s Signature + + t.Parallel() + buf, err := WorkingSig.ToBuffer("") + if err != nil { + t.Fatalf("Couldn't generate signify format: %v", err) + } + + if err := s.FromBuffer(buf); err != nil { + t.Fatalf("Couldn't read or parse signify format: %v", err) + } + + if s != WorkingSig { + t.Fatal("generating and parsing back signature was different") + } + +} + +func TestParseSig(t *testing.T) { + var sig Signature + + t.Parallel() + if err := sig.FromBuffer([]byte(WorkingSigString)); err != nil { + t.Fatalf("%v\n", err) + } + + if sig != WorkingSig { + t.Fatal("Parsed signature not correct\n") + } +} + +// TestKey generates a signify format buffer for a public key, then +// parses the same buffer back into a key. +func TestKey(t *testing.T) { + var p PubKey + + t.Parallel() + buf, err := WorkingKey.ToBuffer("") + if err != nil { + t.Fatalf("Couldn't generate signify format: %v", err) + } + + if err := p.FromBuffer(buf); err != nil { + t.Fatalf("Couldn't read or parse signify format: %v", err) + } + + if p != WorkingKey { + t.Fatal("generating and parsing back key was different") + } + +} + +func TestParsePubKey(t *testing.T) { + var key PubKey + + t.Parallel() + if err := key.FromBuffer([]byte(WorkingKeyString)); err != nil { + t.Fatalf("%v\n", err) + } + + if key != WorkingKey { + t.Fatal("Parsed public key not correct\n") + } + +} + +func TestGenSigB64(t *testing.T) { + t.Parallel() + + b64, err := WorkingSig.ToBuffer("app_a.bin") + if err != nil { + t.Fatalf("error: %v", err) + } + + if string(b64) != WorkingSigString { + fmt.Printf("Expected (%v bytes): %v\n", len(WorkingSigString), WorkingSigString) + fmt.Printf("Got (%v bytes): %v\n", len(string(b64)), string(b64)) + + t.Fatal("Known good sig generates no good string") + } +} + +func TestGenPubKeyB64(t *testing.T) { + t.Parallel() + + b64, err := WorkingKey.ToBuffer("") + if err != nil { + t.Fatalf("error: %v", err) + } + + if string(b64) != WorkingKeyString { + fmt.Printf("Expected (%v bytes): %v\n", len(WorkingKeyString), WorkingKeyString) + fmt.Printf("Got (%v bytes): %v\n", len(string(b64)), string(b64)) + + t.Fatal("Known good key generates no good string") + } +} + +func TestSigFile(t *testing.T) { + t.Parallel() + + var s Signature + + if err := WorkingSig.ToFile("test.sig", "a comment", true); err != nil { + t.Fatalf("Couldn't generate or write file test.sig: %v", err) + } + + if err := s.FromFile("test.sig"); err != nil { + t.Fatalf("Couldn't read or parse test.sig: %v", err) + } + + if s != WorkingSig { + t.Fatal("Signature read from file not same as written") + } +} + +func TestPubFile(t *testing.T) { + t.Parallel() + + var p PubKey + + if err := WorkingKey.ToFile("test.pub", "a comment", true); err != nil { + t.Fatalf("Couldn't generate or write file test.pub: %v", err) + } + + if err := p.FromFile("test.pub"); err != nil { + t.Fatalf("Couldn't read or parse test.pub: %v", err) + } + + if p != WorkingKey { + t.Fatal("Key read from file not same as written") + } +}