diff --git a/go-tpm-non-restricted-key/go.mod b/go-tpm-non-restricted-key/go.mod new file mode 100644 index 0000000..520a0f2 --- /dev/null +++ b/go-tpm-non-restricted-key/go.mod @@ -0,0 +1,8 @@ +module git.hatter.ink/go-tpm-non-restricted-key + +go 1.24.3 + +require ( + github.com/google/go-tpm v0.9.5 // indirect + golang.org/x/sys v0.8.0 // indirect +) diff --git a/go-tpm-non-restricted-key/go.sum b/go-tpm-non-restricted-key/go.sum new file mode 100644 index 0000000..8ba003d --- /dev/null +++ b/go-tpm-non-restricted-key/go.sum @@ -0,0 +1,4 @@ +github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= +github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/go-tpm-non-restricted-key/main.go b/go-tpm-non-restricted-key/main.go new file mode 100644 index 0000000..8b9b84c --- /dev/null +++ b/go-tpm-non-restricted-key/main.go @@ -0,0 +1,211 @@ +package main + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "io" + "log" + "math/big" + "os" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/tpmutil" +) + +func generateSrk(f *os.File) { + tmpl := tpm2.Public{ + Type: tpm2.AlgRSA, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagFixedTPM | // Key can't leave the TPM. + tpm2.FlagFixedParent | // Key can't change parent. + tpm2.FlagSensitiveDataOrigin | // Key created by the TPM (not imported). + tpm2.FlagUserWithAuth | // Uses (empty) password. + tpm2.FlagNoDA | // This flag doesn't do anything, but it's in the spec. + tpm2.FlagRestricted | // Key used for TPM challenges, not general decryption. + tpm2.FlagDecrypt, // Key can be used to decrypt data. + RSAParameters: &tpm2.RSAParams{ + Symmetric: &tpm2.SymScheme{Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB}, + KeyBits: 2048, + ModulusRaw: make([]byte, 256), + }, + } + + srk, _, err := tpm2.CreatePrimary(f, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", tmpl) + if err != nil { + log.Fatalf("creating srk: %v", err) + } + out, err := tpm2.ContextSave(f, srk) + if err != nil { + log.Fatalf("saving context: %v", err) + } + if err := os.WriteFile("srk.ctx", out, 0644); err != nil { + log.Fatalf("writing context: %v", err) + } +} + +func generateAppKey(f *os.File) { + srkCtx, err := os.ReadFile("srk.ctx") + if err != nil { + log.Fatalf("read srk: %v", err) + } + srk, err := tpm2.ContextLoad(f, srkCtx) + if err != nil { + log.Fatalf("load srk: %v", err) + } + + tmpl := tpm2.Public{ + Type: tpm2.AlgECC, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagFixedTPM | // Key can't leave the TPM. + tpm2.FlagFixedParent | // Key can't change parent. + tpm2.FlagSensitiveDataOrigin | // Key created by the TPM (not imported). + tpm2.FlagUserWithAuth | // Uses (empty) password. + tpm2.FlagSign, // Key can be used to sign data. + ECCParameters: &tpm2.ECCParams{ + Sign: &tpm2.SigScheme{Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA256}, + CurveID: tpm2.CurveNISTP256, + Point: tpm2.ECPoint{ + XRaw: make([]byte, 32), + YRaw: make([]byte, 32), + }, + }, + } + + privBlob, pubBlob, _, hash, ticket, err := tpm2.CreateKey(f, srk, tpm2.PCRSelection{}, "", "", tmpl) + if err != nil { + log.Fatalf("create aik: %v", err) + } + log.Printf("privBlob: %x\n", privBlob) + log.Printf("pubBlob: %x\n", pubBlob) + log.Printf("hash: %x\n", hash) + log.Printf("ticket: %v %s\n", ticket.Type, ticket.Digest) + + appKey, _, err := tpm2.Load(f, srk, "", pubBlob, privBlob) + if err != nil { + log.Fatalf("load app key: %v", err) + } + + // Write key context to disk. + appKeyCtx, err := tpm2.ContextSave(f, appKey) + if err != nil { + log.Fatalf("saving context: %v", err) + } + if err := os.WriteFile("app.ctx", appKeyCtx, 0644); err != nil { + log.Fatalf("writing context: %v", err) + } +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + if err == nil { + return true + } + if errors.Is(err, os.ErrNotExist) { + return false + } + panic("Should not happen") +} + +type tpmSinger struct { + tpm io.ReadWriter + h tpmutil.Handle + pub crypto.PublicKey +} + +func (s *tpmSinger) Public() crypto.PublicKey { + return s.pub +} + +func (s *tpmSinger) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + sig, err := tpm2.Sign(s.tpm, s.h, "", digest, nil, nil) + if err != nil { + return nil, fmt.Errorf("signing data: %v", err) + } + if sig.RSA != nil { + return sig.RSA.Signature, nil + } + if sig.ECC != nil { + return asn1.Marshal(struct { + R *big.Int + S *big.Int + }{sig.ECC.R, sig.ECC.S}) + } + return nil, fmt.Errorf("unsupported signature type: %v", sig.Alg) +} + +func newSigner(tpm io.ReadWriter, h tpmutil.Handle) (*tpmSinger, error) { + tpmPub, _, _, err := tpm2.ReadPublic(tpm, h) + if err != nil { + return nil, fmt.Errorf("read public blob: %v", err) + } + pub, err := tpmPub.Key() + if err != nil { + return nil, fmt.Errorf("decode public key: %v", err) + } + return &tpmSinger{tpm, h, pub}, nil +} + +func getSinger(f *os.File) (*tpmSinger, error) { + keyCtx, err := os.ReadFile("app.ctx") + if err != nil { + log.Fatalf("read app key: %v", err) + } + key, err := tpm2.ContextLoad(f, keyCtx) + if err != nil { + log.Fatalf("load app key: %v", err) + } + + return newSigner(f, key) +} + +// reference: https://ericchiang.github.io/post/tpm-keys/ +func main() { + f, err := os.OpenFile("/dev/tpmrm0", os.O_RDWR, 0) + if err != nil { + log.Fatalf("opening tpm: %v", err) + } + defer f.Close() + + if !fileExists("srk.ctx") { + generateSrk(f) + } + if !fileExists("app.ctx") { + generateAppKey(f) + } + + priv, err := getSinger(f) + if err != nil { + log.Fatalf("create signer: %v", err) + } + + publicKeyDer, err := x509.MarshalPKIXPublicKey(priv.Public()) + if err != nil { + log.Fatalf("public key: %v", err) + } else { + log.Printf("public key pem: %s\n", base64.StdEncoding.EncodeToString(publicKeyDer)) + } + + msg := []byte("Hello world!") + digest := sha256.Sum256(msg) + + sig, err := priv.Sign(rand.Reader, digest[:], crypto.SHA256) + if err != nil { + log.Fatalf("signing data: %v", err) + } + log.Printf("sig: %s\n", hex.EncodeToString(sig)) + pub, ok := priv.Public().(*ecdsa.PublicKey) + if !ok { + log.Fatalf("expected ecdsa.PublicKey got: %T", priv.Public()) + } + if !ecdsa.VerifyASN1(pub, digest[:], sig) { + log.Fatalf("failed to verify signature") + } +}