Go proper TLS

Yksinekrtainen pannu

Ominaisuudet:

  • HTTP Strict Transport Security on lisätty
  • Vain turvallisimmat salausalgoritmit (standardoidut, mukana Go:n TLS-toteutuksessa) ovat sallittuja
  • Vain TLS 1.2+ on sallittu
package main

import (
    "crypto/tls"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Strict-Transport-Security", "max-age=15768000; includeSubDomains")
        w.Write([]byte("Hello world!\n"))
    })

    tls_cfg := &tls.Config{
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
        },
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        MinVersion:               tls.VersionTLS12,
        PreferServerCipherSuites: true,
    }

    srv := &http.Server{
        Addr:         ":443",
        TLSConfig:    tls_cfg,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }

    log.Fatal(srv.ListenAndServeTLS("markonnakkijadata.crt", "markonnakkijadata.key"))

}

SSL Server Test antaa palvelimen TLS-toteutukselle arvosanaksi A+, kun käytössä on tarpeeksi pitkät salausavaimet (RSA >= 4096 tai vastaava).

Hmm. Mitähän sitten..

Varmennepohjainen tunnistautuminen

hhmm

package main

import (
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Strict-Transport-Security", "max-age=15768000; includeSubDomains")
        w.Write([]byte("Hello world!\n"))

        // Tulostetaan asiakkaan varmenteiden CommonName-attribuutit
        // Huom! Asiakas saattaa lähettää myös varmentajien varmenteet
        for _, x := range req.TLS.PeerCertificates {
            log.Println(x.Subject.CommonName)
        }
    })

    // Ladataan varmentajien varmenteet
    capool := x509.NewCertPool()
    cacert, err := ioutil.ReadFile("markonnakkijaCA.crt")
    if err != nil {
        panic(err)
    }
    capool.AppendCertsFromPEM(cacert)

    tls_cfg := &tls.Config{
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
        },
        ClientCAs:                capool,                         // Asiakkaiden varmenteiden hyväksytyt varmentajat
        ClientAuth:               tls.RequireAndVerifyClientCert, // Asiakkaiden täytyy tunnistautua varmenteilla
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        MinVersion:               tls.VersionTLS12,
        PreferServerCipherSuites: true,
    }

    srv := &http.Server{
        Addr:         ":443",
        TLSConfig:    tls_cfg,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }

    log.Fatal(srv.ListenAndServeTLS("markonnakkijadata.crt", "markonnakkijadata.key"))

}

Sisääntulevan varmenteen parsiminen

hghg

package main

import (
    "context"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "net"
    "net/http"
    "regexp"
    "strings"
)

// Avain contextia varten
type key string

const certKey key = "cert"

func HelloCertificate(w http.ResponseWriter, r *http.Request) {
    // Varmenne on ladattavissa pyyntöjen kontekstista
    varmenne := r.Context().Value(certKey).(x509.Certificate)
    fmt.Fprintf(w, "Varmenteen CN: %s", varmenne.Subject.CommonName)
}

func newContextWithCertificate(ctx context.Context, req *http.Request) context.Context {
    // Luetaan edusta-webbipalvelimen asettama otsaketieto, jossa varmenteen pitäisi tulla
    header := req.Header.Get("TLS_CLIENT_CERT")
    // Luetaan pyynnön tehnyt osoite
    ip, _, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        panic("failed to determine client IP: " + err.Error())
    }

    // Jos otsaketieto puuttuu, keskeytetään käsittely
    if header == "" {
        panic("Asiakkaalta ei tullut otsaketta TLS_CLIENT_CERT")
    }
    // Jos pyyntö ei tullut edusta-webbipalvelimelta, keskeytetään käsittely tietoturvasyistä
    if ip != "192.168.122.222" {
        panic("Pyyntö ei tullut edusta-webbipalvelimelta")
    }

    // Normalisoidaan otsaketieto
    varmenne := normalizePem(req.Header.Get("TLS_CLIENT_CERT"))

    // Muunnetaan varmenne ASN.1 DER-muotoon
    block, _ := pem.Decode([]byte(varmenne))
    if block == nil {
        panic("failed to parse certificate PEM")
    }

    // Parsitaan varmenne Go:n x509-objektiksi
    x509cert, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
        panic("failed to parse certificate: " + err.Error())
    }

    // Tässä vaiheessa voidaan esim. suorittaa lisää varmenteen tarkistuksia, tai
    // tehdä kyselyitä käyttäjähakemistoon...

    // Palautetaan uusi konteksti, johon on tallennettu asiakkaan varmenne
    return context.WithValue(ctx, certKey, *x509cert)
}

// httpd muuttaa varmenteen rivinvaihdot välilyönneiksi
// pem.Decode vaatii, että rivinvaihdot ovat entisellään, joten nopea viritys
func normalizePem(in string) string {
    r, _ := regexp.Compile("^---[-]+BEGIN[^-]+---[-]+([^-]*)--[-]+END [^-]+---[-]+$")
    if r.MatchString(in) {
        rs := r.FindAllStringSubmatch(in, -1)[0]
        return "-----BEGIN CERTIFICATE-----\n" +
            strings.Replace(strings.TrimSpace(rs[1]), " ", "\n", -1) +
            "\n-----END CERTIFICATE-----\n"
    }
    return in
}

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        // Muodostetaan uusi konteksti, jossa on asiakkaan varmenne
        ctx := newContextWithCertificate(req.Context(), req)
        next.ServeHTTP(rw, req.WithContext(ctx))
    })
}

func main() {
    HelloCertificateHandler := http.HandlerFunc(HelloCertificate)
    // Kääritään varsinainen palvelu middlewaren sisään
    http.Handle("/", middleware(HelloCertificateHandler))
    http.ListenAndServe(":8080", nil)
}

results matching ""

    No results matching ""