pátek 10. prosince 2010

Jak na xs:anyURI v JAXB

Při práci s JAXB (Java API for XML Binding) jsem narazili na zajímavou chybu při unmarshalování. Jestliže byl typ XML elementu xs:anyURI a hodnota obsahovala na začátku, či konci mezery např.

<test:Config xmlns:test="http://www.mycompany.com/test/">
  <test:MyServiceUri>
    http://www.myserver.com/myService
  </test:MyServiceUri>
</test:Config>

pak navzdory tomu, že to bylo validní vůči dané XML Schema instanci, unmarshalovaná hodnota tohoto elementu byla null v případě cílového typu java.net.URI a nebo obsahovala i ony nežádoucí mezery v případě cílového typu String.

Takže první krok k vyřešení problému bylo nahlášení chyby do Issue trackeru JAXB RI a pak nezbývalo než hledat, jak to ošetřit na mé straně než bude vydána opravená verze JAXB. Nakonec jsem použil workaround, v němž vstupní XML soubor (String, InputStream, apod.) načtu se zapnutou validací jako DOM a teprve ten předám k unmarshalování. Nechtěné mezery v DOMu mizí jako zázrakem.

Tento workaround funguje bohužel jen při použití Java 6 - vypadá to, že od Java 5 soudruzi ze Sunu (blahé paměti) zdařile pohnuli s XML parserem. (Takže by mohlo stačit i endorsovat novější XML Parser do Javy 5.)

V reálném nasazení je většinou nutné udělat i nějaké ošetření stavu, kdy vstupní dokument validní není, ale obsahuje jen malou chybku, kterou Unmarshaller hravě zvládne vyřešit (např. přehozené dva elementy). Na tyto případy jsem se osobně rozhodl rezignovat a počkám si až bude oficiálně vydaná oprava přímo v JAXB RI.

A když to vezmeme prakticky, pak to vypadá následovně. Potřebujeme XML Schema, vůči kterému vstupní dokument validujeme:

<xs:schema targetNamespace="http://www.mycompany.com/test/"
 xmlns:test="http://www.mycompany.com/test/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
 elementFormDefault="qualified">

 <xs:element name="Config">
  <xs:complexType>
   <xs:sequence>
    <xs:element name="MyServiceUri" type="xs:anyURI" />
   </xs:sequence>
  </xs:complexType>
 </xs:element>
</xs:schema>

Z toho vygenerujeme pomocí XJC výsledný typ:


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "myServiceUri"
})
@XmlRootElement(name = "Config")
public class Config {

    @XmlElement(name = "MyServiceUri", required = true)
    @XmlSchemaType(name = "anyURI")
    protected String myServiceUri;

    public String getMyServiceUri() {
        return myServiceUri;
    }

    public void setMyServiceUri(String value) {
        this.myServiceUri = value;
    }
}

A teď jen ono magické načítání:

public static void main(String[] args) throws Exception {
 final Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
   new File("test.xsd"));
 final File xmlFile = new File("test.xml");
 
 final Unmarshaller unmarshaller = JAXBContext.newInstance(Config.class.getPackage().getName())
   .createUnmarshaller();
 unmarshaller.setSchema(schema);
 
 // unmarshall XML File directly - there's the bug
 final Config configFromFile = (Config) unmarshaller.unmarshal(xmlFile);
 System.out.println("Service URI when unmarshalled from File: '" + configFromFile.getMyServiceUri() + "'");
 
 // load file to DOM (with validation enabled)
 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 dbf.setSchema(schema);
 dbf.setNamespaceAware(true);
 Document doc = dbf.newDocumentBuilder().parse(xmlFile);
 
 // unmarshall DOM
 Config configFromDom = (Config) unmarshaller.unmarshal(doc);
 System.out.println("Service URI when unmarshalled from DOM: '" + configFromDom.getMyServiceUri() + "'");
}

Což by mělo vytisknout:

Service URI when unmarshalled from File: '
    http://www.myserver.com/myService
  '
Service URI when unmarshalled from DOM: 'http://www.myserver.com/myService'

Chybu a workaround si můžete vyzkoušet v přiloženém Java projektu.

pátek 30. července 2010

Jak na CRL a jejich URL

Občas se stane, že si při práci s X509 certifikáty nevystačíte jen s možnostmi, které nabízí balík java.security.cert a rádi byste vydolovali více informací. Já jsem například řešil požadavek na získání CRL (Certificate Revocation List) souboru pro zadaný certifikát. V takovýchto případech se hodí využít kryptografickou knihovnu Bouncy Castle a pomocí ní vypreparovat třeba i střeva z vašich certifikátů.

