BMF-RKSV-Technik / at-registrierkassen-mustercode

111 stars 39 forks source link

Startbeleg - kryptografische Gültigkeit ist nicht gegeben #506

Open DavStock opened 7 years ago

DavStock commented 7 years ago

Hallo,

ich habe nun mehrere Versuche in verschiedenste Richtungen gemacht einen Startbeleg zu erstellen und komme absolut nicht weiter. Programmiert wurde in C# und anscheinend gibt es Probleme mit der Signatur bzw. dem maschinenlesbaren Code:

_R1-AT1_KRO-CTG-WSRKA_114_2017-03-20T00:00:00_115,20_0,00_0,00_0,00_0,00_Ooi+hMll5Ru/qycIA9M2Xw==_7F4D87C4_S1JPLUNURy0=_UcHtCodZP/ey3Ql0QBOjrt0OfkW9NU8Gkbr1DJCWdpt5CsCKfGnp3XjAjvHrKpi8i6eaX5N4m6r1WiCYNUloZw==

AES:

+PVmNm3LMsRxLvZkJDY/7g6tNjkJCPQH2kUHXyzCzy0=

Ich habe alles nach bestem Wissen und Gewissen lt. BMF Detailanforderungen und aTrust Demos runtercodiert. Eventuell mache ich etwas mit den JWS Daten falsch, denn verwende ich die Base64Url für "Signatureinheit ausgefallen" sagt mir FON, dass der Beleg zwar korrekt signiert wurde aber eben, dass noch kein Startbeleg erstellt wurde. Versuche ich "Echtdaten" zu verschlüsseln und zu signieren bekomme ich den krypografischen Fehler. Hier die Meldung lt. Prüftool:

{ "verificationId" : "VERIFICATION_FROM_CASHBOX", "version" : 1, "verificationName" : "Prüfergebnis - Kasse", "verificationTextualDescription" : "Bei der Belegprüfung wird untersucht, ob die Vorgaben der RKSV in Bezug auf den maschinenlesbaren Code am Beleg und auf die meldepflichtigen Metadaten der Sicherheitseinrichtung befolgt werden. Im Fehlerfall sind die genauen Fehlerinformationen nachfolgend angeführt.", "verificationState" : "FAIL", "verificationResultDetailedMessage" : "Der vorliegende Beleg weist Fehler im maschinenlesbaren Code auf. Am besten, Sie übermitteln zur Problembehandlung die nachfolgende Fehlerbeschreibung an Ihren Kassenhersteller.", "verificationTimestamp" : "2017-03-28T14:22:41.502+02:00", "verificationResultList" : [ { "verificationId" : "RECEIPT_FULL", "version" : 1, "verificationName" : "Detailprüfung des maschinenlesbaren Codes", "verificationTextualDescription" : "Dieses Modul und die dazugehörigen Submodule überprüfen die Gültigkeit des Belegs. Dabei werden sowohl Formatprüfungen, kryptographische Prüfungen (verschlüsselter Umsatzzähler, Signatur) als auch Prüfungen im Zusammenhang mit dem Status der Kasse bzw. der Signatur-/Siegelerstellungseinheit durchgeführt.", "verificationState" : "FAIL", "verificationTimestamp" : "2017-03-28T14:22:41.558+02:00", "verificationResultList" : [ { "verificationId" : "CRYPTO", "version" : 1, "verificationName" : "Kryptographische Prüfungen", "verificationTextualDescription" : "In diesem Modul und den dazugehörigen Submodulen werden die kryptographische Validität des Umsatzzählers und des Signaturwerts überprüft. Für den Umsatzzähler kann eine detaillierte Prüfung nur beim Startbeleg durchgeführt werden, da nur in diesem Fall der entschlüsselte Wert bekannt ist (Umsatzzähler = 0).", "verificationState" : "FAIL", "verificationTimestamp" : "2017-03-28T14:22:41.569+02:00", "verificationResultList" : [ { "verificationId" : "CRYPTO_SIGNATURE", "version" : 1, "verificationName" : "Kryptographie: Überprüfung der kryptographischen Gültigkeit der Signatur", "verificationTextualDescription" : "In diesem Modul wird die kryptographische Gültigkeit der Signatur mit Hilfe des öffentlichen Schlüssels überprüft.", "verificationState" : "FAIL", "verificationResultDetailedMessage" : "Die kryptographische Gültigkeit der Signatur ist nicht gegeben. Es scheint aber nicht der definierte Fall der ausgefallenen Sicherheitseinrichtung zuzutreffen.", "verificationTimestamp" : "2017-03-28T14:22:41.570+02:00" } ] } ] } ] }

Es ist doch so, dass der JWS_Header mit "." getrennt mit der Base64Url codierten Payload verbunden, gehasht (UTF8?) - dann signiert und die Signatur als Base64String an die plain Payload angehängt wird um damit den maschinenlesbaren Code zu erhalten (siehe oben) oder nicht?

Coding sieht wie folgt aus (leichte Abänderungen für Demo):

RKWrapper rkw = new RKWrapper(); //*.dll von aTrust - initialisiert die Karte und holt die Certnumber string payloadPlain = string.Format("_R1-{0}_{1}_{2}_{3}_{4}_{5}_{6}_{7}_{8}_{9}_{10}_{11}", ZDA, cashBoxID, BelegNr, rechDat, rechBetrag, "0,00", "0,00", "0,00", "0,00", umsatz, certSerNr, prevSig) string JWS_Payload = Base64url(payloadPlain); string toBeSignedStr = string.Format("{0}.{1}", JWS_Protected_Header, JWS_Payload); byte[] tbsBytes = Encoding.UTF8.GetBytes(toBeSignedStr); byte[] signature; rkw.Sign(tbsBytes, out signature); // hier signiert aTrust *.dll meine Daten?! string JWS_Signature = Convert.ToBase64String(signature); string maschinenlesbarerCode = string.Format("{0}_{1}", payload, JWS_Signature);

Irgendeinen Punkt verstehe ich da anscheinen noch nicht ganz und bitte daher um eure Unterstützung.

Danke und LG

PS: Solltet ihr noch irgendwelche Infos / Daten brauchen bitte einfach bescheid geben!

ErichFreitag commented 7 years ago

Der Startbeleg muss einen Barumsatz von 0 haben.

asitplus-pteufl commented 7 years ago

BItte die Folien im Readme beachten. direkter link: https://github.com/a-sit-plus/at-registrierkassen-mustercode/blob/master/Dokumente/2016-12-12%20SIG-Check.pdf

Vielleicht haben Sie Padding-Zeichen (=) im JWS-Base64? Die sind in der JWS-Spez. nicht erlaubt.

DavStock commented 7 years ago

@ErichFreitag entschuldige - ich habe den falschen maschinenlesbaren code genommen. Beim getesteten ist der Umsatz natürlich 0,00 !! Es ist egal welche Belegart ich teste ich bekomme immer denselben Fehler (bei Storno-, Null- oder Trainingsbelegen).

@asitplus-pteufl Das mit den "=" muss ich mir genauer ansehen. D.h. nach Base64Url Umwandlung der Plain-Payload müsste ich noch "=" mit "" ersetzen?! Danke.

asitplus-pteufl commented 7 years ago

ja, bitte unbedingt die folien von oben im Detail anschauen, da sind alle Punkte drinnen die bei der Sig-Erstellung schief gehen können.

DavStock commented 7 years ago

Ich habe das Coding nun zusätzlich abgesichert wegen dem "=". Die Folien habe ich auch noch einmal durchstudiert und das Coding stimmt 1:1 mit der Vorgehensweise überein. Es passen auch alle Überprüfungen laut Testtool bis auf dieser kryptografische Fehler in der Signatur.

Was ich dabei nicht ganz verstehe - in der Meldung steht immer, dass dies mit einem öffentlichen Schlüssel abgeglichen wird. Welcher öffentlicher Schlüssel?

