1// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//go:generate tlsconfig
16
17package rules
18
19import (
20	"crypto/tls"
21	"fmt"
22	"go/ast"
23
24	"github.com/securego/gosec/v2"
25)
26
27type insecureConfigTLS struct {
28	gosec.MetaData
29	MinVersion       int16
30	MaxVersion       int16
31	requiredType     string
32	goodCiphers      []string
33	actualMinVersion int16
34	actualMaxVersion int16
35}
36
37func (t *insecureConfigTLS) ID() string {
38	return t.MetaData.ID
39}
40
41func stringInSlice(a string, list []string) bool {
42	for _, b := range list {
43		if b == a {
44			return true
45		}
46	}
47	return false
48}
49
50func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gosec.Context) *gosec.Issue {
51	if ciphers, ok := n.(*ast.CompositeLit); ok {
52		for _, cipher := range ciphers.Elts {
53			if ident, ok := cipher.(*ast.SelectorExpr); ok {
54				if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
55					err := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
56					return gosec.NewIssue(c, ident, t.ID(), err, gosec.High, gosec.High)
57				}
58			}
59		}
60	}
61	return nil
62}
63
64func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gosec.Context) *gosec.Issue {
65	if ident, ok := n.Key.(*ast.Ident); ok {
66		switch ident.Name {
67		case "InsecureSkipVerify":
68			if node, ok := n.Value.(*ast.Ident); ok {
69				if node.Name != "false" {
70					return gosec.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify set true.", gosec.High, gosec.High)
71				}
72			} else {
73				// TODO(tk): symbol tab look up to get the actual value
74				return gosec.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify may be true.", gosec.High, gosec.Low)
75			}
76
77		case "PreferServerCipherSuites":
78			if node, ok := n.Value.(*ast.Ident); ok {
79				if node.Name == "false" {
80					return gosec.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites set false.", gosec.Medium, gosec.High)
81				}
82			} else {
83				// TODO(tk): symbol tab look up to get the actual value
84				return gosec.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites may be false.", gosec.Medium, gosec.Low)
85			}
86
87		case "MinVersion":
88			if ival, ierr := gosec.GetInt(n.Value); ierr == nil {
89				t.actualMinVersion = (int16)(ival)
90			} else {
91				if se, ok := n.Value.(*ast.SelectorExpr); ok {
92					if pkg, ok := se.X.(*ast.Ident); ok && pkg.Name == "tls" {
93						t.actualMinVersion = t.mapVersion(se.Sel.Name)
94					}
95				}
96			}
97
98		case "MaxVersion":
99			if ival, ierr := gosec.GetInt(n.Value); ierr == nil {
100				t.actualMaxVersion = (int16)(ival)
101			} else {
102				if se, ok := n.Value.(*ast.SelectorExpr); ok {
103					if pkg, ok := se.X.(*ast.Ident); ok && pkg.Name == "tls" {
104						t.actualMaxVersion = t.mapVersion(se.Sel.Name)
105					}
106				}
107			}
108
109		case "CipherSuites":
110			if ret := t.processTLSCipherSuites(n.Value, c); ret != nil {
111				return ret
112			}
113
114		}
115
116	}
117	return nil
118}
119
120func (t *insecureConfigTLS) mapVersion(version string) int16 {
121	var v int16
122	switch version {
123	case "VersionTLS13":
124		v = tls.VersionTLS13
125	case "VersionTLS12":
126		v = tls.VersionTLS12
127	case "VersionTLS11":
128		v = tls.VersionTLS11
129	case "VersionTLS10":
130		v = tls.VersionTLS10
131	}
132	return v
133}
134
135func (t *insecureConfigTLS) checkVersion(n ast.Node, c *gosec.Context) *gosec.Issue {
136	if t.actualMaxVersion == 0 && t.actualMinVersion >= t.MinVersion {
137		// no warning is generated since the min version is greater than the secure min version
138		return nil
139	}
140	if t.actualMinVersion < t.MinVersion {
141		return gosec.NewIssue(c, n, t.ID(), "TLS MinVersion too low.", gosec.High, gosec.High)
142	}
143	if t.actualMaxVersion < t.MaxVersion {
144		return gosec.NewIssue(c, n, t.ID(), "TLS MaxVersion too low.", gosec.High, gosec.High)
145	}
146	return nil
147}
148
149func (t *insecureConfigTLS) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
150	if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil {
151		actualType := c.Info.TypeOf(complit.Type)
152		if actualType != nil && actualType.String() == t.requiredType {
153			for _, elt := range complit.Elts {
154				if kve, ok := elt.(*ast.KeyValueExpr); ok {
155					issue := t.processTLSConfVal(kve, c)
156					if issue != nil {
157						return issue, nil
158					}
159				}
160			}
161			return t.checkVersion(complit, c), nil
162		}
163	}
164	return nil, nil
165}
166