Jak tedy implementace vypadá? Nejdříve zjistíme, zda certifikát vůbec podporuje rozšíření CRLDistributionPoints a jesliže ano, máme vyhráno. Knihovna Bouncy Castle obsahuje už připravené třídy přímo pro toto rozšíření. Stačí se tedy ponořit do stromových struktur distribučních bodů a získat tak seznam URL, na kterých by měly být připraveny ke stažení CRL soubory.

import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Set;

import org.bouncycastle.asn1.DERString;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.x509.extension.X509ExtensionUtil;

/**
 * Returns (initialized, but maybe empty) set of URLs of CRLs for given
 * certificate.
 * 
 * @param aCert
 *            X509 certificate.
 * @return
 */
public static Set<String> getCrlUrls(final X509Certificate aCert) {
 final Set<String> tmpResult = new HashSet<String>();
 //get CRLDistributionPoints extension from X509 certificate
 final byte[] crlDPExtension = aCert.getExtensionValue(X509Extensions.CRLDistributionPoints.getId());
 if (crlDPExtension != null) {
  CRLDistPoint crlDistPoints = null;
  try {
   //decode instance of CRLDistPoint
   crlDistPoints = CRLDistPoint.getInstance(X509ExtensionUtil.fromExtensionValue(crlDPExtension));
  } catch (IOException e) {
   e.printStackTrace();
  }
  if (crlDistPoints != null) {
   final DistributionPoint[] distPoints = crlDistPoints.getDistributionPoints();
   distPoint: for (DistributionPoint dp : distPoints) {
    final DistributionPointName dpName = dp.getDistributionPoint();
    final GeneralNames generalNames = (GeneralNames) dpName.getName();
    if (generalNames != null) {
     final GeneralName[] generalNameArr = generalNames.getNames();
     if (generalNameArr != null) {
      for (final GeneralName generalName : generalNameArr) {
       if (generalName.getTagNo() == GeneralName.uniformResourceIdentifier) {
        final DERString derString = (DERString) generalName.getName();
        final String uri = derString.getString();
        if (uri != null && uri.startsWith("http")) {
         // ||uri.startsWith("ftp")
         tmpResult.add(uri);
         continue distPoint;
        }
       }
      }
     }
    }
   }
  }
 } else {
  System.out.println("CRLDistributionPoints extension not supported by the certificate.");
 }
 return tmpResult;
}

Úžasná posloupnost zavíracích závorek, co říkáte? To jsou ty zmiňované stromové struktúry. :-)

A jak to vlastně použít? S rozumem, pánové, s rozumem. :-) Malý příklad, jak to může fungovat, je zde:

/**
 * Sample usage of {@link #getCrlUrls(X509Certificate)}.
 * 
 * @param args
 */
public static void main(String[] args) {
 try {
  // this works only on Windows with 32-bit Sun Java 6
  final KeyStore ks = KeyStore.getInstance("WINDOWS-MY");
  if (ks != null) {
   ks.load(null, null);
   // suppose, we have at least one certificate in the keystore
   final X509Certificate certificate = (X509Certificate) ks.getCertificate(ks.aliases().nextElement());
   System.out.println("Certificate: " + certificate.getSubjectDN());
   final Set<String> crlUrlSet = getCrlUrls(certificate);
   System.out.println("Set of CRL URLs: " + crlUrlSet);
  }
 } catch (Exception e) {
  e.printStackTrace();
 }
}

Což může vypsat například toto:

Certificate: SERIALNUMBER=PXXX, CN=Josef Cacek, OU=PZZZ, L=YYY, C=CZ
Set of CRL URLs: [http://postsignum.ttc.cz/crl/psqualifiedca2.crl, http://www.postsignum.cz/crl/psqualifiedca2.crl, http://www2.postsignum.cz/crl/psqualifiedca2.crl]

úterý 1. června 2010

jOpenSpace 2010


Jaká byla letošní Java (ne)konference jOpenSpace, která se odehrála o víkendu 28.-30. května:
  • tradiční – nechyběly tam:



    • javové i nejavové lightning talky
    • open space diskuse
    • degustace vín
    • sportovní aktivity – překvapilo mě relativně velké procento vytrvalostních běžců
  • netradiční – probíhala v Bílém sklepě rodiny Adámkových ve Chvalovicích na Znojemsku
  • komornější – bylo nás okolo 15
  • volební – někteří účastníci ve Chvalovicích přímo volili do PS, ostatní, kteří si občanskou povinnost odbyli doma, o volbách alespoň diskutovali a sledovali výsledky
  • s wow efektem – smartphony už jsou letos samozřejmost, ale takový iPad, to je věc
Díky patří Michalovi Šrajerovi z Inmite za výborně zorganizovanou akci a samozřejmě i Petrovi Adámkovi, který poskytl prostory (a archívy) Bílého sklepa pro tuto akci.
Teď už zbývá jen těšit se na další ročník (ne)konference jOpenSpace.


Fotky z akce najdete na Picasawebu.