Přeskočit obsah

Postup vydání certifikátu

V této kapitole je popsán postup volání jednotlivých komunikací se systémy I.CA, jak by měly být postupně za sebou provolány při procesu vydání certifikátu. Proces vydání lze rozdělit na dvě varianty vydání certifikátu, viz Varianty vydání certifikátu.

Tato kapitola popisuje postup pro vydání osobních certifikátů. Vydání serverového certifikátu se skládá ze stejných kroků, ale v některých krocích je odlišné naplnění požadavků odesílaných na systémy I.CA.

U obou typů procesů vydání certifikátu je proces zahájen tím že se načte žádost o certifikát. Prerekvizitou pro vydání certifikátu je mít nastavený kód RA a vlastnit certifikát (komerční nebo twins pár) s operátorským oprávněním.

Proces vydání certifikátu

Diagram posloupnosti operací při procesu vydání certifikátu

1. Ověření operátorského certifikátu (BicaConnector.ControlOperatorAsync)

Před zahájením zpracování žádosti by se měl ověřit certifikát, kterým se podepisují těla http požadavků, má operátorské oprávnění a lze s ním vydávat nové certifikáty. Zavoláním této komunikace lze předejít budoucím chybným voláním následujících komunikací, kdy by všechny vracely chybu použití špatného certifikátu pro komunikaci s daným systémem.

Ověření probíhá na základě podpisu http těla požadavku komunikace BicaConnector.ControlOperatorAsync, kdy BICA získá informace o podpisovém certifikátu a ověří jej, zda má operátorské oprávnění.

2. Získání ID pro žádost o certifikát (CaConnector.GetReqIdAsync)

Po načtení žádosti by se mělo nejprve žádosti přiřadit id. Id žádosti se přiřazuje na začátku procesu z důvodu, že je součástí protokolu, který je potřeba získat před odesláním žádosti na CA.

Metoda GetReqIdAsync vrací poslední evidované číslo žádosti pro daný kód RA. Získané číslo žádosti tedy není možné ihned použít ale je potřeba jej inkrementovat do doby dokud se z něj nestane unikátní číslo žádosti (např. z CA se získá id žádosti 101, v RA jsou rozpracované žádosti 102 a 103, nově vytvářené žádosti je tedy potřeba přiřadit id 104)

Odpověď je zapouzdřena ve třídě GetReqIdResp. Číslo žádosti je uvedené ve vlastnosti GetReqIdResp.ReqID.

Vlastnost GetReqIdResp.ReqIDTwin slouží pro porovnání id žádostí párových certifikátů při vydávání certifikátů twins. Tato vlastnost je přidána z důvodu oddělené kvalifikované (QICA) a komerční CA (SICA). Slouží ke kontrole aby se twins žádosti nenastavili špatné id. Srovnává se ReqIdTwin získáné ze SICA s ReqId ziskáným ze QICA a naopak.

Pokud se vydává pár certifikátů TWINS, je potřeba potřeba získat id žádosti z QICA i SICA!

3. Získání protokolu o podání žádosti (BicaConnector.GetProtocolAsync)

Posledním krokem před odesláním žádosti na server CA je získání protokolu o podání žádosti, který je potřeba vytisknout a podepsat (v rámci elektronického vydávání pouze el. podepsat). Protokol je generován systémem BICA. Aplikace RA zašle dostupná data (data ze žádosti o certifikát a osobní údaje žadatele získané operátorem RA nebo jiným způsobem), která jsou k dispozici ve třídě GetProtocolReqBody a GetProtocolBodyData.

