1

I understand there are lot of questions on this same topic out there. Unfortunately nothing solved my exact problem. So let me be specific. I'm developing a set of REST APIs which require certificate authentication(weird but its like that). So for testing we created self-signed certificates with openssl as follows:

openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 356 -nodes -subj '/CN=Lead Services'
openssl req -new -newkey rsa:4096 -keyout server.key -out server.csr -nodes -subj '/CN=*.lservices.eu'
openssl x509 -req -sha256 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out services/server.crt
openssl x509 -req -sha256 -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out services/client.crt

Now, an nginx was setup to run with these ssl certificates, requiring client authentication. The nginx config looks like this:

    listen 443 ssl;
    server_name  lacs.lservices.eu localhost;

    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_certificate certs/server.crt;
    ssl_certificate_key certs/server.key;
    ssl_client_certificate certs/ca.crt;
    ssl_verify_client on;

Testing the API with postman using the above generated client certificate worked fine. All good so far. We had created a swagger-ui and it gives an option to test directly from browser. But here the client authentication is not working.

I tried importing the CA certificates to Trusted CA store and Client certificate to Personal in my Windows 10 and tried testing from Chrome and Edge, but server rejects the calls with 400 No required SSL certificate was sent.

Update

Adding the output of openssl s_client -connect localhost:443

CONNECTED(00000218)
depth=0 CN = *.lservices.eu
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = *.lservices.eu
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/CN=*.lservices.eu
   i:/CN=Lead Services
---
Server certificate
-----BEGIN CERTIFICATE-----
......certificate content removed ---
-----END CERTIFICATE-----
subject=/CN=*.lservices.eu
issuer=/CN=Lead Services
---
Acceptable client certificate CA names
/CN=Lead Services
Client Certificate Types: RSA sign, DSA sign, ECDSA sign
Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:0x07+0x08:0x08+0x08:0x09+0x08:0x0A+0x08:0x0B+0x08:0x04+0x08:0x05+0x08:0x06+0x08:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:ECDSA+SHA1:RSA+SHA224:RSA+SHA1:DSA+SHA224:DSA+SHA1:DSA+SHA256:DSA+SHA384:DSA+SHA512
Shared Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:ECDSA+SHA1:RSA+SHA224:RSA+SHA1:DSA+SHA224:DSA+SHA1:DSA+SHA256:DSA+SHA384:DSA+SHA512
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 2245 bytes and written 455 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: C2A281888B9899B68D6734DBDACE351F7975709F887FDD1EC011419D25F67B36
    Session-ID-ctx:
    Master-Key: CE307DF955A22CA2F901E95078DDEFF5AB8668B4789B7979504DC054E1DA8400B244B10C825DB899F713BE9E3665C63A
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 5c 99 a8 3c e8 e4 c2 f3-ce 19 db fd c7 35 4f f0   \..<.........5O.
    0010 - 80 6f b7 59 19 c6 50 9c-c1 ad 2c 5c 06 30 94 05   .o.Y..P...,\.0..
    0020 - 95 02 2b af 80 07 c4 0b-fd b2 64 a3 7c 2b d7 91   ..+.......d.|+..
    0030 - 33 d0 65 3a 47 23 2c 07-6a c6 62 7f 90 bb 75 50   3.e:G#,.j.b...uP
    0040 - 18 35 ae 08 38 59 ec 59-9c 6e 77 69 40 16 49 46   .5..8Y.Y.nwi@.IF
    0050 - ce 5f b8 35 88 e1 d9 7a-6b e5 21 db 30 83 c0 b8   ._.5...zk.!.0...
    0060 - 72 37 91 d9 ce e4 e8 d4-88 e0 b0 10 2c cc c2 c6   r7..........,...
    0070 - ac 39 da 68 de cb 92 be-6c 61 77 cc cd 10 0b 47   .9.h....law....G
    0080 - ae 0b 97 40 0b 41 46 98-5b fe 1e 17 a2 40 4a 2a   ...@.AF.[....@J*
    0090 - c8 d1 02 2e 3e 73 df 8e-ac 5c 3e 85 54 a2 ee 68   ....>s...\>.T..h
    00a0 - 05 3f 76 2f 5a e4 36 dc-c4 1b c2 f1 81 70 b3 63   .?v/Z.6......p.c

    Start Time: 1577357945
    Timeout   : 300 (sec)
    Verify return code: 21 (unable to verify the first certificate)
---

How can I make it work? Is there any restrictions on self-signed certificates that it cannot be used like this? Any help appreciated!

Kris
  • 8,680
  • 4
  • 39
  • 67

2 Answers2

0

Generally:

1.) Verify if server accepts client certs from your CA. Check Acceptable client certificate CA from:

openssl s_client -connect <server:tls-port>

2.) Verify if imported personal cert matches requirements from Acceptable client certificate CA from 1.)

It is not clear how did you create client.csr - probably there is a problem. I hope you have used different key.

