Files
external-signer-tpm/main.go
2025-05-29 23:18:01 +08:00

268 lines
6.4 KiB
Go

package main
import (
"crypto"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"github.com/google/go-tpm/tpm"
"github.com/urfave/cli/v2"
)
type TpmKey struct {
Tpm string `json:"tpm"`
Keyblob string `json:"keyblob"`
}
type ErrorResponse struct {
Success bool `json:"success"`
Error string `json:"error"`
}
type ExternalSpecResponse struct {
Success bool `json:"success"`
Agent string `json:"agent"`
Specification string `json:"specification"`
Commands []string `json:"commands"`
}
type ExternalPublicKeyResponse struct {
Success bool `json:"success"`
PublicKeyBase64 string `json:"public_key_base64"`
}
type ExternalSignatureResponse struct {
Success bool `json:"success"`
SignatureBase64 string `json:"signature_base64"`
}
func printResponse(response any) error {
errorMessageJsonBytes, jsonErr := json.Marshal(response)
if jsonErr != nil {
return jsonErr
} else {
fmt.Println(string(errorMessageJsonBytes))
return nil
}
}
func printResponseSlient(response any) {
if err := printResponse(response); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
}
}
func main() {
err := innerMain()
if err != nil {
errorMessage := &ErrorResponse{
Success: false,
Error: fmt.Sprintf("%v", err),
}
if printResponse(errorMessage) != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
}
os.Exit(1)
}
}
func innerMain() error {
app := cli.App{
Name: "external-signer-tpm",
Usage: "External signer TPM",
Commands: []*cli.Command{
buildExternalSpecCommand(),
buildExternalPublicKeyCommand(),
buildExternalSignCommand(),
buildGenerateKeyCommand(),
},
Action: func(ctx *cli.Context) error {
fmt.Println("External signer TPM, specification: https://openwebstandard.org/rfc1")
return nil
},
}
if err := app.Run(os.Args); err != nil {
return err
}
return nil
}
func buildExternalSpecCommand() *cli.Command {
return &cli.Command{
Name: "external_spec",
Usage: "External specification",
Flags: []cli.Flag{},
Action: func(ctx *cli.Context) error {
externalSpecResponse := &ExternalSpecResponse{
Success: true,
Agent: "external-signer-tpm/0.1.0",
Specification: "External/1.0.0-alpha",
Commands: []string{
"external_public_key",
"external_sign",
},
}
printResponseSlient(externalSpecResponse)
return nil
},
}
}
func buildExternalPublicKeyCommand() *cli.Command {
return &cli.Command{
Name: "external_public_key",
Usage: "External public key",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "parameter",
Usage: "Parameter",
Required: true,
},
},
Action: func(ctx *cli.Context) error {
parameter := ctx.String("parameter")
tpmKey, err := parseParameter(parameter)
if err != nil {
return fmt.Errorf("cannot parse parameter: %s", err)
}
keyblob, err := base64.StdEncoding.DecodeString(tpmKey.Keyblob)
if err != nil {
return fmt.Errorf("cannot decode keyblob: %s", err)
}
pubKey, err := tpm.UnmarshalRSAPublicKey(keyblob)
if err != nil {
return fmt.Errorf("cannot get public key: %s", err)
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return fmt.Errorf("cannot marshal public key: %s", err)
}
externalPublicKeyResponse := &ExternalPublicKeyResponse{
Success: true,
PublicKeyBase64: base64.StdEncoding.EncodeToString(pubKeyBytes),
}
printResponseSlient(externalPublicKeyResponse)
return nil
},
}
}
func buildExternalSignCommand() *cli.Command {
return &cli.Command{
Name: "external_sign",
Usage: "External sign",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "parameter",
Usage: "Parameter",
Required: true,
},
&cli.StringFlag{
Name: "alg",
Usage: "Algorithm",
Required: true,
},
&cli.StringFlag{
Name: "message-base64",
Usage: "Message base64 encoded",
Required: true,
},
},
Action: func(ctx *cli.Context) error {
parameter := ctx.String("parameter")
alg := ctx.String("alg")
messageBase64 := ctx.String("message-base64")
message, err := base64.StdEncoding.DecodeString(messageBase64)
if err != nil {
return fmt.Errorf("unmarshal --message-base64 failed: %v", err)
}
println("ALG: ", alg)
tpmKey, err := parseParameter(parameter)
if err != nil {
return fmt.Errorf("cannot parse parameter: %s", err)
}
rwc, err := tpm.OpenTPM(tpmKey.Tpm)
if err != nil {
return fmt.Errorf("cannot open TPM %s, error: %s", tpmKey.Tpm, err)
}
defer rwc.Close()
// Compute the auth values as needed.
var srkAuth [20]byte
srkInput := os.Getenv(srkAuthEnvVar)
if srkInput != "" {
sa := sha1.Sum([]byte(srkInput))
copy(srkAuth[:], sa[:])
}
var usageAuth [20]byte
usageInput := os.Getenv(usageAuthEnvVar)
if usageInput != "" {
ua := sha1.Sum([]byte(usageInput))
copy(usageAuth[:], ua[:])
}
var migrationAuth [20]byte
migrationInput := os.Getenv(migrationAuthEnvVar)
if migrationInput != "" {
ma := sha1.Sum([]byte(migrationInput))
copy(migrationAuth[:], ma[:])
}
keyblob, err := base64.StdEncoding.DecodeString(tpmKey.Keyblob)
if err != nil {
return fmt.Errorf("cannot decode keyblob: %s", err)
}
keyHandle, err := tpm.LoadKey2(rwc, keyblob, srkAuth[:])
if err != nil {
return fmt.Errorf("cannot load keyblob: %s", err)
}
defer tpm.CloseKey(rwc, keyHandle)
hash := crypto.SHA256.New()
if _, err = hash.Write(message); err != nil {
return fmt.Errorf("error building hash of data: %s", err)
}
hashed := hash.Sum(nil)
signature, err := tpm.Sign(rwc, usageAuth[:], keyHandle, crypto.SHA256, hashed[:])
if err != nil {
return fmt.Errorf("cannot perform sign operation: %s", err)
}
externalSignatureResponse := ExternalSignatureResponse{
Success: true,
SignatureBase64: base64.StdEncoding.EncodeToString(signature),
}
printResponseSlient(externalSignatureResponse)
return nil
},
}
}
func parseParameter(parameter string) (*TpmKey, error) {
parameterBytes, err := base64.StdEncoding.DecodeString(parameter)
if err != nil {
return nil, fmt.Errorf("parse --parameter base64 failed: %v", err)
}
var tpmKey TpmKey
err = json.Unmarshal(parameterBytes, &tpmKey)
if err != nil {
return nil, fmt.Errorf("parse --parameter base64 failed: %v", err)
}
return &tpmKey, nil
}