|
|
|
// Mint, (C) 2018 Minio, Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// 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 main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const testName = "TLS-tests"
|
|
|
|
|
|
|
|
const (
|
|
|
|
// PASS indicate that a test passed
|
|
|
|
PASS = "PASS"
|
|
|
|
// FAIL indicate that a test failed
|
|
|
|
FAIL = "FAIL"
|
|
|
|
// NA indicates that a test is not applicable
|
|
|
|
NA = "NA"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
log.SetOutput(os.Stdout)
|
|
|
|
log.SetFormatter(&mintJSONFormatter{})
|
|
|
|
log.SetLevel(log.InfoLevel)
|
|
|
|
|
|
|
|
endpoint := os.Getenv("SERVER_ENDPOINT")
|
|
|
|
secure := os.Getenv("ENABLE_HTTPS")
|
|
|
|
if secure != "1" {
|
|
|
|
log.WithFields(log.Fields{"name:": testName, "status": NA, "message": "TLS is not enabled"}).Info()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
testTLSVersions(endpoint)
|
|
|
|
testTLSCiphers(endpoint)
|
|
|
|
testTLSEllipticCurves(endpoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts TLS1.0 or TLS1.1 connections - fail if so.
|
|
|
|
// Tests whether the endpoint accepts TLS1.2 connections - fail if not.
|
|
|
|
func testTLSVersions(endpoint string) {
|
|
|
|
const function = "TLSVersions"
|
|
|
|
startTime := time.Now()
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts TLS1.0 or TLS1.1 connections
|
|
|
|
args := map[string]interface{}{
|
|
|
|
"MinVersion": "tls.VersionTLS10",
|
|
|
|
"MaxVersion": "tls.VersionTLS11",
|
|
|
|
}
|
|
|
|
_, err := tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS10,
|
|
|
|
MaxVersion: tls.VersionTLS11,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint accepts insecure connection", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts TLS1.2 connections
|
|
|
|
args = map[string]interface{}{
|
|
|
|
"MinVersion": "tls.VersionTLS12",
|
|
|
|
}
|
|
|
|
_, err = tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint rejects secure connection", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
successLog(function, args, startTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts SSL3.0, TLS1.0 or TLS1.1 connections - fail if so.
|
|
|
|
// Tests whether the endpoint accepts TLS1.2 connections - fail if not.
|
|
|
|
func testTLSCiphers(endpoint string) {
|
|
|
|
const function = "TLSCiphers"
|
|
|
|
startTime := time.Now()
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts insecure ciphers
|
|
|
|
args := map[string]interface{}{
|
|
|
|
"MinVersion": "tls.VersionTLS12",
|
|
|
|
"CipherSuites": unsupportedCipherSuites,
|
|
|
|
}
|
|
|
|
_, err := tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
CipherSuites: unsupportedCipherSuites,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint accepts insecure cipher suites", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts at least one secure cipher
|
|
|
|
args = map[string]interface{}{
|
|
|
|
"MinVersion": "tls.VersionTLS12",
|
|
|
|
"CipherSuites": supportedCipherSuites,
|
|
|
|
}
|
|
|
|
_, err = tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
CipherSuites: supportedCipherSuites,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint rejects all secure cipher suites", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts at least one default cipher
|
|
|
|
args = map[string]interface{}{
|
|
|
|
"MinVersion": "tls.VersionTLS12",
|
|
|
|
"CipherSuites": nil,
|
|
|
|
}
|
|
|
|
_, err = tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
CipherSuites: nil, // default value
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint rejects default cipher suites", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
successLog(function, args, startTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts the P-384 or P-521 elliptic curve - fail if so.
|
|
|
|
// Tests whether the endpoint accepts Curve25519 or P-256 - fail if not.
|
|
|
|
func testTLSEllipticCurves(endpoint string) {
|
|
|
|
const function = "TLSEllipticCurves"
|
|
|
|
startTime := time.Now()
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts curves using non-constant time implementations.
|
|
|
|
args := map[string]interface{}{
|
|
|
|
"CurvePreferences": unsupportedCurves,
|
|
|
|
}
|
|
|
|
_, err := tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
CurvePreferences: unsupportedCurves,
|
|
|
|
CipherSuites: supportedCipherSuites,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint accepts insecure elliptic curves", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests whether the endpoint accepts curves using constant time implementations.
|
|
|
|
args = map[string]interface{}{
|
|
|
|
"CurvePreferences": unsupportedCurves,
|
|
|
|
}
|
|
|
|
_, err = tls.Dial("tcp", endpoint, &tls.Config{
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
CurvePreferences: supportedCurves,
|
|
|
|
CipherSuites: supportedCipherSuites,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
failureLog(function, args, startTime, "", "Endpoint does not accept secure elliptic curves", err).Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
successLog(function, args, startTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
func successLog(function string, args map[string]interface{}, startTime time.Time) *log.Entry {
|
|
|
|
duration := time.Since(startTime).Nanoseconds() / 1000000
|
|
|
|
return log.WithFields(log.Fields{
|
|
|
|
"name": testName,
|
|
|
|
"function": function,
|
|
|
|
"args": args,
|
|
|
|
"duration": duration,
|
|
|
|
"status": PASS,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func failureLog(function string, args map[string]interface{}, startTime time.Time, alert string, message string, err error) *log.Entry {
|
|
|
|
duration := time.Since(startTime).Nanoseconds() / 1000000
|
|
|
|
fields := log.Fields{
|
|
|
|
"name": testName,
|
|
|
|
"function": function,
|
|
|
|
"args": args,
|
|
|
|
"duration": duration,
|
|
|
|
"status": FAIL,
|
|
|
|
"alert": alert,
|
|
|
|
"message": message,
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
fields["error"] = err
|
|
|
|
}
|
|
|
|
return log.WithFields(fields)
|
|
|
|
}
|
|
|
|
|
|
|
|
type mintJSONFormatter struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *mintJSONFormatter) Format(entry *log.Entry) ([]byte, error) {
|
|
|
|
data := make(log.Fields, len(entry.Data))
|
|
|
|
for k, v := range entry.Data {
|
|
|
|
switch v := v.(type) {
|
|
|
|
case error:
|
|
|
|
// Otherwise errors are ignored by `encoding/json`
|
|
|
|
// https://github.com/sirupsen/logrus/issues/137
|
|
|
|
data[k] = v.Error()
|
|
|
|
default:
|
|
|
|
data[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
serialized, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
|
|
|
|
}
|
|
|
|
return append(serialized, '\n'), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Secure Go implementations of modern TLS ciphers
|
|
|
|
// The following ciphers are excluded because:
|
|
|
|
// - RC4 ciphers: RC4 is broken
|
|
|
|
// - 3DES ciphers: Because of the 64 bit blocksize of DES (Sweet32)
|
|
|
|
// - CBC-SHA256 ciphers: No countermeasures against Lucky13 timing attack
|
|
|
|
// - CBC-SHA ciphers: Legacy ciphers (SHA-1) and non-constant time
|
|
|
|
// implementation of CBC.
|
|
|
|
// (CBC-SHA ciphers can be enabled again if required)
|
|
|
|
// - RSA key exchange ciphers: Disabled because of dangerous PKCS1-v1.5 RSA
|
|
|
|
// padding scheme. See Bleichenbacher attacks.
|
|
|
|
var supportedCipherSuites = []uint16{
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Supported elliptic curves: Implementations are constant-time.
|
|
|
|
var supportedCurves = []tls.CurveID{tls.X25519, tls.CurveP256}
|
|
|
|
|
|
|
|
var unsupportedCipherSuites = []uint16{
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, // Broken cipher
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // Sweet32
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, // Broken cipher
|
|
|
|
|
|
|
|
// all RSA-PKCS1-v1.5 ciphers are disabled - danger of Bleichenbacher attack variants
|
|
|
|
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // Sweet32
|
|
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
|
|
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
|
|
|
|
tls.TLS_RSA_WITH_RC4_128_SHA, // Broken cipher
|
|
|
|
|
|
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // Disabled because of RSA-PKCS1-v1.5 - AES-GCM is considered secure.
|
|
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // Disabled because of RSA-PKCS1-v1.5 - AES-GCM is considered secure.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unsupported elliptic curves: Implementations are not constant-time.
|
|
|
|
var unsupportedCurves = []tls.CurveID{tls.CurveP384, tls.CurveP521}
|