SSL Zertifikat erstellen (& unter sparkjava verwenden)

Hallo,

Da meine Antwort ein bißchen umfangreicher ausfällt habe ich sie in Abschnitte unterteilt.


1. Hintergrund

Da mir meine Daten sehr wichtig sind habe ich mir vor längerer Zeit einen eigenen DNS-Server (dnsmasq) auf einer Linux-Kiste eingerichtet, um Google und Konsorten auszubremsen (d.h. keine Datenspuren bei ihnen zu hinterlassen).
Also im Prinzip das was heute unter dem Namen "Pi-Hole" vielen bekannt sein dürfte.
So werden z.B.

"*.google-analytics.com",
"*.googletagmanager.com" und
"*.googlesyndication.com"

per DNS "hart geerdet".
Da aber manche Sachen (z.B. JS-Libs) von Google und Konsorten durchaus nützlich sind, werden etwa

https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.6/cookieconsent.min.css
https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.min.css

zuerst über den DNS auf "127.0.0.1" aufgelöst und dann über einen lokal laufenden ("127.0.0.1") und in Java geschrieben HTTPS-Server ausgeliefert.
Als Haupt-Vorteile sind zu nennen:
  • ich hinterlasse keine Datenspuren (Stichwort Referrer) bei Google & Co.
  • schnellere Auslieferung, da lokal
  • in den Logs sehe ich welchen "unerwünschten Datenverkehr" manche Seite erzeugen
  • für einige JS-Dateien lassen sich auch "verbesserte" ;-) Versionen ausliefern!

Mein Java-HTTPS-Server basiert im Kern auf

https://stackoverflow.com/questions/2308479/simple-java-https-server --> SimpleHTTPSServer

ist aber in Sachen Funktionalität stark aufgebohrt.

Damit der Datenverkehr ordnungsgemäß verschlüsselt über https ablaufen kann sind natürlich auf dem Server und Client (z.B. Browser) Zertifikate notwendig.
Wie diese erzeugt werden und wo man sie wie hinpackt beschreibt der folgende Anschnitt:


2. Server-Seite

2.1 Schlüssel und Zertifikate


Die nachfolge Anleitung basiert hauptsächlich auf

https://jamielinux.com/docs/openssl-certificate-authority/introduction.html

, ich habe sie zur leichteren Nachvollziehbarkeit vereinfacht. Daher taugt diese NICHT für den professionellen Gebrauch!!!
Sie ist zu Übungszwecken und für den "Hausgebrauch" geeignet.
So wird z.B. immer wenn ein Passwort gefordert ist einfach "password" genutzt.

Für die Server-Seite werden 3 Schlüssel und 3 Zertifikate (jeweils in der Ausprägung Root-, Intermediate- und Server-) erzeugt.
Benötigte Werkzeuge/Programme sind:

Eine Übersicht für wichtige openssl-Befehle ist auf

https://wiki.openssl.org/index.php/Command_Line_Utilities

zu finden.



2.1.1 Root-Key / Root-Zertifikat

Zuerst wird der 4096-Bit Root-Key erzeugt. Dies geschieht per

openssl genrsa -aes256 -out root.key.pem 4096

Bei der Frage nach dem Passwort geben wir (zweimal) "password" an.
Um sich das Ergebnis auszusehen tippt man

openssl rsa -in root.key.pem -text

ein.

Zur Erzeugung des Root-Zertikates benötigen wir eine Konfigurationsdatei, dazu den folgenden Text als "openssl_root.cnf" abspeichern:

Code:
# based on https://jamielinux.com/docs/openssl-certificate-authority/appendix/root-configuration-file.html

# OpenSSL root CA configuration file.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
new_certs_dir     = .
database          = index.txt
serial            = serial.txt

# The root key and root certificate.
private_key       = root.key.pem
certificate       = root.cert.pem


# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = FR
stateOrProvinceName_default     = Paris
localityName_default            = Paris
0.organizationName_default      = DS Global Digital Certificates
organizationalUnitName_default  =
commonName_default              = DS Global Digital Certificates Root Cert
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

Wie man sieht wird das Zertifikat von der fiktiven Firma "DS Global Digital Certificates" in Paris, Frankreich ausgestellt.
Eine kleine Hommage an die Citroen DS!
Diese Daten können natürlich nach eigenem Gusto verändert werden.
Das Root-Zertifikat wird mit

openssl req -config openssl_root.cnf -key root.key.pem -new -x509 -days 5844 -sha256 -extensions v3_ca -out root.cert.pem

erzeugt und läuft 16 Jahre(=5844 Tage).
Bei der Frage nach der "pass phrase" einfach wieder "password" angeben, bei den anderen Fragen einfach "Return" drücken.

Um sich das Root-Zertifikat anzusehen gibt es den Befehl

openssl x509 -noout -text -in root.cert.pem



2.1.2 Intermediate-Key / Intermediate-Zertifikat

Bei professionellen Zertifikaten wird in aller Regel ein Intermediate (= Zwischen)-Zertifikat ausgestellt.
Auf die Gründe gehe ich hier nicht ein!

Die Erzeugung des Intermediate-Keys erfolgt analog zu oben per

openssl genrsa -aes256 -out intermediate1.key.pem 4096

Bei der Frage nach dem Passwort geben wir wieder (zweimal) "password" an.
Ansehen kann man sich das Ganze wieder mit

openssl rsa -in intermediate1.key.pem -text

Für den nächsten Schritt benötigen wir wieder ein Konfigurations-Datei, die wie folgt aussieht:

Code:
# based on https://jamielinux.com/docs/openssl-certificate-authority/appendix/intermediate-configuration-file.html

# OpenSSL intermediate CA configuration file.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
new_certs_dir     = .
database          = intermediate_index.txt
serial            = intermediate_serial.txt

########################################################### ALF
copy_extensions = copy

# The root key and root certificate.
private_key       = intermediate1.key.pem
certificate       = intermediate1.cert.pem


# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = FR
stateOrProvinceName_default     = Paris
localityName_default            = Paris
0.organizationName_default      = DS Global Digital Certificates
organizationalUnitName_default  =
commonName_default              = DS Global Digital Certificates Intermediate X1 2019
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

Obige Zeilen als "openssl_intermediate1.cnf" abspeichern.
Dann einen sog. "certificate signing request" (csr) mittels

openssl req -config openssl_intermediate1.cnf -new -sha256 -key intermediate1.key.pem -out intermediate1.csr.pem

generieren. Die Frage(n) nach dem Passwort wie üblich mit "password" benantworten, die übrigen einfach mit "Return" bestätigen.

Als Voraussetzung für den nächsten Schritt benötigen wir noch die Datei "index.txt", die per

touch index.txt

erzeugt wird und die Datei "serial.txt", welche ihrerseits mit

echo 1000 >serial.txt

mit dem richtigen Inhalt befüllt wird.
Jetzt wird das Intermediate-Zertifikat mit

openssl ca -config openssl_root.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in intermediate1.csr.pem -out intermediate1.cert.pem

signiert. Frage nach dem Passwort wie üblich mit "password", die Frage, ob das Zertifikat signiert werden soll mit "y" beantworten.

Jetzt haben wir ein signiertes Zwischen-Zertifikat ("intermediate1.cert.pem"), dessen Inhalt wir mit

openssl x509 -noout -text -in intermediate1.cert.pem

begutachten können.
Ob die Signierung auch erfolgreich geklappt hat kann man mit

openssl verify -CAfile root.cert.pem intermediate1.cert.pem

überprüfen.



2.1.3 Server-Key / Server-Zertifikat

Der Server-Key wird

openssl genrsa -out server1.key.pem 2048

erzeugt. Hierbei fällt gegenüber oben auf, daß hier "nur" mit 2048 Bits gearbeitet wird und daß nicht nach einem Passwort gefragt wird, dies liegt daran, daß der Parameter "-aes256" fehlt. Wer diese "Sicherheitslücke" nicht haben will kann auch mit Passwort arbeiten.

Um den "certificate signing request" (CSR) für das Server-Zertifikat zu erzeugen, benötigen wir wieder eine Konfigurations-Datei "openssl_server1.cnf", die wie folgt aussieht

Code:
[req]
distinguished_name = req_distinguished_name
req_extensions     = v3_req
x509_extensions    = v3_ca
#prompt = no

[req_distinguished_name]
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = DE
stateOrProvinceName_default     = Bayern
localityName_default            = Munich
0.organizationName_default      = HTTPS Secure Web Services GmbH
organizationalUnitName_default  =
commonName_default              = Server S1
emailAddress_default            =

