PyDERASN: hogyan írtam ASN.1 könyvtárat slots and blobs segítségével

ASN.1 Ez a strukturált információt leíró nyelv szabványa (ISO, ITU-T, GOST), valamint az információ kódolásának szabálya. Programozóként számomra ez csupán egy újabb formátum a sorosításra és az adatok reprezentálására, hasonlóan a JSON-hoz, XML-hez, XDR-hez és másokhoz. Rendkívül elterjedt a mindennapi életünkben, és sokan találkoznak vele: mobil, telefonos és VoIP kommunikációban (UMTS, LTE, WiMAX, SS7, H.323), hálózati protokollokban (LDAP, SNMP, Kerberos), minden, ami a kriptográfiához kapcsolódik (X.509, CMS, PKCS szabványok), bankkártyákon és biometrikus útleveleken, és még sok más helyen.

A cikkben PyDERASN: Python ASN.1 könyvtár, amely aktívan alkalmazható kriptográfiai projektekben. Atlasban.

PyDERASN: hogyan írtam ASN.1 könyvtárat slots and blobs segítségével
Általában nem ajánlatos az ASN.1-et kriptográfiai feladatokhoz használni: az ASN.1 és a kodekjei bonyolultak. Ez azt jelenti, hogy a kód nem lesz egyszerű, ami mindig egy plusz támadási felületet képvisel. Elég csak megnézni az ASN.1 könyvtárak sebezhetőségeinek listáját. Bruce Schneier a Cryptography Engineering szintén nem javasolja ennek a szabványnak a használatát a bonyolultsága miatt: „A legjobban ismert TLV kódolás az ASN.1, de hihetetlenül bonyolult, ezért elkerüljük.” De sajnos ma már szabványokkal rendelkezünk, amelyek aktívan használják nyilvános kulcsok infrastruktúrája X.509 tanúsítványokat , CRL, OCSP, TSP, CMP protokollokat,CMC , üzeneteket,, és számos PKCS szabványt CMS. Ezért számítani kell arra, hogy tudni kell dolgozni az ASN.1-gyel, ha valaki kriptográfiával foglalkozik. Az ASN.1 többféle módon/kodekkel kódolható:BER

(Basic Encoding Rules)

