How to access the client's certificate in Go? - ssl

Below is my code for an HTTPS server in Go. The server requires the client to provide an SSL certificate, but doesn't verify the certificate (by design).
You can generate a private/public key pair as follows if you'd like to give it a try:
$ openssl genrsa -out private.pem 2048
$ openssl req -new -x509 -key private.pem -out public.pem -days 365
Does anyone know how I can access the client certificate in the handler? Let's say I would like to report some properties about the certificate the client presented back to the client.
Thanks,
Chris.
package main
import (
"fmt"
"crypto/tls"
"net/http"
)
const (
PORT = ":8443"
PRIV_KEY = "./private.pem"
PUBLIC_KEY = "./public.pem"
)
func rootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Nobody should read this.")
}
func main() {
server := &http.Server{
TLSConfig: &tls.Config{
ClientAuth: tls.RequireAnyClientCert,
MinVersion: tls.VersionTLS12,
},
Addr: "127.0.0.1:8443",
}
http.HandleFunc("/", rootHandler)
err := server.ListenAndServeTLS(PUBLIC_KEY, PRIV_KEY)
if err != nil {
fmt.Printf("main(): %s\n", err)
}
}

Seems r *http.Request has TLS *tls.ConnectionState field which in turn has PeerCertificates []*x509.Certificate field, so
fmt.Fprint(w, r.TLS.PeerCertificates)
probably can do

Related

converting .pem keys to .der compiles but results in errors