Komunikace BicaConnector.GetProtocolAsync slouží pro vygenerování více druhů protokolů, z toho důvodu je naplnění instance třídy GetProtocolBodyData velice variabilní. V rámci získání protokolu o podání žádosti o certifikát je potřeba nastavit tyto položky:

  • ChallengePassword - heslo pro zneplatnění
  • Date - datum vytvoření protokolu
  • DateAndTime - datum podpisu protokolu
  • FirmName - název společnosti, je potřeba uvést pouze pokud je certifikát vydáván zaměstnanci nebo právnické osobě.
  • Locality - umístění kde protokol vznikl (lokalita kde se nachází RA)
  • RaCode - identifikační kód RA
  • RaOperator - celé jméno operátora RA
  • Address - adresa žadatele
  • Algorithm - Algoritmus klíčového páru
  • CertificateTransparency - příznak o zveřejnění certifikátu
  • Identification - rodné číslo nebo datum narození
  • Mpsv - příznak o přidělení IK MPSV
  • Name - křestní jméno žadatele
  • PrimaryDocument - primární doklad totožnosti (typicky občanský průkaz)
  • SecondaryDocument - sekundární doklad totožnosti (povinný pouze pro kvalifikované certifikáty)
  • PublicCertificate - příznak zda bude certifikát vydán jako veřejný
  • ReqId - číslo žádosti
  • Surname - Příjmení žadatele
  • Dn - předmět žádosti o certifikát (Dictionary položek OID a seznam hodnot)
  • San - alternativní jméno předmětu (Dictionary položek OID a seznam hodnot) - pouze pokud je SAN k dispozici
  • Eku - rozšířené použití klíče (Dictionary položek OID a seznam hodnot) - pouze pokud žádost obsahuje EKU
  • Signer - podepisující (jméno žadatele, které se vkládá do závěrečné části pod kolonku pro podpis žadatele)

V případě párového certifikátu TWINS je potřeba nastavit ještě položky:

  • Algorithm2 - algoritmus klíče párové žádosti o certifikát
  • Dn2 - předmět žádosti o párový certifikát
  • San2 - alternativní jméno předmětu (Dictionary položek OID a seznam hodnot) pro párovou/ý žádost / certifikát - pouze pokud je SAN k dispozici
  • Eku2 - rozšířené použití klíče (Dictionary položek OID a seznam hodnot) pro párovou/ý žádost / certifikát - pouze pokud žádost obsahuje EKU

Pro získání instance třídy GetProtocolBodyData je k dispozici builder třída GetProtocolBodyDataBuilder. Pro získání položek typu Dictionary{string, List{string}} (např. Dn, San, Eku) lze využít builder třídu GetProtocolDictionaryBuilder.

Instance třídy GetProtocolBodyData se poté vkládá do objektu GetProtocolReqBody. Tento objekt obsahuje základní základní informace o žádosti:

  • CertType - typ certifikátu (viz typy certifikátů)
  • Language - jazyk v jakém bude protokol vygenerován (hodnoty - cs, en, sk)
  • PayParentId - id plátce za certifikát (viz plátce za certifikát)
  • Data - instance objektu GetProtocolBodyData
  • Template - typ šablony, zde vždy hodnota PROTOCOL

Pro vytvoření instance GetProtocolReqBody lze využít builder třídu GetProtocolBodyBuilder

Instanci třídy GetProtocolReqBody je potřeba převést do JSON a zakódovat do Base64String. Tuto hodnotu je potřeba vložit do objektu GetProtocolReq. Pro tvorbu instance GetProtocolReq lze využít builder třídu GetProtocolReqBuilder, která přebírá instanci GetProtocolReqBody a provede převedení do JSON a zakódování do Base64String.

Instanci třídy GetProtocolReq již lze předat parametru funkce BicaConnector.GetProtocolAsync, která zašle požadavek na vygenerování protokolu. Ten je navrácen ve třídě GetProtocolResp ve vlastnosti Protocol, kde je zakódován v Base64String. Protokol je generován ve formátu PDF.

4. Odeslání protokolu o podání žádosti na systém STORAGE (StorageConnector.PutCertificateRequest)

Pokud je žádost o certifikát vyřizována papírovou formou, tento krok se musí přeskočit.

V případně elektronické formy vydání certifikátu je před odesláním žádosti na CA nejprve potřeba žádost zaevidovat v systému Storage, kde se pro žádost budou archivovat jednotlivé protokoly.

Funkce StorageConnector.PutCertificateRequest na vstupu přebírá instanci třídy PutCertificateRequestReq, která musí mít inicializovány následující položky:

  • ReqId - id žádosti o certifikát. Pokud se jedná o žádost pro párový certifikát TWINS, udává se id hlavní žádosti (id žádosti pro kvalifikovaný certifikát z páru TWINS).
  • CertProfile - typ profilu certifikátu (viz profily žadatelů)
  • OperatorCertificate - informace o kvalifikovaném certifikátu z páru TWINS operátora (sériové číslo a AKID)
  • PayParent - id plátce za certifikát (viz plátce za certifikát)
  • CertType - typ certifikátu (viz typy certifikátů)
  • Documents - seznam přiložených dokumentů. Seznam musí obsahovat dokument s typem PROTOCOL, který vznikl v kroku 2. Informace o dokumentu jsou zapouzdřeny ve třídě StorageDocumentObject.

Pro získání instance třídy PutCertificateRequestReq je k dispozici builder třída PutCertificateRequestReqBuilder, která obsahuje metodu i pro vytvoření instance třídy StorageDocumentObject a přidání do seznamu dokumentů.

Po úspěšném zavolání metody StorageConnector.PutCertificateRequest lze pokračovat dále v odeslání žádosti na CA. Pokud v této komunikaci nastane chyba, nesmí být žádost odeslána na CA.

5. Odeslání žádosti na systém CA (CaConnector.PutReqAsync)

Funkce pro odeslání žádosti Caconnector.PutReqAsync přebírá jako parametr instanci třídy PutReqReq, která se skládá z následujících položek:

KeyAndRequestData (KeyAndRequestDataObject)

Obsahuje samotnou žádost o certifikát, ID žádosti a další údaje týkající se žádosti (např. číslo karty, pokud byl klíč generován na kartě). Instanci třídy lze získat pomocí builder třídy KeyAndRequestDataBuilder a je potřeba inicializovat tyto položky:

  • Request - data žádosti o certifikát v PEM
  • ReqId - id žádosti

Další položky se nastavují v závislosti na typu certifikátu a kde je uložen klíčový pár (např. CardNumber a QSCD pokud bude certifikát uložen na čipové kartě, která má status QSCD).

Pokud se jedná o žádost pro párový certifikát TWINS je potřeba inicializovat seznam ReqIDInterconnections, ve které jsou k uvedeny všechny párové žádosti (je potřeba uvést i id žádosti, které je zadáno ve vlastnosti ReqId).

CertificateProperties PutReqCertProps

Parametry pro vytvářený certifikát. Pro získání instance třídy PutReqCertProps lze využít builder třídu PutReqCertPropsBuilder. Objekt musí mít inicializovány alespoň tyhle hodnoty:

  • CertChallengePassword - heslo pro zneplatnění certifikátu
  • CertType - typ certifikátu, viz typy certifikátů
  • MpsvIk - Příznak zda se má vložit do certifikátu IK MPSV (hodnoty - yes, no) (lze použít pouze pro kvalifikované certifikáty, komerční certifikáty musí mít hodnotu "no")

BillingData (BillingDataObject)

Informace o plátci za certifikát. Instanci BillingDataObject lze získat pomocí builder třídy BillingDataBuilder. Instance musí mít inicializovanou hodnotu PayParentId (viz plátce za certifikát).

UserData (PutReqUserData)

Osobní údaje a další vyžadované informace o žadateli. Instanci PutReqUserData lze získat pomocí builder třídy PutReqUserDataBuilder, a musí mít inicializované následující vlastnosti:

  • Name - křestní jméno žadatele
  • Surname - příjmení žadatele
  • Address - adresa trvalého bydliště žadatele
  • Document - Doklad totožnosti
  • Language - Jazyk (Hodnoty - cs, en, sk)

Ostatní položky jsou povinné pouze pro specifické typy nebo profily certifikátů, např.:

  • Document2 - vyžadováno pro kvalifikované certifikáty
  • Sex - pohlaví žadatele (M - muž, F - žena). Je potřeba zadat pouze pro kvalifikovaný certifikát, bude obsahovat IK MPSV (pohlaví je vyžadováno při přidělení IK MPSV).
  • Company - vyžadováno pro zaměstnanecké profily certifikátů (instanci třídy CompanyObject lze získat pomocí builder třídy CompanyBuilder).

Instanci třídy PutReqReq lze získat pomocí builder třídy PutReqReqBuilder.

Pokud odeslání žádosti proběhne v pořádku není navrácena žádná návratová hodnota. V případě chyby je navrácena výjimka CAServerException.

Při odesílání žádosti je potřeba dbát na druh žádosti, zda se jedná o žádost pro kvalifikovaný nebo komerční certifikát. Žádost je podle tohoto druhu potřeba odeslat na správnou CA:

  • Žádost o kvalifikovaný certifikát je potřeba odeslat na kvalifikovanou CA (QICA)
  • Žádost o komerční certifikát je potřeba odeslat na komerční CA (SICA)

Při odesílání žádosti o párový certifikát TWINS je potřeba vytvořit požadavek na odeslání PutReqReq pro každý certifikát z páru zvlášť a samostatně jej odeslat na danou CA.

6. Získání vydaného certifikátu ze systému CA (CaConnection.GetCertificateAsync)

Získání vydaného certifikátu probíhá pomocí volání komunikace CaConnection.GetCertificateAsync. Metoda v parametru přebírá instanci třídy GetCertReqBody. Vyhledávat certifikát lze buď pomocí sériového čísla, čísla žádosti nebo SHA-1 otisku klíče.

Při vydávání prvotního certifikátu se využívá čísla žádosti, pro dotaz je tedy potřeba vytvořit instanci třídy GetCertReqBody a inicializovat její vlastnost ReqId. Pro inicializaci instance GetCertReqBody lze využít builder třídu GetCertReqBodyBuilder.

Metoda CaConnections.GetCertificateAsync vrací v odpovědi instanci třídy GetCertRespBody. V této instanci je nejprve potřeba zkontrolovat naplnění vlastnosti GetCertRespBody.RegInfo.State. Tato vlastnost indikuje aktuální stav zpracování žádosti o certifikát. Rozlišují se tři stavy:

Dotazy vzniklé v komunikaci GetCertificateAsync je možné cachovat, viz Obecný popis třídy CaConnector.

Pokud je stav žádosti inprocess, je potřeba se po čase dotázat stejným způsobem na aktuální stav a tento dotaz opakovat dokud se stav žádosti nezmění na certissued nebo rejected.

Pokud se jedná o žádost o certifikát pro pár certifikátů TWINS, je potřeba se dotazovat samostatně na QICA i SICA v jakém stavu vydání se nachází každý z páru certifikátu TWINS.

7. Získání smlouvy o vydání certifikátu (BicaConnector.GetProtocolAsync)

K získání smlouvy o certifikát se používá funkce BicaConnector.GetProtocolAsync podobně jako v kroku 3. Smlouvu je poté potřeba vytisknout a vlastnoručně podepsat nebo v případě elektronické formy vydání elektronicky podepsat (operátor i klient).

Pro vygenerování smlouvy je potřeba zaslat systému BICA informace pro vyplnění šablony smlouvy. Instance třídy GetProtocolBodyData by měla mít inicializované tyto vlastnosti:

  • CertDateTimeFrom - Platnost certifikátu od
  • CertDateTimeTo - platnost certifikát do
  • Date - datum vytvoření protokolu
  • DateAndTime - datum a čas, který bude uveden v protokolu
  • Locality - umístění RA
  • RaCode - identifikační kód RA
  • RaOperator - jméno operátora RA
  • SerialNumber - sériové číslo certifikátu
  • Address - adresa trvalého bydliště žadatele
  • Algorithm - algoritmus klíčového páru certifikátu
  • Identification - rodné číslo / datum narození
  • Name - křestní jméno majitele certifikátu
  • ReqId - id žádosti o certifikát
  • Surname - Příjmení majitele certifikátu
  • Dn - předmět certifikátu jako Dictionary{string, List{string}}
  • DnIssuer - předmět vydavatelského certifikátu jako Dictionary{string, List{string}}
  • Signer - jméno majitele certifikátu (je uvedeno v podpisové části smlouvy)

V případě párového certifikátu TWINS je potřeba nastavit ještě položky:

  • CertType2 - typ párového certifikátu, viz typy certifikátů
  • CertDateTimeFrom2 - platnost párového certifikátu od
  • CertDateTimeTo2 - platnost párového certifikátu do
  • SerialNumber2 - sériové číslo párového certifikátu
  • Algorithm2 - algoritmus klíčového páru párového certifikátu
  • Dn2 - předmět párového certifikátu jako Dictionary{string, List{string}}
  • DnIssuer2 - předmět vydavatelského certifikátu párového certifikátu jako Dictionary{string, List{string}}

Instance třídy GetProtocolBodyData se poté vkládá do objektu GetProtocolReqBody. Tento objekt obsahuje základní základní informace o žádosti:

  • CertType - typ certifikátu, viz typy certifikátů
  • Language - jazyk v jakém bude protokol vygenerován (hodnoty - cs, en, sk)
  • PayParentId - id plátce za certifikát
  • Data - instance objektu GetProtocolBodyData
  • Template - typ šablony, zde vždy hodnota AGREEMENT

Pro vytvoření instance GetProtocolReqBody lze využít builder třídu GetProtocolBodyBuilder

Instance třídy GetProtocolReqBody je potřeba převést do JSON a zakódovat do Base64String. Tuto hodnotu je potřeba vložit do objektu GetProtocolReq. Pro tvorbu instance GetProtocolReq lze využít builder třídu GetProtocolReqBuilder, která převedení na json a zakódování do Base64String má implementováno.

Instanci třídy GetProtocolReq již lze předat parametru funkce BicaConnector.GetProtocolAsync, která zašle požadavek na vygenerování smlouvy. Ta je navrácena ve třídě GetProtocolResp ve vlastnosti Protocol, kde je smlouva zakódována v Base64String. Po dekódování hodnoty je smlouva dostupná ve formátu PDF.

8. Uložení protokolu a smlouvy na úložiště dokumentů STORAGE (StorageConnector.PutCertificateAgreementAsync)

Pokud byl certifikát vydán papírovou formou, tento krok se neprovádí a proces vydání certifikátu je dokončen.

V případě vydání certifikátu el. formou, posledním krokem vydání certifikátu je nahrání el. podepsaného protokolu a smlouvy na úložiště dokumentů. Tento krok je nutno provést, v opačném případě se certifikát po definované době (1 měsíc) automaticky zneplatní.

StorageConnector.PutCertificateAgreementAsync na vstupu přebírá instanci třídy PutCertAgreementReq. Instanci lze získat pomocí builder třídy PutCertAgreementReqBuilder a musí mít inicializovány tyto vlastnosti:

  • ReqId - id žádosti o certifikát (v případě žádosti o párový certifikát TWINS se používá id hlavní žádosti o kvalifikovaný certifikát)
  • ApplicantCertificate - informace (sériové číslo a AKID) nově vydaného certifikátu
  • IssuedCertificate - informace (sériové číslo a AKID) nově vydaného certifikátu (stejné jako vlastnost ApplicantCertificate)
  • OperatorCertificate - iinformace o kvalifikovaném certifikátu z páru TWINS operátora (sériové číslo a AKID)
  • CertType - typ certifikátu, viz typy certifikátů
  • Aggreement - smlouva o vydání certifikátu el. podepsaná operátorským kvalifikovaným certifikátem z páru TWINS a nově vydaným klientským certifikátem

Po úspěšném provedení komunikace StorageConnector.PutCertificateAgreementAsync je dokončen i proces el. vydání certifikátu.

Ukázka vydání certifikátu elektronicky pomocí unit testů

[TestClass]
public class IssueCertCompleteTests
{
    ILogger<IssueCertCompleteTests> _log;
    CaConnector _caConnector;
    StorageConnector _storageConnector;
    BicaConnector _bicaConnector;

    HttpClient _httpClient;
    ILoggerFactory _loggerFactory;

    const string URL_SICA = "https://tests.ica.cz:443/cgi-bin/racom.cgi";
    const string URL_QICA = "https://testq.ica.cz:443/cgi-bin/racom.cgi";

    const string URL_BICA = "https://tbica.ica.cz/cgi-bin/BicaRaComAPI.cgi";

    const string URL_STORAGE = "https://td.ica.cz/cgi-bin/StorageRaComAPI.cgi";

    string _opraCertSN = "037ea08c17b870f6b4";
    string _codeRA = "000";

    string _certType = "Q-PE-CZ";
    string _profile = "NATURAL-PERSON";
    string _payParentId = "15705";


    [TestInitialize]
    public void Initialize()
    {
        _httpClient = new HttpClient();
        _loggerFactory = LoggerFactory.Create((x) =>
        {
            var log4netProvider = new Log4NetProviderOptions("log4net.config");
            x.AddLog4Net(log4netProvider);
            x.SetMinimumLevel(LogLevel.Debug);
        });
        _log = _loggerFactory.CreateLogger<IssueCertCompleteTests>();

        var clientName = "icara v5.0.0.0";
        var getCertCache = new MemoryGetCertCache();

        _caConnector = new CaConnector(_httpClient, _loggerFactory, clientName, getReqIdCache: null, getCertCache: getCertCache,
            getProvCertCache: null);
        _storageConnector = new StorageConnector(_loggerFactory, clientName, _httpClient);
        _bicaConnector = new BicaConnector(_loggerFactory, clientName, _httpClient);


        var opraSigner = new DefaultOperatorSigner(LoadCertificate());
        _caConnector.ServerUrl = URL_QICA;
        _caConnector.OperatorSigner = opraSigner;
        _caConnector.SetRaCodeFromString(_codeRA);

        _bicaConnector.ServerUrl = URL_BICA;
        _bicaConnector.OperatorSigner = opraSigner;
        _bicaConnector.SetRaCodeFromString(_codeRA);

        _storageConnector.ServerUrl = URL_STORAGE;
        _storageConnector.OperatorSigner = opraSigner;
        _storageConnector.SetRaCodeFromString(_codeRA);
    }

    X509Certificate2 LoadCertificate(string certSN = null)
    {
        X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);

        string sn = certSN ?? _opraCertSN;

