Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

[![GoDoc](https://godoc.org/github.com/folbricht/tpmk?status.svg)](https://godoc.org/github.com/folbricht/tpmk)

This toolkit strives to simplify common tasks around key and certificates involving TPM2. It also provides the tools necessary to make use of keys in the module for TLS connections in Go. It does not attempt to provide a feature-rich interface to support all possible use-cases and features. tpmk consists of a Go library and a tool with a simple interface. It currently provides:
This toolkit strives to simplify common tasks around key and certificates involving TPM2. It also provides the tools necessary to make use of keys in the module for TLS connections and OpenPGP in Go. It does not attempt to provide a feature-rich interface to support all possible use-cases and features. tpmk consists of a Go library and a tool with a simple interface. It currently provides:

- Generating RSA/SHA256 primary keys in the TPM and exporting the public key
- Generating x509 certificates and signing with a (file) CA
- Writing of arbirary data to NV storage - intended to be used to store certificates
- Writing of arbitrary data to NV storage - intended to be used to store certificates
- Creating SSH certificates for keys in the TPM
- Generating OpenPGP public keys based on RSA keys in the TPM
- OpenPGP Signing and Decryption

A range of features and options are **not** available at this point, but may be implemented in the future. Suggestions and contributions are welcome.

Expand Down Expand Up @@ -52,6 +54,12 @@ go get -u github.com/folbricht/tpmk/cmd/tpmk
- `pub` Convert a PKCS1 key to OpenSSH format
- `client` Start SSH client and execute remote command

- `openpgp` Commands to use keys in OpenPGP format

- `generate` Create an OpenPGP identity based on a key in the TPM
- `sign` Sign data with a TPM key
- `decrypt` Decrypt data using the private TPM key

## Use-cases / Examples

### RSA key generation and certificate storage in NV
Expand Down Expand Up @@ -242,6 +250,22 @@ if err != nil {
fmt.Println(string(b))
```

### Create an OpenPGP identity and sign data with it

This example shows how to produce an OpenPGP public key (identity) and use it to sign data.

In order to sign with a TPM key, an OpenPGP identity needs to be created with name and email address. This identity contains the public key and should be stored (either separately or in a TPM NV index). For this command, the key (handle 0x81000000 in this example) has to be present in the TPM already and must have the `sign` attribute to allow signing.

```sh
tpmk openpgp generate -n Testing -e [email protected] 0x81000000 identity.pgp
```

The same key and identity should be used when signing data. In this case a detached and armored signature is created and written to STDOUT.

```sh
tpmk openpgp sign -a 0x81000000 identity.pgp data.txt -
```

## Links

- TPM2 specification - [https://trustedcomputinggroup.org/resource/tpm-library-specification/](https://trustedcomputinggroup.org/resource/tpm-library-specification/)
Expand Down
1 change: 1 addition & 0 deletions cmd/tpmk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func main() {
newKeyCommand(),
newx509Command(),
newSSHCommand(),
newOpenPGPCommand(),
)
rootCmd.SetOutput(os.Stderr)

Expand Down
19 changes: 19 additions & 0 deletions cmd/tpmk/openpgp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"github.com/spf13/cobra"
)

func newOpenPGPCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "openpgp",
Short: "Manage OpenPGP identities and sign and decrypt data",
SilenceUsage: true,
}
cmd.AddCommand(
newOpenPGPGenCommand(),
newOpenPGPSignCommand(),
newOpenPGPDecryptCommand(),
)
return cmd
}
149 changes: 149 additions & 0 deletions cmd/tpmk/openpgpdecrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package main

import (
"io"
"os"

"github.com/folbricht/tpmk"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"

"github.com/spf13/cobra"
)

type openpgpDecryptOptions struct {
armor bool
pubArmor bool
device string
password string
}

func newOpenPGPDecryptCommand() *cobra.Command {
var opt openpgpDecryptOptions

cmd := &cobra.Command{
Use: "decrypt <handle> <public> <input> <output>",
Short: "Decrypt data with a TPM key",
Long: `Decrypt data using an existing private key in the TPM.
The key must already be present and be an RSA key. Input can either
be a file or '-' to read the data from STDIN. Use '-' to write the
output to STDOUT.

This command does not validate any signatures, but it will exit with
a non-zero exit code if an invalid MAC is encountered. Note that the
decrypted data is written to the output even though validation may
only happen after it is fully decrypted. The exit code has to be used
to determine if decryption was successful.

While the public key can also be read from STDIN, it is not possible
to read the input data from there at the same time.`,
Example: ` tpmk openpgp decrypt 0x81000000 pub.pgp encrypted.pgp decrypted.txt
tpmk openpgp decrypt -a 0x81000000 pup.pgp - -
tpmk openpgp decrypt -a -m 0x81000000 - secret.txt.pgp -`,
Args: cobra.ExactArgs(4),
RunE: func(cmd *cobra.Command, args []string) error {
return runOpenPGPDecrypt(opt, args)
},
SilenceUsage: true,
}
flags := cmd.Flags()
flags.BoolVarP(&opt.armor, "armor", "a", false, "Input data is armored")
flags.BoolVarP(&opt.pubArmor, "public-armor", "m", false, "Public key is armored")
flags.StringVarP(&opt.device, "device", "d", "/dev/tpmrm0", "TPM device, 'sim' for simulator")
flags.StringVarP(&opt.password, "password", "p", "", "Password for the TPM key")
return cmd
}

func runOpenPGPDecrypt(opt openpgpDecryptOptions, args []string) error {
handle, err := parseHandle(args[0])
if err != nil {
return err
}
pubkey := args[1]
input := args[2]
output := args[3]

if pubkey == "-" && input == "-" {
return errors.New("only the public key or the input can be read from stdin, not both")
}

// Open device or simulator
dev, err := tpmk.OpenDevice(opt.device)
if err != nil {
return err
}
defer dev.Close()

// Open the TPM key
priv, err := tpmk.NewRSAPrivateKey(dev, handle, opt.password)
if err != nil {
return err
}

// Read the public key
var pubData io.Reader = os.Stdin
if pubkey != "-" {
f, err := os.Open(pubkey)
if err != nil {
return err
}
defer f.Close()
pubData = f
}
if opt.pubArmor {
block, err := armor.Decode(pubData)
if err != nil {
return err
}
if block.Type != openpgp.PublicKeyType {
return errors.New("not a public key")
}
pubData = block.Body
}
entity, err := tpmk.ReadOpenPGPEntity(packet.NewReader(pubData), priv)
if err != nil {
return err
}

// Data reader
var r io.Reader = os.Stdin
if input != "-" {
f, err := os.Open(input)
if err != nil {
return err
}
defer f.Close()
r = f
}
if opt.armor {
block, err := armor.Decode(r)
if err != nil {
return err
}
r = block.Body
}

// Output writer
var w io.Writer = os.Stdout
if output != "-" {
f, err := os.Create(output)
if err != nil {
return err
}
defer f.Close()
w = f
}

// Decrypt the data, md.UnverifiedBody needs to be read whole before any signature or
// MAC is checked.
md, err := openpgp.ReadMessage(r, openpgp.EntityList([]*openpgp.Entity{entity}), nil, nil)
if err != nil {
return err
}
if _, err = io.Copy(w, md.UnverifiedBody); err != nil {
return err
}
return md.SignatureError
}
101 changes: 101 additions & 0 deletions cmd/tpmk/openpgpgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"io"
"os"

"github.com/folbricht/tpmk"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"

"github.com/spf13/cobra"
)

type openpgpGenOptions struct {
armor bool
device string
password string
name string
comment string
email string
}

func newOpenPGPGenCommand() *cobra.Command {
var opt openpgpGenOptions

cmd := &cobra.Command{
Use: "generate <handle> <pubkeyfile>",
Short: "Generate an public key",
Long: `Generate an OpenPGP public key using an existing private key in the TPM.
The key must already be present and be an RSA key. The generated public
key will contain one identity which must be provided with -n and -e.

Use '-' to write the public key to STDOUT.`,
Example: ` tpmk openpgp generate -n Testing -e [email protected] 0x81000000 pub.pgp
tpmk openpgp generate -a -n Testing -e [email protected] 0x81000000 -`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runOpenPGPGen(opt, args)
},
SilenceUsage: true,
}
flags := cmd.Flags()
flags.BoolVarP(&opt.armor, "armor", "a", false, "Create ASCII armored output")
flags.StringVarP(&opt.name, "name", "n", "", "Identity name")
flags.StringVarP(&opt.comment, "comment", "c", "", "Identity comment")
flags.StringVarP(&opt.email, "email", "e", "", "Identity email")
flags.StringVarP(&opt.device, "device", "d", "/dev/tpmrm0", "TPM device, 'sim' for simulator")
flags.StringVarP(&opt.password, "password", "p", "", "Password for the TPM key")
_ = cmd.MarkFlagRequired("name")
_ = cmd.MarkFlagRequired("email")
return cmd
}

func runOpenPGPGen(opt openpgpGenOptions, args []string) error {
handle, err := parseHandle(args[0])
if err != nil {
return err
}
output := args[1]

// Open device or simulator
dev, err := tpmk.OpenDevice(opt.device)
if err != nil {
return err
}
defer dev.Close()

// Open the TPM key
priv, err := tpmk.NewRSAPrivateKey(dev, handle, opt.password)
if err != nil {
return err
}

// Build an identity with the TPM key
entity, err := tpmk.NewOpenPGPEntity(opt.name, opt.comment, opt.email, nil, priv)
if err != nil {
return err
}

var w io.Writer = os.Stdout
if output != "-" {
f, err := os.Create(output)
if err != nil {
return err
}
defer f.Close()
w = f
}

if opt.armor {
encoder, err := armor.Encode(w, openpgp.PublicKeyType, nil)
if err != nil {
return err
}
defer encoder.Close()
w = encoder
}

// Serialize the entity (public part)
return entity.Serialize(w)
}
Loading