Hier noch mein maschinenlebarer Code als Startbeleg: qr-code-rep.txt

...und das Ergebnis via Testtool: 0000_cashbox_full.txt

Ich prüfe gerade noch die Signaturerstellung selbst (wegen DER-Format) aber da die Signierung vom aTrust Coding ausgeführt wird glaube ich nicht hier noch Fehler zu finden.

DavStock commented 7 years ago

Ich probiers nochmal mit einer Anfrage und genaueren Daten. Habe leider nach wie vor diesen Fehler beim Startbeleg (bei jedem Beleg). Also Schritt für Schritt:

Umsatzzähler bei Startbeleg wird mit dem Wert 0 und dem AES verschlüsselt und sieht so aus (decrypting wird erfolgreich geprüft - Wert = 0):

fQeXmFQxrUJZ8OdM87aKsw==

Plain Payload besteht aus 12 Elementen:

_R1-AT1_KRO-CTG-WSRKA_271_2017-03-30T10:18:28_0,00_0,00_0,00_0,00_0,00_fQeXmFQxrUJZ8OdM87aKsw==_7F4D87C4_S1JPLUNURy0=

JWS-Header:

eyJhbGciOiJFUzI1NiJ9

JWS-Payload:

X1IxLUFUMV9LUk8tQ1RHLVdTUktBXzI3MV8yMDE3LTAzLTMwVDEwOjE4OjI4XzAsMDBfMCwwMF8wLDAwXzAsMDBfMCwwMF9mUWVYbUZReHJVSlo4T2RNODdhS3N3PT1fN0Y0RDg3QzRfUzFKUExVTlVSeTA9

Die beiden zusammen, also "JWS-Header.JWS-Payload" werden von "=" befreit, SHA256 verschlüsselt und über die aTrust API signiert (ich gehe bisher davon aus, dass diese kein DER-Format hat). Die zurückgebene Signatur wird Base64Url behandelt und sieht wie folgt aus:

RHtFNNxC0TttMqctr8bPIFPgsp1PXTyNLbOfBWuZ0oVtHgn1REoSDGEjaezILUYikYJG/FPu5RE0kWPnmL3VCA==

Hier liegt angeblich der Fehler. C# Coding zum erstellen der Signatur wäre wie folgt:

            RKWrapper rkw = new RKWrapper();
            SHA256Managed h = new SHA256Managed();
            byte[] toBeHashed = Encoding.UTF8.GetBytes(string.Format("{0}.{1}", JWS_Protected_Header.Replace("=", ""), JWS_Payload.Replace("=", "")));
            tbsBytes = h.ComputeHash(toBeHashed);
            byte[] signature;
            ret = rkw.Sign(tbsBytes, out signature);
            JWS_Signature = Convert.ToBase64String(signature);

Zum Erstellen des QR-Codes (und zum Testen im Prüftool) nehme ich dann folgenden String (bestehend aus 13 Elementen):

_R1-AT1_KRO-CTG-WSRKA_271_2017-03-30T10:18:28_0,00_0,00_0,00_0,00_0,00_fQeXmFQxrUJZ8OdM87aKsw==_7F4D87C4_S1JPLUNURy0=_RHtFNNxC0TttMqctr8bPIFPgsp1PXTyNLbOfBWuZ0oVtHgn1REoSDGEjaezILUYikYJG/FPu5RE0kWPnmL3VCA==

Der QR-Code wird mit dem ECC-Level "M" und 2px pro Modul erstellt.

ErichFreitag commented 7 years ago

Schauen sie auch die anderen Issues zu diesem Thema durch. Manchmal macht z.B. schon die Signierfunktion auch einen Hash, sodass dann einer zuviel wäre.

DavStock commented 7 years ago

@ErichFreitag habe das nach meinem Kommentar noch einmal kontrolliert und gesehen, dass dort auch ein Coding für einen Hash enthalten ist. Dies wäre der Auszug aus dem Coding welches ich 1:1 von aTrust übernommen habe:

            IntPtr pSig = IntPtr.Zero;
            IntPtr pTBS = IntPtr.Zero;

            try
            {
                ulong tbsLen = (ulong)dataToSign.Length;
                ulong sigLen = 256;

                pSig = Marshal.AllocHGlobal(256);
                pTBS = Marshal.AllocHGlobal(dataToSign.Length);

                if (pSig == IntPtr.Zero || pTBS == IntPtr.Zero)
                {
                    LastError = "Sign: memory allocation failed";
                    ret = 0x06;
                }
                else
                {
                    Marshal.Copy(dataToSign, 0, pTBS, dataToSign.Length);

                    if (is32Bit)
                    {
                        Sign32(ref signature, ref ret, pSig, pTBS, tbsLen, sigLen);
                    }
                    else
                    {
                        Sign64(ref signature, ref ret, pSig, pTBS, tbsLen, sigLen);
                    }
                }

            }
            catch (Exception ex)
            {
                ret = 0x6; // Function failed
                LastError = "Sign Exception : " + ex.Message;
            }
            finally
            {
                // Free memory
                Marshal.FreeHGlobal(pSig);
            }
        }

Meiner Meinung nach wird hier in den ersten paar Zeilen der Hash erstellt. Habe daher testhalber meinen Hash wieder entfernt und erneut signiert - die Fehlermeldung bleibt leider die selbe.

ErichFreitag commented 7 years ago

Wie sieht der Zertifikatseintrag im crypto-file aus?

DavStock commented 7 years ago

Mein Crypto-File sieht aktuell wie folgt aus:

{ "base64AESKey" : "+PVmNm3LMsRxLvZkJDY/7g6tNjkJCPQH2kUHXyzCzy0=", "certificateOrPublicKeyMap" : { "7F4D87C4" : { "id" : "7F4D87C4", "signatureDeviceType" : "CERTIFICATE", "signatureCertificateOrPublicKey" : "049F18731B3BF3492B0744D34FB6B9C02E0B3E929E38B807157EFD0EBD996F533587988DA54A6B73DD45F974F5825FCFDC97CB0C47AA6953EF8E0BFC859CFBAA41" } } }

Ich habe hier mal den PublicKey der Karte verwendet. Es ist aber egal ob ich die Zertifikatsnummer bei "signatureCertificateOrPublicKey" eintrage oder den Public Key oder die Rohdaten der Karte.

ErichFreitag commented 7 years ago

Sie müssen das Zertifikat im .PEM-Format eintragen.

DavStock commented 7 years ago

Wenn ich das .cer in ein .pem konvertiere erhalte ich nur eine neue Datei - wie trage ich die nun ein? Entschuldigen Sie @ErichFreitag - da steh ich doch etwas am Schlauch.

ErichFreitag commented 7 years ago

Das Zertifikat im .PEM-Format sieht typisch wie folgt aus: -----BEGIN CERTIFICATE----- MIIFPzCCAyegAwIBAgIELwe0czANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMC QVQxSDBGBgNVBAoMP0EtVHJ1c3QgR2VzLiBmLiBTaWNoZXJoZWl0c3N5c3RlbWUg aW0gZWxl... -----END CERTIFICATE-----

Den Inhalt zwischen BEGIn und END nehmen sie, entfernen alle Zeilenumbrüche und tragen es als Zertifikatswert unter signatureCertificateOrPublicKey ein.

DavStock commented 7 years ago

Nun sieht mein Crypto-File so aus:

{ "base64AESKey" : "+PVmNm3LMsRxLvZkJDY/7g6tNjkJCPQH2kUHXyzCzy0=", "certificateOrPublicKeyMap" : { "7F4D87C4" : { "id" : "7F4D87C4", "signatureDeviceType" : "CERTIFICATE", "signatureCertificateOrPublicKey" : "MIIFUDCCAzigAwIBAgIEf02HxDANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCQVQxSDBGBgNVBAoMP0EtVHJ1c3QgR2VzLiBmLiBTaWNoZXJoZWl0c3N5c3RlbWUgaW0gZWxla3RyLiBEYXRlbnZlcmtlaHIgR21iSDEjMCEGA1UECwwaQS1UcnVzdCBSZWdpc3RyaWVya2Fzc2UuQ0ExIzAhBgNVBAMMGkEtVHJ1c3QgUmVnaXN0cmllcmthc3NlLkNBMB4XDTE3MDIwMjA4NDM1NloXDTIyMDIwMjA3NDM1NlowQDELMAkGA1UEBhMCQVQxGjAYBgNVBAMMEUdMTiA5MTEwMDE1ODY3MDA4MRUwEwYDVQQFEwwxNzcxMjg1MTM5MzAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASfGHMbO/NJKwdE00+2ucAuCz6Snji4BxV+/Q69mW9TNYeYjaVKa3PdRfl09YJfz9yXywxHqmlT744L/IWc+6pBo4IBuTCCAbUwfwYIKwYBBQUHAQEEczBxMEYGCCsGAQUFBzAChjpodHRwOi8vd3d3LmEtdHJ1c3QuYXQvY2VydHMvQS1UcnVzdC1SZWdpc3RyaWVya2Fzc2UtQ0EuY2VyMCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5hLXRydXN0LmF0L29jc3AwDgYDVR0PAQH/BAQDAgbAMBEGA1UdDgQKBAhLV7tFSZMfhzBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsLmEtdHJ1c3QuYXQvY3JsL0EtVHJ1c3QtUmVnaXN0cmllcmthc3NlLkNBMAkGA1UdEwQCMAAwWAYDVR0gBFEwTzBNBgYqKAARARgwQzBBBggrBgEFBQcCARY1aHR0cDovL3d3dy5hLXRydXN0LmF0L2RvY3MvY3AvQS1UcnVzdC1SZWdpc3RyaWVya2Fzc2UwEwYDVR0jBAwwCoAIQEeeruOQ37YwLgYDVR0RBCcwJYEjcmVuYXRlLmtyb2dnZXJAa3JvZ2dlci13ZXJrc3RhdHQuYXQwHgYHKigACgELAQQTDBFHTE4gOTExMDAxNTg2NzAwODANBgkqhkiG9w0BAQsFAAOCAgEAerZprhyMOB/lTc6SIDDL757f4pnlZpQGOt+OK61r7mk2AKoQzc+PEn9ZNJ4/kuvBOdJOV86Kmi/BPZ9pF9u8zQ1RomK1qJpcnIAiFjXCVlr9lcy57eKlzQo8/w6v4TjbVcgaMGT14Wg6a8/GhD3ovhEfFhtWpav2ftbocF3JlW0JDNIzlvRORUxEHr7so/sVHYHK3l4xCaW2Ju53g8FMsrSCrkT3fwCGEo8GHl9DxZlrJ5aB4Bvn9Y9hiOYXdK495Ipod1wpMFlQXWIjAOyVam8pD5pqt7I5dGpNBZsnoT/W4D7M2u5XXN2sqfdfXB0G/9NC2Gt89eBeyWypxs2lFwaXaf7A9Pwd9bt7WekN/+RmMBV0xbGtslL83Lt7HUYTmv9NOdtnhbFL2+FH1+L01QLbRM4A4BWRqDEFvDKh8Tm5afvkWhNXk6xATK9LXUzkD0g7Tl9C3QeEWWYkpmbMvQKBmFQgT+jIzODChdLsnnZyU91v46oxa+52lCIl+59puBagbt4yBOBheWuJH51+YjvWw5Rt426Tc1HLPiw1YIULGRXMM2vjnzxUsSL1Psb1m/P+l3lldt17x6Z3kGuHqagKsRAYTlDNSh7nS3Fr+6gabZM/vgdmkRLNgPdI0tcO4SA/C33rqhUlgvDWsFGIwlT9itfQ6NCE0AJT4Qh2Zjw=" } } }

.....also mit Zertifikat im PEM Format. Fehlermeldung im Prüftool:

Die kryptographische Gültigkeit der Signatur ist nicht gegeben. Es scheint aber nicht der definierte Fall der ausgefallenen Sicherheitseinrichtung zuzutreffen.

Es hat sich leider nichts verändert 👎

ErichFreitag commented 7 years ago

Schön langsam - jetzt stimmt mal das. Haben sie jetzt betreff Hash oder nicht-Hash nochmals nachgesehen? Prüfen sie bitte auch, ob der BASE64-codierte Signaturwert 88 Zeichen lang ist.

DavStock commented 7 years ago

Der zuletzt getestete Base64 codierte Signaturwert

RHtFNNxC0TttMqctr8bPIFPgsp1PXTyNLbOfBWuZ0oVtHgn1REoSDGEjaezILUYikYJG/FPu5RE0kWPnmL3VCA==

hat genau 88 Zeichen. Gehasht wird nur von aTrust Coding beim Signieren.

ErichFreitag commented 7 years ago

OK. Nachdem die Inhalte hier schon sehr unübersichtlich sind bitte nochmals von vorne:

Bitte um den DEP-Eintrag den sie prüfen (regkassen-ver-dep) als Datei. Bitte um den QR-Code des Belegs den sie prüfen (regkassen-ver-receipt) als Datei. Bitte um den cryptogaphicMaterialContainer.json als Datei. Bitte um die Prüfergebnisse beider Tools als Datei.

DavStock commented 7 years ago

Hallo Herr @ErichFreitag - entschuldigen Sie die späte Antwort! Da kam noch einiges dazwischen. Wenn ich mich nicht täusche benötigen Sie folgende Files von mir: 0000_cashbox_full-DEPTEST.json.txt cryptographicMaterialContainer.json.txt qr-code-rep.json.txt 0000_cashbox_full-QRTEST.json.txt dep-export.json.txt

Egal wie ich das drehe ich habe das Gefühl, dass das Signieren selbst nicht passt. Wenn jede Prüfung erfolgreich bestanden wird aber das Signieren selbst anscheinend nicht?! Wäre für mich nun am naheliegendsten. Danke nochmals für Ihre Zeit.

ErichFreitag commented 7 years ago

Danke für die Daten.

Ohne Gewähr sehe ich mal die folgenden Fehler:

Vielleicht können sie mit der BASE64-Codierung auch schon etwas eingrenzen. Das Zertifikat selbst sieht OK aus.

pqrst_rksv_datenmodell_signaturerstellung

DavStock commented 7 years ago

@ErichFreitag

Ich konnte nun, ohne zu wissen wie, erfolgreich jegliche Art von Belege signieren (keinerlei Änderungen am Coding)! Ich habe noch den Fehler mit dem Verkettungswert entdeckt und erfolgreich behoben (der Hash war inkorrekt).

Der DEP Export war leider auch fehlerhaft wegen, wie sie sagten, der Base64Url Codierung.

Der Umsatzzähler wurde nun auch richtiggestellt da das Decrypting anscheinend auch fehlerhaft war (inkorrekter Hash). Wie gesagt konnte ich nun erfolgreich alle Arten von Belege signieren und kümmere mich nun um das korrekte Eintragen ins DEP.

Vielen Dank für ihre Mühe und Zeit!

ErichFreitag commented 7 years ago

Gerne und fein, Herr Stockinger!

In einer ruhigen Minute sollten sie nochmals nachdenken, warum es plötzlich "ohne Änderungen" (?) funktioniert - das ist zumindest bemerkenswert, warum auch immer. Vielleicht die Summe der Anpassungen...

DavStock commented 7 years ago

Daran arbeite ich gerade - die Ursache wäre höchst interessant da ich auch für die Zukunft wissen sollte was zu tun ist, wenn dieser Fehler doch wieder auftritt. Da ich die genannten Änderungen erst nachträglich gemacht habe, also nachdem die Kryptographie gepasst hat, ist es nun schwer nachzuvollziehen.