Záloha k Let's Encrypt? Buypass nabízí certifikáty zdarma s ACME

8. 1. 2019
Doba čtení: 4 minuty

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Certifikační autorita Buypass spustila vydávání DV certifikátů zdarma a podporuje protokol ACME. Pomocí existujících nástrojů tedy můžete získat důvěryhodný TLS certifikát od jiné autority než Let's Encrypt.

Certifikační autorita Let's Encrypt změnila před třemi lety přístup k šifrování na webu a stále zaznamenává obrovský růst. Více než 152 milionů doménových jmen je pokryto, což umožňuje důvěryhodný přístup k obsahu. Tohle škálování je možné díky tomu, že jsou certifikáty vydávány automatizovaně a zadarmo.

Má to ale i svá úskalí: Let's Encrypt se stal středobodem pro DV certifikáty a jakmile by se s ním něco stalo, měla by řada správců velký problém. Závislost na jednom podobném subjektu je velmi nebezpečná z mnoha důvodů. Přestože je protokol ACME navržen otevřeně a univerzálně, ostatní autority se do něj příliš nehrnou.

ACME pro automatizaci

Současně se vznikem autority Let's Encrypt vznikl protokol ACME (Automated Certificate Management Environment), který zajišťuje celou komunikaci s autoritou, vytvoření uživatelského účtu, zaslání žádosti o vystavení certifikátu, proces validace požadavku a poté samotné odeslání podepsaného certifikátu.

Přestože je protokol navržen naprosto univerzálně a obsahuje i podporu pro placené uživatelské účty, ostatní certifikační autority se do jeho nasazování nijak zvlášť nehrnuly. Teprve nyní se zřejmě dostáváme do fáze, kdy se budeme s protokolem ACME setkávat i jinde.

Pro uživatele je velkou výhodou to, že existuje celá řada hotových nástrojů v mnoha jazycích běžící na řadě platforem. O některých z nich jsme už psali: ACME.sh, Dehydrated, Caddy nebo modul přímo do Apache. Pokud tedy protokol ACME nasadí další autority, bude přechod k nim velmi snadný. Stačí jen pomocí konfigurační volby nasměrovat komunikaci na jinou autoritu.

Buypass s certifikáty zdarma

První vlaštovkou je v tomto směru norská certifikační autorita Buypass, která nyní zprovoznila na své infrastruktuře podporu protokolu ACME a uživatelům dává DV certifikáty zdarma. Z uživatelského hlediska jde tedy o alternativu k Let's Encrypt, která může docela dobře sloužit jako záloha. Pokud nastane například dočasný výpadek jedné z autorit, bude možné použít tu druhou. Na druhou autoritu s podporou ACME upozornil na svém webu známý bezpečnostní odborník Scott Helme.

Služba se jmenuje Buypass Go SSL a nabízí zdarma certifikáty s dobou platnosti 180 dnů. Má vlastní kořenovou certifikační autoritu Buypass Class 2 Root CA, která je široce rozšířená a důvěryhodná napříč aplikacemi. Koncové DV certifikáty pak vydává mezilehlá autorita Buypass Class 2 CA 5.

Jak to použít

Autorita má na svém webu stránku se základními technickými parametry. To podstatné je, že ACME server sídlí na adrese https://api.buypass.com/acme/directory. Při generování certifikátu je potřeba tuto adresu klientovi předat, obvykle parametrem --server. Výsledný požadavek tak bude vypadat například takto:

$ acme.sh --issue -d example.com  -w /home/letsencrypt/webroot/ --server https://api.buypass.com/acme/directory

Komunikace s autoritou pak probíhá tak, jak jste zvyklí, můžete generovat další certifikáty, obnovovat již existující nebo je revokovat. Autorita doporučuje klienta Certbot, ale můžete využít svého oblíbeného.

Certifikáty se samozřejmě objevují v databázích Certificate Transparency, jinak by už dnes nebyly považovány za důvěryhodné. Autorita je automaticky přidává do logů Google Skydiver a Comodo Sabre. Záznamy si můžete prohlédnout například na Crt.sh nebo Censys.io.

Vlastnosti a omezení certifikátů

Jak jsme si už řekli, certifikáty vydané autoritou Buypass mají platnost 180 dnů. Tedy dvakrát delší než je tomu u Let's Encrypt. To lze díky automatizaci jen těžko považovat za nějakou dramatickou výhodu, prostě se proces obnovování pouští méně častěji. Mimochodem, současná maximální doba platnosti certifikátů je 825 dnů a tato hranice se bude pravděpodobně postupně dále zkracovat.

Limit je 20 certifikátů v jedné doméně vytvořených za týden. Autorita vychází ze seznamu veřejných suffixů, ve kterých je možné si zaregistrovat subdoménu. Pokud tedy máte doménu example.com, můžete si každý týden nechat vygenerovat samostatné certifikáty pro www.example.com, blog.example.com, data.example.com a tak dále až do limitu.

Za týden je možné vytvořit 5 duplicitních certifikátů pro stejnou sadu domén. Zároveň je možné mít až 5 neplatných validací za jednu hodinu pro jedno doménové jméno a uživatelský účet. Pak jsou tu další limity, na které při běžném provozu nenarazíte: 300 souběžných autorizací na jednom uživatelském účtu a maximálně 20 požadavků za sekundu.

Nevýhody a problémy

Nic není tak horké, jak se upeče. Tak i autorita Buypass má svá omezení a nevýhody. Tou hlavní je, že autorita dovoluje do certifikátu vložit nejvýše dvě doménová jména a ještě ne libovolná. Vypadá to, že je povolena pouze varianta bez www a s ním, pravidla pro to ale nejsou příliš jasná. Pokud zkusíte nepovolenou variantu, dozvíte se:

Sign failed: "detail":"Requested combination of domains is not allowed"

Autorita také nepodporuje IPv6, takže pokud máte například síťové prvky jen s veřejnou IPv6 adresou, certifikát si na nich nevygenerujete. Omezení budete muset obcházet například pomocí validace v DNS z jiného serveru.

bitcoin_smenarna

Co je horší je fakt, že autorita nevaliduje DNSSEC, takže jí nevadí chybně podepsaný nebo nepodepsaný DNS záznam porušující řetězec důvěry. Naopak správně validuje CAA záznam, takže si dejte pozor při výměně autorit. Správný záznam vypadá takto:

example.com.    CAA 0 issue "buypass.com"

Bohužel ještě ke všemu výše uvedené vlastnosti nejsou nikde dokumentovány, autorita je vůbec velmi skoupá na informace. Doufejme, že se to časem zlepší. Do té doby sice může sloužit jako záloha k Let's Encrypt, ale opravdu jen jako nouzová.

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.

'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »