Autoryzacja KSeF

0

Dzień dobry,

  1. czy swagger na wersji testowej działa (jest 6 grudnia 2022). Walcze już dość długo z InitSessionTokenRequest i teraz mam TypeError: NetworkError when attempting to fetch resource. Gdy tak przez nieuwagę odpaliłem to na serwerze produkcyjnym to miałem "Plik niezgodny ze strukurą xsd".
  2. w/w TokenRequest : nie mogę tego zwalczyć. w aplikacji dostaję 400 - złe żądanie. Challenge robię, pobieram czas, tłumaczę go na unix (mam nadzieję że ok, robię to sam licząc, odejmując godzinę i w milisekundach). szyfruję kluczem publicznym RSA ten zbitek z tokena +|+ ten czas, robie base64. Tak zaszyfrowany token wygląda teraz dość dobrze , wcześniej bywało różnie. Szyfruję za pomocą openssl, kluczem pobranym ze strony ksef. I ciągle ..400.
  3. Tokena zrobiłem gdzieś na stronie ksef - dla wersji testowej.
  4. Czy dobrze rozumiem temat : najpierw /online/Session/AuthorisationChallenge, potem /online/Session/InitToken ? Nie muszę wcześniej specjanie się logować, nie muszę deklarować kluczy aes itd. ? Cały czas mówię o wersji testowej.

Ktoś mógłby mi wskazać palcem właściwą drogę ?
Używam VB z Visual Studio 2019.

Z góry dziekuję.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:InitSessionTokenRequest
	xmlns="http://ksef.mf.gov.pl/schema/gtw/svc/online/types/2021/10/01/0001"
	xmlns:ns2="http://ksef.mf.gov.pl/schema/gtw/svc/types/2021/10/01/0001"
	xmlns:ns3="http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001">
	<ns3:Context>
      <Challenge>20221206-CR-E0D0A1F48F-02E6C517DD-C4</Challenge>
      <Identifier xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:SubjectIdentifierByCompanyType">
          <ns2:Identifier>58xxxxxxx1</ns2:Identifier>
      </Identifier>
      <DocumentType>
          <ns2:Service>KSeF</ns2:Service>
          <ns2:FormCode>
              <ns2:SystemCode>FA (1)</ns2:SystemCode>
              <ns2:SchemaVersion>1-0E</ns2:SchemaVersion>
              <ns2:TargetNamespace>http://crd.gov.pl/wzor/2021/11/29/11089/</ns2:TargetNamespace>
              <ns2:Value>FA</ns2:Value>
          </ns2:FormCode>
      </DocumentType>
      <Token>kCEwEepHM82t8OYkKJdJJedZ5o2pkefAS5DfO1MZt0p71VKUqGZ0CXJMvhioieywOL03+T3wElpmU04UgVPOnaRIG74LHC+3D42hgGwJfijzm4Vrf1hOL4vGkiY2tgO82xrDBDLsjJo4i4BTDyKP9hPbAiCqwFKCEC61/FTXE1IiY6UDN+EZxyP5YwRRkz5OTlz5a6J0mzWhKlYKsPNg4/8FyEKoD3U26JQ+qCpCHdFk7P8PeuuHHW7hyg+6LAY7BJM8Omk2YhJopFE7CdH5S8hHd+IDB9SRiU4ADMPrKdmNXc77MrJeJyOUD3+vG3R2VBBdV1dszf+9rveOAZLjiw==</Token>
	</ns3:Context>
</ns3:InitSessionTokenRequest>
0

rozwiązałeś może ten problem ? Bo mam to samo, robie tak jak Ty i nic.

0

@casebe fx: Opisz dokładniej jaki masz problem możesz też wrzucić request, bo problem z pierwszego postu jest dość stary i np zmieniła się struktura i namespace KSeF do jakiego się logujemy. Ja przeszedłem w tym tygodniu przez proces autoryzacji i wysyłki faktury na środowisku przedprodukcyjnym (demo) bez problemu wiec może będę umiał pomóc.

0
Michał Podbielski napisał(a):

@casebe fx: Opisz dokładniej jaki masz problem możesz też wrzucić request, bo problem z pierwszego postu jest dość stary i np zmieniła się struktura i namespace KSeF do jakiego się logujemy. Ja przeszedłem w tym tygodniu przez proces autoryzacji i wysyłki faktury na środowisku przedprodukcyjnym (demo) bez problemu wiec może będę umiał pomóc.

Od dwóch tygodni próbuję zainicjować sesję w python, ale ciagle mam błąd 400 lub 415. Nie wiem już o co chodzi. Kod python raczej jest ok, tu chyba chodzi o ta schemę xsd i xml. @alapierre próbował mnie naprowadzić, ale ciągle wyskakuje błąd.
Naprawdę byłbym bardzo wdzięczny, gdybyś spojrzał na mój kod.
Pozdrawiam!

import base64
import requests
import json
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from datetime import datetime, timezone
from time import sleep

BASE_URL = "https://ksef-test.mf.gov.pl/api"

def authorisation_challenge(nip):
    HEADERS = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    
    endpoint_url = f"{BASE_URL}/online/Session/AuthorisationChallenge"
    request_body = {
        "contextIdentifier": {
            "type": "onip",
            "identifier": nip
        }
    }
    
    response = requests.post(endpoint_url, headers=HEADERS, data=json.dumps(request_body))
    response.raise_for_status()
    
    return response.json()

def iso_to_milliseconds(timestamp: str) -> int:
    dt_obj = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
    dt_obj = dt_obj.replace(tzinfo=timezone.utc)
    epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
    delta = dt_obj - epoch
    milliseconds = int(delta.total_seconds() * 1000)
    
    return milliseconds

def round_to_nearest_thousand(milliseconds: int) -> int:
    return round(milliseconds / 1000) * 1000

def encrypt(public_key_str, token, timestamp):
    public_key = RSA.importKey(public_key_str)
    e = public_key.e
    n = public_key.n
    pubkey = RSA.construct((n, e))
    
    text = token + '|' + timestamp
    cipher = PKCS1_v1_5.new(pubkey)
    encrypted_text = cipher.encrypt(bytes(text, encoding='utf-8'))
    
    return base64.b64encode(encrypted_text).decode('utf-8')

PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwocTwdNgt2+PXJ2fcB7k1kn5eFUTXBeep9pHLx6MlfkmHLvgjVpQy1/hqMTFfZqw6piFOdZMOSLgizRKjb1CtDYhWncg0mML+yhVrPyHT7bkbqfDuM2ku3q8ueEOy40SEl4jRMNvttkWnkvf/VTy2TwA9X9vTd61KJmDDZBLOCVqsyzdnELKUE8iulXwTarDvVTx4irnz/GY+y9qod+XrayYndtU6/kDgasAAQv0pu7esFFPMr83Nkqdu6JD5/0yJOl5RShQXwlmToqvpih2+L92x865/C4f3n+dZ9bgsKDGSkKSqq7Pz+QnhF7jV/JAmtJBCIMylxdxI/xfDHZ5XwIDAQAB
-----END PUBLIC KEY-----"""  # [Insert your public key here]
AUTH_TOKEN = "922E535070046F867A1C094EEB8867B30FAC9833E5256C021073AFCA772AD9C8"  # [Insert your auth token here]

response_data = authorisation_challenge("1111111111")
print(response_data)
challenge_time_iso = response_data['timestamp']
sleep(1)

challenge_time = iso_to_milliseconds(challenge_time_iso)
challenge_time = round_to_nearest_thousand(challenge_time)

combined_data = f"{AUTH_TOKEN}|{challenge_time}"
print(f"Combined token and challenge time (Token|ChallengeTime): {combined_data}")

result = encrypt(PUBLIC_KEY, AUTH_TOKEN, str(challenge_time))
print(f"Encoded token and challenge time: {result}")


sleep(1)

xml_template = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:InitSessionTokenRequest xmlns="http://ksef.mf.gov.pl/schema/gtw/svc/online/types/2021/10/01/0001" xmlns:ns2="http://ksef.mf.gov.pl/schema/gtw/svc/types/2021/10/01/0001" xmlns:ns3="http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001">
    <ns3:Context>
        <Challenge>{challenge}</Challenge>
        <Identifier xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:SubjectIdentifierByCompanyType">
            <ns2:Identifier>1111111111</ns2:Identifier>
        </Identifier>
        <DocumentType>
            <ns2:Service>KSeF</ns2:Service>
            <ns2:FormCode>
                <ns2:SystemCode>FA (2)</ns2:SystemCode>
                <ns2:SchemaVersion>1-0E</ns2:SchemaVersion>
                <ns2:TargetNamespace>http://crd.gov.pl/wzor/2023/06/29/12648/</ns2:TargetNamespace>
                <ns2:Value>FA</ns2:Value>
            </ns2:FormCode>
        </DocumentType>
        <Token>{token}</Token>
    </ns3:Context>
</ns3:InitSessionTokenRequest>
"""

formatted_xml = xml_template.format(challenge=challenge_time_iso, token=result)

init_token_endpoint = "https://ksef-test.mf.gov.pl/api/online/Session/InitToken"
xml_headers = {
    
    "Content-Type": "application/octet-stream",
    "Accept": "application/json"
    
}
response = requests.post(init_token_endpoint, headers=xml_headers, data=formatted_xml)
print (response.status_code)
print (response.text)

0

chyba wiem co jest źle
<Challenge>{challenge}</Challenge>
i jako {challenge} podstawiasz (challenge=challenge_time_iso } czyli challenge_time_iso = response_data['timestamp'] jeśli dobrze rozumiem kod
a tam powinno być respond_data['challenge'] czyli Token wyzwania autoryzacyjnego otrzymanego razem ze znacznikiem czasowym.

0
Michał Podbielski napisał(a):

chyba wiem co jest źle
<Challenge>{challenge}</Challenge>
i jako {challenge} podstawiasz (challenge=challenge_time_iso } czyli challenge_time_iso = response_data['timestamp'] jeśli dobrze rozumiem kod
a tam powinno być respond_data['challenge'] czyli Token wyzwania autoryzacyjnego otrzymanego razem ze znacznikiem czasowym.

Bardzo dziękuję za pomoc, ale walczę dalej :)

Rozumiem, że w <Challenge>{challenge}</Challenge> ma mieścić się token/challenge + timestamp z challenge ?
A w jakim formacie (to znaczy jak połaczony) ma byc ten {challenge} ?

Czy ma być to 20230918-CR-C4ED688DCD-8C1C588180-07 (+ | : ???) 2023-09-18T19:32:53.677Z czyli np 20230918-CR-C4ED688DCD-8C1C588180-072023-09-18T19:32:53.677Z
czy na przykład 20230918-CR-C4ED688DCD-8C1C588180-07+2023-09-18T19:32:53.677Z ?
czy może 20230918-CR-C4ED688DCD-8C1C588180-07+1695065574000 , 20230918-CR-C4ED688DCD-8C1C588180-07|1695065574000 czy jeszcze inaczej.

na webinarze z MF gość podstawia 20230918-CR-C4ED688DCD-8C1C588180-07 i 2023-09-18T19:32:53.677Z . gdzieś na forum czytałem , że mają to być milisekundy w unix.

pogubiłem się trochę....

0

To jest tak po wywołaniu AuthorisationChallenge otrzymujesz:
{"timestamp":"2023-09-18T19:55:21.654Z","challenge":"20230918-CR-B56C507960-8829354003-C6"}

i 20230918-CR-B56C507960-8829354003-C6 trafia bezposrednio do <Challenge> jako jawny tekst
a do <Token> trafia zaszyfrowane kluczem publicznym 99AC8862________________64A0E2|1695066921654
gdzie - 99AC8862__________________64A0E2 to token wygenerowany na koncie KSEF
a 1695066921654 - liczba milisekund od 1970-01-01

0
import base64
import requests
import json
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from datetime import datetime, timezone
from time import sleep

BASE_URL = "https://ksef-test.mf.gov.pl/api"

def authorisation_challenge(nip):
    HEADERS = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    
    endpoint_url = f"{BASE_URL}/online/Session/AuthorisationChallenge"
    request_body = {
        "contextIdentifier": {
            "type": "onip",
            "identifier": nip
        }
    }
    
    response = requests.post(endpoint_url, headers=HEADERS, data=json.dumps(request_body))
    response.raise_for_status()
    
    return response.json()

def iso_to_milliseconds(timestamp: str) -> int:
    dt_obj = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
    dt_obj = dt_obj.replace(tzinfo=timezone.utc)
    epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
    delta = dt_obj - epoch
    milliseconds = int(delta.total_seconds() * 1000)
    
    return milliseconds

def round_to_nearest_thousand(milliseconds: int) -> int:
    return round(milliseconds / 1000) * 1000

def encrypt(public_key_str, token, timestamp):
    public_key = RSA.importKey(public_key_str)
    e = public_key.e
    n = public_key.n
    pubkey = RSA.construct((n, e))
    
    text = token + '|' + timestamp
    cipher = PKCS1_v1_5.new(pubkey)
    encrypted_text = cipher.encrypt(bytes(text, encoding='utf-8'))
    
    return base64.b64encode(encrypted_text).decode('utf-8')

PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwocTwdNgt2+PXJ2fcB7k1kn5eFUTXBeep9pHLx6MlfkmHLvgjVpQy1/hqMTFfZqw6piFOdZMOSLgizRKjb1CtDYhWncg0mML+yhVrPyHT7bkbqfDuM2ku3q8ueEOy40SEl4jRMNvttkWnkvf/VTy2TwA9X9vTd61KJmDDZBLOCVqsyzdnELKUE8iulXwTarDvVTx4irnz/GY+y9qod+XrayYndtU6/kDgasAAQv0pu7esFFPMr83Nkqdu6JD5/0yJOl5RShQXwlmToqvpih2+L92x865/C4f3n+dZ9bgsKDGSkKSqq7Pz+QnhF7jV/JAmtJBCIMylxdxI/xfDHZ5XwIDAQAB
-----END PUBLIC KEY-----"""  # [klucz publiczny z dokumentacji API]
AUTH_TOKEN = "922E535070046F867A1C094EEB8867B30FAC9833E5256C021073AFCA772AD9C8"  # [token autoryzacyjny wygenerowany na MF]

response_data = authorisation_challenge("1111111111")
print(response_data)
challenge_time_iso = response_data['timestamp']

wynik_challenge = response_data['challenge']

print('wynik challenge:  ', wynik_challenge)
sleep(1)

challenge_time = iso_to_milliseconds(challenge_time_iso)
challenge_time = round_to_nearest_thousand(challenge_time)
print('challenge_time:  ',challenge_time)

combined_data = f"{AUTH_TOKEN}|{challenge_time}"
print(f"Polaczony token i czas (Token|ChallengeTime): {combined_data}")

result = encrypt(PUBLIC_KEY, AUTH_TOKEN, str(challenge_time))
print(f"Token i czas z szyfrowania: {result}")

sleep(1)

xml_template = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:InitSessionTokenRequest xmlns="http://ksef.mf.gov.pl/schema/gtw/svc/online/types/2021/10/01/0001" xmlns:ns2="http://ksef.mf.gov.pl/schema/gtw/svc/types/2021/10/01/0001" xmlns:ns3="http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001">
    <ns3:Context>
        <Challenge>{challenge}</Challenge>
        <Identifier xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:SubjectIdentifierByCompanyType">
            <ns2:Identifier>1111111111</ns2:Identifier>
        </Identifier>
        <DocumentType>
            <ns2:Service>KSeF</ns2:Service>
            <ns2:FormCode>
                <ns2:SystemCode>FA (2)</ns2:SystemCode>
                <ns2:SchemaVersion>1-0E</ns2:SchemaVersion>
                <ns2:TargetNamespace>http://crd.gov.pl/wzor/2023/06/29/12648/</ns2:TargetNamespace>
                <ns2:Value>FA</ns2:Value>
            </ns2:FormCode>
        </DocumentType>
        <Token>{token}</Token>
    </ns3:Context>
</ns3:InitSessionTokenRequest>
"""

formatted_xml = xml_template.format(challenge=wynik_challenge, token=result)

init_token_endpoint = "https://ksef-test.mf.gov.pl/api/online/Session/InitToken"
xml_headers = {
    
    "Content-Type": "application/octet-stream",
    "Accept": "application/json"
    
}
response = requests.post(init_token_endpoint, headers=xml_headers, data=formatted_xml)
print (response.status_code)
print (response.text)

to jest poprawiony kod. Challenge idzie do <Challenge>{challenge}</Challenge> a Token i czas z challenge jest szyfrowany i idzie do <Token>{token}</Token>.
Cały wynik z konsoli dalej 400:

{'timestamp': '2023-09-19T02:43:53.022Z', 'challenge': '20230919-CR-B9CA737CBD-A80538ABB3-94'}
wynik challenge: 20230919-CR-B9CA737CBD-A80538ABB3-94
challenge_time: 1695091433000
Polaczony token i czas (Token|ChallengeTime): 922E535070046F867A1C094EEB8867B30FAC9833E5256C021073AFCA772AD9C8|1695091433000
Token i czas z szyfrowania: OfdQR+b5bf9ZvGOxQ2+zeKnp0KNSGFs7nKqE71iBUL0b49u5y2+doiwqGqFF+4rNEk8/qxslLkabnkUvDrHoHShQl2K+WjSt25LBP6fJqWkNE+9FTNTzOl6jF6uEFPtTnsbxx3qc28WKGOzmvfDVYdOnk7upoHa5loXv2UbVYQvJBlkk9sWpLQw+ykPK3I7MZy6zx/0/n3QF6EbNaBEr/GjjXGJtjRD+YHpImSXniIQkOe1zjyM5TO3DCIqrBOnZeJCqThmxQYwmihqheKtmzH/KkwGnolFTWaUYWlwZHhqb3xRegWUDIeHxTC62qejcjLSCmlaPyPi00Bqga6rLXg==
400
{"exception":{"serviceCtx":"srvTEMFB","serviceCode":"20230919-EX-7A3F07718B-4CF1EBAC1C-4A","serviceName":"online.session.session.token.init","timestamp":"2023-09-19T02:43:55.363Z","referenceNumber":"20230919-SE-413CF10753-20F4A6C0E7-16","exceptionDetailList":[{"exceptionCode":21401,"exceptionDescription":"Dokument nie jest zgodny ze schemą (xsd)."}]}}

Może problem jest w timestamp, gdzie pokazuje o 2 godziny wcześniej niż aktualna godzina .....

0

to na pewno nie to bo wtedy byłby błąd o niezgodności czasu , ale dlaczego xml_template zaczynasz od podwójnego cudzysłowy, nie znam python ale jeśli powoduje to że cały xml jest wysyłany w cudzysłowie to na pewno będzie niezgodnie z xsd , poza tym jak złożyłem xml z danych podanych to zwalidował się z xsd.

1 użytkowników online, w tym zalogowanych: 0, gości: 1