V tomto čistě praktickém článku se podíváme na javové rozhraní XML Digital Signature realizující XML podpis a s jeho použitím podepíšeme jednoduchý XML soubor.

Předpokladem pro další práci je znalost základů Javy a XML podpisů. Chcete-li se s XML podpisem seznámit nebo si jeho znalost osvěžit, přečtěte si nejprve článek Úvod do XML Digital signing.

Vygenerování klíčů a certifikátu

Začneme tím, že si vytvoříme testovací dvojici soukromého a veřejného klíče pro podpis algoritmem RSA. K tomu použijeme keytool, kterýžto nástroj je součástí Javy. Zadáme na příkazovém řádku následující příkaz:

keytool -genkey -alias interval_test -keyalg RSA -keysize 1024 -validity 365 -storetype pkcs12 -keystore interval_test.p12

Tento příkaz vygeneruje soukromý a veřejný klíč k algoritmu RSA. Veřejný klíč zabalí do samopodepsaného (self-signed) certifikátu, tedy certifikátu podepsaného soukromým klíčem patřícím k veřejnému klíči tohoto certifikátu. Soukromý klíč spolu s certifikátem pak uloží do souboru interval_test.p12, který je formátu pkcs12, a to pod aliasem interval_test.

Po zadání uvedeného příkazu nejprve vyplníme heslo, chránící přístup do souboru s klíči. Dále vyplníme položky obsahující údaje o identitě subjektu certifikátu:

Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]: Interval Test
What is the name of your organizational unit?
  [Unknown]:
What is the name of your organization?
  [Unknown]:
What is the name of your City or Locality?
  [Unknown]:
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:
Is CN=Interval Test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
  [no]: yes

Následujícím příkazem si nyní můžeme vypsat obsah souboru interval_test.p12:

keytool -list -v -storetype pkcs12 -keystore interval_test.p12

Po zadání přístupového hesla se vypíší tyto údaje:

Enter keystore password:
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 1 entry
Alias name: interval_test
Creation date: 13.6.2007
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Interval Test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=Interval Test, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: 466faf39
Valid from: Wed Jun 13 10:47:53 CEST 2007 until: Thu Jun 12 10:47:53 CEST 2008
Certificate fingerprints:
      MD5: 43:29:97:C2:4B:75:55:E5:E1:88:AF:BE:F2:22:2C:44
      SHA1: 66:44:4E:F2:47:F5:48:D5:1D:C0:96:3A:25:68:98:55:75:3C:C1:FA
      Signature algorithm name: SHA1withRSA
      Version: 3
*******************************************
*******************************************

Java XML Digital Signature

Digitální podpis ve formátu XML standardizuje rozhraní JSR 105. Toto rozhraní včetně jeho implementace je součástí Javy od verze 6, takže ke spuštění příkladu vám starší verze Javy nebudou nic platné.

Nejprve načteme soukromý klíč a certifikát ze souboru, kam jsme je uložili.

// Určení typu úložiště
KeyStore ks = KeyStore.getInstance("pkcs12");
// Heslo chránící přístup k úložišti
char[] password = "password".toCharArray();
// Načtení souboru s úložištěm
ks.load(Sign.class.getResourceAsStream("interval_test.p12"), password);
// Získání certifikátu s aliasem interval_test
final Certificate c = ks.getCertificate("interval_test");
// Získání soukromého klíče s aliasem interval_test
final Key key = ks.getKey("interval_test", password);
// Získání řetězce certifikátů ke kořenové autoritě
final Certificate[] certChain = ks.getCertificateChain("interval_test");

Dalším krokem bude načtení podepisovaného XML souboru. Rozhraní XML podpisu je postaveno nad rozhraním org.w3c.dom pro práci s XML soubory, a to k mé velké lítosti, neboť toto rozhraní považuji pro práci s XML soubory za poněkud nepraktické – dávám přednost rozhraní DOM4J, ale o tom zas někdy jindy…

// Načtení podepisovaného dokumentu ze souboru
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
final DocumentBuilder builder = dbf.newDocumentBuilder();
final Document doc = builder.parse(Sign.class
    .getResourceAsStream("test.xml"));

Nyní vytvoříme všechny elementy, které chceme, aby náš XML podpis obsahoval. Protože v úvodu odkazovaný článek blíže nerozebírá syntaxi elementu KeyInfo, trochu ji zde popíšeme. Některé méně zajímavé elementy však pro zjednodušení vynecháme.

Element KeyInfo slouží ke zpřístupnění klíče nebo certifikátu, které je možné použít k ověření podpisu. Tento element není automaticky podepsaný, takže by bylo možné například vyměnit certifikát v něm obsažený za jiný se stejným veřejným klíčem, aniž by to mělo vliv na platnost podpisu. Tomu lze zabránit, přidáme-li elementu KeyInfo identifikační atribut Id a do podpisu přidáme i odkaz na tento element.

Takto vypadá základní struktura elementu KeyInfo:

<KeyInfo>
  <!– Identifikátor páru klíčů,
    které jsou použity k podpisu –>
  <KeyName />
  <!– Strukturovaná hodnota veřejného
    klíče sloužící k ověření podpisu –>
  <KeyValue />
  <!– Informace o certifikátech, seznamy
    zneplatněných certifikátů ap. –>
  <X509Data>
    <!– Informace o vydavateli certifikátu –>
    <X509IssuerSerial>
      <!– Jedinečné jméno vydavatele certifikátu –>
      <X509IssuerName>CN=Obecné jméno, OU=Organizační jednotka, O=organizace,
        L=Lokace, ST=Stát/Provincie, C=Země</X509IssuerName>
      <!– Sériové číslo certifikátu –>
      <X509SerialNumber>123456789</X509SerialNumber>
    </X509IssuerSerial>
    <!– Jedinečné jméno subjektu certifikátu –>
    <X509SubjectName />
    <!– Certifikát zakódovaný kódováním BASE64 –>
    <X509Certificate>…</X509Certificate>
     </X509Data>
</KeyInfo>

Nyní tedy vytvoříme jednoduchý element KeyInfo v Javě. Přidáme do něj element KeyValue s hodnotou klíče a element X509Data s informacemi o vydavateli certifikátu, subjektu certifikátu a certifikátem v binární podobě. Elementy X509IssuerSerial a X509SubjectName mají tu výhodu, že jsou čitelné i při otevření souboru s XML podpisem v obyčejném textovém editoru, není potřeba mít žádnou sofistikovanější aplikaci.

// Získání XMLSignatureFactory, která je základní třídou
// realizující XML podpis. Název použižité implementace je "DOM"
final XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Vytvoření elementu KeyInfo
final KeyInfoFactory kif = fac.getKeyInfoFactory();
// Seznam všech dětských elementů elementu KeyInfo
final List<XMLStructure> keyInfoList = new ArrayList<XMLStructure>();
// Vytvoření elementu KeyValue s hodnotou veřejného klíče,
// který slouží k ověření podpisu
final KeyValue keyValue = kif.newKeyValue(cert.getPublicKey());
// Přidání elementu KeyValue do seznamu
// dětských elementů elementu KeyInfo
keyInfoList.add(keyValue);
// Seznam dětských elementů elementu X509Data
final List x509dataList = new ArrayList();
// Vytvoření elementu X509IssuerSerial
final X509IssuerSerial issuerSerial = kif.newX509IssuerSerial(cert
    .getIssuerX500Principal().getName(), cert.getSerialNumber());
// Přidání elementu X509IssuerSerial do seznamu
// dětských elementů elementu X509Data
x509dataList.add(issuerSerial);
// Přidání elementu X509SubjectName do seznamu
// dětských elementů elementu X509Data
x509dataList.add(cert.getSubjectX500Principal().getName());
// Přidání elementu X509Certificate do seznamu
// dětských elementů elementu X509Data
x509dataList.add(cert);
// Přidání elementu X509Data do seznamu
// dětských elementů elementu KeyInfo
keyInfoList.add(kif.newX509Data(x509dataList));
// Vytvoření elementu KeyInfo
final KeyInfo keyInfo = kif.newKeyInfo(keyInfoList);

Nyní je na řadě vytvoření elementu SignedInfo s informacemi o tom, co a jakým způsobem bylo podepsáno. Způsob kanonizace podepsaného dokumentu určuje algoritmus, jakým se má dokument převést do nějaké kanonické podoby – například zda se XML komentáře zachovávají nebo ne. Způsob výpočtu otisku zprávy (message digest), což je v našem případě SHA256, určuje algoritmus aplikovaný na dokument po provedení jeho kanonizace a eventuelně nějakých dalších transformací. Obsahuje-li podpis transformace, nemusí být na první pohled jasné, co je vlastně podepsáno.

Způsob vytvoření podpisu určuje dva algoritmy. První je algoritmem šifrování (nejčastěji se setkáte s algoritmem RSA), druhý algoritmus určuje způsob výpočtu otisku z elementu SignedInfo. Implementace XML podpisu v Javě 6 umožňuje s algoritmem RSA použít pro výpočet otisku pouze algoritmus SHA1. Potřebujete-li z bezpečnostních důvodů používat delší otisky, je možné k vytvoření XML podpisu využít knihovnu Apache XML Security, která umožňuje s algoritmem RSA použít algoritmy SHA256, SHA384 a SHA512.

// Vytvoření odkazu na podepisovaný element
final Reference reference = fac.newReference("#1", fac.newDigestMethod(
    DigestMethod.SHA256, null));
// Kanonizační metoda
final CanonicalizationMethod canonicalizationMethod =
  fac.newCanonicalizationMethod(
      CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
      (C14NMethodParameterSpec) null);
// Algoritmus podpisu
final SignatureMethod signatureMethod =
  fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
// Vytvoření elementu SignedInfo
final SignedInfo signedInfo = fac
    .newSignedInfo(canonicalizationMethod, signatureMethod,
        Collections.singletonList(reference));
// Vytvoření elementu XMLSignature
XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo);
// Podepsání dokumentu
final DOMSignContext domSignContext = new DOMSignContext(key, doc
    .getDocumentElement());
domSignContext.putNamespacePrefix(XMLSignature.XMLNS, "ds");
signature.sign(domSignContext);

Výsledný dokument nyní uložíme do souboru test_podepsany.xml v aktuálním adresáři.

// Uložení podepsaného dokumentu do souboru test_podepsany.xml
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(
    new FileOutputStream("test_podepsany.xml")));

Můžete si stáhnout a otestovat zdrojový kód programu, soubor s klíčem a certifikátem i dokument před a po podepsání.

podklady/1999-2008 a zdroje

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