0

I have a bunch of clients (too many to easily retrofit) each holding a single certificate (signed by a non-standard CA over which I have no control; I just generated the CSRs).

Now I need to setup a "secured" web service where server is identified in the usual ssl (https) way and client should be identified using its certificate (Subject is already holding all info I need to uniquely identyfy client).

Unfortunately in the signing process almost all "purpose" flags have been cleared:

openssl x509 -in 57EMM020001.cer -noout -purpose 
Certificate purposes:
SSL client : No
SSL client CA : No
SSL server : No
SSL server CA : No
Netscape SSL server : No
Netscape SSL server CA : No
S/MIME signing : Yes
S/MIME signing CA : No
S/MIME encryption : No
S/MIME encryption CA : No
CRL signing : No
CRL signing CA : No
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : No
Time Stamp signing : No
Time Stamp signing CA : No

I assume "culprit" for "400 The SSL certificate error" is the first one: "SSL client : No".

Is there any way to tell my local nginx server (over which I have full control) to disregard these settings?

I fully understand this is "bad" in general because if a certificate was not generated for a specific purpose... well, it shouldn't be used for that purpose! but this is my, very specific, server and I feel I'm entitled to decide who I trust.

Is there some way to convince nginx to do things in a slightly looser way?

My current (not working) setup is quite simple:

server {
    listen 443 ssl;

    resolver 127.0.0.1 8.8.8.8;
    set $backend "http://localhost:5000";

    server_name updates.example.com;

    ssl_certificate /etc/ssl/certs/server.pem;
    ssl_certificate_key /etc/ssl/private/server.key;

    ssl_client_certificate /etc/ssl/certs/CAroot.cer;
    ssl_verify_client on;

    location / {
        proxy_connect_timeout   60;
        proxy_read_timeout      60;
        proxy_send_timeout      60;
        proxy_intercept_errors  off;
        proxy_http_version      1.1;
        proxy_set_header        Host               $http_host;
        proxy_set_header        X-Real-IP          $remote_addr;
        proxy_set_header        X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto  $scheme;
        proxy_set_header        X-Client-Subject   $ssl_client_s_dn;
        proxy_pass $backend;
    }    
}

Of course I checked that:

openssl verify -verbose -CAfile /etc/ssl/certs/CAroot.cer 57EMM020001.cer 
57EMM020001.cer: OK

Thanks in Advance

ZioByte
  • 296
  • 4
  • 17
  • How will you also configure every web browser in the world to ignore the bad certificate? There is no way to do it. You need to get the CA fixed. – Michael Hampton Sep 23 '20 at 16:43
  • @MichaelHampton: That's not needed nor wanted. This is needed to implement a RESTful API between some (20k+) embedded targets and a single server (essentially for updating purposes). Requests are placed by a python client (no browser involved at all) and served by a python/flask backend (nginx used for reverse proxy). Please see answer I posted for whoever may have similar problems; if You can come up with a more elegant solution I'll be glad to accept it. – ZioByte Sep 23 '20 at 17:46
  • If you control all the clients then yes, you can make it work. Others who read this question because they have a similar issue might not be so fortunate. Maybe someone is able to find another answer, but fixing the misbehaving CA is still a good idea. – Michael Hampton Sep 23 '20 at 17:50
  • It surely is a good idea, but utterly unfeasible as this specific CA is State owned and I hav e NO way to even ask for a fix. I'm actually trying to use a certificate meant to be used in validating reporting to said State Agency for a completely different purpose (receive trusted software updates). Rationale behind this is that certificate is already securely installed on targets and recalling 20k+ machines for refurbishing would be "inconvenient". – ZioByte Sep 23 '20 at 18:08

1 Answers1

1

I found a workable solution, but that's not really elegant so I will leave this unaccepted for a while in hope someone can come up with a better solution.

Removing ssl_client_certificate and switching to ssl_verify_client optional_no_ca will actually fetch the client certificate, but it will not try to check it so the backend can do its checks autonomously.

My current nginx setup is:

server {
    listen 443 ssl;

    resolver 127.0.0.1 8.8.8.8;
    set $backend "http://localhost:5000";

    server_name updates.example.com;

    ssl_certificate /etc/ssl/certs/server.pem;
    ssl_certificate_key /etc/ssl/private/server.key;

    ssl_verify_client optional_no_ca;

    location / {
        proxy_connect_timeout   60;
        proxy_read_timeout      60;
        proxy_send_timeout      60;
        proxy_intercept_errors  off;
        proxy_http_version      1.1;
        proxy_set_header        Host               $http_host;
        proxy_set_header        X-Real-IP          $remote_addr;
        proxy_set_header        X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto  $scheme;
        proxy_set_header        X-Client-Subject   $ssl_client_s_dn;
        proxy_set_header        X-Client-Cert      $ssl_client_cert;
        proxy_pass $backend;
    }
}

Backend (a simple python/flask installation) includes:

def get_id(headers):
    cer = headers.get('X-Client-Cert')
    with NamedTemporaryFile('w+') as ntf:
        for lin in cer.splitlines():
            ntf.write(lin.strip())
            ntf.write('\n')
        ntf.flush()
        p = run(['openssl', 'verify', '-CAfile', cafile, ntf.name])
    if p.returncode == 0:
        m = search(r'/CN=([~/]+)', headers.get('X-Client-Subject'))
        if m:
            return m.group(1)
    return None
ZioByte
  • 296
  • 4
  • 17