|
package main_test |
|
|
|
import ( |
|
"crypto" |
|
"crypto/rand" |
|
"crypto/rsa" |
|
"crypto/sha256" |
|
"crypto/tls" |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/pem" |
|
"errors" |
|
"fmt" |
|
"log" |
|
"math/big" |
|
"net" |
|
"net/http" |
|
"net/http/httptest" |
|
"net/http/httputil" |
|
"testing" |
|
"time" |
|
|
|
"github.com/stretchr/testify/assert" |
|
) |
|
|
|
// Self imposed tutorial from the Blog post here https://ericchiang.github.io/post/go-tls/ |
|
// Thanks Eric! |
|
func TestSomething(t *testing.T) { |
|
t.Run("encode with public and decode using private key", func(t *testing.T) { |
|
// Make private and public key |
|
privKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
plainText := []byte("the rain in spain stays mainly in the plane") |
|
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, &privKey.PublicKey, plainText) |
|
assert.Nil(t, err) |
|
|
|
decodedMessage, err := rsa.DecryptPKCS1v15(rand.Reader, privKey, cipherText) |
|
assert.Nil(t, err) |
|
|
|
assert.Equal(t, decodedMessage, plainText) |
|
|
|
// The cool part about this is you can prove you hold a private key without ever showing it to somebody. |
|
|
|
}) |
|
|
|
t.Run("you can sign some content", func(t *testing.T) { |
|
privKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
plainText := []byte("Folk magic, hot off the grill") |
|
hash := sha256.Sum256(plainText) |
|
fmt.Printf("%#x\n", hash) |
|
|
|
// make a signature using the private key and some HASHED content |
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256, hash[:]) |
|
assert.Nil(t, err) |
|
|
|
verifyUsingPublicKey := func(pub *rsa.PublicKey, msg, signature []byte) error { |
|
aHash := sha256.Sum256(msg) |
|
return rsa.VerifyPKCS1v15(pub, crypto.SHA256, aHash[:], signature) |
|
|
|
} |
|
|
|
// Did privKey sign this ? |
|
// this won't work |
|
assert.Error(t, verifyUsingPublicKey(&privKey.PublicKey, []byte("hello world"), signature)) |
|
|
|
// this won't work either |
|
assert.Error(t, verifyUsingPublicKey(&privKey.PublicKey, []byte("Folk magic, something something"), signature)) |
|
|
|
// this is legit.. |
|
assert.Nil(t, verifyUsingPublicKey(&privKey.PublicKey, plainText, signature)) |
|
|
|
// This might be very helpful for say, a certificate authority, who wants to be able to distribute documents which can't be altered without everyone detecting. |
|
}) |
|
|
|
t.Run("now to make a cert and break the client", func(t *testing.T) { |
|
// generate a new key-pair |
|
rootKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
rootCert, rootCertPEM, err := makeRootCert(rootKey) |
|
assert.Nil(t, err) |
|
|
|
fmt.Printf("%s\n", rootCertPEM) |
|
//fmt.Printf("%#x\n", rootCert.Signature) |
|
servKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
_, servCertPEM, err := makeServCert(rootKey, rootCert, servKey) |
|
// PEM encode the private key |
|
servKeyPEM := pem.EncodeToMemory(&pem.Block{ |
|
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey), |
|
}) |
|
|
|
// Create a TLS cert using the private key and certificate |
|
servTLSCert, err := tls.X509KeyPair(servCertPEM, servKeyPEM) |
|
assert.Nil(t, err) |
|
|
|
ok := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("HI!")) } |
|
s := httptest.NewUnstartedServer(http.HandlerFunc(ok)) |
|
|
|
// Configure the server to present the certficate we created |
|
s.TLS = &tls.Config{ |
|
Certificates: []tls.Certificate{servTLSCert}, |
|
} |
|
|
|
// Make a new client |
|
//certPool := x509.NewCertPool() |
|
//certPool.AppendCertsFromPEM(rootCertPEM) |
|
// configure a client to use trust those certificates |
|
client := &http.Client{ |
|
// Transport: &http.Transport{ |
|
// TLSClientConfig: &tls.Config{RootCAs: certPool}, |
|
// }, |
|
} |
|
|
|
s.StartTLS() |
|
_, clientErr := client.Get(s.URL) |
|
assert.NotNil(t, clientErr) |
|
s.Close() |
|
}) |
|
|
|
t.Run("now to make a cert and get the client to work", func(t *testing.T) { |
|
// generate a new key-pair |
|
rootKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
rootCert, rootCertPEM, err := makeRootCert(rootKey) |
|
assert.Nil(t, err) |
|
|
|
fmt.Printf("%s\n", rootCertPEM) |
|
//fmt.Printf("%#x\n", rootCert.Signature) |
|
servKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
_, servCertPEM, err := makeServCert(rootKey, rootCert, servKey) |
|
// PEM encode the private key |
|
servKeyPEM := pem.EncodeToMemory(&pem.Block{ |
|
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey), |
|
}) |
|
|
|
// Create a TLS cert using the private key and certificate |
|
servTLSCert, err := tls.X509KeyPair(servCertPEM, servKeyPEM) |
|
assert.Nil(t, err) |
|
|
|
ok := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("HI!")) } |
|
s := httptest.NewUnstartedServer(http.HandlerFunc(ok)) |
|
|
|
// Configure the server to present the certficate we created |
|
s.TLS = &tls.Config{ |
|
Certificates: []tls.Certificate{servTLSCert}, |
|
} |
|
|
|
// Make a new client |
|
certPool := x509.NewCertPool() |
|
certPool.AppendCertsFromPEM(rootCertPEM) |
|
// configure a client to use trust those certificates |
|
client := &http.Client{ |
|
Transport: &http.Transport{ |
|
TLSClientConfig: &tls.Config{RootCAs: certPool}, |
|
}, |
|
} |
|
|
|
s.StartTLS() |
|
resp, clientErr := client.Get(s.URL) |
|
assert.Nil(t, clientErr) |
|
s.Close() |
|
// fmt.Println(resp, clientErr, "bla") |
|
dump, err := httputil.DumpResponse(resp, true) |
|
assert.Nil(t, err) |
|
|
|
fmt.Printf("%s\n", dump) |
|
}) |
|
|
|
t.Run("now to make a cert and get the client to work (with MTLS)", func(t *testing.T) { |
|
// generate a new key-pair |
|
rootKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
rootCert, rootCertPEM, err := makeRootCert(rootKey) |
|
assert.Nil(t, err) |
|
|
|
fmt.Printf("%s\n", rootCertPEM) |
|
servKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
_, servCertPEM, err := makeServCert(rootKey, rootCert, servKey) |
|
// PEM encode the private key |
|
servKeyPEM := pem.EncodeToMemory(&pem.Block{ |
|
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey), |
|
}) |
|
|
|
// Create a TLS cert using the private key and certificate |
|
servTLSCert, err := tls.X509KeyPair(servCertPEM, servKeyPEM) |
|
assert.Nil(t, err) |
|
|
|
ok := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("HI!")) } |
|
s := httptest.NewUnstartedServer(http.HandlerFunc(ok)) |
|
|
|
certPool := x509.NewCertPool() |
|
certPool.AppendCertsFromPEM(rootCertPEM) |
|
|
|
// Configure the server to present the certficate we created |
|
s.TLS = &tls.Config{ |
|
Certificates: []tls.Certificate{servTLSCert}, |
|
ClientAuth: tls.RequireAndVerifyClientCert, |
|
ClientCAs: certPool, |
|
} |
|
|
|
// Make a new client |
|
clientKey, err := rsa.GenerateKey(rand.Reader, 2048) |
|
assert.Nil(t, err) |
|
|
|
_, clientCertPEM, err := makeClientCert(rootKey, rootCert, clientKey) |
|
assert.Nil(t, err) |
|
|
|
// encode and load the cert and private key for the client |
|
clientKeyPEM := pem.EncodeToMemory(&pem.Block{ |
|
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey), |
|
}) |
|
|
|
clientTLSCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM) |
|
assert.Nil(t, err) |
|
|
|
// configure a client to use trust those certificates |
|
client := &http.Client{ |
|
Transport: &http.Transport{ |
|
TLSClientConfig: &tls.Config{ |
|
RootCAs: certPool, |
|
Certificates: []tls.Certificate{clientTLSCert}, |
|
}, |
|
}, |
|
} |
|
|
|
s.StartTLS() |
|
resp, clientErr := client.Get(s.URL) |
|
assert.Nil(t, clientErr) |
|
s.Close() |
|
// fmt.Println(resp, clientErr, "bla") |
|
dump, err := httputil.DumpResponse(resp, true) |
|
assert.Nil(t, err) |
|
|
|
fmt.Printf("%s\n", dump) |
|
}) |
|
|
|
} |
|
|
|
// This is a helper function to create a cert template with a serial number and other required fields. |
|
// Certificates are just public keys with some extra information. |
|
// |
|
func certTemplate() (*x509.Certificate, error) { |
|
// generate a random serial number (a real cert authority would have some logic behind this) |
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) |
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) |
|
if err != nil { |
|
return nil, errors.New("failed to generate serial number: " + err.Error()) |
|
} |
|
// Mantra of the day... |
|
// - Certificates are public keys with some attached information (like what domains they work for). |
|
// - In order to create a certificate, we need to both specify that information and provide a public key. |
|
tmpl := x509.Certificate{ |
|
SerialNumber: serialNumber, |
|
Subject: pkix.Name{Organization: []string{"Yhat, Inc."}}, |
|
SignatureAlgorithm: x509.SHA256WithRSA, |
|
NotBefore: time.Now(), |
|
NotAfter: time.Now().Add(time.Hour), // valid for an hour |
|
BasicConstraintsValid: true, |
|
} |
|
return &tmpl, nil |
|
} |
|
|
|
//Certificates must be signed by the private key of a parent certificate. |
|
func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (cert *x509.Certificate, certPEM []byte, err error) { |
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv) |
|
if err != nil { |
|
return |
|
} |
|
// parse the resulting certificate so we can use it again |
|
cert, err = x509.ParseCertificate(certDER) |
|
if err != nil { |
|
return |
|
} |
|
// PEM encode the certificate (this is a standard TLS encoding) |
|
b := pem.Block{Type: "CERTIFICATE", Bytes: certDER} |
|
certPEM = pem.EncodeToMemory(&b) |
|
return |
|
} |
|
|
|
// Certificates must be signed by the private key of a parent certificate, |
|
// of course, there always has to be a certificate without a parent, |
|
// and in these cases the certificate's private key must be used in lieu of a parent's. |
|
// |
|
func makeRootCert(rootKey *rsa.PrivateKey) (cert *x509.Certificate, certPEM []byte, err error) { |
|
|
|
rootCertTmpl, err := certTemplate() |
|
if err != nil { |
|
return |
|
} |
|
rootCertTmpl.IsCA = true |
|
rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature |
|
rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} |
|
rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")} |
|
|
|
return createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) |
|
} |
|
|
|
func makeServCert(parentKey *rsa.PrivateKey, parentCert *x509.Certificate, servKey *rsa.PrivateKey) (cert *x509.Certificate, certPEM []byte, err error) { |
|
|
|
// create a template for the server |
|
servCertTmpl, err := certTemplate() |
|
if err != nil { |
|
log.Fatalf("creating cert template: %v", err) |
|
} |
|
servCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature |
|
servCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} |
|
servCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")} |
|
|
|
return createCert(servCertTmpl, parentCert, &servKey.PublicKey, parentKey) |
|
} |
|
|
|
func makeClientCert(parentKey *rsa.PrivateKey, parentCert *x509.Certificate, clientKey *rsa.PrivateKey) (cert *x509.Certificate, certPEM []byte, err error) { |
|
|
|
// create a template for the client |
|
clientCertTmpl, err := certTemplate() |
|
if err != nil { |
|
log.Fatalf("creating cert template: %v", err) |
|
} |
|
clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature |
|
clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} |
|
return createCert(clientCertTmpl, parentCert, &clientKey.PublicKey, parentKey) |
|
} |