Jan Garaj
  • 25,598
  • 3
  • 38
  • 59
  • 'Acceptable client certificate CA names' contains the CA name which is same in Client Certificate also. I have mentioned how I have created the client certificate in the question itself. Updated the output of connect. – Kris Dec 26 '19 at 11:04
  • @Kris - as I said: It is not clear how did you generate client cert - you have showed only last command from a few. `client.csr` is a input file (signing request) in your command, but it not clear how it was created. – Jan Garaj Dec 26 '19 at 14:45
0

After trying several combinations of openssl calls, I finally figured a sequence that would allow me to generate a client certificate that Chrome would accept. I then created two shell scripts to handle the process, one responsible for generating a self-signed server certificate that doubles as CA, the other to generate the client certificate signed with it.

create_server_certificate.sh

#!/bin/bash

# Generate a self-signed certificate and validating private key.
#
# See: https://serverfault.com/a/870832/486539

FILE_PREFIX="server"
SERVER_NAME="$HOSTNAME"

# See: https://stackoverflow.com/a/14203146/476920
for i in "$@"
do
  case $i in
    --host=*)
      SERVER_NAME="${i#*=}"
    ;;
    --name=*)
      FILE_PREFIX="${i#*=}"
    ;;
    *)
      echo "Unkwnown option: \""$i'"'
      exit 1
    ;;
  esac
done

CONFIG="
[ req ]
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = san_self_signed
[ req_distinguished_name ]
CN=$SERVER_NAME
[ san_self_signed ]
subjectAltName = DNS:$SERVER_NAME, DNS:localhost
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign, cRLSign
extendedKeyUsage = serverAuth, clientAuth, timeStamping
"

SERVER_CERT="$OUTPUT_DIR/${FILE_PREFIX}_crt.pem"
SERVER_KEY="$OUTPUT_DIR/${FILE_PREFIX}_key.pem"

openssl req \
  -newkey rsa:2048 -nodes \
  -keyout "$SERVER_KEY" \
  -x509 -sha256 -days 3650 \
  -config <(echo "$CONFIG") \
  -out "$SERVER_CERT"

openssl x509 -noout -text -in "$SERVER_CERT"

create_client_certificate.sh

#!/bin/bash

# Generate a client SSL certificate and validating private key for client-side
# authentication on web browsers.
#
# See:
#
# * https://serverfault.com/a/870832/486539
# * https://blog.devolutions.net/2020/07/tutorial-how-to-generate-secure-self-signed-server-and-client-certificates-with-openssl

CLIENT_ID="client"
CLIENT_PREFIX="client"
ROOT_PREFIX="server"

# See: https://stackoverflow.com/a/14203146/476920
for i in "$@"
do
  case $i in
    --id=*)
      CLIENT_ID="${i#*=}"
    ;;
    --root=*)
      ROOT_PREFIX="${i#*=}"
    ;;
    --out=*)
      CLIENT_PREFIX="${i#*=}"
    ;;
    *)
      echo "Unkwnown option: \""$i'"'
      exit 1
    ;;
  esac
done

CLIENT_CRT="${CLIENT_PREFIX}_crt.pem"
CLIENT_REQ="${CLIENT_PREFIX}_csr.pem"
CLIENT_KEY="${CLIENT_PREFIX}_key.pem"

ROOT_CRT="${ROOT_PREFIX}_crt.pem"
ROOT_KEY="${ROOT_PREFIX}_key.pem"

CONFIG="
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
CN=$CLIENT_ID
"

# Generate client private key.
openssl ecparam -name prime256v1 -genkey -noout -out "$CLIENT_KEY"

# Generate client certificate request.
openssl req -new -sha256 -key "$CLIENT_KEY" -out "$CLIENT_REQ" -config <(echo "$CONFIG")

# Generate client certificate.
openssl x509 -req -in "$CLIENT_REQ" -CA "$ROOT_CRT" -CAkey "$ROOT_KEY" -CAcreateserial -out "$CLIENT_CRT" -days 1000 -sha256

# Generate a PFX certificate that can be imported to Google Chrome.
openssl pkcs12 -inkey "$CLIENT_KEY" -in "$CLIENT_CRT" -export -out "${CLIENT_PREFIX}.pfx"

openssl x509 -noout -text -in "$CLIENT_CRT"

rm "$CLIENT_REQ"

First run create_server_certificate.sh to generate the server / CA certificate, then create_client_certificate.sh to generate the client certificate signed by the former. Next import server_crt.pem in the Authorities tab of Chrome's Manage certificates screen, and client.pfx in the Your certificates tab.

To test your certificates, run the test OpenSSL server with:

openssl s_server -cert server_crt.pem -key server_key.pem -CAfile client_crt.pem -accept 4443 -www -Verify 1

And direct Chrome to the suitable local address, e.g. https://localhost:4443.

xperroni
  • 2,606
  • 1
  • 23
  • 29