Ověřujeme-li platnost elektronického podpisu, musíme také zkontrolovat, zda je v pořádku certifikát, který k ověření platnosti podpisu používáme. Tématem tohoto článku je, jak to provedeme v jazyku Java.

Načtení certifikátu

Přehledný popis certifikátu obsahuje článek Co to je digitální certifikát, takže nevíte-li, co si pod pojmem digitální certifikát představit, začněte čtení tímto článkem.

Certifikát v Javě reprezentuje třída java.security.cert.X509Certificate. Bohužel tato třída sama o sobě zpřístupňuje pouze základní údaje obsažené v certifikátu. Ostatní údaje vrátí jako pole bytů, které obsahuje tyto údaje v jazyku ASN.1 zakódované s použitím kódování DER. K jejich rozkódování a rozparsování lze použít například knihovnu Bouncycastle.

Co je třeba u certifikátu zkontrolovat?

Digitální certifikát obsahuje různé informace, z nichž některé jsou důležité pro rozhodnutí, zda mu budeme důvěřovat nebo nikoli.

Při kontrole certifikátu bychom měli vzít v potaz obzvláště tyto údaje:

  • subjekt certifikátu
  • platnost v požadovaném datu
  • účel použití, ke kterému byl certifikát vydán
  • certifikační cesta (řetěz certifikátů)
  • seznam zneplatněných certifikátů

Nyní se tedy podíváme, jak tyto údaje ověříme.

Subjekt certifikátu

Zkontrolovat, zda subjekt certifikátu odpovídá předpokládanému podpisovateli, je tak základní úkon, že jsem jej sem málem ani neuvedla. Zajímavější to může být třeba v případě, kdy dotyčný vlastní více certifikátů lišících se obsaženými údaji. U některých podpisů totiž nemusí stačit vědět, kdo se to podepsal, ale také může být důležité, v jaké ze svých rolí se podepsal. Jestli třeba jako majitel firmy nebo jako obyčejný zaměstnanec.

Platnost v daném datu

Certifikáty se kvůli bezpečnosti vydávají obvykle na dobu jednoho roku. Důvodem je zamezení možnosti, že by někdo metodou hrubé síly našel k veřejnému klíči odpovídající soukromý klíč ještě v době platnosti certifikátu. Z toho také vyplývá, že byste si vždy měli pro následný certifikát raději vygenerovat nový pár klíčů a nenechávat si z pohodlnosti ten starý.

Třída X509Certificate obsahuje k ověření platnosti tyto metody:

// ověří platnost certifikátu v aktuálním okamžiku
checkValidity()
// ověří platnost certifikátu v daném okamžiku
checkValidity(Date date)

Uvedené metody nevracejí žádný výsledek ověření. Není-li certifikát v požadovaném okamžiku platný, vyhodí volání metody výjimku, a to v závislosti na tom, zda certifikát ještě neplatí (CertificateNotYetValidException) nebo už neplatí (CertificateExpiredException).

Účel použití

Certifikát může obsahovat jeden či více účelů, ke kterým lze odpovídající soukromý klíč použít. Chcete-li například certifikát k podepisování e-mailů, lze jej získat, aniž byste se museli zvednout ze židle. Komunikace s certifikační autoritou totiž probíhá přes e-mailovou adresu, pro niž je požadovaný certifikát určen. Takto získaný certifikát však ověřuje pouze onu e-mailovou adresu. O identitě jeho majitele neříká vůbec nic, proto jej nelze použít k ničemu jinému než pouze k podepisování e-mailů za účelem potvrzení, že daný e-mail skutečně přišel z uvedené adresy.

Účel použití není povinná položka certifikátu, ale pokud ji certifikát obsahuje, hodilo by se zkontrolovat, zda je účel dodržen.

Základních účelů použití klíče je devět. Metoda getKeyUsage() vrací pole délky 9 typu boolean[], kde hodnota i-té položky říká, zda je soukromý klíč určen k i-tému účelu z následujícího seznamu:

  1. Digitální podpis (digitalSignature)
  2. Ověření pravosti (nonRepudation)
  3. Šifrování klíče (keyEncipherment)
  4. Šifrování dat (dataEncipherment)
  5. Výměna klíčů (keyAgreement)
  6. Podpis certifikátu (keyCertSign)
  7. Podpis seznamu zneplatněných certifikátů (CRLCertificate Revocation List) (cRLSign)
  8. Pouze šifrování při dohodě na klíči (encipherOnly)
  9. Pouze dešifrace při dohodě na klíči (decipherOnly)

Ze standardů není zcela jasné, které položky mají být nastaveny u kvalifikovaných certifikátů. Více podrobností naleznete v článku Jiřího Peterky Kvalifikovaný certifikát na dvě věci II.