Calling my hyper based API now ported to HTTPS, with Python's requests I'm getting
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)' on every request.
As per the docs for tokio_rustls:
Certificate has to be DER-encoded X.509
PrivateKey has to be DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format.
The keys I used in my PKEY and CERT variables are my certbot generated .pem keys converted to .der format using those commands:
openssl x509 -outform der -in /etc/letsencrypt/live/mysite.com/cert.pem -out /etc/letsencrypt/live/mysite.com/cert.der
openssl pkcs8 -topk8 -inform PEM -outform DER -in /etc/letsencrypt/live/mysite.com/privkey.pem -out /etc/letsencrypt/live/mysite.com/privkey.der -nocrypt
And loaded up with include_bytes!() macro.
Well it compiles, polls... and just throws this error on every request Bad connection: cannot decrypt peer's message whilst the caller gets the SSLError mentioned in the beginning.
Here is the script used for the API:
fn tls_acceptor_impl(cert_der: &[u8], key_der: &[u8]) -> tokio_rustls::TlsAcceptor {
let key = PrivateKey(cert_der.into());
let cert = Certificate(key_der.into());
Arc::new(
ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.unwrap(),
)
.into()
}
fn tls_acceptor() -> tokio_rustls::TlsAcceptor {
tls_acceptor_impl(PKEY, CERT)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(...);
let mut listener = tls_listener::builder(tls_acceptor())
.max_handshakes(10)
.listen(AddrIncoming::bind(&addr).unwrap());
let (tx, mut rx) = mpsc::channel::<tokio_rustls::TlsAcceptor>(1);
let http = Http::new();
loop {
tokio::select! {
conn = listener.accept() => {
match conn.expect("Tls listener stream should be infinite") {
Ok(conn) => {
let http = http.clone();
// let tx = tx.clone();
// let counter = counter.clone();
tokio::spawn(async move {
// let svc = service_fn(move |request| handle_request(tx.clone(), counter.clone(), request));
if let Err(err) = http.serve_connection(conn, service_fn(my_query_handler)).await {
eprintln!("Application error: {}", err);
}
});
},
Err(e) => {
eprintln!("Bad connection: {}", e);
}
}
},
message = rx.recv() => {
// Certificate is loaded on another task; we don't want to block the listener loop
let acceptor = message.expect("Channel should not be closed");
}
}
}
How can I make any sense of the errors, when the certificate keys work on Web (as they are the apache2 server's keys)? I've tried various other encodings, that are against the docs, and all fail in the same way.
I'm not familiar enough with rust, but I know that proper configuration of a TLS server (no matter which language) requires the server certificate, the key matching the certificate and all intermediate CA needed to build the trust chain from server certificate to the root CA on the clients machine. These intermediate CA are not provided in your code. That's why the Python code fails to validate the certificate.
What you need is probably something like this:
ServerConfig::builder()
....
.with_single_cert(vec![cert, intermediate_cert], key)
Where intermediate_cert is the internal representation of the Let’s Encrypt R3 CA, which you can find here.

SSL implementation with Tungstenite: SSL alert number 42

I created a working WebSocket server with async_tungstenite and async_std.
I now want to add SSL using async_native_tls.
If I understood correctly, this crates provides a function accept which takes a TcpStream, handles the TLS handshake and provides a TlsStream<TcpStream> which should behave like a TcpStream but handles the encryption and decryption behind the scene.
To test the server, I created a self-signed certificate.
Based on that, here is how the code handling new TCP connections evolved:
async fn accept_connection(stream: TcpStream, addr: SocketAddr) {
//Websocket stream
let accept_resut = async_tungstenite::accept_async(stream).await;
if let Err(err) = accept_resut {
println!(
"Error while trying to accept websocket: {}",
err.to_string()
);
panic!(err);
}
println!("New web socket: {}", addr);
}
async fn accept_connection(stream: TcpStream, addr: SocketAddr) {
//Open tls certificate !should be done one time and not for each connection!
let file = File::open("identity.pfx").await.unwrap();
let acceptor_result = TlsAcceptor::new(file, "glacon").await;
if let Err(err) = acceptor_result {
println!("Error while opening certificate: {}", err.to_string());
panic!(err);
}
let acceptor = acceptor_result.unwrap();
//Get a stream where tls is handled
let tls_stream_result = acceptor.accept(stream).await;
if let Err(err) = tls_stream_result {
println!("Error during tls handshake: {}", err.to_string());
panic!(err);
}
let tls_stream = tls_stream_result.unwrap();
//Websocket stream
let accept_resut = async_tungstenite::accept_async(tls_stream).await;
if let Err(err) = accept_resut {
println!(
"Error while trying to accept websocket: {}",
err.to_string()
);
panic!(err);
}
println!("New web socket: {}", addr);
}
With this implementation, I now call from a webpage
const sock = new WebSocket('wss://localhost:8020');
This results in the error:
Error while trying to accept websocket:
IO error: error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate:../ssl/record/rec_layer_s3.c:1543:SSL alert number 42
thread 'async-std/runtime' panicked at 'Box<Any>', src/main.rs:57:9
It seems like the handshake was successful as the error does not occur during the acceptor.accept. The error states that the certificate is not valid so here is how I created my self-signed certificate.
The openssl version is 1.1.1f
# Create a key
openssl req -nodes -new -key server.key -out server.csr
# Create the self-signed certificate
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
# Convert the certificate to pfx format
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt
I thought that this problem had to do with security feature from the browser as the "SSL alert number 42" seems to come from the client. I tried to disable this option in Firefox settings
Query OCSP responder servers to confirm the current validity of certificates
I also tried to add my server.crt to the Authorities of the certificate manager.
Neither of these worked.
The problem came from the security features of Firefox.
Firefox detects that the certificate is not signed by an authority and sends back an error.
It seems like adding the certificate to the known authorities does not work.
To avoid this issue, I found this thread which indicates that an exception should be added for the address and port of your development Websocket server.
Go to Settings > Certificates > View Certificates > Servers > Add Exception...
Type in your local server (for me localhost:8020).
Add exception.

GOlang/https: timeout waiting for client preface

I switched SSL by using ListenAndServeTLS
func main() {
serverMux := http.NewServeMux()
serverMux.HandleFunc("/v1/ws1", handler1)
serverMux.HandleFunc("/v1/ws2", handler2)
serverMux.HandleFunc("/v1/ws3", handler3)
serverMux.HandleFunc("/static/", handlerStatic(http.FileServer(http.Dir("/var/project/"))))
go func() {
wsSSLServer := &http.Server{
Addr: ":443",
Handler: serverMux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
certPath := "/etc/letsencrypt/live/example.com/"
fmt.Println(wsSSLServer.ListenAndServeTLS(certPath+"fullchain.pem", certPath+"privkey.pem"))
}()
wsServer := &http.Server{
Addr: ":80",
Handler: serverMux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
fmt.Println(wsServer.ListenAndServe())
}
and now I get lots of these errors in the logs:
http2: server: error reading preface from client x.x.x.x:xxxxx:
timeout waiting for client preface
what does it mean?
I got the same error using Firefox as the client. Regenerating the SSL key/cert solved the issue, I guess the certificate expired.
For localhost development:
openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days XXX -subj '/CN=localhost'

Go TLS connection

I connect to some server via openssl:
openssl s_client -crlf -connect somehost.com:700 -cert key.pem
And it works. Connection is successful.
But when I tried to do same from Go code (example from documentation), it doesn't work for me:
import (
"crypto/tls"
"crypto/x509"
)
func main() {
// Connecting with a custom root-certificate set.
const rootPEM = `
-----BEGIN CERTIFICATE-----
my key text
-----END CERTIFICATE-----`
// First, create the set of root certificates. For this example we only
// have one. It's also possible to omit this in order to use the
// default root set of the current operating system.
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
panic("failed to parse root certificate")
}
conn, err := tls.Dial("tcp", "somehost.com:700", &tls.Config{
RootCAs: roots,
})
if err != nil {
panic("failed to connect: " + err.Error())
}
conn.Close()
}
My text error is:
panic: failed to connect: x509: certificate is valid for otherhost.com, not somehost.com [recovered]
Question: what did I do wrong? And maybe I didn't add some tls.Config parameters?
openssl s_client is just a test tool which connects but it does not care much if the certificate is valid for the connection. Go instead cares if the certificate could be validated, so you get the information that the certificate is invalid because the name does not match.
what did I do wrong?
Based on the error message you did access the host by the wrong hostname. Or you've configured your server badly so that it sends the wrong certificate.
I didn't need to check ssl certificate of server. It was demo server of some domain registry. So I need server to check my certificate.
const certPEM = `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
`
const certKey = `-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----`
cert, err := tls.X509KeyPair([]byte(certPEM), []byte(certKey))
if err != nil {
t.Error("server: loadkeys: %s", err)
}
cfg := tls.Config{
InsecureSkipVerify: true,
ServerName: "somehost.com",
Certificates: []tls.Certificate{cert},
}
conn, err := tls.Dial("tcp", "somehost.com:700", &cfg)
if err != nil {
t.Error("failed to connect: " + err.Error())
}
defer conn.Close()
So this code works in my case.

TLS with selfsigned certificate

I'm trying to establish a TLS connection with the use of a self signed server certificate.
I generated the certificate with this example code: http://golang.org/src/pkg/crypto/tls/generate_cert.go
My relevant client code looks like that:
// server cert is self signed -> server_cert == ca_cert
CA_Pool := x509.NewCertPool()
severCert, err := ioutil.ReadFile("./cert.pem")
if err != nil {
log.Fatal("Could not load server certificate!")
}
CA_Pool.AppendCertsFromPEM(severCert)
config := tls.Config{RootCAs: CA_Pool}
conn, err := tls.Dial("tcp", "127.0.0.1:8000", &config)
if err != nil {
log.Fatalf("client: dial: %s", err)
}
And the relevant server code like that:
cert, err := tls.LoadX509KeyPair("./cert.pem", "./key.pem")
config := tls.Config{Certificates: []tls.Certificate{cert}}
listener, err := tls.Listen("tcp", "127.0.0.1:8000", &config)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("server: accept: %s", err)
break
}
log.Printf("server: accepted from %s", conn.RemoteAddr())
go handleConnection(conn)
}
Because the server certificate is self signed is use the same certificate for the server and the clients CA_Pool however this does not seem to work since i always get this error:
client: dial: x509: certificate signed by unknown authority
(possibly because of "x509: invalid signature: parent certificate
cannot sign this kind of certificate" while trying to verify
candidate authority certificate "serial:0")
What's my mistake?
It finally worked with the go built in x509.CreateCertificate,
the problem was that I did not set the IsCA:true flag,
I only set the x509.KeyUsageCertSign which made creating the self signed certificate work, but crashed while verifying the cert chain.
The problem is that you need a CA certificate in the server-side config, and this CA must have signed the server's certificate.
I have written some Go code that will generate a CA certificate, but it hasn't been reviewed by anyone and is mostly a toy for playing around with client certs. The safest bet is probably to use openssl ca to generate and sign the certificate. The basic steps will be:
Generate a CA Certificate
Generate a Server key
Sign the Server key with the CA certificate
Add the CA Certificate to the client's tls.Config RootCAs
Set up the server's tls.Config with the Server key and signed certificate.
Kyle, is correct. This tool will do what you want and it simplifies the entire process:
https://github.com/deckarep/EasyCert/releases (only OSX is supported since it uses the openssl tool internally)
and the source:
https://github.com/deckarep/EasyCert
Basically with this tool it will generate a bundle of files but you will need the three that it outputs when it's done.
a CA root cer file
a Server cer file
a Server key file
In my case, the certificate I appended was not encoded correctly in pem format.
If using keytools, ensure to append -rfc while exporting the certificate from keystore, pem encoded could be opened in a text editor to display:
-----BEGIN CERTIFICATE-----
MIIDiDCCAnCgAwIBAgIEHKSkvDANBgkqhkiG9w0BAQsFADBi...
I saw the same error when using mysql client in Go:
Failed to connect to database: x509: cannot validate certificate for 10.111.202.229 because it doesn't contain any IP SANs
and setting InsecureSkipVerify to true (to skip verification of certificate) resolved it for me:
https://godoc.org/crypto/tls#Config
The following code worked for me:
package main
import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
)
func main() {
rootCertPool := x509.NewCertPool()
pem, err := ioutil.ReadFile("/usr/local/share/ca-certificates/ccp-root-ca.crt")
if err != nil {
log.Fatal(err)
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
log.Fatal("Failed to append root CA cert at /usr/local/share/ca-certificates/ccp-root-ca.crt.")
}
mysql.RegisterTLSConfig("custom", &tls.Config{
RootCAs: rootCertPool,
InsecureSkipVerify: true,
})
db, err := gorm.Open("mysql", "ccp-user:I6qnD6zNDmqdDLXYg3HqVAk2P#tcp(10.111.202.229:3306)/ccp?tls=custom")
defer db.Close()
}
You need to use the InsecureSkipVerify flag, refer to https://groups.google.com/forum/#!topic/golang-nuts/c9zEiH6ixyw.
The related code of this post (incase the page is offline):
smtpbox := "mail.foo.com:25"
c, err := smtp.Dial(smtpbox)
host, _, _ := net.SplitHostPort(smtpbox)
tlc := &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
if err = c.StartTLS(tlc); err != nil {
fmt.Printf(err)
os.Exit(1)
}
// carry on with rest of smtp transaction
// c.Auth, c.Mail, c.Rcpt, c.Data, etc