diff --git a/cmd/tpm-vuln-checker/cmds.go b/cmd/tpm-vuln-checker/cmds.go index 5974822..05d0995 100644 --- a/cmd/tpm-vuln-checker/cmds.go +++ b/cmd/tpm-vuln-checker/cmds.go @@ -15,15 +15,17 @@ package main import ( "fmt" - "io" - "net/url" + "github.com/fatih/color" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/cloud" "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve" "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" + "github.com/manifoldco/promptui" ) -const ( - swtpmURL = "tcp://127.0.0.1:2321" +var ( + NonVulnerableStyle = color.New(color.FgGreen, color.BgBlack, color.Bold).SprintFunc() + VulnerableStyle = color.New(color.FgRed, color.BgBlack, color.Bold).SprintFunc() ) type context struct { @@ -34,6 +36,7 @@ type versionCmd struct { } type checkCmd struct { + NonInteractive bool `flag optional name:"batch" help:"Always uploads anonymized data without asking"` } func (v *versionCmd) Run(ctx *context) error { @@ -42,38 +45,48 @@ func (v *versionCmd) Run(ctx *context) error { } func (v *checkCmd) Run(ctx *context) error { - var err error - var rwc io.ReadWriteCloser - if ctx.Emulator { - var url *url.URL - url, err = url.Parse(swtpmURL) - if err != nil { - return err - } - rwc, err = tss.OpenNetTPM(url) - if err != nil { - return err - } - } else { - rwc, err = tss.OpenTPM() - if err != nil { - return err - } + socket, err := tss.NewTPM(ctx.Emulator) + if err != nil { + return err + } + defer socket.Close() + if !tss.IsTPM2(socket) { + return fmt.Errorf("no TPM 2.0 found") } - defer rwc.Close() - found, err := cve.Detect(rwc) + tpmInfo, err := tss.ReadTPM2VendorAttributes(socket) if err != nil { return err } - tpmInfo, err := tss.ReadTPM2VendorAttributes(rwc) + fmt.Printf("TPM Manufacturer: \t%s\nTPM Spec Revision: \t%s\nTPM Family: \t\t%s\nTPM Firmware: \t\t0x%s,0x%s\n", + tpmInfo.Manufacturer.String(), tpmInfo.SpecRevision.String(), tpmInfo.Family.String(), + tpmInfo.FWVersion1.String(), tpmInfo.FWVersion2.String()) + vulnerable, cveData, err := cve.Detect(socket) if err != nil { return err } - fmt.Printf("%s\n%s\n%s\n", tpmInfo.Manufacturer.String(), tpmInfo.SpecRevision, tpmInfo.Family) - if found { - fmt.Println("found") + if vulnerable { + fmt.Printf("CVE 2023-1017-1018: \t%s", VulnerableStyle("Vulnerable")) + } else { + fmt.Printf("CVE 2023-1017-1018: \t%s", NonVulnerableStyle("Not Vulnerable")) + } + fmt.Println() + if v.NonInteractive { + if err := cloud.UploadAnonData(tpmInfo, cveData, vulnerable); err != nil { + return err + } } else { - fmt.Println("Not found") + prompt := promptui.Prompt{ + Label: "Do you want to upload this data anonymized for analysis and tpm firmware update support", + IsConfirm: true, + } + fmt.Println() + _, err := prompt.Run() + if err != nil { + return nil + } + if err := cloud.UploadAnonData(tpmInfo, cveData, vulnerable); err != nil { + return err + } } return nil } @@ -81,5 +94,5 @@ func (v *checkCmd) Run(ctx *context) error { var cli struct { Emulator bool `help:"Enable emulator mode."` Version versionCmd `cmd help:"Prints the version of the program"` - Check checkCmd `short:"c" cmd help:"Check TPM for CVE2023-1017-1018"` + Check checkCmd `short:"c" cmd help:"Check TPM for CVE 2023-1017-1018"` } diff --git a/cmd/tpm-vuln-checker/main.go b/cmd/tpm-vuln-checker/main.go index 81d6675..8fc1e40 100644 --- a/cmd/tpm-vuln-checker/main.go +++ b/cmd/tpm-vuln-checker/main.go @@ -50,5 +50,7 @@ func main() { Summary: true, })) err := ctx.Run(&context{Emulator: cli.Emulator}) + fmt.Println() + fmt.Println() ctx.FatalIfErrorf(err) } diff --git a/go.mod b/go.mod index 0153e7c..9d46191 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,18 @@ go 1.20 require ( github.com/alecthomas/kong v0.7.1 + github.com/fatih/color v1.14.1 github.com/google/go-tpm v0.3.3 + github.com/manifoldco/promptui v0.9.0 ) -require golang.org/x/sys v0.3.0 // indirect +require ( + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect +) + +require ( + github.com/google/uuid v1.3.0 + golang.org/x/sys v0.3.0 // indirect +) diff --git a/go.sum b/go.sum index dbb976a..879d6c5 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,12 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -26,6 +32,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -58,6 +66,8 @@ github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -75,6 +85,13 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -142,9 +159,11 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go new file mode 100644 index 0000000..5cadb1e --- /dev/null +++ b/pkg/cloud/cloud.go @@ -0,0 +1,59 @@ +package cloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + + "github.com/google/uuid" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" +) + +const ( + uploadURL = "https://upload.vuln.immune.gmbh" +) + +type AnonInfo struct { + Info *tss.TPM20Info `json:"info"` + Vulnerable bool `json:"vulnerable"` + Raw *cve.CVEData `json:"cvedata"` +} + +func UploadAnonData(info *tss.TPM20Info, raw *cve.CVEData, vuln bool) error { + if info == nil { + return fmt.Errorf("tpm info is nil") + } + var payload AnonInfo + payload.Info = info + payload.Vulnerable = vuln + payload.Raw = raw + data, err := json.Marshal(payload) + if err != nil { + return err + } + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + id := uuid.New().String() + part, _ := writer.CreateFormFile("file", id+".json") + io.Copy(part, bytes.NewReader(data)) + writer.Close() + request, err := http.NewRequest("POST", uploadURL, body) + if err != nil { + return err + } + request.Header.Set("Content-Type", writer.FormDataContentType()) + client := &http.Client{} + response, err := client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound { + return fmt.Errorf("http status code %d, body: %v", response.StatusCode, response.Body) + } + return nil +} diff --git a/pkg/cve/cve.go b/pkg/cve/cve.go index 4594276..d21f038 100644 --- a/pkg/cve/cve.go +++ b/pkg/cve/cve.go @@ -24,23 +24,29 @@ import ( "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" ) +type CVEData struct { + RawString string + Err tpm2.ParameterError +} + func hex2int(hexStr string) uint64 { cleaned := strings.Replace(hexStr, "0x", "", -1) result, _ := strconv.ParseUint(cleaned, 16, 64) return uint64(result) } -func parserParameterError(err error) (*tpm2.ParameterError, error) { - var paramErr tpm2.ParameterError +func parserParameterError(err error) (*CVEData, error) { + var cveData CVEData strErr := err.Error() if err == nil { return nil, fmt.Errorf("error is nil") } + cveData.RawString = strErr f := func(c rune) bool { return c == ',' || c == ':' || c == ' ' } info := strings.FieldsFunc(strErr, f) - if info[0] == "parameter" || info[0] == "session" { + if info[0] == "parameter" || info[0] == "session" || info[0] == "handle" { param, err := strconv.Atoi(info[1]) if err != nil { return nil, fmt.Errorf("couldn't parse parameter error parameter") @@ -49,14 +55,14 @@ func parserParameterError(err error) (*tpm2.ParameterError, error) { return nil, fmt.Errorf("couldn't parse parameter error code") } code := hex2int(info[4]) - paramErr.Parameter = tpm2.RCIndex(param) - paramErr.Code = tpm2.RCFmt1(code) - return ¶mErr, nil + cveData.Err.Parameter = tpm2.RCIndex(param) + cveData.Err.Code = tpm2.RCFmt1(code) + return &cveData, nil } return nil, fmt.Errorf("couldn't parse error strings: %s", strErr) } -func Detect(rwc io.ReadWriteCloser) (bool, error) { +func Detect(rwc io.ReadWriteCloser) (bool, *CVEData, error) { _ = tpm2.Startup(rwc, tpm2.StartupClear) session, _, err := tss.StartAuthSession( rwc, @@ -68,33 +74,33 @@ func Detect(rwc io.ReadWriteCloser) (bool, error) { tpm2.AlgXOR, tpm2.AlgSHA256) if err != nil { - return false, err + return false, nil, err } defer tpm2.FlushContext(rwc, session) hnd, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", tss.ECCPublicKey) if err != nil { - return false, err + return false, nil, err } defer tpm2.FlushContext(rwc, hnd) // We don't test oobwrite because it's dangerous err = oobRead(rwc, tpm2.HandleEndorsement, session, nil) if err == nil { - return false, fmt.Errorf("no tpm error returned") + return false, nil, fmt.Errorf("no tpm error returned") } - paramErr, err := parserParameterError(err) + cveData, err := parserParameterError(err) if err != nil { - return false, fmt.Errorf("couldn't parse parameter error %v", err) + return false, nil, fmt.Errorf("couldn't parse parameter error %v", err) } - if paramErr != nil && paramErr.Parameter == 1 { - switch paramErr.Code { + if cveData != nil && cveData.Err.Parameter == 1 { + switch cveData.Err.Code { case 0x1a: - return false, nil + return false, cveData, nil case 0x15: - return true, nil + return true, cveData, nil } } - return false, fmt.Errorf("unknown TPM session error") + return false, cveData, nil } func oobRead(rwc io.ReadWriteCloser, owner, sess tpmutil.Handle, payload []byte) error { diff --git a/pkg/tss/tpm.go b/pkg/tss/tpm.go index 747dcc5..0fd35fb 100644 --- a/pkg/tss/tpm.go +++ b/pkg/tss/tpm.go @@ -19,15 +19,14 @@ import ( "io" "net" "net/url" + "strconv" "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil" ) const ( - mssimCommandPort = "2321" - mssimPlatformPort = "2322" - mssimURLScheme = "mssim" + swtpmURL = "tcp://127.0.0.1:2321" ) // TCGVendorID represents a unique TCG manufacturer code. @@ -59,10 +58,47 @@ var vendors = map[TCGVendorID]string{ 1196379975: "Google", } +type TCGFamily uint32 + +var families = map[TCGFamily]string{ + 841887744: "2.0", +} + +type TCGSpecRevision uint32 +type TCGFirmwareVersion1 uint32 +type TCGFirmwareVersion2 uint32 + type TPM20Info struct { Manufacturer TCGVendorID - Family string - SpecRevision string + Family TCGFamily + SpecRevision TCGSpecRevision + FWVersion1 TCGFirmwareVersion1 + FWVersion2 TCGFirmwareVersion2 +} + +func (version TCGFirmwareVersion1) String() string { + if version == 0 { + return "0" + } else { + return strconv.FormatUint(uint64(version), 16) + } +} + +func (version TCGFirmwareVersion2) String() string { + if version == 0 { + return "0" + } else { + return strconv.FormatUint(uint64(version), 16) + } +} + +func (family TCGFamily) String() string { + return families[family] +} + +func (spec TCGSpecRevision) String() string { + tmp := fmt.Sprintf("%d", spec) + return fmt.Sprintf("%c.%s", tmp[0], tmp[1:]) } var ECCPublicKey = tpm2.Public{ @@ -194,6 +230,11 @@ func Property(conn io.ReadWriteCloser, prop uint32) (uint32, error) { } } +func IsTPM2(tpm io.ReadWriteCloser) bool { + _, err := Property(tpm, uint32(tpm2.FamilyIndicator)) + return err == nil +} + func ReadTPM2VendorAttributes(tpm io.ReadWriteCloser) (*TPM20Info, error) { manu, err := Property(tpm, uint32(tpm2.Manufacturer)) if err != nil { @@ -207,10 +248,20 @@ func ReadTPM2VendorAttributes(tpm io.ReadWriteCloser) (*TPM20Info, error) { if err != nil { return nil, err } + version1, err := Property(tpm, uint32(tpm2.FirmwareVersion1)) + if err != nil { + return nil, err + } + version2, err := Property(tpm, uint32(tpm2.FirmwareVersion2)) + if err != nil { + return nil, err + } return &TPM20Info{ Manufacturer: TCGVendorID(manu), - Family: fmt.Sprintf("%d", family), - SpecRevision: fmt.Sprintf("%d", spec), + Family: TCGFamily(family), + SpecRevision: TCGSpecRevision(spec), + FWVersion1: TCGFirmwareVersion1(version1), + FWVersion2: TCGFirmwareVersion2(version2), }, nil } @@ -227,3 +278,25 @@ func StartAuthSession(rw io.ReadWriter, tpmKey, bindKey tpmutil.Handle, nonceCal } return decodeStartAuthSession(resp) } + +func NewTPM(emulator bool) (io.ReadWriteCloser, error) { + var err error + var rwc io.ReadWriteCloser + if emulator { + var url *url.URL + url, err = url.Parse(swtpmURL) + if err != nil { + return nil, err + } + rwc, err = OpenNetTPM(url) + if err != nil { + return nil, err + } + } else { + rwc, err = OpenTPM() + if err != nil { + return nil, err + } + } + return rwc, nil +} diff --git a/pkg/tss/tpm_windows.go b/pkg/tss/tpm_windows.go index 1a18453..b540cf9 100644 --- a/pkg/tss/tpm_windows.go +++ b/pkg/tss/tpm_windows.go @@ -13,8 +13,13 @@ // limitations under the License. package tss +import ( + "io" + + "github.com/google/go-tpm/tpmutil" +) + func OpenTPM() (io.ReadWriteCloser, error) { - _ = tpmPath conn, err := tpmutil.OpenTPM() if err != nil { return nil, err