Aby to nebylo tak snadné, existují ještě další účely použití klíče. Tyto rozšiřující účely použití se získají voláním metody List<String> getExtendedKeyUsage(), která vrací seznam OID (Object Identifier – identifikátor objektu) rozšiřujících účelů použití. Možné hodnoty jsou tyto:

  • 1.3.6.1.5.5.7.3.1 – autentizace webového serveru
  • 1.3.6.1.5.5.7.3.2 – autentizace webového klienta
  • 1.3.6.1.5.5.7.3.3 – podpis kódu
  • 1.3.6.1.5.5.7.3.4 – podpis e-mailu
  • 1.3.6.1.5.5.7.3.8 – podpis časového razítka
  • 1.3.6.1.5.5.7.3.9 – podpis odpovědi OCSP (Online Certificate Status Protocol – online protokol ke zjištění, zda není certifikát zneplatněn)

Uvedeny jsou pouze některé základní rozšiřující účely použití klíče, ale seznam registrovaných účelů je o něco větší a stále se může rozšiřovat.

Neobsahuje-li certifikát žádné rozšiřující účely použití, vrací metoda getExtendedKeyUsage() hodnotu null.

Certifikační cesta (řetěz certifikátů)

Certifikát, kterým je vytvořen podpis, je podepsán nějakou certifikační autoritou. K ověření tohoto podpisu je potřeba mít k dispozici příslušný certifikát této autority. Certifikát certifikační autority může být podepsaný jinou certifikační autoritou nebo tou samou certifikační autoritou (self-signed certificate). V tom druhém případě jde o kořenový certifikát. Posloupnost všech těchto certifikátů od certifikátu uživatele až ke kořenovému certifikátu se nazývá certifikační cesta nebo také řetěz certifikátů.

Při ověření certifikační cesty se ověřuje, zda jsou podpisy certifikátů z této cesty platné a zda kořenový certifikát patří důvěryhodné certifikační autoritě, tedy autoritě, které věříme, že nedá certifikát potvrzující nějaké údaje (třeba identitu) špatné osobě.

Operační systém Windows obsahuje vestavěné úložiště důvěryhodných certifikačních autorit s certifikáty mnoha významných certifikačních autorit z celého světa. Mezi ně, bohužel, zatím nepatří žádná z českých certifikačních autorit. Ty je potřeba do tohoto úložiště přidat ručně. Z Javy je od její verze 1.6 přístup k tomuto úložišti velmi snadný:

KeyStore ks = KeyStore.getInstance(„Windows-ROOT“)
ks.load(null, null);

Má to ovšem drobný háček. Windows přidělují certifikátům aliasy (popisné názvy) takovým způsobem, že více certifikátů může obdržet totožné aliasy. Typicky se to stane u certifikátů se stejným subjektem i vydavatelem, které se třeba liší jen termínem platnosti. Java ovšem předpokládá, že aliasy jsou jednoznačné, neboť k obsahu úložiště se dá přistupovat pouze přes aliasy objektů v něm uložených (mohou to být certifikáty ale i soukromé klíče). Enumerace obsahující aliasy všech objektů sice obsahuje duplicitní aliasy vícekrát, ale při vyzvednutí objektu odpovídajícího tomuto aliasu samozřejmě obdržíme vždy tentýž objekt. Řešením je leda přejmenovat v úložišti aliasy tak, aby se tam žádné duplicitní nevyskytovaly.

Java také obsahuje úložiště s důvěryhodnými certifikačními autoritami, a to v souboru $JRE_HOME/lib/security/cacerts, který je ve formátu JKS (Java Keystore).

K vytvoření certifikační cesty potřebujeme mít k dispozici všechny její certifikáty. Z toho důvodu je velmi praktické tyto certifikáty při vytváření digitálního podpisu k podpisu přikládat. Samozřejmě z tohoto pravidla existují výjimky, třeba má-li být aplikace používána pouze v rámci jedné firmy, a tedy uživatelské certifikáty jsou všechny podepsány stejnou autoritou, může být z důvodu úspory místa k podpisu přidáván pouze certifikát uživatele a certifikát certifikační autority se může brát z nějakého předem známého místa nebo být součástí aplikace.

Máme-li seznam obsahující všechny certifikáty certifikační cesty, vytvoříme z něj certifikační cestu takto:

List<X509Certificate> certs;
// nějak získáme certifikáty certifikační cesty
path = CertificateFactory.getInstance(„X.509“).generateCertPath(certs);

Nyní tuto certifikační cestu ověříme. K tomu potřebujeme nějaké úložiště s certifikáty důvěryhodných certifikačních autorit. Máme-li všechny potřebné seznamy zneplatněných certifikátů, můžeme je ověřit zároveň s ověřením certifikační cesty.

