https client authentication using X509

1,739 views
Skip to first unread message

oleg.lo...@gmail.com

unread,
May 26, 2015, 12:39:36 PM5/26/15
to golan...@googlegroups.com
dear all,
I'm new with Go.

I would like to implement an HTTPS authentication mechanism using either X509 certifcate, OAuth...

I have written an HTTPS server just as at the end of this message.

First, to see if this program can validate an user certificate, we can run:
(where /etc/certs/ca contains all known CA certificates)

$>  ./bin/main /etc/certs/ca/ mycert.pem mykey.pem
...
INFO : shains = [[0xc208306d80 0xc208189200]]
...

The ouput shows the user certificate is correctly validated


If I remove the CA validating my certificate from /etc/certs/ca, running the program displays a warning

WARN : failed to parse certificate: x509: certificate signed by unknown authority


So, I guess user certificate validation works well.


Now, I want my HTTPS server to be able to validate user certificate, but when I connect (using firefox) I have the following error

2015/05/26 17:23:12 http: TLS handshake error from [::1]:51227: tls: failed to verify client's certificate: x509: certificate signed by unknown authority


Any clue?

Thanks,
Oleg Lodygenky





HTTPS server code :

func main() {

        var logger = NewPrefixed("main#main")

        if len(os.Args) < 4 {
                logger.Fatal("Usage: %v caCertPath certPath keyPath", os.Args[0])
        }

        //
        // Retrieve CA certificates
        //
        var caRootPool, err = PopulateCertPool(os.Args[1])    // this is x509.NewCertPool.AppendCertsFromPEM() for all found files
        if err != nil {
                logger.Fatal(err.Error())
        }

        //
        // Retrieve  certificate
        //
        theCert, err := CerficateFromPEMs(os.Args[2], os.Args[3])   // this is tls.LoadX509KeyPair() and x509.ParseCertificate()
        if err != nil {
                logger.Fatal(err.Error())
        }

        //
        // Verify cert against known CA
        //
        vOpts := x509.VerifyOptions{Roots: caRootPool}
        chains, err := theCert.Verify(vOpts)
        if err != nil {
                logger.Warn("failed to parse certificate: " + err.Error())
        }
        logger.Info("shains = %v\n", chains)

        ca_b, _ := ioutil.ReadFile(os.Args[2])
        priv_b, _ := ioutil.ReadFile(os.Args[3])
        priv, _ := x509.ParsePKCS1PrivateKey(priv_b)

        cert := tls.Certificate{
                Certificate: [][]byte{ca_b},
                PrivateKey:  priv,
        }

        config := tls.Config{
                RootCAs:      caRootPool,
                Certificates: []tls.Certificate{cert},
                MinVersion: tls.VersionTLS10,
                ClientAuth: tls.VerifyClientCertIfGiven,
        }
        config.Rand = rand.Reader
        var a Authenticator

        http.Handle("/", a)
        server := http.Server{Addr: ":4040", TLSConfig: &config}

        // start https
    logger.Info("Listening HTTPS : 4040")

        server.ListenAndServeTLS(os.Args[2], os.Args[3])
}



Peter Waller

unread,
May 27, 2015, 3:58:27 AM5/27/15
to oleg.lo...@gmail.com, golang-nuts
Hi Oleg,

On 26 May 2015 at 16:24, <oleg.lo...@gmail.com> wrote:>
> config := tls.Config{
> RootCAs: caRootPool,
> Certificates: []tls.Certificate{cert},
> MinVersion: tls.VersionTLS10,
> ClientAuth: tls.VerifyClientCertIfGiven,
> }

Looking at the go doc, RootCAs seems to only be used for clients
authenticating servers:

http://golang.org/pkg/crypto/tls/#Config

Did you already try to set `ClientCAs:`?

Hope that helps,

- Peter
Message has been deleted
Message has been deleted

Oleg Lodygensky

unread,
May 27, 2015, 4:55:45 AM5/27/15
to golan...@googlegroups.com, oleg.lo...@gmail.com
thanks for your answer, I'll try that
:)


But I relaize I also have to use P12 files, using https://github.com/golang/go/issues/10621

Because I had to generate my priv/pub keys using the "-nodes" openssl option to make it works with my program, otherwise I have the following error

crypto/tls: failed to parse private key



But my certificate (.P12) stored in Firefox is encrypted



Oleg

Oleg Lodygensky

unread,
May 27, 2015, 4:59:44 AM5/27/15
to golan...@googlegroups.com
yes setting ClientCAs seems better
:)

But I now have the following error

tls: client's certificate's extended key usage doesn't permit it to be used for client authentication


Now, I'm digging into that

:)


Oleg

Oleg Lodygensky

unread,
May 27, 2015, 5:20:36 AM5/27/15
to golan...@googlegroups.com
this is certainly due to my certificate only

$> openssl x509 -in mycert.pem -inform PEM -text -noout
[...]
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            Netscape Cert Type:
                SSL Client, S/MIME, Object Signing
            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment
[...]

Oleg Lodygensky

unread,
May 27, 2015, 10:06:23 AM5/27/15
to golan...@googlegroups.com
Finally everything works
:)

First set tls.Config.ClientAuth =  tls.RequestClientCert

Then validate "by hand"


For those interested, here is the code of the HTTPS server that validates a user presenting an X509 cert






package main

/*
 * Copyrights     : CNRS
 * Author         : Oleg Lodygensky
 * Acknowledgment : XtremWeb-HEP is based on XtremWeb 1.8.0 by inria : http://www.xtremweb.net/
 * Web            : http://www.xtremweb-hep.org
 *
 *      This file is part of XtremWeb-HEP.
 *
 *    XtremWeb-HEP is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    XtremWeb-HEP is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with XtremWeb-HEP.  If not, see <http://www.gnu.org/licenses/>.
 *
 */



import (
    "crypto/rand"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)



/**
 * This is the standard main function
 */

func main() {

    var logger = NewPrefixed("main#main")

    //    verbose := flag.Bool("v", false, "enable/disable verbose mode")

    if len(os.Args) < 4 {
        logger.Fatal("Usage: %v caCertPath serverCertPath serverKeyPath", os.Args[0])

    }

    //
    // Retrieve CA certificates
    //
    caRootPool, err := PopulateCertPool(os.Args[1])
    if err != nil {
        logger.Fatal(err.Error())
    }
    logger.Info("CA pool length = %v", len(caRootPool.Subjects()))


    //
    // Retrieve certificate
    //
    serverCert, err := CerficateFromPEMs(os.Args[2], os.Args[3])

    if err != nil {
        logger.Fatal(err.Error())
    }

    logger.Info("serverCert.Subject = %v", serverCert.Subject)
    logger.Info("serverCert.Issuer  = %v", serverCert.Issuer)

    //
    // Verify user cert against known CA

    //
    vOpts := x509.VerifyOptions{Roots: caRootPool}
    chains, err := serverCert.Verify(vOpts)

    if err != nil {
        logger.Warn("failed to parse certificate: " + err.Error())
    }
    logger.Info("shains = %v\n", chains)

    ca_b, _ := ioutil.ReadFile(os.Args[2])
    priv_b, _ := ioutil.ReadFile(os.Args[3])
    priv, _ := x509.ParsePKCS1PrivateKey(priv_b)

    cert := tls.Certificate{
        Certificate: [][]byte{ca_b},
        PrivateKey:  priv,
    }

    config := tls.Config{
//        RootCAs:      caRootPool,
        ClientCAs:      caRootPool,
        Certificates: []tls.Certificate{cert},
        //MinVersion:   tls.VersionSSL30, //don't use SSLv3, https://www.openssl.org/~bodo/ssl-poodle.pdf
        MinVersion: tls.VersionTLS10,
        //MinVersion:   tls.VersionTLS11,
        //MinVersion:   tls.VersionTLS12,
//        ClientAuth: tls.VerifyClientCertIfGiven,
        ClientAuth: tls.RequestClientCert,
//        ClientAuth: tls.RequireAnyClientCert,
//        ClientAuth: tls.RequireAndVerifyClientCert,

    }
    config.Rand = rand.Reader
    var a Authenticator

    http.Handle("/", a)
    server := http.Server{Addr: ":4040", TLSConfig: &config}

    // start https
    logger.Info("Listening HTTPS : 4040")

    server.ListenAndServeTLS(os.Args[2], os.Args[3])
}

type Authenticator struct{}

func (a Authenticator) ServeHTTP(w http.ResponseWriter,r *http.Request) {

    logger := NewPrefixed("Authenticator#ServHTTP")

    logger.Debug("len(r.TLS.PeerCertificates) = %v", len(r.TLS.PeerCertificates))

    if (len(r.TLS.PeerCertificates) < 1) {
        fmt.Fprint(w, "<p>You are not logged in</p>")
        return
    }

    for i, c := range r.TLS.PeerCertificates {
        logger.Debug("r.TLS.PeerCertificates[%v].Subject = %v", i, c.Subject)
        logger.Debug("r.TLS.PeerCertificates[%v].Issuer = %v", i, c.Issuer)
        for j, a := range c.EmailAddresses {
            logger.Debug("r.TLS.PeerCertificates[%v].EmailAddresses[%v] = %v", i, j, a)
        }
    }
    logger.Debug("len(r.TLS.VerifiedChains) = %v", len(r.TLS.VerifiedChains))


    caRootPool, err := PopulateCertPool(os.Args[1])
    if err != nil {
        fmt.Fprint(w, "<p>Internal error : cant retreive CA Rool Pool</p>")
        return
    }
    logger.Debug("CA pool length = %v", len(caRootPool.Subjects()))


    vOpts := x509.VerifyOptions{Roots: caRootPool}

    userCert := r.TLS.PeerCertificates[0]
    chains, err := userCert.Verify(vOpts)

    if err != nil {
        logger.Warn("failed to parse certificate: " + err.Error())
        fmt.Fprint(w, "<p>Certificate error: can't validate your certificate</p>")
        return

    }

    logger.Info("shains = %v\n", chains)

    fmt.Fprint(w, "<p>You are logged in as ", userCert.EmailAddresses[0], "</p>")
}

Reply all
Reply to author
Forward
0 new messages