XAdES) is Base64 kódolt ASN.1 DER objektumok találhatók, mint a JSON-alapú protokollban, a Let’s Encrypt-től. A legjobb, ha megértjük a különböző kodekeket és a BER/CER/DER kódolási elveit, amelyeket cikkekben és könyvekben találhatunk:ASN.1 egyszerű szavakkal ACME ASN.1 — Kommunikáció heterogén rendszerek között Olivier Dubuisson által ASN.1 Complete Prof. John Larmouth-tól, A BER bináris byte-orientált (például a PER, amely népszerű a mobil kommunikációban — bit-orientált) TLV-formátum. Minden elem a következőképpen van kódolva: tag (, tag), amely az kódolt elem típusát azonosítja (egész szám, szöveg, dátum stb.), a tartalom hosszát (.

length) és magát a tartalmat (Tag), amely az kódolt elem típusát azonosítja (egész szám, szöveg, dátum, stb.), a hosszát (Llength) a tartalom és a tartalom (VA BER lehetővé teszi a hossz értékének megadása nélkül a kódolást, amely különleges határozatlan hosszú értéket állít be, és a kommunikációt az End-Of-Octets címkével zárja le. A hossz kódolásán kívül sok variáció van a típusok kódolásában is, például:

  • INTEGER, OBJECT IDENTIFIER, BIT STRING és az elem hossza nem lehet normálisan kódolva (nem a minimális formában);
  • A BOOLEAN igaz bármilyen nem null tartalom esetén;
  • A BIT STRING tartalmazhat 'felesleges' nullás biteket;
  • A BIT STRING, OCTET STRING, valamint az összes származtatott karakterlánc típus, beleértve a dátum/időt is, darabokra (chunk) bonthatók változó hosszúságúakra, amelyek hosszát a (de)kódolás alatt előre nem ismerjük;
  • Az UTCTime/GeneralizedTime különböző módokon rendelkezhet az időzóna eltolásának megadásáról és 'felesleges' nullás másodpercekről;
  • A SEQUENCE alapértelmezett értékeit lehet kódolni, de nem kötelező;
  • A BIT STRING utolsó biteinek elnevezett értékeit nem feltétlenül szükséges kódolni;
  • A SEQUENCE (OF)/SET (OF) elemei szabadon rendezhetők.

Az eddig említett okokból kifolyólag, az adatok kódolása úgy, hogy az az eredeti formához hasonló legyen - nem mindig lehetséges. Ezért jött létre egy szabályhalmaz: a DER - szigorúan szabályozza az egyetlen megengedett kódolási módot, ami kritikus kriptográfiai feladatok esetén, ahol például egy bit változása érvényteleníti az aláírást vagy a hash-t. A DER lényeges hátránya, hogy az összes elem hossza előre ismert kell legyen a kódolás során, ami megakadályozza az adatok folyamatos sorozatba állítását. A CER codec mentes ettől a hátránytól, és az adatok egyértelmű ábrázolását is garantálja. Sajnos (vagy szerencsére, hogy még nem bonyolultabb dekódolóink vannak?) nem vált népszerűvé. Ezért a gyakorlatban 'kevert' BER és DER formátumú adatokkal találkozunk. Mivel a CER és DER a BER részhalmazai, bármely BER dekódoló képes ezeket feldolgozni.

Problémák a pyasn1-gyel

A munkahelyünkön sok programot írunk Pythonban a kriptográfiával kapcsolatban. Néhány éve szinte nem voltak elérhető szabad könyvtárak: vagy nagyon alapszintű könyvtárak, amelyek csak az egész szám és a struktúra fejlécének kódolására/dekódolására képesek, vagy ez a könyvtár pyasn1. Éveken át éltünk vele, és kezdetben nagyon elégedettek voltunk, mivel lehetővé teszi az ASN.1 struktúrák magas szintű objektumként való kezelését: például a dekódolt X.509 tanúsítvány lehetővé teszi a mezői elérését szótár-interfészen keresztül: cert[«tbsCertificate»][«serialNumber»] megmutatja a tanúsítvány sorozatszámát. Hasonlóképpen, összetett objektumokat "építhetünk" úgy, hogy listaként és szótárként dolgozunk velük, majd egyszerűen meghívhatjuk a pyasn1.codec.der.encoder.encode funkciót, és megkaphatjuk a dokumentum sorosított reprezentációját.

Ugyanakkor hátrányok, problémák és korlátok derültek ki. A pyasn1-ben voltak és sajnos még mindig vannak hibák: a cikk írásának időpontjában a pyasn1 egyik alapvető típusa — GeneralizedTime, helytelenül dekódolva és kódolva.

A projektjeinkben helytakarékosság miatt gyakran csak a fájl elérési útját, a mesterséget és a hosszát tartjuk nyilván a bájtokban, amelyekre hivatkozni szeretnénk. Például egy tetszőleges aláírt fájl valószínűleg a CMS SignedData ASN.1 struktúrában lesz található:

  0     [1,3,1018]  ContentInfo SEQUENCE
  4     [1,1,   9]   . contentType: ContentType OBJECT IDENTIFIER 1.2.840.113549.1.7.2 (id_signedData)
 19-4   [0,0,1003]   . content: [0] EXPLICIT [UNIV 16] ANY
 19     [1,3, 999]   . . DEFINED BY id_signedData: SignedData SEQUENCE
 23     [1,1,   1]   . . . version: CMSVersion INTEGER v3 (03)
 26     [1,1,  19]   . . . digestAlgorithms: DigestAlgorithmIdentifiers SET OF
                           [...]
 47     [1,3, 769]   . . . encapContentInfo: EncapsulatedContentInfo SEQUENCE
 51     [1,1,   8]   . . . . eContentType: ContentType OBJECT IDENTIFIER 1.3.6.1.5.5.7.12.2 (id_cct_PKIData)
 65-4   [1,3, 751]   . . . . eContent: [0] EXPLICIT OCTET STRING 751 bytes OPTIONAL

                 Itt van a KÉSZÍTENDŐ FÁJL TARTALMA 751 bájtnyi méretben

820     [1,2, 199]   . . . signerInfos: SignerInfos SET OF
823     [1,2, 196]   . . . . 0: SignerInfo SEQUENCE
826     [1,1,   1]   . . . . . version: CMSVersion INTEGER v3 (03)
829     [0,0,  22]   . . . . . sid: SignerIdentifier CHOICE subjectKeyIdentifier
                               [...]
956     [1,1,  64]   . . . . . signature: SignatureValue OCTET STRING 64 bytes
                     . . . . . . C1:B3:88:BA:F8:92:1C:E6:3E:41:9B:E0:D3:E9:AF:D8
                     . . . . . . 47:4A:8A:9D:94:5D:56:6B:F0:C1:20:38:D2:72:22:12
                     . . . . . . 9F:76:46:F6:51:5F:9A:8D:BF:D7:A6:9B:FD:C5:DA:D2
                     . . . . . . F3:6B:00:14:A4:9D:D7:B5:E1:A6:86:44:86:A7:E8:C9

és mi meg tudjuk szerezni az eredeti aláírt fájlt 65 bájt eltolással, 751 bájt hosszúsággal. A pyasn1 nem tárol ilyen információkat a dekódolt objektumaiban. Kifejlesztettek egy úgynevezett TLVSeeker-t — egy kis könyvtárat, amely lehetővé teszi a címkék és objektumhosszak dekódolását, amelynek interfészén parancsoltuk, hogy 'ugorj a következő címkére', 'lépj be a címkébe' (belépünk a SEQUENCE objektumba), 'ugorj a következő címkére', 'jelentsd az offseted és az objektum hosszát, ahol éppen vagyunk'. Ez volt a 'kézi' navigálás az ASN.1 DER-sorozatos adatokban. De BER-kódolt adatokkal nem lehetett így dolgozni, mert például az OCTET STRING bájtstruktúra több chunk-ban is kódolva lehetett.

Egy másik hátrány a mi feladatainkhoz a pyasn1 számára — nem lehetett megérteni a dekódolt objektumok alapján, hogy egy adott mező jelen volt-e a SEQUENCE-ben vagy sem. Például, ha a struktúra tartalmaz egy mezőt, Field SEQUENCE OF Smth OPTIONAL, akkor az teljesen hiányozhatott a beérkezett adatokból (OPTIONAL), vagy jelen lehetett, de nullás hosszúságú (üres lista) lehetett. Általánosságban nem lehetett ezt megállapítani. Pedig ez szükséges a beérkező adatok szigorú érvényességi ellenőrzéséhez. Képzelje el, hogy egy tanúsítványkibocsátó központ kiadna egy tanúsítványt 'nem éppen' érvényes ASN.1 séma szerinti adatokkal! Például a 'TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı' tanúsítványkibocsátó központ a gyökér tanúsítványában túllépett a megengedetteken. RFC 5280 a megfelelő hosszhatárain a subject komponens — nem lehet őszintén dekódolni a sémával. A DER kódoló megköveteli, hogy a mező, amelynek értéke DEFAULT, ne legyen kódolva az átvitel során — ilyen dokumentumok léteznek a valóságban, és a PyDERASN első verziója még tudatosan megengedte ezt a nem érvényes (DER szempontjából) viselkedést a visszafelé kompatibilitás érdekében.

Egy másik korlátozás a nehézség abban, hogy könnyen megállapítsuk, hogy egy adott objektum milyen formátumban (BER/DER) volt kódolva a struktúrában. Például a CMS szabvány kimondja, hogy az üzenetet BER-nek kell kódolnia, de a signedAttrs mező, amelyen a kriptográfiai aláírás készül, DER-ben kell lennie. Ha DER-rel dekódoljuk, akkor az CMS feldolgozása során hibát kapunk, ha pedig BER-rel dekódoljuk, akkor nem fogjuk tudni, hogy a signedAttrs milyen formában volt. Ennek eredményeként a TLVSeeker segítségével (amelynek megfelelője nincs a pyasn1-ben) kell keresnünk az egyes signedAttrs mezők helyét, és külön-külön kell dekódolnunk DER-ben az elkódolt reprezentációból.

Nagyon vágytunk a DEFINED BY mezők automatikus feldolgozásának lehetőségére, amelyek nagyon gyakran előfordulnak. Az ASN.1 struktúra dekódolása után számos ANY mező maradhat, amelyeket tovább kell feldolgozni a struktúrában megadott OBJECT IDENTIFIER alapján választott séma szerint. A Python kódban ez azt jelenti, hogy if-et kell írnunk, és azt követően hívni kell a dekódert az ANY mező számára.

A PyDERASN megjelenése

Az Atlasban rendszeresen, amikor problémákat találunk, vagy javítjuk a használt szabad szoftvereket, folyamatosan küldjük fel a javításokat. A pyasn1-be többször küldtünk fejlesztéseket, de a pyasn1 kódja nem a legegyszerűbb a megértésre, és néha nem kompatibilis API-változások történtek benne, amik problémákat okoztak. Ráadásul hozzászoktunk a generatív tesztelés írásához, ami nem volt jelen a pyasn1-ben.

Egy szép napon úgy döntöttem, hogy elég, ha ezt tűröm, és itt az ideje, hogy megpróbáljak egy saját könyvtárat írni __slot__-okkal, offset-ekkel és szépen megjelenő blob-okkal! Egyszerűen egy ASN.1 kodek létrehozása nem lett volna elegendő — át kellett volna alakítani az összes, egymástól függő projektünket erre, több százezer sor kóddal, amelyek tele vannak ASN.1 struktúrákkal kapcsolatos munkával. Tehát az egyik követelmény számára: a jelenlegi pyasn1 kód könnyen átírható legyen. Az egész szabadságomat arra költöttem, hogy megírjam ezt a könyvtárat, és minden projektet átállítottam rá. Mivel ezek szinte 100%-os tesztlefedettséggel rendelkeznek, ez a könyvtár teljes működőképességét is jelenti.

A PyDERASN is gyakorlatilag 100%-os tesztlefedettséggel rendelkezik. Generatív tesztelést használ a nagyszerű könyvtárral hypothesis. Ezen kívül fuzzingot is végeztek fuzzing py-afl-hez 32 magos gépeken. Annak ellenére, hogy gyakorlatilag már nem maradt Python2 kódunk, a PyDERASN továbbra is megőrzi a kompatibilitást, és emiatt egyetlen six függősége van. Továbbá, tesztelve van az ASN.1:2008 megfelelőségi tesztcsomag.

A használati elve hasonló a pyasn1-hez — magas szintű Python objektumokkal való munka. Az ASN.1 séma leírása hasonló.

class TBSCertificate(Sequence):
    schema = (
        ("version", Version(expl=tag_ctxc(0), default="v1")),
        ("serialNumber", CertificateSerialNumber()),
        ("signature", AlgorithmIdentifier()),
        ("issuer", Name()),
        ("validity", Validity()),
        ("subject", Name()),
        ("subjectPublicKeyInfo", SubjectPublicKeyInfo()),
        ("issuerUniqueID", UniqueIdentifier(impl=tag_ctxp(1), optional=True)),
        ("subjectUniqueID", UniqueIdentifier(impl=tag_ctxp(2), optional=True)),
        ("extensions", Extensions(expl=tag_ctxc(3), optional=True)),
    )

A PyDERASN azonban szigorú típuselméletet alkalmaz. A pyasn1-ben, ha egy mező típusa CMSVersion(INTEGER) volt, akkor hozzárendelhettünk int vagy INTEGER-t is. A PyDERASN szigorúan megköveteli, hogy a hozzárendelt objektum CMSVersion legyen. Továbbá, mivel Python3 kódot írunk, használunk típusannotációkat, ezért a funkcióinkban nem érthetetlen arguments vannak, mint például def func(serial, contents), hanem def func(serial: CertificateSerialNumber, contents: EncapsulatedContentInfo), és a PyDERASN segít ilyen kódot fenntartani.

Ugyanakkor a PyDERASN rendkívül kényelmes engedményeket tesz a típuselmélet terén. A pyasn1 nem engedte meg, hogy a SubjectKeyIdentifier().subtype(implicitTag=Tag(...)) mezőt SubjectKeyIdentifier() objektumhoz rendeljük (a szükséges IMPLICIT TAG nélkül), és gyakran kellett másolni és újra létrehozni objektumokat csak a megváltozott IMPLICIT/EXPLICIT címkék miatt. A PyDERASN szigorúan csak az alap típust figyeli — a címkéket automatikusan behelyettesíti a már meglévő ASN.1 struktúra séma alapján. Ez jelentősen leegyszerűsíti az alkalmazások kódját.

Ha hiba történik a dekódolás során, akkor a pyasn1-ben nehéz megérteni, hol pontosan is történt. Például a fent említett török tanúsítvány esetén a következő hibát kapjuk: UTF8String (tbsCertificate:issuer:rdnSequence:3:0:value:DEFINED BY 2.5.4.10:utf8String) (at 138) unsatisfied bounds: 1 ⇐ 77 ⇐ 64 ASN.1 struktúrák írásakor az emberek hibázhatnak, és ez segít könnyebben debuggolni az alkalmazásokat vagy kideríteni a másik fél által kódolt dokumentumok problémáit.

A PyDERASN első verziójában nem volt támogatás BER kódolásra. Ez sokkal később jelent meg, és a UTCTime/GeneralizedTime időzónás feldolgozása még mindig nem támogatott. Ez a jövőben érkezik, mivel a projektet jellemzően munka melletti szabadidőben írják.

Az első verzióban nem volt DEFINED BY mezőkkel való munka. Néhány hónap múlva ez a lehetőség megjelent és aktívan használni kezdték, jelentősen csökkentve az alkalmazások kódját — egy dekódolási művelet során teljes mértékben meg lehetett kapni a struktúrát a legmélyebb részletekig. Ehhez a sémában meg kell határozni, hogy mely mezők mit "határoznak meg". Például, a CMS séma leírása: class ContentInfo(Sequence): schema = ( ("contentType", ContentType(defines=((("content",), { id_authenticatedData: AuthenticatedData(), id_digestedData: DigestedData(), id_encryptedData: EncryptedData(), id_envelopedData: EnvelopedData(), id_signedData: SignedData(), }),))), ("content", Any(expl=tag_ctxc(0))), )

ez azt jelenti, hogy ha a contentType OID-t tartalmaz az id_signedData értékkel, akkor a content mezőt (ami ugyanebben a SEQUENCE-ben található) a SignedData séma szerint kell dekódolni. Miért kell ennyi zárójel? A mező egyszerre több mezőt "meghatározhat", ahogyan az az EnvelopedData struktúrákban is előfordul. A meghatározott mezők az úgynevezett decode path alapján azonosíthatók — ez határozza meg egy elem pontos helyét az összes struktúrában.

Nem mindig akarjuk vagy van lehetőségünk ezeket a definíciókat közvetlenül a sémába beletenni. Lehetnek alkalmazás-specifikus esetek, amikor az OID-ek és struktúrák csak egy külső projektben ismertek. A PyDERASN lehetőséget biztosít arra, hogy ezeket a definíciókat közvetlenül a struktúra dekódolásakor adjuk meg:

ContentInfo().decode(data, ctx={"defines_by_path": (( ( "content", DecodePathDefBy(id_signedData), "certificates", any, "certificate", "tbsCertificate", "extensions", any, "extnID", ), ((("extnValue",), { id_ce_authorityKeyIdentifier: AuthorityKeyIdentifier(), id_ce_basicConstraints: BasicConstraints(), [...] id_ru_subjectSignTool: SubjectSignTool(), }),), ),)})

Itt azt mondjuk, hogy a CMS SignedData esetében minden csatolt tanúsítvány esetében dekódolni kell minden kiterjesztésüket (AuthorityKeyIdentifier, BasicConstraints, SubjectSignTool stb.). A decode path-on keresztül megadjuk, hogy melyik elemhez kell "hozzárendelni" a definíciókat, mintha azok a sémában lettek volna megadva.

Végül, a PyDERASN lehetőséget biztosít a

parancssorból ASN.1 fájlok dekódolására, és gazdag csinos nyomtatást . Bármilyen ASN.1-et dekódolhatunk, vagy pontosan megadhatunk egy sémát, és valami hasonlót láthatunk:. Tetszőleges ASN.1 dekódolható, de megadhatunk egy pontosan meghatározott sémát is, és valami ilyesmit láthatunk:

PyDERASN: hogyan írtam ASN.1 könyvtárat slots and blobs segítségével

Megjelenített információ: objektum eltolás, címke hossza, tartalom hossza, EOC (end-of-octets) megléte, BER-kódolás jelzése, határidős hosszúságú kódolás jelzése, EXPLICIT címke hossza és eltolása (ha van), objektum mélysége a struktúrákban, IMPLICIT/EXPLICIT a címke értéke, a séma szerinti objektum neve, alap ASN.1 típusa, sorszám a SEQUENCE/SET OF belül, CHOICE értéke (ha van), emberi olvasású INTEGER/ENUMERATED/BIT STRING a sémában, bármilyen alap típus értéke, DEFAULT/OPTIONAL jelző a sémából, annak jelzése, hogy az objektum automatikusan lett dekódolva a DEFINED BY alapján és hogy melyik OID miatt, emberi olvasású OID.

A pretty printing rendszer kifejezetten úgy lett kialakítva, hogy PP-objektumok sorozatát generálja, amelyeket már külön eszközökkel vizualizálnak. A képernyőképen egy egyszerű színes szövegben látható renderer. Léteznek JSON/HTML formátumú renderer-ek is, hogy ezt ki lehessen világítani ASN.1-ben a böngészőben. asn1js projektben.

Egyéb könyvtárak

Ez nem volt cél, de a PyDERASN jelentősen gyorsabb mint a pyasn1. Például, CRL fájlok dekódolása, amelyek mérete megabájtban mérhető, olyan hosszú időt vehet igénybe, hogy el kell gondolkodni az adatok tárolásának (gyors) közbenső formátumain és az alkalmazások architektúrájának megváltoztatásán. A pyasn1 a CRL-t CACert.org a laptopomon több mint 20 perc alatt dekódolja, míg a PyDERASN mindössze 28 másodperc alatt! Van egy asn1crypto, amely a kriptográfiai struktúrákkal való gyors munkára irányul: ez a samme CRL-t teljesen (nem lustán) 29 másodperc alatt dekódolja, de majdnem kétszer annyi RAM-ot használ Python3 alatt (983 MiB szemben 498 MiB) és 3,5-szer annyit Python2 alatt (1677 szemben 488), míg a pyasn1 akár 4,3-szor annyit is elhasznál (2093 szemben 488).

Az asn1crypto, amelyet említettem, nem került megfontolásra, mert a projekt még csak formálódott, és nem hallottunk róla. Most is inkább nem tekintenénk rá, mivel azonnal észrevettem, hogy a GeneralizedTime nem fogad el tetszőleges formátumot, és a sorozatolás során csendben eltávolítja a másodpercre utaló részeket. Ez elfogadható X.509 tanúsítványokkal való munkához, de általános esetben nem megfelelő.

Jelenleg a PyDERASN az általam ismert legszigorúbb nyílt Python/Go DER dekódoló. A kedvenc Go programozási nyelvemben az encoding/asn1 könyvtárban nem szigorú a vizsgálat. OBJEKT AZONOSÍTÓ és UTCTime/GeneralizedTime stringek. Néha a szigorúság akadályozhat (elsősorban a régi alkalmazások visszafelé kompatibilitása miatt, amelyeket senki sem fog javítani), ezért a PyDERASN során a dekódolásnál különféle beállításokat lehet megadni. különböző beállítások. a ellenőrzések enyhítése.

A projekt kódja maximálisan egyszerű próbál lenni. Az egész könyvtár egy fájl. A kódot úgy írták, hogy a megértést helyezi előtérbe, a teljesítmény optimalizálásának és a DRY kódnak a nélkül. Nincs benne, ahogy már említettem, teljes körű BER dekódolás a UTCTime/GeneralizedTime stringekhez, valamint a REAL, RELATIVE OID, EXTERNAL, INSTANCE OF, EMBEDDED PDV, CHARACTER STRING típusú adatokhoz. Minden más esetben én személy szerint nem látok értelmet más könyvtárak használatának Pythonban.

Mint minden projektem, típus. PyGOST, GoGOST, NNCP, GoVPN, a PyDERASN teljesen nyílt forráskódú szoftver, amely a következő feltételekkel terjeszthető LGPLv3+, és ingyenesen letölthető. Példák a használatra megtalálhatók itt és a a PyGOST tesztekben..

Szergej Matvejev, kriptohacker, a következő tagja Szoftverfejlesztési Alap, Python/Go-fejlesztő, vezető szakember FGUP "NTC 'Atlas'".

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster