feat: init commit

This commit is contained in:
2025-05-29 23:18:01 +08:00
parent 50abef98c3
commit acd5f732f0
7 changed files with 440 additions and 0 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
external-signer-tpm
# ---> Go # ---> Go
# If you prefer the allow list template instead of the deny list, see community template: # If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore

View File

@@ -1,2 +1,4 @@
# external-signer-tpm # external-signer-tpm
> Reference: https://github.com/google/go-tpm/tree/main/examples/tpm-sign

7
common.go Normal file
View File

@@ -0,0 +1,7 @@
package main
const (
srkAuthEnvVar = "TPM_SRK_AUTH"
usageAuthEnvVar = "TPM_USAGE_AUTH"
migrationAuthEnvVar = "TPM_MIGRATION_AUTH"
)

136
generate.go Normal file
View File

@@ -0,0 +1,136 @@
package main
import (
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"github.com/google/go-tpm/tpm"
"github.com/urfave/cli/v2"
)
func buildGenerateKeyCommand() *cli.Command {
return &cli.Command{
Name: "generate_key",
Usage: "Generate key",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "parameter",
Usage: "The path to the TPM device to use",
DefaultText: "/dev/tpm0",
},
&cli.StringFlag{
Name: "keyblob",
Usage: "Output path of the generated keyblob",
DefaultText: "keyblob",
},
&cli.StringFlag{
Name: "public-key",
Usage: "Output path of the generated keyblob's public key",
DefaultText: "publickey",
},
&cli.StringFlag{
Name: "pcrs",
Usage: "A comma-separated list of PCR numbers against which the generated key will be bound. If blank, it will not be bound to any PCR values.",
},
},
Action: func(ctx *cli.Context) error {
tpmname := ctx.String("tpmname")
keyblob := ctx.String("keyblob")
publicKey := ctx.String("public-key")
pcrs := ctx.String("pcrs")
generateAction(tpmname, keyblob, publicKey, pcrs)
return nil
},
}
}
func generateAction(tpmname, keyblobPath, pubKeyPath, pcrsStr string) {
var pcrs []int
if pcrsStr != "" {
for _, pcr := range strings.Split(pcrsStr, ",") {
pcrNum, err := strconv.Atoi(pcr)
if err != nil {
fmt.Fprintf(os.Stderr, "Bad value in pcrs argument: %s\n", pcr)
return
}
pcrs = append(pcrs, pcrNum)
}
}
rwc, err := tpm.OpenTPM(tpmname)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", tpmname, err)
return
}
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 := tpm.CreateWrapKey(rwc, srkAuth[:], usageAuth, migrationAuth, pcrs)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't make a new signing key: %s\n", err)
return
}
fmt.Printf("Writing keyblob to %s\n", keyblobPath)
if err = os.WriteFile(keyblobPath, keyblob, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing keyblob file: %s\n", err)
return
}
pubKey, err := tpm.UnmarshalRSAPublicKey(keyblob)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not get public key: %s\n", err)
return
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not marshal public key: %s\n", err)
return
}
fmt.Printf("Writing public key to %s\n", pubKeyPath)
if err = os.WriteFile(pubKeyPath, pubKeyBytes, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing public key file: %s\n", err)
return
}
tpmKey := &TpmKey{
Tpm: tpmname,
Keyblob: base64.StdEncoding.EncodeToString(keyblob),
}
tpmKeyJson, err := json.Marshal(&tpmKey)
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshal tmp key: %s\n", err)
return
}
println(base64.StdEncoding.EncodeToString(tpmKeyJson))
}

15
go.mod Normal file
View File

@@ -0,0 +1,15 @@
module git.hatter.ink/external-signer-tpm
go 1.24.1
require (
github.com/google/go-tpm v0.9.5
github.com/urfave/cli/v2 v2.27.6
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/sys v0.8.0 // indirect
)

12
go.sum Normal file
View File

@@ -0,0 +1,12 @@
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

267
main.go Normal file
View File

@@ -0,0 +1,267 @@
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
}