        X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindBySerialNumber, sn, true);
        if (certificates.Count > 0)
            return certificates[0];

        foreach (X509Certificate2 x509 in store.Certificates)
        {
            if (x509.Subject.IndexOf(sn) != -1)
                return x509;
        }

        certificates = store.Certificates;
        return certificates[0];
    }

    [TestMethod]
    public async Task IssueCertPaperless_01_SendReq()
    {
        try
        {
            _log.LogInformation("IssueCertPaperless_01_SendReq START");
            // 1 - ověření operátorského certifikátu
            await _bicaConnector.ControlOperatorAsync();

            // 2 - získání ID žádosti o certifikát
            var lastReqId = await _caConnector.GetReqIdAsync();
            var reqId = BigInteger.Parse(lastReqId.ReqId);
            ++reqId;
            var newReqId = reqId.ToString();
            File.WriteAllText("newReqId.txt", newReqId);

            // 3 - Získání protokolu o podání žádosti

            var dn = new GetProtocolDictionaryBuilder()
                .AddItem("2.5.4.3", "RaCon UnitTest")
                .AddItem("2.5.4.4", "UnitTest")
                .AddItem("2.5.4.5", "IDCCZ-202080120")
                .AddItem("2.5.4.5", "PASCZ-123456478")
                .AddItem("2.5.4.6", "CZ")
                .AddItem("2.5.4.42", "RaCon")
                .Build();

            var protocolData = new GetProtocolBodyDataBuilder()
                .SetChallengePwd("xxxx")
                .SetDate("14.7.2023")
                .SetDateAndTime("14.7.2023 10:48")
                .SetDn(dn)
                .SetLocality("Zlín")
                .SetRaCode(_codeRA)
                .SetRaOperator("OPRA Jakub Maňák")
                .SetReqId(newReqId)
                .SetIdentification("700101123")
                .SetAddress("Dlouhá 159, 76000 Zlín")
                .SetAlgorithm("RSA")
                .SetCertificateTransparency("ANO")
                .SetMpsv("NE")
                .SetName("RaCon")
                .SetOtherDocument("LL 454544545")
                .SetPrimaryDocument("OP CZ202080120")
                .SetPublishCertificate("ANO")
                .SetSecondaryDocument("PP CZ12345678")
                .SetSurname("UnitTest")
                .SetSigner("RaCon UnitTest")
                .Build();

            var getProtocolBody = new GetProtocolBodyBuilder()
                .SetPayParentId(_payParentId)
                .SetLanguage("cs")
                .SetBodyData(protocolData)
                .SetCertType(_certType)
                .SetTemplate(RAConnection.BICA.GetProtocol.TemplateTypes.PROTOCOL)
                .Build();

            var getProtocolReq = new GetProtocolReqBuilder()
                .SetProtocolRequest(getProtocolBody)
                .Build();

            var protocolResp = await _bicaConnector.GetProtocolAsync(getProtocolReq);
            if (protocolResp is null or { Protocol: null or { Length: 0 } })
                Assert.Fail("No Protocol returned");

            var protocolPdfData = Convert.FromBase64String(protocolResp.Protocol);
            File.WriteAllBytes("protocol.pdf", protocolPdfData);

            // 4 - Odeslání žádosti na Storage

            var pdfProtocolSigned = File.ReadAllText(@"..\..\..\TestFiles\pdf_signed_base64.txt");

            var storagePutReqReq = new PutCertificateRequestReqBuilder()
                .SetCertProfile(_profile)
                .SetCertType(_certType)
                .SetOperatorCertificate("64464679099687827124", "9afd0a8bd8921c72c67a1f86efb77e1f82f3fe7b")
                .SetPayParent(_payParentId)
                .SetProtocol(pdfProtocolSigned, RAConnection.Storage.PutCertificateRequest.StorageFileFormats.pdf)
                .SetReqId(newReqId)
                .Build();

            await _storageConnector.PutCertificateRequestAsync(storagePutReqReq);

            // 5 - odeslání žádosti na CA
            var reqData = File.ReadAllText(@"..\..\..\TestFiles\PEMRequest.txt");

            var keyAndReqData = new KeyAndRequestDataBuilder()
                .SetReqID(newReqId.ToString())
                .SetRequest(reqData)
                .Build();

            var certProps = new PutReqCertPropsBuilder()
                .SetCertChallengePassword("xxxx")
                .SetCertType(_certType)
                .SetMpsvik(false)
                .Build();

            var billingData = new BillingDataBuilder()
                .SetPaymentType(RAConnection.CA.PutReq.BillingDataPaymentType.invoice)
                .SetPayParentId(_payParentId)
                .Build();

            var address = new AddressBuilder()
                .SetCity("Zlín")
                .SetCountry("CZ")
                .SetStreetNumber("159")
                .SetStret("Dlouhá")
                .SetZipCode("76000")
                .Build();

            var userData = new PutReqUserDataBuilder()
                .SetAddress(address)
                .SetBirthNumber("701214101")
                .SetDocument("idc", "636363663", "CZ")
                .SetDocument2("PAS", "69696969", "CZ")
                .SetLanguage("cs")
                .SetName("RaCon")
                .SetSex("M")
                .SetSurname("UnitTest")
                .SetPhoneNumber("+420654987321")
                .Build();

            var putReq = new PutReqReqBuilder()
                .SetKeyAndRequestData(keyAndReqData)
                .SetCertProps(certProps)
                .SetBillingData(billingData)
                .SetUserData(userData)
                .Build();

            await _caConnector.PutReqAsync(putReq);

            // Pokračovat test metodou IssueCertPaperless_02_WaitForCert, kde proběhne čekání na certifikát
            _log.LogInformation("IssueCertPaperless_01_SendReq SUCCESS");
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "IssueCertPaperless_01_SendReq FAILED!!!");
            throw;
        }
    }

    [TestMethod]
    public async Task IssueCertPaperless_02_WaitForCert()
    {
        try
        {
            _log.LogInformation("IssueCertPaperless_02_WaitForCert START");
            var reqId = File.ReadAllText("newReqId.txt");

            // 6 - Získání vydaného certifikátu ze systému CA 
            var getCertReq = new GetCertReqBodyBuilder()
                .SetReqId(reqId)
                .Build();
            GetCertRespBody resp = null;
            do
            {
                resp = await _caConnector.GetCertificateAsync(getCertReq);
            }
            while (resp is not null and { ReqInfo: not null and { State: CaConnector.GETCERT_STATE_INPROCESS } });

            // Pokračovat test metodou IssueCertpaperless_03_SignAndSave
            _log.LogInformation("IssueCertPaperless_02_WaitForCert SUCCESS");
            if (resp.ReqInfo.State == CaConnector.GETCERT_STATE_CERTISSUED)
                File.WriteAllText("issuedCert.cer", resp.Certificate.Pem);
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "IssueCertPaperless_02_WaitForCert FAILED!!!");
            throw;
        }
    }

    [TestMethod]
    public async Task IssueCertPaperless_03_SignAndSave()
    {
        try
        {
            _log.LogInformation("IssueCertPaperless_03_SignAndSave START");

            // 6 - Získání certifikátu ze systému CA
            var reqId = File.ReadAllText("newReqId.txt");
            var getCertReq = new GetCertReqBodyBuilder()
                .SetReqId(reqId)
                .Build();

            var getCertResp = await _caConnector.GetCertificateAsync(getCertReq);

            // 7 - získání smlouvy o vydání certifikátu

            var dn = new GetProtocolDictionaryBuilder()
                .AddItem("2.5.4.3", "Jan Novák")
                .AddItem("2.5.4.4", "Novák")
                .AddItem("2.5.4.5", "IDCCZ-202080120")
                .AddItem("2.5.4.5", "PASCZ-123456478")
                .AddItem("2.5.4.6", "CZ")
                .AddItem("2.5.4.42", "Jan")
                .Build();

            var issuerDn = new GetProtocolDictionaryBuilder()
                .AddItem("2.5.4.3", "I.CA Development Public CA/RSA 05/2022")
                .AddItem("2.5.4.6", "CZ")
                .AddItem("2.5.4.10", "První certifikační autorita, a.s.")
                .AddItem("2.5.4.5", "NTRCZ-26439395")
                .AddItem("2.5.4.11", "Vývoj")
                .AddItem("2.5.4.11", "Test")
                .Build();

            var protocolData = new GetProtocolBodyDataBuilder()
                .SetCertDateTimeFrom("14.7.2023 10:45")
                .SetCertDateTimeTo("14.7.2024 10:44")
                .SetDate("14.7.2023")
                .SetDateAndTime("14.7.2023 10:48")
                .SetDn(dn)
                .SetDnIssuer(issuerDn)
                .SetLocality("Zlín")
                .SetRaCode("000")
                .SetRaOperator("OPRA Jakub Maňák")
                .SetSerialNumber("1234123412341234")
                .SetReqId("4848481000123")
                .SetIdentification("700101123")
                .SetAddress("Dlouhá 159, 76000 Zlín")
                .SetAlgorithm("RSA")
                .SetName("Jan")
                .SetSecondaryDocument("PP CZ12345678")
                .SetSurname("Novák")
                .SetSigner("Jan Novák")
                .Build();

            var getProtocolBody = new GetProtocolBodyBuilder()
                .SetPayParentId(_payParentId)
                .SetLanguage("cs")
                .SetBodyData(protocolData)
                .SetCertType(_certType)
                .SetTemplate(RAConnection.BICA.GetProtocol.TemplateTypes.AGREEMENT)
                .Build();

            var getProtocolReq = new GetProtocolReqBuilder()
                .SetProtocolRequest(getProtocolBody)
                .Build();

            var protocolResp = await _bicaConnector.GetProtocolAsync(getProtocolReq);
            if (protocolResp is null or { Protocol: null or { Length: 0 } })
                Assert.Fail("No Agreement returned returned");

            // 8 - Uložení protokolu a smlouvy na úložiště dokumentů STORAGE
            var issuedCert = X509Certificate2.CreateFromPem(getCertResp.Certificate.Pem);

            var issuedCertDecSN = TestUtils.HexSnToDec(issuedCert.SerialNumber);
            var issuedCertAkid = issuedCert.GetAkid();

            var putAgreementBody = new PutCertAgreementReqBuilder()
                .SetAgreement(null, RAConnection.Storage.PutCertificateRequest.StorageFileFormats.pdf)
                .SetApplicantCertificate(issuedCertDecSN, issuedCertAkid)
                .SetCertType(_certType)
                .SetIssuedCertificate(issuedCertDecSN, issuedCertAkid)
                .SetOperatorCertificate("64464679099687827124", "9afd0a8bd8921c72c67a1f86efb77e1f82f3fe7b")
                .SetProtocol(null, RAConnection.Storage.PutCertificateRequest.StorageFileFormats.pdf)
                .SetReqId(reqId)
                .Build();

            await _storageConnector.PutCertificateAgreementAsync(putAgreementBody);

            _log.LogInformation("IssueCertPaperless_03_SignAndSave SUCCESS");
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "IssueCertPaperless_03_SignAndSave FAILED!!!");
            throw;
        }
    }
}