[v3_req]
subjectAltName = @alt_names
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth

[v3_ca]
subjectAltName = @alt_names

[alt_names]
DNS.1=*.googleapis.com
DNS.2=*.googlesyndication.com
DNS.3=*.googleadservices.com
DNS.4=*.googletagmanager.com
DNS.5=*.googletagservices.com
DNS.6=*.google-analytics.com
DNS.7=apis.google.com
DNS.8=*.cloudflare.com
DNS.9=*.jsdelivr.net
DNS.10=*.citroends4.de
DNS.11=*.citroends4.com


IP.1=127.0.0.1
IP.2=192.168.12.34

Man sieht, daß das Server-Zertifikat auf die fiktive Firma "HTTPS Secure Web Services GmbH", die sich in München/Bayern/Deutschland befindet, ausgestellt ist.
Der Name des Zertifikates ist "Server S1".
Diese Angaben kann man wieder nach den persönlichen Bedürfnissen anpassen.
Der wichtige(re) Teil befindet sich allerdings ganz unten nach der Zeile "[alt_names]", hier kann/soll/muss man die Namen (DNS.<Nummer>) bzw. die IP-Adressen (IP.<Nummer>), auf die der HTTPS-Server "hören" soll eintragen.

Um nun den CSR zu erhalten gibt man den Befehl

openssl req -config openssl_server1.cnf -key server1.key.pem -new -sha256 -out server1.csr.pem

ein. Nach dem Passwort wird dieses mal nicht gefragt, da wir ja oben keines vergeben haben, alle sonstigen Fragen einfach wieder per "Return" bestätigen.

Um sich den CSR anzusehen wird der Befehl

openssl req -in server15.csr.pem -noout -text

verwendet.

Zur Vorbereitung des nächsten Schrittes benötigen wir die Dateien "intermediate_index.txt" und "intermediate_serial.txt", die mit

touch intermediate_index.txt

und

echo 2001 >intermediate_serial.txt

erzeugt werden.
Mithilfe dieser beiden Dateien kann die Signierung durchgeführt werden:

openssl ca -config openssl_intermediate1.cnf -extensions server_cert -days 1828 -notext -md sha256 -in server1.csr.pem -out server1.cert.pem

Passwort ist wieder "password", alle anderen Fragen mit "y" (für Ja) beantworten.
Durch den letzten Schritt sind wir nun stolzer Besitzer eines signierten Server-Zertifikates ("server1.cert.pem")!!!
Dieses können wir mit

openssl x509 -noout -text -in server1.cert.pem

in all seiner Schönheit bewundern!




2.1.4 Erzeugung JKS-Dateien für Java

Seltsamerweise (zumindest ich kann es mir nicht erklären) möchte Java unter Windows bzw. unter Linux die JKS-Datei in unterschiedlichen Formaten haben.
Für Windows ist dies im sog. "pkcs12"-Format der Fall, für Linux ist das "JKS"-Format gewünscht.
Um die Datei(-en) "server1.jks" (für Windows) bzw. "server1_JKS.jks" (für Linux) zu erzeugen muss man als Vorarbeit zunächst alle oben generierten Zertifikate plus den Server-Key
in eine Datei "all1.pem" packen, dies geschieht per

cat root.cert.pem intermediate1.cert.pem server1.cert.pem server1.key.pem >all1.pem

Danach erzeugen wir die Datei "server1.p12" mit

openssl pkcs12 -export -inkey server1.key.pem -in all1.pem -name server1 -out server1.p12

die wir als Eingangsdatei für das Java-Programm "keytool" benötigen. Wie üblich Passwort "password".

Unter Windows importieren wir unsere "server1.p12"-Datei mit

keytool -importkeystore -srckeystore server1.p12 -srcstoretype pkcs12 -deststoretype pkcs12 -storepass password -destkeystore server1.jks

unter Linux geschieht derselbe Schritt per

keytool -importkeystore -srckeystore server1.p12 -srcstoretype pkcs12 -deststoretype JKS -storepass password -destkeystore server1_JKS.jks

Das Passwort ist "password", als Ausgabe sollte etwas mit "... erfolgreich importiert" erscheinen.
Um sich das Ergebnis des obigen Importes anzusehen bedarf es des Befehls

keytool -list -v -keystore server1.jks

Hier sollten die 3 Zertifikate (Root-, Intermediate- und Server-Zertifikat) plus der private Server-Key angezeigt werden.



2.1.5 Java Https-Server starten

Wie weiter oben schon erwähnt basiert mein Java HTTPS-Server auf

https://stackoverflow.com/questions/2308479/simple-java-https-server --> SimpleHTTPSServer

Wir verwenden hier eine minimal abgewandelte Version, die auf Port 443 läuft und den Context "/" hat (siehe: httpsServer.createContext("/", new MyHandler()); ).
Der Name für die JKS-Datei "server1.jks"(Windows) bzw. server1_JKS.jks"(Linux) wurde ebenfalls an unsere Gegebenheiten angepasst.
Das Passwort (siehe: char[] password = "password".toCharArray(); ) stimmt zufällig mit unserem überein.
Außerdem habe ich in die "MyHandler"-Methode noch ein paar Ausgaben eingebaut.

Folgendes als "SimpleHTTPSServer.java" abspeichern

Code:
import java.io.*;
import java.net.InetSocketAddress;
import java.lang.*;
import java.net.URL;
import com.sun.net.httpserver.HttpsServer;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.*;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLConnection;
import java.net.URI;

import java.util.Date;
import java.util.Map;
import java.util.List;

import java.text.SimpleDateFormat;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

import java.net.InetAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;

public class SimpleHTTPSServer {

    public static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            HttpsExchange httpsExchange = (HttpsExchange) t;
            URI uri = t.getRequestURI();
            String query = uri.getQuery();
            Map<String, List<String>> headers= t.getRequestHeaders();
            String host= "";
            if (headers.get("Host")!=null) host= headers.get("Host").get(0);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            
            System.out.println(sdf.format(new Date()) + " " + t.getRequestMethod() + "  " + t.getProtocol() + "  https://" + host + uri.getPath() + (query==null ? "" : "?" + query) );
            for(String s: headers.keySet() )
            {
                System.out.printf("                       %-18s  ", s);
                int values= 0;
                for(String v: headers.get(s) )
                {
                    if (values>0) System.out.print(" | ");
                    System.out.print(v);
                    values++;
                }
                System.out.println(" ");
            }
            
            String response = "This is the response";
            t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
            t.sendResponseHeaders(200, response.getBytes().length);
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {

        try {
            // setup the socket address
            InetSocketAddress address = new InetSocketAddress(443);

            // initialise the HTTPS server
            HttpsServer httpsServer = HttpsServer.create(address, 0);
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // initialise the keystore
            char[] password = "password".toCharArray();
            KeyStore ks = KeyStore.getInstance("JKS");
            FileInputStream fis = new FileInputStream("server1.jks");
            ks.load(fis, password);

            // setup the key manager factory
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, password);

            // setup the trust manager factory
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ks);

            // setup the HTTPS context and parameters
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
                public void configure(HttpsParameters params) {
                    try {
                        // initialise the SSL context
                        SSLContext context = getSSLContext();
                        SSLEngine engine = context.createSSLEngine();
                        params.setNeedClientAuth(false);
                        params.setCipherSuites(engine.getEnabledCipherSuites());
                        params.setProtocols(engine.getEnabledProtocols());

                        // Set the SSL parameters
                        SSLParameters sslParameters = context.getSupportedSSLParameters();
                        params.setSSLParameters(sslParameters);

                    } catch (Exception ex) {
                        System.out.println("Failed to create HTTPS port");
                    }
                }
            });
            httpsServer.createContext("/", new MyHandler());
            httpsServer.setExecutor(null); // creates a default executor
            httpsServer.start();

        } catch (Exception exception) {
            System.out.println("Failed to create HTTPS server on port " + 443 + " of localhost");
            exception.printStackTrace();

        }
    }
}

und per

javac SimpleHTTPSServer.java

kompilieren.
Dann mit

java SimpleHTTPSServer

starten.



3. Client-Seite

Nachdem die Server-Seite abgeschlossen ist, wenden wir uns nun der Client-Seite zu.
Nachfolgend werden nur der Firefox-Browser und die auf Chromium basierenden Browser (Google Chrome, Edge, Opera, Vivaldi) betrachtet.



3.1 Chromium basierende Browser (Google Chrome, Edge, Opera, Vivaldi)

Da diese Browser den Windows-eigenen Zertifikat-Store verwenden, müssen wir nur unser Root-Zertifikat nach Windows importieren.
Eigentlich würde dafür die Datei "root.cert.pem" genügen, dies hat allerdings den kleinen optischen Nachteil, daß der Zertifikatsinhaber nicht angezeigt wird.
Um diesen optischen Mangel zu beheben wandeln wir unser Root-Zertifikat mit

openssl pkcs12 -export -in root.cert.pem -cacerts -nokeys -caname "DS Global Digital Certificates Root Cert" -out root_with_nice_name.pfx

in die Datei "root_with_nice_name.pfx" um, die wir nun in den Windows Zertifikat-Store importieren.
Dies geschieht am einfachsten mit

(linke Windows-Taste) + "R" ---> certmgr.msc ---> OK

Es öffnet sich ein Fenster "certmgr", in diesem auf

Vertrauenswürdige Stammzertifizierungsstellen ---> Zertifikate

klicken.
Nun per Rechtsklick auf

Vertrauenswürdige Stammzertifizierungsstellen ---> Zertifikate ---> Alle Aufgaben... ---> Importieren

den Import starten und als "Zu importierende Datei" unser oben erzeugtes "root_with_nice_name.pfx" (evtl. den Datei-Typ auf ".pfx" ändern).
Ansonsten alle Einstellungen lassen, das Passwort ist "password". Nach

Weiter ---> Weiter ---> Weiter ---> Fertigstellen

ist das Root-Zertifikat im Windows-Zertifikat-Store einsatzbereit.
Unter

"DS Global Digital Certificates Root Cert" ---> Doppelklick

kann man sich das erfolgreich importierte Werk nochmal ansehen.

Daß letztendlich auch alles funktioniert kann man sogleich im Browser durch die Eingabe von

https://127.0.0.1/

überprüfen, welche dieser mit einem freundlichen "This is the response" beantworten sollte.

Per Klick auf das

Schloss-Symbol ---> Zertifikat (Gültig) ---> Zertifizierungspfad

sehen wir die gesamte Zertifikats-Kette bestehend aus den 3 oben erzeugten Zertifikaten.

Sieht man sich die zugehörige Ausgabe des "SimpleHTTPSServer"s an, so erkennt man, daß nicht nur die URL "https://127.0.0.1" angefordert wurde, sondern auch noch das zugehörige "favicon.ico":
(Hinweis: Die Uhrzeiten sind gefaked!)

Code:
2019-12-15 49:87:94 GET  HTTP/1.1  https://127.0.0.1/
                       Accept-encoding     gzip, deflate, br
                       Accept              text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
                       Sec-fetch-user      ?1
                       Connection          keep-alive
                       Host                127.0.0.1
                       Sec-fetch-site      none
                       Sec-fetch-mode      navigate
                       User-agent          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
                       Accept-language     de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
                       Upgrade-insecure-requests  1
2019-12-15 49:87:94 GET  HTTP/1.1  https://127.0.0.1/favicon.ico
                       Accept-encoding     gzip, deflate, br
                       Accept              image/webp,image/apng,image/*,*/*;q=0.8
                       Connection          keep-alive
                       Referer             https://127.0.0.1/
                       Host                127.0.0.1
                       Sec-fetch-site      same-origin
                       Sec-fetch-mode      no-cors
                       User-agent          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
                       Accept-language     de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7

Richtig interessant wird es beim Aufruf einer Seite, die eine Datei von "https://ajax.googleapis.com/" einbindet:
Ich habe mir als Beispiel mal

https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_lib_google

ausgewählt.
Die zugehörige Ausgabe von "SimpleHTTPSServer" ist (Uhrzeiten wieder gefaked!):

Code:
2019-12-15 55:88:95 GET  HTTP/1.1  https://fonts.googleapis.com/css?family=Source Code Pro
                       Accept-encoding     gzip, deflate, br
                       Accept              text/css,*/*;q=0.1
                       Connection          keep-alive
                       Referer             https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_lib_google
                       Host                fonts.googleapis.com
                       Sec-fetch-site      cross-site
                       Sec-fetch-mode      no-cors
                       User-agent          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
                       Accept-language     de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
2019-12-15 55:88:95 GET  HTTP/1.1  https://apis.google.com/js/client.js?onload=checkAuth
                       Accept-encoding     gzip, deflate, br
                       Accept              */*
                       Connection          keep-alive
                       Referer             https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_lib_google
                       Host                apis.google.com
                       Sec-fetch-site      cross-site
                       Sec-fetch-mode      no-cors
                       User-agent          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
                       Accept-language     de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
2019-12-15 55:88:95 GET  HTTP/1.1  https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
                       Accept-encoding     gzip, deflate, br
                       Accept              */*
                       Connection          keep-alive
                       Referer             https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_lib_google
                       Host                ajax.googleapis.com
                       Sec-fetch-site      cross-site
                       Sec-fetch-mode      no-cors
                       User-agent          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
                       Accept-language     de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7

Im "Normalfall" gehen alle diese Angaben (inklusive der IP-Adresse) und insbesondere der "Refer(r)er" (=https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_lib_google) an Google!!!


3.2 Firefox-Browser

Da der Firefox-Browser seinen eigenen Zertifikat-Store hat muss man hier anders vorgehen:
Im Browser eingeben

about:preferences#privacy ---> Zertifikate anzeigen... ---> Zertifizierungsstellen ---> Importieren...

dann die Datei "root.cert.pem" auswählen, danach ein Häckchen bei

Dieser CA vertrauen, um Websites zu identifizieren.

machen.
Nach einem finalem

OK

sollte das Root-Zertifikat importiert sein.
Dies kann man durch runterscrollen auf "DS Global Digital Certificates Root Cert" und durch Doppelklick auf ebendiesen Eintrag überprüfen.

Nach der Eingabe von

https://127.0.0.1/

in die Adresszeile sollte ein "This is the response" als Antwort erscheinen.



==================================================

Zusätzlicher Hinweis für "CitroenDsVier":

Wenn Du diese Anleitung komplett abgearbeitet hast bist Du nun im Besitz der Datei "server1_JKS.jks".
Diese musst Du in Deinem Server laden.
Also wie in der von Dir geposteten Anleitung auf

http://sparkjava.com/documentation ---> How do I enable SSL/HTTPS?

in die Zeile

Code:
String keyStoreLocation = "server1_JKS.jks ";

die oben erzeugte Datei "server1_JKS.jks" (mit Pfad) eingeben.

Außerdem musst Du noch die IP-Adresse Deines Servers in die Datei "openssl_server1.cnf" (Abschnitt 2.1.3) eintragen.


HTH

BigNum
 
  • Gefällt mir
Reaktionen: CitroenDsVier, Chief Gewickelt und snaxilian
Whow, vielen Dank für die Antwort! Das werde ich mir mit genügend Zeit in Ruhe zu Herzen nehmen. Beim Lesen gerade bin ich aber auf 2 Fragen gestoßen:

BigNum schrieb:
Der wichtige(re) Teil befindet sich allerdings ganz unten nach der Zeile "[alt_names]", hier kann/soll/muss man die Namen (DNS.<Nummer>) bzw. die IP-Adressen (IP.<Nummer>), auf die der HTTPS-Server "hören" soll eintragen.
In meinem Fall dann "DNS.1=localhost" oder "IP.1=127.0.0.1" ? oder beides?

BigNum schrieb:
Außerdem musst Du noch die IP-Adresse Deines Servers in die Datei "openssl_server1.cnf" (Abschnitt 2.1.3) eintragen.
Selbe Frage, = localhost?
 
  • Gefällt mir
Reaktionen: tarifa
CitroenDsVier schrieb:
In meinem Fall dann "DNS.1=localhost" oder "IP.1=127.0.0.1" ? oder beides?
Im Zweifelsfall beides. Vielleicht brauchst Du sogar noch die "richtige" IP-Adresse (sprich soetwas wie z.B. 192.168.123.45).
Da ich nicht weiß mit, ob Dein "Java-Spark-Server" mit der IP oder per DNS-Name angesprochen wird, kann ich Dir auch nicht sagen, welche Angabe Du weglassen kannst.
Wegen dem "Platz" brauchst Du Dir keine Sorgen machen: Mein "SimpleHTTPSServer" hat über 2300! DNS-Einträge und ich glaube 4 IP-Einträge.

Oder als Superkurzantwort: Selber ausprobieren!


HTH

BigNum
 
  • Gefällt mir
Reaktionen: tarifa und CitroenDsVier
Bin gerade die Anleitung durchgegangen, es läuft auf Anhieb! Vielen vielen Dank @BigNum !!

als IP / DNS Einträge habe ich zur Zeit nur DNS.1=localhost und IP.1=127.0.0.1 eingetragen. Damit läuft es auf localhost problemlos. Was ich da später wenn es an ein lauffähiges System geht eintragen muss, werde ich dann ausprobieren.

Einen typo habe ich gefunden (der bei der Menge an Text sicherlich nicht ausbleibt):
BigNum schrieb:
Um sich den CSR anzusehen wird der Befehl

openssl req -in server15.csr.pem -noout -text

verwendet.
Hier muss es "server1.csr.pem" heißen, nicht "server15..."
 
hallo BigNum


wow vielen dank für die sehr ausführliche Erläuterung. Die ist sehr hilfreich - insbes. die Dinge zum Intermediate-Zert.



BigNum schrieb:
1. Hintergrund

Da mir meine Daten sehr wichtig sind habe ich mir vor längerer Zeit einen eigenen DNS-Server (dnsmasq) auf einer Linux-Kiste eingerichtet, um Google und Konsorten auszubremsen (d.h. keine Datenspuren bei ihnen zu hinterlassen). Also im Prinzip das was heute unter dem Namen "Pi-Hole" vielen bekannt sein dürfte.
So werden z.B.

"*.google-analytics.com",
"*.googletagmanager.com" und
"*.googlesyndication.com"

per DNS "hart geerdet".
Da aber manche Sachen (z.B. JS-Libs) von Google und Konsorten durchaus nützlich sind, werden etwa

https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.6/cookieconsent.min.css
https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.min.css

zuerst über den DNS auf "127.0.0.1" aufgelöst und dann über einen lokal laufenden ("127.0.0.1") und in Java geschrieben HTTPS-Server ausgeliefert.
Als Haupt-Vorteile sind zu nennen:
  • ich hinterlasse keine Datenspuren (Stichwort Referrer) bei Google & Co.
  • schnellere Auslieferung, da lokal
  • in den Logs sehe ich welchen "unerwünschten Datenverkehr" manche Seite erzeugen
  • für einige JS-Dateien lassen sich auch "verbesserte" ;-) Versionen ausliefern!

Mein Java-HTTPS-Server basiert im Kern auf

https://stackoverflow.com/questions/2308479/simple-java-https-server --> SimpleHTTPSServer

ist aber in Sachen Funktionalität stark aufgebohrt.

Damit der Datenverkehr ordnungsgemäß verschlüsselt über https ablaufen kann sind natürlich auf dem Server und Client (z.B. Browser) Zertifikate notwendig. Wie diese erzeugt werden und wo man sie wie hinpackt beschreibt der folgende Anschnitt:

2.1.2 Intermediate-Key / Intermediate-Zertifikat

Bei professionellen Zertifikaten wird in aller Regel ein Intermediate (= Zwischen)-Zertifikat ausgestellt.
Auf die Gründe gehe ich hier nicht ein!

Die Erzeugung des Intermediate-Keys erfolgt analog zu oben per

openssl genrsa -aes256 -out intermediate1.key.pem 4096

Bei der Frage nach dem Passwort geben wir wieder (zweimal) "password" an.
Ansehen kann man sich das Ganze wieder mit

openssl rsa -in intermediate1.key.pem -text

Für den nächsten Schritt benötigen wir wieder ein Konfigurations-Datei, die wie folgt aussieht:

Code:
# based on https://jamielinux.com/docs/openssl-certificate-authority/appendix/intermediate-configuration-file.html

# OpenSSL intermediate CA configuration file.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
new_certs_dir     = .
database          = intermediate_index.txt
serial            = intermediate_serial.txt

########################################################### ALF
copy_extensions = copy

# The root key and root certificate.
private_key       = intermediate1.key.pem
certificate       = intermediate1.cert.pem


# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = FR
stateOrProvinceName_default     = Paris
localityName_default            = Paris
0.organizationName_default      = DS Global Digital Certificates
organizationalUnitName_default  =
commonName_default              = DS Global Digital Certificates Intermediate X1 2019
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

Obige Zeilen als "openssl_intermediate1.cnf" abspeichern.
Dann einen sog. "certificate signing request" (csr) mittels

vielen Dank nochmals

vg
 
Zurück
Oben