-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
250 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package tls | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"fmt" | ||
"math/big" | ||
"net" | ||
"time" | ||
) | ||
|
||
// TLSConfig is adapted from http.Server.ServeTLS | ||
func TLSConfig(hosts []string) (*tls.Config, error) { | ||
certPEMBlock, keyPEMBlock, err := generateKeyPair(hosts) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to generate TLS keys %w", err) | ||
} | ||
|
||
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) | ||
if err != nil { | ||
return nil, fmt.Errorf("Failed to generate X509 key pair %w", err) | ||
} | ||
|
||
tlsConfig := &tls.Config{ | ||
Certificates: []tls.Certificate{cert}, | ||
} | ||
|
||
return tlsConfig, nil | ||
} | ||
|
||
// Adapted from https://go.dev/src/crypto/tls/generate_cert.go | ||
func generateKeyPair(hosts []string) ([]byte, []byte, error) { | ||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("Failing to generate new ecdsa key: %w", err) | ||
} | ||
|
||
// A CA is supposed to choose unique serial numbers, that is, unique for the CA. | ||
maxSerialNumber := new(big.Int).Lsh(big.NewInt(1), 128) | ||
serialNumber, err := rand.Int(rand.Reader, maxSerialNumber) | ||
|
||
if err != nil { | ||
return nil, nil, fmt.Errorf("Failed to generate a random serial number: %w", err) | ||
} | ||
|
||
template := x509.Certificate{ | ||
SerialNumber: serialNumber, | ||
Subject: pkix.Name{ | ||
Organization: []string{"Weaveworks"}, | ||
}, | ||
NotBefore: time.Now(), | ||
NotAfter: time.Now().Add(time.Hour * 24 * 365), | ||
|
||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
BasicConstraintsValid: true, | ||
} | ||
|
||
for _, h := range hosts { | ||
if ip := net.ParseIP(h); ip != nil { | ||
template.IPAddresses = append(template.IPAddresses, ip) | ||
} else { | ||
template.DNSNames = append(template.DNSNames, h) | ||
} | ||
} | ||
|
||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("Failed to create certificate: %w", err) | ||
} | ||
|
||
certPEMBlock := &bytes.Buffer{} | ||
|
||
err = pem.Encode(certPEMBlock, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("Failed to encode cert pem: %w", err) | ||
} | ||
|
||
keyPEMBlock := &bytes.Buffer{} | ||
|
||
b, err := x509.MarshalECPrivateKey(priv) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("Unable to marshal ECDSA private key: %v", err) | ||
} | ||
|
||
err = pem.Encode(keyPEMBlock, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("Failed to encode key pem: %w", err) | ||
} | ||
|
||
return certPEMBlock.Bytes(), keyPEMBlock.Bytes(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package tls_test | ||
|
||
import ( | ||
"crypto/tls" | ||
"crypto/x509" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
wegotls "github.com/weaveworks/weave-gitops/pkg/server/tls" | ||
) | ||
|
||
func TestGenerateKeyPair(t *testing.T) { | ||
tlsConfig, err := wegotls.TLSConfig([]string{"foo"}) | ||
assert.NoError(t, err) | ||
|
||
require.Len(t, tlsConfig.Certificates, 1) | ||
cert, err := x509.ParseCertificate(tlsConfig.Certificates[0].Certificate[0]) | ||
require.NoError(t, err) | ||
|
||
// Make sure DNS name is included | ||
assert.Equal(t, []string{"foo"}, cert.DNSNames) | ||
// Important for Chrome that we mark it for server auth | ||
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, cert.ExtKeyUsage) | ||
// Serial number should be unique, (sorry if you randomly get 1!) | ||
assert.NotEqual(t, 1, cert.SerialNumber, "Maybe you randomly did get a 1?") | ||
} | ||
|
||
func TestTLSConfigCanBeServed(t *testing.T) { | ||
tlsConfig, err := wegotls.TLSConfig([]string{"127.0.0.1"}) | ||
require.NoError(t, err) | ||
|
||
ts, client := getTestClient(t, tlsConfig) | ||
ts.StartTLS() | ||
|
||
defer ts.Close() | ||
|
||
res, err := client.Get(ts.URL) | ||
require.NoError(t, err) | ||
greeting, err := io.ReadAll(res.Body) | ||
require.NoError(t, err) | ||
res.Body.Close() | ||
assert.Equal(t, "hello", string(greeting)) | ||
} | ||
|
||
func TestTLSHostnameIsChecked(t *testing.T) { | ||
tlsConfig, err := wegotls.TLSConfig([]string{"123.123.123.123"}) | ||
require.NoError(t, err) | ||
|
||
ts, client := getTestClient(t, tlsConfig) | ||
ts.StartTLS() | ||
|
||
defer ts.Close() | ||
|
||
_, err = client.Get(ts.URL) | ||
require.Regexp(t, "x509: certificate is valid for 123\\.123\\.123\\.123, not 127\\.0\\.0\\.1", err) | ||
} | ||
|
||
func getTestClient(t *testing.T, tlsConfig *tls.Config) (*httptest.Server, http.Client) { | ||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
fmt.Fprint(w, "hello") | ||
})) | ||
|
||
ts.TLS = tlsConfig | ||
|
||
certs := x509.NewCertPool() | ||
|
||
for _, c := range tlsConfig.Certificates { | ||
roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) | ||
require.NoError(t, err) | ||
|
||
for _, root := range roots { | ||
certs.AddCert(root) | ||
} | ||
} | ||
|
||
client := http.Client{ | ||
Transport: &http.Transport{ | ||
TLSClientConfig: &tls.Config{ | ||
RootCAs: certs, | ||
}, | ||
}, | ||
} | ||
|
||
return ts, client | ||
} |