diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cde0123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..905f85b --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,25 @@ +# This is an example .goreleaser.yml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + goarch: + - amd64 + main: ./cmd/tpm-vuln-checker +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/cmd/tpm-vuln-checker/cmds.go b/cmd/tpm-vuln-checker/cmds.go index d400096..3f71527 100644 --- a/cmd/tpm-vuln-checker/cmds.go +++ b/cmd/tpm-vuln-checker/cmds.go @@ -18,7 +18,8 @@ import ( "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/cve201715361" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve20231017" "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" "github.com/manifoldco/promptui" ) @@ -27,6 +28,7 @@ var ( NonVulnerableStyle = color.New(color.FgGreen, color.BgBlack, color.Bold).SprintFunc() VulnerableStyle = color.New(color.FgRed, color.BgBlack, color.Bold).SprintFunc() MaybeVulnerableStyle = color.New(color.FgYellow, color.BgBlack, color.Bold).SprintFunc() + InfoStyle = color.New(color.FgBlue, color.BgBlack, color.Bold).SprintFunc() ) type context struct { @@ -37,7 +39,7 @@ type versionCmd struct { } type checkCmd struct { - NonInteractive bool `flag optional name:"batch" help:"Always uploads anonymized data without asking"` + NonInteractive bool `flag optional name:"auto-upload" help:"Always uploads anonymized data without asking"` } func (v *versionCmd) Run(ctx *context) error { @@ -58,9 +60,10 @@ func (v *checkCmd) Run(ctx *context) error { if err != nil { return err } - fmt.Printf("TPM Manufacturer: \t%s\nTPM Spec Revision: \t%s\nTPM Family: \t\t%s\n", + fmt.Printf("TPM Manufacturer: \t\t%s\nTPM Spec Revision: \t\t%s\nTPM Family: \t\t\t%s\n", tpmInfo.Manufacturer.String(), tpmInfo.SpecRevision.String(), tpmInfo.Family.String()) - vulnerable, cveData, err := cve.Detect(socket) + fmt.Printf("\nStarting TPM vulnerabilities checks.. This may take few seconds!\n\n") + vulnerable, cveData20231017, err := cve20231017.IsVulnerable(socket) if err != nil { if err.Error() == "unknown" { fmt.Printf("CVE 2023-1017/2023-1018: \t%s", MaybeVulnerableStyle("Probably Not Vulnerable")) @@ -69,14 +72,26 @@ func (v *checkCmd) Run(ctx *context) error { } } else { if vulnerable { - fmt.Printf("CVE 2023-1017/2023-1018: \t%s", VulnerableStyle("Vulnerable")) + fmt.Printf("CVE 2023-1017/2023-1018: \t%s\n", VulnerableStyle("Vulnerable")) + fmt.Printf("%s", InfoStyle("Please apply the latest BIOS update to update the TPM firmware. OEMs/ODMs ship TPM updates as part of BIOS updates.")) } else { fmt.Printf("CVE 2023-1017/2023-1018: \t%s", NonVulnerableStyle("Not Vulnerable")) } } fmt.Println() + vulnerable, cveData201715361, err := cve201715361.IsVulnerable(socket) + if err != nil { + fmt.Printf("CVE 2017-15361: \t\t%s", MaybeVulnerableStyle("Check Error")) + } else { + if vulnerable { + fmt.Printf("CVE 2017-15361: \t\t%s\n", VulnerableStyle("Vulnerable")) + } else { + fmt.Printf("CVE 2017-15361: \t\t%s", NonVulnerableStyle("Not Vulnerable")) + } + } + fmt.Println() if v.NonInteractive { - if err := cloud.UploadAnonData(tpmInfo, cveData, vulnerable); err != nil { + if err := cloud.UploadAnonData(tpmInfo, cveData20231017, cveData201715361); err != nil { return err } } else { @@ -89,7 +104,7 @@ func (v *checkCmd) Run(ctx *context) error { if err != nil { return nil } - if err := cloud.UploadAnonData(tpmInfo, cveData, vulnerable); err != nil { + if err := cloud.UploadAnonData(tpmInfo, cveData20231017, cveData201715361); err != nil { return err } } diff --git a/docs/_config.yml b/docs/_config.yml index 09bd172..499569d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,6 +1,6 @@ domain: vuln.immune.gmbh url: https://vuln.immune.gmbh markdown: kramdown -remote_theme: immune-gmbh/midnight@master +remote_theme: pages-themes/midnight@v0.2.0 plugins: -- jekyll-remote-theme +- jekyll-remote-theme \ No newline at end of file diff --git a/go.mod b/go.mod index 452a11d..e3b684b 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,10 @@ require ( ) require ( - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/chzyer/readline v1.5.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect + golang.org/x/sys v0.6.0 // indirect ) -require ( - github.com/google/uuid v1.3.0 - golang.org/x/sys v0.3.0 -) +require github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 879d6c5..1f60eb8 100644 --- a/go.sum +++ b/go.sum @@ -12,12 +12,15 @@ 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/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= 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/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/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= @@ -163,9 +166,10 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h 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-20220310020820-b874c991c1a5/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/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index be477a0..a8f838a 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -22,7 +22,8 @@ import ( "net/http" "github.com/google/uuid" - "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve201715361" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/cve20231017" "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" ) @@ -31,19 +32,19 @@ const ( ) type AnonInfo struct { - Info *tss.TPM20Info `json:"info"` - Vulnerable bool `json:"vulnerable"` - Raw *cve.CVEData `json:"cvedata"` + Info *tss.TPM20Info `json:"info"` + CVEData20231017 *cve20231017.CVEData `json:"cveData-20231017"` + CVEData201715361 *cve201715361.CVEData `json:"cveData-201715361"` } -func UploadAnonData(info *tss.TPM20Info, raw *cve.CVEData, vuln bool) error { +func UploadAnonData(info *tss.TPM20Info, cveData20231017 *cve20231017.CVEData, cveData201715361 *cve201715361.CVEData) error { if info == nil { return fmt.Errorf("tpm info is nil") } var payload AnonInfo payload.Info = info - payload.Vulnerable = vuln - payload.Raw = raw + payload.CVEData20231017 = cveData20231017 + payload.CVEData201715361 = cveData201715361 data, err := json.Marshal(payload) if err != nil { return err diff --git a/pkg/cve201715361/cve-2017-15361.go b/pkg/cve201715361/cve-2017-15361.go new file mode 100644 index 0000000..77e380f --- /dev/null +++ b/pkg/cve201715361/cve-2017-15361.go @@ -0,0 +1,104 @@ +// MIT License +// +// Copyright (c) 2017, Jonathan Rudenberg +// Copyright (c) 2017, CRoCS, EnigmaBridge Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package cve201715361 + +import ( + "crypto/rsa" + "fmt" + "io" + "math/big" + + "github.com/google/go-tpm/tpm2" + "github.com/immune-gmbh/tpm-vuln-checker/pkg/tss" +) + +type CVEData struct { + Vulnerable bool +} + +type test struct { + Prime *big.Int + Fingerprints map[int64]struct{} +} + +var tests = make([]test, 17) + +func init() { + bigOne := big.NewInt(1) + n := &big.Int{} + // relations table from https://github.com/crocs-muni/roca/pull/40 + for i, r := range [][2]int64{ + {2, 11}, {6, 13}, {8, 17}, {9, 19}, {3, 37}, {26, 53}, {20, 61}, + {35, 71}, {24, 73}, {13, 79}, {6, 97}, {51, 103}, {53, 107}, + {54, 109}, {42, 127}, {50, 151}, {78, 157}, + } { + fps := make(map[int64]struct{}) + bp := big.NewInt(r[1]) + br := big.NewInt(r[0]) + for j := int64(0); j < r[1]; j++ { + if n.Exp(big.NewInt(j), br, bp).Cmp(bigOne) == 0 { + fps[j] = struct{}{} + } + } + tests[i] = test{ + Prime: big.NewInt(r[1]), + Fingerprints: fps, + } + } +} + +// IsWeak returns true if a RSA public key is vulnerable to Return of +// Coppersmith's Attack (ROCA). +func IsVulnerable(rwc io.ReadWriteCloser) (bool, *CVEData, error) { + var cveData CVEData + _ = tpm2.Startup(rwc, tpm2.StartupClear) + session, _, err := tss.StartAuthSession( + rwc, + tpm2.HandleNull, + tpm2.HandleNull, + make([]byte, 16), + nil, + tpm2.SessionHMAC, + tpm2.AlgXOR, + tpm2.AlgSHA256) + if err != nil { + return false, nil, fmt.Errorf("") + } + defer tpm2.FlushContext(rwc, session) + + hnd, publicKey, err := tpm2.CreatePrimary(rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", tss.RSAPublicKey) + if err != nil { + return false, nil, fmt.Errorf("") + } + defer tpm2.FlushContext(rwc, hnd) + tmp := &big.Int{} + key := publicKey.(*rsa.PublicKey) + for _, t := range tests { + if _, ok := t.Fingerprints[tmp.Mod(key.N, t.Prime).Int64()]; !ok { + cveData.Vulnerable = false + return false, &cveData, nil + } + } + cveData.Vulnerable = true + return true, &cveData, nil +} diff --git a/pkg/cve/cve.go b/pkg/cve20231017/cve-2023-1017.go similarity index 93% rename from pkg/cve/cve.go rename to pkg/cve20231017/cve-2023-1017.go index 5fa738d..0ff836d 100644 --- a/pkg/cve/cve.go +++ b/pkg/cve20231017/cve-2023-1017.go @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package cve +package cve20231017 import ( "errors" @@ -26,9 +26,10 @@ import ( ) type CVEData struct { - RawString string - Valid int - Code uint64 + RawString string + Valid int + Code uint64 + Vulnerable bool } func hex2int(hexStr string) uint64 { @@ -64,7 +65,7 @@ func parserError(err error) (*CVEData, error) { return nil, fmt.Errorf("couldn't parse error strings: %s", strErr) } -func Detect(rwc io.ReadWriteCloser) (bool, *CVEData, error) { +func IsVulnerable(rwc io.ReadWriteCloser) (bool, *CVEData, error) { _ = tpm2.Startup(rwc, tpm2.StartupClear) session, _, err := tss.StartAuthSession( rwc, @@ -97,8 +98,10 @@ func Detect(rwc io.ReadWriteCloser) (bool, *CVEData, error) { if cveData != nil && cveData.Valid == 1 { switch cveData.Code { case 0x1a: + cveData.Vulnerable = false return false, cveData, nil case 0x15: + cveData.Vulnerable = true return true, cveData, nil } } else if cveData != nil && cveData.Valid != 1 { diff --git a/pkg/tss/tpm.go b/pkg/tss/tpm.go index b7f964d..cc9365f 100644 --- a/pkg/tss/tpm.go +++ b/pkg/tss/tpm.go @@ -113,6 +113,22 @@ var ECCPublicKey = tpm2.Public{ }, } +var RSAPublicKey = tpm2.Public{ + Type: tpm2.AlgRSA, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | + tpm2.FlagUserWithAuth | tpm2.FlagRestricted | tpm2.FlagDecrypt, + AuthPolicy: []byte{}, + RSAParameters: &tpm2.RSAParams{ + Symmetric: &tpm2.SymScheme{ + Alg: tpm2.AlgAES, + KeyBits: 128, + Mode: tpm2.AlgCFB, + }, + KeyBits: 2048, + }, +} + func RunCommand(rw io.ReadWriter, tag tpmutil.Tag, Cmd tpmutil.Command, in ...interface{}) ([]byte, error) { resp, code, err := tpmutil.RunCommand(rw, tag, Cmd, in...) if err != nil {