private boolean validatePath(KeyStore ks, CertPath certPath, Collection<X509CRL> crls) {
  PKIXParameters params;
  try {
    params = new PKIXParameters(ks);
    if(crls == null) {
      // Nekontroluje se CRL
      params.setRevocationEnabled(false);
    } else {
      final CollectionCertStoreParameters csp = new CollectionCertStoreParameters(crls);
      final CertStore cs = CertStore.getInstance(„Collection“, csp);
      params.addCertStore( cs );
    }
    final CertPathValidator certPathValidator
      = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
    final CertPathValidatorResult result = certPathValidator.validate(certPath, params);
    final PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result;
    final TrustAnchor ta = pkixResult.getTrustAnchor();
    // Certifikát, na jehož základě je certifikační cesta ověřená
    final X509Certificate trustedCert = ta.getTrustedCert();
  } catch (KeyStoreException e) {
    // úložiště certifikátů nebylo inicializováno
    return false;
  } catch (InvalidAlgorithmParameterException e) {
    // špatné parametry metody certPathValidator.validate(certPath, params)
    return false;
  } catch (NoSuchAlgorithmException e) {
    // neznámý algoritmus ověření certifikační cesty
    return false;
  } catch (CertPathValidatorException e) {
    // certifikační cesta není ověřená
    return false;
  }
  return true;
}

Seznam zneplatněných certifikátů

Má-li majitel certifikátu podezření, že jeho certifikát byl zkompromitován, požádá certifikační autoritu, která mu certifikát vydala, o jeho zneplatnění. Certifikační autorita vydává pravidelně seznam zneplatněných certifikátů. Tento seznam obsahuje kromě všech zneplatněných certifikátů také datum, kdy byl seznam vydán, a datum, kdy vyjde jeho příští aktualizace. Aby seznam nebylo možné zfalšovat, je samozřejmě podepsán certifikační autoritou, která jej vydala. Zneplatněné certifikáty jsou na seznamu pouze v době, kdy by jinak byly platné.

Seznam zneplatněných certifikátů je při vytváření podpisu možné přiložit k podpisu a některé formáty podpisu to přímo vyžadují, ale většinou tyto seznamy bývají hodně dlouhé. Existuje ještě protokol k zjišťování stavu zneplatnění certifikátu on-line. Je to protokol OCSP (Online Certificate Status Protocol). České certifikační autority poskytující kvalifikované certifikáty však tento protokol nepodporují a v tomto článku se jím nebudeme zabývat.

Certifikát obvykle obsahuje adresu, na které se CRL nalézá. Bohužel se k této hodnotě nedostaneme v Javě tak snadno, jak bychom si přáli. Od třídy X509Certificate dostaneme pouze pole bajtů obsahující zakódovanou hodnotu této rozšiřující položky certifikátu. Musíme použít již zmíněnou knihovnu Bouncycastle, abychom se k URL dostali.

// hodnota rozšiřující položky certifikátu se seznamem
// distribučních bodů CRL
byte[] bytes = cert1.getExtensionValue( „2.5.29.31“ );

// vrátí první url z daného seznamu distribučních bodů
private URL getCRLDistributionPoint(byte[] bytes) {
  CRLDistPoint cdp;
  try {
    cdp = CRLDistPoint.getInstance( X509ExtensionUtil.fromExtensionValue(bytes));
  } catch ( IOException e1 ) {
    // parametr bytes má špatný formát
    return null;
  }
  DistributionPoint[] points = cdp.getDistributionPoints();
  for ( int i = 0; i < points.length; i++ ) {
    DistributionPoint point = points[i];
    DistributionPointName name = point.getDistributionPoint();
    GeneralName[] gns = ( (GeneralNames) name.getName() ).getNames();
    for ( int j = 0; j < gns.length; j++ ) {
      GeneralName gn = gns[j];
      if ( gn.getTagNo() == GeneralName.uniformResourceIdentifier ) {
        URI uri;
        try {
          uri = new URI( ( (DERString) gn.getName() ).getString() );
          URL crlURL = uri.toURL();
          return crlURL;
        } catch ( URISyntaxException e ) {
          // špatný formát URI
        } catch ( MalformedURLException e ) {
          // špatný formát URL
        }
      }
    }
  }
  return null;
}

Nyní tedy máme adresu distribučního bodu CRL a zbývá z něj toto CRL získat:

CertificateFactory cf = CertificateFactory.getInstance( „X.509“ );
InputStream is = crlURL.openStream();
X509CRL crl = (X509CRL) cf.generateCRL( is );

K ověření, že certifikát nebyl zneplatněn, nyní stačí seznam obsahující CRL ke všem certifikátům certifikační cesty dát jako parametr metodě validatePath(KeyStore ks, CertPath certPath, Collection<X509CRL> crls) ověřující certifikační cestu.

Souhrn

Jak vidíte, ověřit vše důležité okolo certifikátu je celkem pracná záležitost. Ověřovaných skutečností je hodně, ale na žádnou bychom neměli zapomenout, abychom mohli certifikátu skutečně věřit.

Odkazy a zdroje

  • Specifikace RFC 2459 – Infrastruktura internetových veřejných klíčů – certifikáty a CRL
  • Specifikace RFC 3039 – Infrastruktura internetových veřejných klíčů – kvalifikované certifikáty

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět