Wenn gRPC nur im Backend verwendet wird, bietet eine sichere Verbindung mit zusätzlicher Client-Zertifikat Authentifizierung meistens ausreichende Sicherheit. Mit dieser Form der Authentifizierung wird nicht nur die Identität des Servers, sondern auch diejenige des Clients bestätigt, um sicher zu gehen, dass sich kein Fremdprogramm mit dem Server verbindet. Dieses Tutorial zeigt Schritt für Schritt wie man den gRPC Server und Client einrichtet (mit selbstsignierten Zertifikaten) für Client-Zertifikat Authentifizierung.
Der Code zum Beispiel kann man hier herunterladen.
Ein digitales Client-Zertifikat ist im Grunde eine Datei, die mit einem Passwort geschützt und in eine Client-Anwendung geladen wird (normalerweise als PKCS12-Dateien mit der Erweiterung ‘.p12’ oder ‘.pfx’).
Ein digitales Zertifikat enthält relevante Informationen wie eine digitale Signatur, Ablaufdatum, Name des Issuers, Name der CA (Certificate Authority), Sperrstatus (revocation), SSL/TLS-Versionsnummer, Seriennummer und möglicherweise weitere Informationen, die alle nach dem X.509-Standard strukturiert sind.
Ein Client-Zertifikat ist ein spezieller Zertifikat-Typ mit dem Ziel, seine Identität für einen anderen Rechner nachzuweisen, in unserem Fall also für den Server. Man erkennt den Zweck eines Zertifikates an Hand der OID (Object Identifier), welche eine Folge von Zahlen ist. Das Zertifikatfeld ‘Enhanced Key Usage’ eines Client-Zertifikates enthält die OID ‘1.3.6.1.5.5.7.3.2’.
Mit dem Suchwort ‘cert’ im Windows Startmenu erscheint ‘Manage computer certificates’, welches die Microsoft Management Konsole öffnet. Mit einem Doppelklick aufs Zertifikat erscheinen die detaillierten Informationen wie oben im Bild.
Zu Beginn einer SSL- oder TLS-Sitzung kann der Server (falls so konfiguriert) vom Client ein Client-Zertifikat verlangen. Nach dem Empfang des Zertifikats identifiziert der Server die Quelle des Zertifikats und entscheidet, ob dem Client der Zugriff erlaubt werden soll.
Normalerweise werden Zertifikaten von Zertifizierungsstellen (‘certificate authority’ oder ‘certification authority’, kurz CA) ausgestellt (z.B. Verisign, Thawte, etc.). Für unser Beispiel werden wir selber die Zertifikate generieren. Es gibt verschiedene Wege selber Zertifikate zu generieren: Powershell, online Tools, OpenSSL, etc. In diesem Beitrag wird OpenSSL verwendet.
Die binäre Dateien des OpenSSL Tools stehen als ZIP-Datei zur Verfügung auf ‹https://sourceforge.net/projects/openssl‹ oder als Installer auf ‹https://slproweb.com/products/Win32OpenSSL.html‹. Lade die letzte Version des Installers ‘Win64OpenSSL_Light-1_1_0L.exe’ (oder höher) herunter und führe den Installer aus. Wichtig ist, dass ‘openssl.exe’ und ‘openssl.cnf’ sich nach der Installation im Verzeichnis ‘C:\OpenSSL-Win64\bin’ befinden.
Im Beispiel gibt es einen Unterordner ‘Certs’ in dem alle OpenSSL Tool Input- und Output-Dateien gespeichert werden.
Die Inhalte des Zertifikatfeldes ‘Enhanced Key Usage’ müssen für das OpenSSL Tool in einer externen Datei definiert werden. In diesem Verzeichnis eine Textdatei ‘openssl-ext.cnf’ mit folgendem Inhalt erstellen:
[client_ssl] extendedKeyUsage = clientAuth [server_ssl] extendedKeyUsage = serverAuth
Eine zweite Datei namens ‘create_certs.cmd’ mit folgendem Inhalt erstellen:
set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg echo Generate CA key: c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out root.key 4096 echo Generate CA certificate: c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -x509 -days 3650 -key root.key -out root.crt -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=Noser RpcWithCertificates Root" echo Generate server key: c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out server.key 4096 echo Generate server signing request: c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -key server.key -out server.csr -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=localhost" echo Self-sign server certificate: c:\OpenSSL-Win64\bin\openssl x509 -req -passin pass:P@ssw0rd -days 3650 -extensions server_ssl -extfile openssl-ext.cnf -in server.csr -CA root.crt -CAkey root.key -set_serial 01 -out server.crt echo Remove passphrase from server key: c:\OpenSSL-Win64\bin\openssl rsa -passin pass:P@ssw0rd -in server.key -out server.key echo Generate client key c:\OpenSSL-Win64\bin\openssl genrsa -passout pass:P@ssw0rd -des3 -out client.key 4096 echo Generate client signing request: c:\OpenSSL-Win64\bin\openssl req -passin pass:P@ssw0rd -new -key client.key -out client.csr -subj "/C=CH/ST=LU/L=Root D4/O=Noser Engineering AG/OU=www.noser.com/CN=localhost" echo Self-sign client certificate: c:\OpenSSL-Win64\bin\openssl x509 -req -passin pass:P@ssw0rd -days 3650 -extensions client_ssl -extfile openssl-ext.cnf -in client.csr -CA root.crt -CAkey root.key -set_serial 01 -out client.crt echo Remove passphrase from client key: c:\OpenSSL-Win64\bin\openssl rsa -passin pass:P@ssw0rd -in client.key -out client.key c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in root.crt -inkey root.key -out root.pfx -passout pass:P@ssw0rd -passin pass:P@ssw0rd -name "Noser Engineering self-signed root cert" c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx -passout pass:P@ssw0rd -name "Noser Engineering self-signed server cert" c:\OpenSSL-Win64\bin\openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:P@ssw0rd -name "Noser Engineering self-signed client cert" pause
Nach dem Ausführen des oberen Skripts hat das Verzeichnis folgenden Inhalt:
Das Skript generiert verschiedene (Zwischen-)Dateien, wovon für uns nur 3 wichtig sind:
Die Datei ‘root.pfx’ enthält das Zertifikat mit ‘Intended Purposes All’ und wird zum Signieren des Server- und Client-Zertifikates verwendet. Das Server-Zertifikat hat ‘Intended Purposes Server Authentication’ und das Client-Zertifikat hat ‘Intended Purposes Client Authentication’. Das Server-Zertifikat wird später im gRPC Server geladen und das Client-Zertifikat im Client-Programm. Alle drei Zertifikate müssen aber zuerst auf dem Rechner installiert werden.
Da es nicht möglich ist mit OpenSSL den ‘friendly name’ in einer ‘crt’-Datei zu generieren, verwenden wir die ‘pfx’-Datei zum Installieren der Zertifikate.
Doppelklick auf root.pfx und wähle ‘Local Machine’.
Das Passwort einfügen (P@ssw0rd).
Für das Root-Zertifikate den ‘Trusted Root Certification Authorities’ Store wählen.
Alle Schritte für ‘server.pfx’ und ‘client.pfx’ wiederholen mit dem Unterschied, dass ‘Intermediate Certification Authorities’ als Store gewählt werden soll.
Danach die Microsoft Management Konsole öffnen durch Eingabe von ‘cert’ im Windows Startmenü. Es erscheint ‘Manage computer certificates’ in den Ergebnissen, welches die Konsole öffnet.
Das Root-Zertifikat soll sich jetzt in ‘Trusted Root Certification Authorities’ befinden.
Und das Server- und Client-Zertifikat in ‘Intermediate Certification Authorities’.
Beim Server muss die Client-Zertifikat Authentifizierung aktiviert werden und das Server-Zertifikat installiert werden.
Als erste muss die Datei ‘server.pfx’ im Projekt kopiert werden und mit ins Ausgabeverzeichnis kopiert werden.
Passwort und Dateiname werden in appsettings.json in einer neuen Sektion ‘Certificate’ definiert:
{ "Logging": { … }, "Certificate": { "File": "server.pfx", "Password": "P@ssw0rd" } }
In der program.cs Datei wird der Kestrel-Server für die Sicherheitsvorkehrungen konfiguriert:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); webBuilder.ConfigureKestrel( opt => { var config = (IConfiguration)opt.ApplicationServices.GetService(typeof(IConfiguration)); var cert = new X509Certificate2( config["Certificate:File"], config["Certificate:Password"]); opt.ConfigureHttpsDefaults( h => { h.ClientCertificateMode = ClientCertificateMode.RequireCertificate; h.CheckCertificateRevocation = false; h.ServerCertificate = cert; }); }); });
Dazu wird ein X509-Zertifikat aus der ‘pfx’-Datei erstellt, zugewiesen und die Client-Zertifikat Authentifizierung eingeschaltet. Da selbstsignierte Zertifikate nicht widerrufen werden können, wird CheckCertificateRevocation ausgeschaltet.
In startup.cs muss der Authentication-Service hinzugefügt und konfiguriert werden.
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate( opt => { opt.AllowedCertificateTypes = CertificateTypes.All; opt.RevocationMode = X509RevocationMode.NoCheck; // Just for development }); services.AddAuthorization(); services.AddControllers(); services.AddGrpc(opt => { opt.EnableDetailedErrors = true; }); }
Als letzter Schritt muss der Zugriff auf den gRPC Service mit dem ‘Authorize’-Attribute verriegelt werden.
[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults.AuthenticationScheme)] public class DemoServiceRpc : DemoService.DemoServiceBase { public override Task<NameMessage> ExchangeNames(NameMessage request, ServerCallContext context) { NameMessage result = new NameMessage { Name = $"Hello {request.Name} from {Assembly.GetExecutingAssembly().FullName}" }; return Task.FromResult(result); } }
Auch beim Client-Programm muss die ‘pfx’-Datei im Ausgabeverzeichnis landen.
Die Sektion ‘Service’ in appsettings.json wird erweitert mit dem Passwort und dem Datei-Namen.
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Service": { "CustomerId": 1, "DelayInterval": 3000, "ServiceUrl": "https://localhost:5001", "CertFileName": "client.pfx", "CertPassword": "P@ssw0rd" } }
Es ist nur möglich, Zertifikate zum HttpClientHandler zu zuweisen. Deshalb wird neu in worker.cs ein HttpClientHandler erstellt, das Zertifikat geladen, zugewiesen und mit dem Handler als Parameter der HttpClient erstellt.
protected DemoService.DemoServiceClient Client { get { if (_client == null) { var cert = new X509Certificate2( _config["Service:CertFileName"], _config["Service:CertPassword"]); var handler = new HttpClientHandler(); handler.ClientCertificates.Add(cert); var client = new HttpClient(handler); var opt = new GrpcChannelOptions() { HttpClient = client, LoggerFactory = _loggerFactory }; ChannelBase channel = GrpcChannel.ForAddress(_config["Service:ServiceUrl"], opt); _client = new DemoService.DemoServiceClient(channel); } return _client; } }
Wenn alle Schritte richtig durchlaufen sind, funktioniert die Client-Server Kommunikation wie vorher. Die Ursachen von eventuellen Problemen sind leider nur sehr schwer zu finden. Im folgenden Beispiel wurde das Root-Zertifikat mit der Microsoft Management Console gelöscht. Beim Verbinden des Clients erscheint dann folgende Ausgabe.
Die Fehlermeldung ‘The remote certificate is invalid according to the validation procedure’ kommt bei jedem Authentifizierungsproblem und ist nicht sehr hilfreich.
Dieses Beispiel zeigt, dass es relativ wenig Schritte braucht um Client-Zertifikat Authentifizierung in den gRPC Server und Client einzubauen. Die Praxis zeigt aber auch, dass es sehr mühsam ist einen Fehler zu finden, falls die Absicherung der Kommunikation nicht auf Anhieb funktioniert.
Im nächsten Blog wird die Absicherung mit Access Tokens erklärt. Diese Technologie versichert den Server, dass der Client authentifiziert und autorisiert, ist um nur die erlaubte Funktionalität des APIs zu benutzen.
[…] ← Vorige Post Nächster Post → […]
[…] ← Voriger Post Nächster Post → […]
[…] erstellt (ClientCertificateMode.NoCertificate). Client-Zertifikat Authentifizierung wird im nächsten Blog erklärt und sorgt dafür, dass der Server den Client authentifiziert bevor der Client auf die […]
[…] ← Voriger Post Nächster Post → […]