Skip to content

Commit

Permalink
Make tkey-sign more compatible with OpenBSD signify
Browse files Browse the repository at this point in the history
- Make flags more or less compatible with signify, except no -s since
  our private key is in the TKey. We make -p obligatory when signing to
  be sure that the signer's public key is what the caller expected.

- Output public key and signature in signify-compatible files instead
  of just printing. We can now verify with signify. See provided
  signify-verify script.

- Simplify internal command handling and handling of flags.

- Break out utility functions and file I/O to their own source files.
  • Loading branch information
mchack-work committed Nov 20, 2023
1 parent e0ddfbe commit 1920241
Show file tree
Hide file tree
Showing 8 changed files with 921 additions and 410 deletions.
95 changes: 64 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,88 @@

# tkey-sign

`tkey-sign` is a utility that can do and verify an Ed25519
cryptographic signature over a digest of a file using the
[Tillitis](https://tillitis.se/) TKey. It uses the [signer device
app](https://github.com/tillitis/tkey-device-signer) for the actual
signatures.

It can also verify a signature by passing files with the message,
signature, and the public key as arguments.
`tkey-sign` creates and verifies cryptographic signatures of files.
The signature is created by the [signer device
app](https://github.com/tillitis/tkey-device-signer) running on the
[Tillitis](https://tillitis.se/) TKey. The signer is automatically
loaded into the TKey by `tkey-sign` when signing or extracting the
public key. The measured private key never leaves the TKey.

See [Release notes](RELEASE.md).

## Usage

Get a public key, possibly modifying the key pair by using a User
Supplied Secret, and storing the public key in file `-p pubkey`.

```
$ tkey-sign <command> [flags...] FILE...
tkey-sign -G/--getkey [-d/--port device] [-s/--speed speed]
[--uss] [--uss-file secret] -p/--public pubkey
```

Commands are:
Sign a file, specified with `-m message` , possibly modifying the
measured key pair by using a User Supplied Secret, and storing the
signature in `-x sigfile` or, by default, in `message.sig`. You need
to supply the public key file as well which `tkey-sign` will
automatically verify that it's the expected public key.

```
sign Create a signature
verify Verify a signature
tkey-sign -S/--sign [-d/--port device] [-s speed] -m message
[--uss] [--uss-file] -p/--public pubkey [-x sig-file]
```

Usage for the sign-command are:
Verify a signature of file `-m message` with public key in `-p pubkey`.
Signature is by default in `message.sig` but can be specified
with `-x sigfile`. Doesn't need a connected TKey.

```
$ tkey-sign sign [flags...] FILE
tkey-sign -V/--verify -m message -p/--public pubkey [-x sigfile]
```
with options:

Alternatively you can use OpenBSD's *signify(1)* to verify the
signature but you need to compute the SHA-512 of the file first and
feed that to the verification. We provide a handy script that does
this:

```
signify-verify message pubkey
```
-p, --show-pubkey Don't sign anything, only output the public key.
--port PATH Set serial port device PATH. If this is not passed,
auto-detection will be attempted.
--speed BPS Set serial port speed in BPS (bits per second). (default
62500)
--uss Enable typing of a phrase to be hashed as the User
Supplied Secret. The USS is loaded onto the TKey along
with the app itself. A different USS results in
different public/private keys, meaning a different identity.
--uss-file FILE Read FILE and hash its contents as the USS. Use '-'
(dash) to read from stdin. The full contents are hashed
unmodified (e.g. newlines are not stripped).
--verbose Enable verbose output.
-h, --help Output this help.

Exit code is 0 on success and non-zero on failure.

See the manual page for details.

## Examples

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
```

Sign a file using the signer's basic secret or the identity of an
already loaded signer while also checking that you have the right
public key in a file:

```
$ tkey-sign -S -m message.txt -p key.pub
```

Verify a signature over a message file with the signature in the
default "message.txt.sig" file:

```
$ tkey-sign -V -p key.pub -m message.txt
```

or

Usage for the verify-command are:
```
$ tkey-sign verify FILE SIG-FILE PUBKEY-FILE
$ signify-verify message.txt key.pub
Signature Verified
$
```

## Building
Expand Down
12 changes: 10 additions & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# Release notes

## unreleased
## v0.0.8

Added functionality to sign arbitrary large files
- Stores signature and public key in OpenBSD signify(1) format.
- Flags work more like signify. Not 100% compatible since we don't
have '-s' (private key is in TKey, remember?) and we require '-p'
when signing to verify that the exported public key is the same as
the TKey's.
- Adds script signify-verify to verify tkey-sign signatures with
signify.
- Adds functionality to sign arbitrary large files by doing a SHA512
over the file and signing the digest.

## v0.0.7

Expand Down
136 changes: 136 additions & 0 deletions cmd/tkey-sign/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (C) 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only

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')
if overWriteP == "y\n" {
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
}

0 comments on commit 1920241

Please sign in to comment.