├── README.md ├── .gitignore ├── key.pem ├── cert.pem ├── client └── client.go ├── LICENSE ├── server └── server.go └── generate_client_cert.go /README.md: -------------------------------------------------------------------------------- 1 | # Go Mutual TLS Example 2 | How to make use of mutual TLS authentication in Go 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIHbAgEBBEF481AOVq3IGh9xi72gzX3k/YF2q4U+tSKCQ4/CGBoSQWqqOH9cfKmq 3 | KciusbPRSE6vk2LuyytA0sBB05D/jT189aAHBgUrgQQAI6GBiQOBhgAEAaTbJMLv 4 | RGz/agPTR6iwSRrn1S4hz+Oy0ZWtfw2HL5HmLHFjyzVS2+VoVX26Zh97KuluBNMG 5 | OHc+V24QnzcWuIT8AT6z6TJIMZvBZx/RECivZW+x8SY68Tsnb5VVxTuYymRTyQSX 6 | bUyn4XuCWE2Yt/aykQXLWy4kamaLiHlCIhkvSRRW 7 | -----END EC PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICCzCCAW2gAwIBAgIRAMVjxr8MPwqdeGXTkzD9pM8wCgYIKoZIzj0EAwQwEjEQ 3 | MA4GA1UEChMHQWNtZSBDbzAeFw0xNTExMjIxNjA2MzhaFw0xNjExMjExNjA2Mzha 4 | MBIxEDAOBgNVBAoTB0FjbWUgQ28wgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAGk 5 | 2yTC70Rs/2oD00eosEka59UuIc/jstGVrX8Nhy+R5ixxY8s1UtvlaFV9umYfeyrp 6 | bgTTBjh3PlduEJ83FriE/AE+s+kySDGbwWcf0RAor2VvsfEmOvE7J2+VVcU7mMpk 7 | U8kEl21Mp+F7glhNmLf2spEFy1suJGpmi4h5QiIZL0kUVqNhMF8wDgYDVR0PAQH/ 8 | BAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8E 9 | BTADAQH/MB0GA1UdEQQWMBSCCWxvY2FsaG9zdIEHYUBhLmNvbTAKBggqhkjOPQQD 10 | BAOBiwAwgYcCQgEab7qaoiiCtSiFyFPHaE7xk3hlk9et1cTIttwfpJRol9PLe8L1 11 | cIX6kRnJU7pjCj+J8ktceAccIC6Qh7td/I7BtAJBQwEt2kUK8E0RdduDeYSQCelB 12 | vQrLoZa5RjhQYTyebC/3nk431yQvfPXU9i+EnQt0oeyA4KCR5TPB2ZO1qe+SQf8= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/levigross/grequests" 11 | ) 12 | 13 | func main() { 14 | cert, err := tls.LoadX509KeyPair("../cert.pem", "../key.pem") 15 | if err != nil { 16 | log.Fatalln("Unable to load cert", err) 17 | } 18 | 19 | clientCACert, err := ioutil.ReadFile("../cert.pem") 20 | if err != nil { 21 | log.Fatal("Unable to open cert", err) 22 | } 23 | 24 | clientCertPool := x509.NewCertPool() 25 | clientCertPool.AppendCertsFromPEM(clientCACert) 26 | 27 | tlsConfig := &tls.Config{ 28 | Certificates: []tls.Certificate{cert}, 29 | RootCAs: clientCertPool, 30 | } 31 | 32 | tlsConfig.BuildNameToCertificate() 33 | ro := &grequests.RequestOptions{ 34 | HTTPClient: &http.Client{ 35 | Transport: &http.Transport{TLSClientConfig: tlsConfig}, 36 | }, 37 | } 38 | resp, err := grequests.Get("https://localhost:8080", ro) 39 | if err != nil { 40 | log.Println("Unable to speak to our server", err) 41 | } 42 | 43 | log.Println(resp.String()) 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Levi Gross 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | // HelloUser is a view that greets a user 13 | func HelloUser(w http.ResponseWriter, req *http.Request) { 14 | fmt.Fprintf(w, "Hello %v! \n", req.TLS.PeerCertificates[0].EmailAddresses[0]) 15 | } 16 | 17 | func main() { 18 | certBytes, err := ioutil.ReadFile("../cert.pem") 19 | if err != nil { 20 | log.Fatalln("Unable to read cert.pem", err) 21 | } 22 | 23 | clientCertPool := x509.NewCertPool() 24 | if ok := clientCertPool.AppendCertsFromPEM(certBytes); !ok { 25 | log.Fatalln("Unable to add certificate to certificate pool") 26 | } 27 | 28 | tlsConfig := &tls.Config{ 29 | // Reject any TLS certificate that cannot be validated 30 | ClientAuth: tls.RequireAndVerifyClientCert, 31 | // Ensure that we only use our "CA" to validate certificates 32 | ClientCAs: clientCertPool, 33 | // PFS because we can 34 | CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, 35 | // Force it server side 36 | PreferServerCipherSuites: true, 37 | // TLS 1.2 because we can 38 | MinVersion: tls.VersionTLS12, 39 | } 40 | 41 | tlsConfig.BuildNameToCertificate() 42 | 43 | http.HandleFunc("/", HelloUser) 44 | 45 | httpServer := &http.Server{ 46 | Addr: ":8080", 47 | TLSConfig: tlsConfig, 48 | } 49 | 50 | log.Println(httpServer.ListenAndServeTLS("../cert.pem", "../key.pem")) 51 | } 52 | -------------------------------------------------------------------------------- /generate_client_cert.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Generate a self-signed X.509 certificate for a TLS server. Outputs to 6 | // 'cert.pem' and 'key.pem' and will overwrite existing files. 7 | 8 | package main 9 | 10 | import ( 11 | "crypto/ecdsa" 12 | "crypto/elliptic" 13 | "crypto/rand" 14 | "crypto/rsa" 15 | "crypto/x509" 16 | "crypto/x509/pkix" 17 | "encoding/pem" 18 | "flag" 19 | "fmt" 20 | "log" 21 | "math/big" 22 | "os" 23 | "time" 24 | ) 25 | 26 | var ( 27 | emailAddress = flag.String("email-address", "", "The email address of the user you wish to create the certificate for") 28 | validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011") 29 | validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for") 30 | isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority") 31 | rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set") 32 | ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521") 33 | ) 34 | 35 | func publicKey(priv interface{}) interface{} { 36 | switch k := priv.(type) { 37 | case *rsa.PrivateKey: 38 | return &k.PublicKey 39 | case *ecdsa.PrivateKey: 40 | return &k.PublicKey 41 | default: 42 | return nil 43 | } 44 | } 45 | 46 | func pemBlockForKey(priv interface{}) *pem.Block { 47 | switch k := priv.(type) { 48 | case *rsa.PrivateKey: 49 | return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} 50 | case *ecdsa.PrivateKey: 51 | b, err := x509.MarshalECPrivateKey(k) 52 | if err != nil { 53 | fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) 54 | os.Exit(2) 55 | } 56 | return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} 57 | default: 58 | return nil 59 | } 60 | } 61 | 62 | func main() { 63 | flag.Parse() 64 | 65 | if len(*emailAddress) == 0 { 66 | log.Fatalf("Missing required --email-address parameter") 67 | } 68 | 69 | var priv interface{} 70 | var err error 71 | switch *ecdsaCurve { 72 | case "": 73 | priv, err = rsa.GenerateKey(rand.Reader, *rsaBits) 74 | case "P224": 75 | priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) 76 | case "P256": 77 | priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 78 | case "P384": 79 | priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) 80 | case "P521": 81 | priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) 82 | default: 83 | fmt.Fprintf(os.Stderr, "Unrecognized elliptic curve: %q", *ecdsaCurve) 84 | os.Exit(1) 85 | } 86 | if err != nil { 87 | log.Fatalf("failed to generate private key: %s", err) 88 | } 89 | 90 | var notBefore time.Time 91 | if len(*validFrom) == 0 { 92 | notBefore = time.Now() 93 | } else { 94 | notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom) 95 | if err != nil { 96 | fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err) 97 | os.Exit(1) 98 | } 99 | } 100 | 101 | notAfter := notBefore.Add(*validFor) 102 | 103 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 104 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 105 | if err != nil { 106 | log.Fatalf("failed to generate serial number: %s", err) 107 | } 108 | 109 | template := x509.Certificate{ 110 | SerialNumber: serialNumber, 111 | Subject: pkix.Name{ 112 | Organization: []string{"Acme Co"}, 113 | }, 114 | NotBefore: notBefore, 115 | NotAfter: notAfter, 116 | 117 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 118 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, 119 | x509.ExtKeyUsageClientAuth}, 120 | BasicConstraintsValid: true, 121 | } 122 | template.DNSNames = append(template.DNSNames, "localhost") 123 | template.EmailAddresses = append(template.EmailAddresses, *emailAddress) 124 | 125 | if *isCA { 126 | template.IsCA = true 127 | template.KeyUsage |= x509.KeyUsageCertSign 128 | } 129 | 130 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) 131 | if err != nil { 132 | log.Fatalf("Failed to create certificate: %s", err) 133 | } 134 | 135 | certOut, err := os.Create("cert.pem") 136 | if err != nil { 137 | log.Fatalf("failed to open cert.pem for writing: %s", err) 138 | } 139 | pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 140 | certOut.Close() 141 | log.Print("written cert.pem\n") 142 | 143 | keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 144 | if err != nil { 145 | log.Print("failed to open key.pem for writing:", err) 146 | return 147 | } 148 | pem.Encode(keyOut, pemBlockForKey(priv)) 149 | keyOut.Close() 150 | log.Print("written key.pem\n") 151 | } 152 | --------------------------------------------------------------------------------