/* * EncryptedStreamSAXParser.java * PROJECT: JDigiDoc * DESCRIPTION: Digi Doc functions for parsing encrypted * data from streams. Designed to parse large encrypted * files. Uses PKCS#11 driver to decrypt the transport key. * This implementation uses SAX parser. * AUTHOR: Veiko Sinivee, S|E|B IT Partner Estonia *================================================== * Copyright (C) AS Sertifitseerimiskeskus * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * GNU Lesser General Public Licence is available at * http://www.gnu.org/copyleft/lesser.html *================================================== */ package ee.sk.xmlenc.factory; import ee.sk.digidoc.Base64Util; import ee.sk.digidoc.DigiDocException; import ee.sk.digidoc.SignedDoc; import ee.sk.digidoc.TokenKeyInfo; import ee.sk.digidoc.factory.PKCS11SignatureFactory; import ee.sk.digidoc.factory.Pkcs12SignatureFactory; import ee.sk.digidoc.factory.SAXDigiDocException; import ee.sk.digidoc.factory.SignatureFactory; import ee.sk.utils.ConfigManager; import ee.sk.xmlenc.EncryptedData; import ee.sk.xmlenc.EncryptedKey; import ee.sk.xmlenc.EncryptionProperty; import org.apache.log4j.Logger; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.Provider; import java.security.Security; import java.security.cert.X509Certificate; import java.util.Stack; import java.util.zip.Inflater; /** * Implementation class for reading and writing encrypted files using a SAX parser * * @author Veiko Sinivee * @version 1.0 */ public class EncryptedStreamSAXParser extends DefaultHandler implements EncryptedStreamParser { private Stack m_tags; private EncryptedData m_doc; private StringBuffer m_sbCollectChars; private TokenKeyInfo m_tki; /** * log4j logger */ private Logger m_logger = null; private int m_totalDecrypted, m_totalDecompressed, m_totalInput; /** * stream to write decrypted data */ private OutputStream m_outStream; /** * value of Recipient atribute to select the <EncryptedKey> */ private String m_recvName; /** * pin code used to decrypt the transport key */ private String m_pin; /** * index of PKCS#11 token used in decryption */ private int m_token; /** * transport key value */ private byte[] m_transpkey; /** * cipher used in decryption of data */ private Cipher m_cipher; /** * flag: decrypting / not decrypting */ private boolean m_bDecrypting; /** * decompressor */ private Inflater m_decompressor; /** one single buffer */ private StringBuffer m_sbParseBuf; private StringBuffer m_sbB64Buf; private static final int ENC_BLOCK_SIZE = 256; private X509Certificate m_decCert; private SecretKey m_transportKey; private int m_nBlockType; private static int DENC_BLOCK_FIRST = 1; private static int DENC_BLOCK_MIDDLE = 2; private static int DENC_BLOCK_LAST = 3; private SignatureFactory m_sigFac; /** * Creates new EncryptedStreamSAXParser and initializes the variables */ public EncryptedStreamSAXParser() { m_tags = new Stack(); m_doc = null; m_pin = null; m_cipher = null; m_outStream = null; m_decCert = null; m_recvName = null; m_bDecrypting = false; m_totalDecrypted = 0; m_totalDecompressed = 0; m_totalInput = 0; m_token = 0; m_sbCollectChars = null; m_decompressor = null; m_transportKey = null; m_transpkey = null; m_sigFac = null; m_logger = Logger.getLogger(EncryptedStreamSAXParser.class); } /** * initializes the implementation class * * @see ee.sk.xmlenc.factory.EncryptedDataParser#init() */ public void init() throws DigiDocException { try { Provider prv = (Provider)Class.forName(ConfigManager. instance().getProperty("DIGIDOC_SECURITY_PROVIDER")).newInstance(); //m_logger.info("Provider"); //prv.list(System.out); Security.addProvider(prv); } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_NOT_FAC_INIT); } } /** * Initializes the Recipient atribute value used for locating the right <EncryptedKey> to be used for deryption * * @param s value of Recipient atribute */ public void setRecipientName(String s) { m_recvName = s; } /** * Initializes the output stream where to write decrypted data * * @param outs output stream already opened by the user */ public void setOutputStream(OutputStream outs) { m_outStream = outs; } /** * Initializes the PIN code used to decrypt the transport key * * @param pin PIN code */ public void setPin(String pin) { m_pin = pin; } /** * Initializes the PKCS#11 token index used for decryption * * @param tok PKCS#11 token index used for decryption */ public void setToken(int tok) { m_token = tok; } /** * Accessor for cdoc container * @return cdoc container */ public EncryptedData getCdoc() { return m_doc; } /** * Reads in a EncryptedData file (.cdoc) * * @param dencStream opened stream with EncrypyedData data The user must open and close it. * @param outs output stream for decrypted data * @param token index of PKCS#11 token used * @param pin pin code to decrypt transport key using PKCS#11 * @param recipientName Recipient atribute value of <EncryptedKey> used to locate the correct transport key to decrypt with * * @return number of bytes successfully decrypted * * @throws DigiDocException for decryption errors */ public int decryptStreamUsingRecipientName(InputStream dencStream, OutputStream outs, int token, String pin, String recipientName) throws DigiDocException { // Use an instance of ourselves as the SAX event handler EncryptedStreamSAXParser handler = this; handler.setRecipientName(recipientName); handler.setOutputStream(outs); handler.setPin(pin); handler.setToken(token); // try find cert of token to decrypt with try { // use default factory as configured m_sigFac = ConfigManager.instance().getSignatureFactory(); m_decCert = m_sigFac.getAuthCertificate(token, pin); } catch(Exception ex) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "Error loading decryption cert!", null); } // Use the default (non-validating) parser SAXParserFactory factory = SAXParserFactory.newInstance(); //factory.setNamespaceAware(true); try { factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(dencStream, handler); } catch (SAXDigiDocException ex) { throw ex.getDigiDocException(); } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML); } if (m_doc == null) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "This document is not in EncryptedData format", null); } return m_totalDecrypted; } /** * Reads in a EncryptedData file (.cdoc) * @param dencStream opened stream with EncrypyedData data * The user must open and close it. * @param outs output stream for decrypted data * @param slot PKCS#11 slot id * @param label pkcs#11 token label * @param pin pin code to decrypt transport key using PKCS#11 * used to locate the correct transport key to decrypt with * @return number of bytes successfully decrypted * @throws DigiDocException for decryption errors */ public int decryptStreamUsingRecipientSlotIdAndTokenLabel(InputStream dencStream, OutputStream outs, int slot, String label, String pin) throws DigiDocException { // Use an instance of ourselves as the SAX event handler EncryptedStreamSAXParser handler = this; handler.setOutputStream(outs); handler.setPin(pin); ConfigManager cfg = ConfigManager.instance(); PKCS11SignatureFactory p11SigFac = null; //PKCS11SignatureFactory sigFac = (PKCS11SignatureFactory)cfg. // getSignatureFactoryOfType(SignatureFactory.SIGFAC_TYPE_PKCS11); SignatureFactory sigFac = cfg.getSignatureFactory(); if(sigFac instanceof PKCS11SignatureFactory) p11SigFac = (PKCS11SignatureFactory)sigFac; else p11SigFac = (PKCS11SignatureFactory)cfg. getSignatureFactoryOfType(SignatureFactory.SIGFAC_TYPE_PKCS11); if(p11SigFac == null) { m_logger.error("No PKCS11 signature factory"); return 0; } m_sigFac = p11SigFac; TokenKeyInfo tki = p11SigFac.getTokenWithSlotIdAndLabel(slot, label); if(tki == null) { m_logger.error("No token with slot: " + slot + " and label: " + label); return 0; } if(tki != null && !tki.isEncryptKey()) { m_logger.error("Token with slot: " + slot + " and label: " + label + " is not an encryption key!"); return 0; } m_decCert = tki.getCert(); m_tki = tki; if(m_logger.isDebugEnabled()) m_logger.debug("Decrypt with slot: " + slot + " label: " + label + " token: " + ((m_tki != null) ? "OK" : "NULL") + " cert: " + ((m_decCert != null) ? "OK" : "NULL")); if(m_decCert == null) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "Error loading decryption cert!", null); } // Use the default (non-validating) parser SAXParserFactory factory = SAXParserFactory.newInstance(); //factory.setNamespaceAware(true); try { factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(dencStream, handler); } catch (SAXDigiDocException ex) { throw ex.getDigiDocException(); } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML); } if (m_doc == null) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "This document is not in EncryptedData format", null); } return m_totalDecrypted; } /** * Reads in a EncryptedData file (.cdoc) * @param dencStream opened stream with EncrypyedData data The user must open and close it. * @param outs output stream for decrypted data * @param token index of PKCS#11 token used * @param pin pin code to decrypt transport key using PKCS#11 * @param tokenType token type - PKCS11 or PKCS12 * @param pkcs12Keystore - PKCS12 keystore filename and path if pkcs12 is used * @return number of bytes successfully decrypted * @throws DigiDocException for decryption errors */ public int decryptStreamUsingTokenType(InputStream dencStream, OutputStream outs, int token, String pin, String tokenType, String pkcs12Keystore) throws DigiDocException { // Use an instance of ourselves as the SAX event handler EncryptedStreamSAXParser handler = this; handler.setOutputStream(outs); handler.setPin(pin); handler.setToken(token); if(tokenType == null || (!tokenType.equals(SignatureFactory.SIGFAC_TYPE_PKCS11) && !tokenType.equals(SignatureFactory.SIGFAC_TYPE_PKCS12))) throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Invalid token type. Must be PKCS11 or PKCS12!", null); // try find cert of token to decrypt with try { m_sigFac = ConfigManager.instance().getSignatureFactoryOfType(tokenType); if(m_sigFac != null && m_sigFac instanceof Pkcs12SignatureFactory) { Pkcs12SignatureFactory pfac = (Pkcs12SignatureFactory)m_sigFac; if(m_logger.isDebugEnabled()) m_logger.debug("Loading pkcs12 keystore: " + pkcs12Keystore); pfac.load(pkcs12Keystore, tokenType, pin); } if(m_sigFac != null) m_decCert = m_sigFac.getAuthCertificate(token, pin); } catch(Exception ex) { m_logger.error("Error loading decryption cert: " + ex); throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Error loading decryption cert!", ex); } if(m_decCert == null) throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Error loading decryption cert!", null); // Use the default (non-validating) parser SAXParserFactory factory = SAXParserFactory.newInstance(); //factory.setNamespaceAware(true); try { factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(dencStream, handler); } catch (SAXDigiDocException ex) { throw ex.getDigiDocException(); } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML); } if (m_doc == null) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "This document is not in EncryptedData format", null); } return m_totalDecrypted; } /** * Reads in a EncryptedData file (.cdoc) * * @param dencStream opened stream with EncrypyedData data The user must open and close it. * @param outs output stream for decrypted data * @param deckey decryption key * @param recipientName Recipient atribute value of <EncryptedKey> used to locate the correct transport key to decrypt with * * @return number of bytes successfully decrypted * * @throws DigiDocException for decryption errors */ public int decryptStreamUsingRecipientNameAndKey(InputStream dencStream, OutputStream outs, byte[] deckey, String recipientName) throws DigiDocException { // Use an instance of ourselves as the SAX event handler EncryptedStreamSAXParser handler = this; handler.setRecipientName(recipientName); handler.setOutputStream(outs); m_transpkey = deckey; m_transportKey = (SecretKey)new SecretKeySpec(m_transpkey,EncryptedData.DIGIDOC_ENCRYPTION_ALOGORITHM); if(m_logger.isDebugEnabled()) m_logger.debug("Transport key: " + ((m_transportKey == null) ? "ERROR" : "OK") + " len: " + m_transpkey.length); // Use the default (non-validating) parser SAXParserFactory factory = SAXParserFactory.newInstance(); //factory.setNamespaceAware(true); try { factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(dencStream, handler); } catch (SAXDigiDocException ex) { throw ex.getDigiDocException(); } catch (Exception ex) { DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML); } if (m_doc == null) { throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "This document is not in EncryptedData format", null); } return m_totalDecrypted; } /** * Start Document handler */ public void startDocument() throws SAXException { m_doc = null; m_sbCollectChars = null; m_decompressor = null; m_totalDecrypted = 0; m_totalDecompressed = 0; m_totalInput = 0; m_sbParseBuf = new StringBuffer(); m_sbB64Buf = new StringBuffer(); m_nBlockType = DENC_BLOCK_FIRST; } /** * End Document handler */ public void endDocument() throws SAXException { } /** * Finds the value of an atribute by name * * @param atts atributes * @param attName name of atribute * * @return value of the atribute */ private String findAtributeValue(Attributes attrs, String attName) { String value = null; for (int i = 0; i < attrs.getLength(); i++) { String key = attrs.getQName(i); if (key.equals(attName) || key.indexOf(attName) != -1) { value = attrs.getValue(i); break; } } return value; } /** * Checks if this document is in <EncryptedData> format * * @throws SAXDigiDocException if the document is not in <EncryptedData> format */ private void checkEncryptedData() throws SAXDigiDocException { if (m_doc == null) { throw new SAXDigiDocException(DigiDocException.ERR_XMLENC_NO_ENCRYPTED_DATA, "This document is not in EncryptedData format!"); } } /** * Checks if the <EncryptedKey> objects exists * * @throws SAXDigiDocException if the objects <EncryptedKey> does not exist */ private void checkEncryptedKey(EncryptedKey key) throws SAXDigiDocException { if (key == null) { throw new SAXDigiDocException(DigiDocException.ERR_XMLENC_NO_ENCRYPTED_KEY, "This <EncryptedKey> object does not exist!"); } } /** * Start Element handler * * @param namespaceURI namespace URI * @param lName local name * @param qName qualified name * @param attrs attributes */ public void startElement(String namespaceURI, String lName, String qName, Attributes attrs) throws SAXDigiDocException { String tName = qName; if (tName.indexOf(":") != -1) { tName = qName.substring(qName.indexOf(":") + 1); } if (m_logger.isDebugEnabled()) { m_logger.debug("Start Element: " + tName + " qname: " + qName + " lname: " + lName + " uri: " + namespaceURI); } m_tags.push(tName); if (tName.equals("KeyName") || tName.equals("CarriedKeyName") || tName.equals("X509Certificate") || tName.equals("EncryptionProperty")) { m_sbCollectChars = new StringBuffer(); } if (tName.equals("CipherValue")) { if (m_tags.search("EncryptedKey") != -1) { // child of <EncryptedKey> m_sbCollectChars = new StringBuffer(); } else { // child of <EncryptedKey> m_sbCollectChars = null; m_bDecrypting = true; } } // <EncryptedData> if (tName.equals("EncryptedData")) { String str = findAtributeValue(attrs, "xmlns"); try { m_doc = new EncryptedData(str); str = findAtributeValue(attrs, "Id"); if (str != null) { m_doc.setId(str); } str = findAtributeValue(attrs, "Type"); if (str != null) { m_doc.setType(str); } str = findAtributeValue(attrs, "MimeType"); if (str != null) { m_doc.setMimeType(str); } if (m_doc.getMimeType() != null && m_doc.getMimeType().equals(EncryptedData.DENC_ENCDATA_MIME_ZLIB)) { m_decompressor = new Inflater(); } } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } try { if(m_transportKey != null) { byte[] iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; m_cipher = m_doc.getCipher(Cipher.DECRYPT_MODE, m_transportKey, iv); } } catch (DigiDocException ex) { m_logger.error("Error using key: " + ((m_transpkey != null) ? Base64Util.encode(m_transpkey) : "NULL") + " - " + ex); SAXDigiDocException.handleException(ex); } } // <EncryptionMethod> if (tName.equals("EncryptionMethod")) { checkEncryptedData(); if(m_tags.search("EncryptedKey") != -1) { // child of <EncryptedKey> EncryptedKey ekey = m_doc.getLastEncryptedKey(); checkEncryptedKey(ekey); try { ekey.setEncryptionMethod(findAtributeValue(attrs, "Algorithm")); } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } } else { // child of <EncryptedData> try { m_doc.setEncryptionMethod(findAtributeValue(attrs, "Algorithm")); } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } } } // <EncryptedKey> if (tName.equals("EncryptedKey")) { checkEncryptedData(); EncryptedKey ekey = new EncryptedKey(); m_doc.addEncryptedKey(ekey); String str = findAtributeValue(attrs, "Recipient"); if (str != null) { ekey.setRecipient(str); } str = findAtributeValue(attrs, "Id"); if (str != null) { ekey.setId(str); } } // <EncryptionProperties> if (tName.equals("EncryptionProperties")) { checkEncryptedData(); String str = findAtributeValue(attrs, "Id"); if (str != null) { m_doc.setEncryptionPropertiesId(str); } } // <EncryptionProperty> if (tName.equals("EncryptionProperty")) { checkEncryptedData(); EncryptionProperty eprop = new EncryptionProperty(); m_doc.addProperty(eprop); String str = findAtributeValue(attrs, "Id"); if (str != null) { eprop.setId(str); } str = findAtributeValue(attrs, "Target"); if (str != null) { eprop.setTarget(str); } str = findAtributeValue(attrs, "Name"); try { if (str != null) { eprop.setName(str); } } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } } } /** * End Element handler * * @param namespaceURI namespace URI * @param lName local name * @param qName qualified name */ public void endElement(String namespaceURI, String sName, String qName) throws SAXException { String tName = qName; if (tName.indexOf(":") != -1) { tName = qName.substring(tName.indexOf(":") + 1); } if (m_logger.isDebugEnabled()) { m_logger.debug("End Element: " + tName); } // remove last tag from stack String currTag = (String)m_tags.pop(); // <KeyName> if (tName.equals("KeyName")) { checkEncryptedData(); EncryptedKey ekey = m_doc.getLastEncryptedKey(); checkEncryptedKey(ekey); ekey.setKeyName(m_sbCollectChars.toString()); m_sbCollectChars = null; // stop collecting } // <CarriedKeyName> if (tName.equals("CarriedKeyName")) { checkEncryptedData(); EncryptedKey ekey = m_doc.getLastEncryptedKey(); checkEncryptedKey(ekey); ekey.setCarriedKeyName(m_sbCollectChars.toString()); m_sbCollectChars = null; // stop collecting } // <X509Certificate> if (tName.equals("X509Certificate")) { checkEncryptedData(); EncryptedKey ekey = m_doc.getLastEncryptedKey(); checkEncryptedKey(ekey); try { X509Certificate cert = SignedDoc.readCertificate(Base64Util. decode(m_sbCollectChars.toString().getBytes())); ekey.setRecipientsCertificate(cert); } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } m_sbCollectChars = null; // stop collecting } // <CipherValue> if(tName.equals("CipherValue")) { checkEncryptedData(); if(m_tags.search("EncryptedKey") != -1) { // child of <EncryptedKey> if(m_cipher == null) { // if transport key has not been found yet EncryptedKey ekey = m_doc.getLastEncryptedKey(); checkEncryptedKey(ekey); ekey.setTransportKeyData(Base64Util. decode(m_sbCollectChars.toString().getBytes())); // decrypt transport key if possible if(m_logger.isDebugEnabled()) m_logger.debug("Recipient: " + ekey.getRecipient() + " cert-nr: " + ekey.getRecipientsCertificate().getSerialNumber() + " decrypt-cert: " + m_decCert.getSerialNumber()); if (m_decCert != null && ekey.getRecipientsCertificate() != null && m_decCert.getSerialNumber().equals(ekey.getRecipientsCertificate().getSerialNumber())) { // decrypt transport key byte[] decdata = null; if(m_sigFac == null) { DigiDocException ex2 = new DigiDocException(DigiDocException.ERR_XMLENC_KEY_DECRYPT, "SignatureFactory not initialized!", null); SAXDigiDocException.handleException(ex2); } try { if(m_logger.isDebugEnabled()) m_logger.debug("Decrypting key: " + m_recvName + " serial: " + m_decCert.getSerialNumber()); if(m_transpkey != null) { decdata = m_transpkey; }else if(m_tki != null) { decdata = ((PKCS11SignatureFactory)m_sigFac). decrypt(ekey.getTransportKeyData(), m_tki.getSlot(), m_tki.getLabel(), m_pin); } else decdata = m_sigFac.decrypt(ekey.getTransportKeyData(), m_token, m_pin); //byte[] decdata = SignedDoc.hex2bin("85C9369D2F9B61EF133F6E62CF525235"); if(m_logger.isDebugEnabled()) m_logger.debug("Using key: " + m_recvName + " decdata: " + Base64Util.encode(decdata)); m_transportKey = (SecretKey)new SecretKeySpec(decdata,EncryptedData.DIGIDOC_ENCRYPTION_ALOGORITHM); byte[] iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; m_cipher = m_doc.getCipher(Cipher.DECRYPT_MODE, m_transportKey, iv); if(m_logger.isDebugEnabled()) m_logger.debug("Transport key: " + ((m_transportKey == null) ? "ERROR" : "OK") + " len: " + decdata.length); } catch (DigiDocException ex) { m_logger.error("Error decrypting key1: " + ((decdata != null) ? Base64Util.encode(decdata) : "NULL") + " - " + ex); SAXDigiDocException.handleException(ex); } catch (Exception ex) { m_logger.error("Error decrypting key2: " + ((decdata != null) ? Base64Util.encode(decdata) : "NULL") + " - " + ex); DigiDocException ex2 = new DigiDocException(DigiDocException.ERR_XMLENC_KEY_DECRYPT, ex.getMessage(), ex); SAXDigiDocException.handleException(ex2); } } } // if m_cipher == null } // child of <EncryptedKey> else { // child of <EncryptedData> m_bDecrypting = false; decryptBlock(null, DENC_BLOCK_LAST); if (m_logger.isInfoEnabled()) { m_logger.info("Total input: " + m_totalInput + " decrypted: " + m_totalDecrypted + " decompressed: " + m_totalDecompressed); } } m_sbCollectChars = null; // stop collecting } // <EncryptionProperty> if (tName.equals("EncryptionProperty")) { checkEncryptedData(); EncryptionProperty eprop = m_doc.getLastProperty(); try { if(eprop != null && m_sbCollectChars != null) eprop.setContent(m_sbCollectChars.toString()); } catch (DigiDocException ex) { SAXDigiDocException.handleException(ex); } m_sbCollectChars = null; // stop collecting } } private byte[] m_lblock = null; private static final int DECBLOCK_SIZE = 8 * 1024; /** * Called with a block of base64 data that must be decoded, decrypted and possibly also decompressed * * @param data base64 encoded input data * @param nBlockType type of block (first, middle, last) * * @throws SAXException */ private void decryptBlock(String data, int nBlockType) throws SAXException { // append new data to parse buffer if(data != null && data.length() > 0) m_sbParseBuf.append(data); String indata = null; if(nBlockType == DENC_BLOCK_LAST) { indata = m_sbParseBuf.toString(); } else { if(m_sbParseBuf.length() > ENC_BLOCK_SIZE) { indata = m_sbParseBuf.substring(0, ENC_BLOCK_SIZE); m_sbParseBuf.delete(0, ENC_BLOCK_SIZE); } } if (m_logger.isDebugEnabled()) { m_logger.debug("IN " + ((data != null) ? data.length() : 0) + " input: " + ((indata != null) ? indata.length() : 0) + " buffered: " + ((m_sbParseBuf != null) ? m_sbParseBuf.length() : 0) + " b64left: " + ((m_sbB64Buf != null) ? m_sbB64Buf.length() : 0) + " block-type: " + nBlockType); } // check that cipher has been initialized if(m_cipher == null) { DigiDocException de = new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Cipher has not been initialized! No transport key for selected recipient?", null); SAXDigiDocException.handleException(de); } // add to data to be b64 decoded if (indata != null) { m_sbB64Buf.append(indata); m_totalInput += indata.length(); } else { return; } try { byte[] encdata = null; byte[] decdata = null; // decode base64 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int nUsed = 0; if (m_sbB64Buf.length() > 0) { nUsed = Base64Util.decodeBlock(m_sbB64Buf.toString(), bos, nBlockType == DENC_BLOCK_LAST); encdata = bos.toByteArray(); // get the cipher if first block of data if (nBlockType == DENC_BLOCK_FIRST && encdata != null && encdata.length > 16) { // skip IV on first block byte[] b1 = new byte[encdata.length - 16]; System.arraycopy(encdata, 16, b1, 0, b1.length); if (m_logger.isDebugEnabled()) m_logger.debug("Removed IV from: " + encdata.length + " block1, left: " + b1.length); encdata = b1; } bos = null; if (m_logger.isDebugEnabled()) m_logger.debug("Decoding: " + m_sbB64Buf.length() + " got: " + ((encdata != null) ? encdata.length : 0) + " last: " + (nBlockType == DENC_BLOCK_LAST)); if(nUsed > 0) m_sbB64Buf.delete(0, nUsed); } // decrypt the data decdata = m_cipher.update(encdata); if(m_logger.isDebugEnabled()) m_logger.debug("Decrypted input: " + indata.length() + " decoded: " + ((encdata != null) ? encdata.length : 0) + " decrypted: " + ((decdata != null) ? decdata.length : 0)); if(m_totalDecrypted == 0 && decdata != null && decdata.length > 16) { byte[] ddata = new byte[decdata.length - 16]; System.arraycopy(decdata, 16, ddata, 0, decdata.length - 16); if(m_logger.isDebugEnabled()) m_logger.debug("Removing IV data from: " + decdata.length + " remaining: " + ddata.length); decdata = ddata; } // padding check on last and before-last block if(nBlockType == DENC_BLOCK_LAST) { int n1 = ((m_lblock != null) ? m_lblock.length : 0); int n2 = ((decdata != null) ? decdata.length : 0); byte[] ddata = new byte[n1 + n2]; if(n1 > 0) System.arraycopy(m_lblock, 0, ddata, 0, n1); if(n2 > 0) System.arraycopy(decdata, 0, ddata, n1, n2); decdata = ddata; if(m_logger.isDebugEnabled()) m_logger.debug("Last block: " + decdata.length); m_lblock = null; } // remove padding on the last block if (decdata != null && encdata != null && nBlockType == DENC_BLOCK_LAST) { int nPadLen = new Integer(decdata[decdata.length - 1]).intValue(); if (m_logger.isDebugEnabled()) m_logger.debug("Check padding 1: " + nPadLen); boolean bPadOk = checkPadding(decdata, nPadLen); if(bPadOk) decdata = removePadding(decdata, nPadLen); if (m_logger.isDebugEnabled()) m_logger.debug("Decdata remaining: " + ((decdata != null) ? decdata.length : 0)); // second padding if(decdata != null && decdata.length > 0) { nPadLen = new Integer(decdata[decdata.length - 1]).intValue(); if (m_logger.isDebugEnabled()) m_logger.debug("Check padding 2: " + nPadLen); if(nPadLen > 0 && nPadLen <= 16 && decdata.length > nPadLen) { bPadOk = checkPadding(decdata, nPadLen); if(bPadOk) decdata = removePadding(decdata, nPadLen); } } else if(m_lblock != null) { nPadLen = new Integer(m_lblock[m_lblock.length - 1]).intValue(); if (m_logger.isDebugEnabled()) m_logger.debug("Check padding 3: " + nPadLen); if(nPadLen > 0 && nPadLen <= 16 && m_lblock.length > nPadLen) { bPadOk = checkPadding(m_lblock, nPadLen); if(bPadOk) m_lblock = removePadding(m_lblock, nPadLen); } } } // decompress if necessary and write to output stream if(m_lblock != null || decdata != null) { // check compression if (m_decompressor != null) { if(nBlockType == DENC_BLOCK_LAST) m_lblock = decdata; int nDecomp = 0; byte[] m_decbuf = null; if(m_lblock != null) { if(m_logger.isDebugEnabled()) m_logger.debug("Decompressing: " + m_lblock.length); m_decompressor.setInput(m_lblock); m_decbuf = new byte[DECBLOCK_SIZE]; if(m_logger.isDebugEnabled()) m_logger.debug("Decompressing: " + m_lblock.length + " into: " + m_decbuf.length); while((nDecomp = m_decompressor.inflate(m_decbuf)) > 0) { if(m_logger.isDebugEnabled()) m_logger.debug("Decompressed: " + m_lblock.length + " into: " + m_decbuf.length + " got: " + nDecomp); m_outStream.write(m_decbuf, 0, nDecomp); m_totalDecompressed += nDecomp; } } if(nBlockType == DENC_BLOCK_LAST && (!m_decompressor.finished() || m_decompressor.getRemaining() > 0)) { if(m_logger.isDebugEnabled()) m_logger.debug("Decompressor finished: " + m_decompressor.finished() + " remaining: " + m_decompressor.getRemaining()); m_decbuf = new byte[1024 * 8]; while((nDecomp = m_decompressor.inflate(m_decbuf)) > 0) { m_outStream.write(m_decbuf, 0, nDecomp); m_totalDecompressed += nDecomp; if(m_logger.isDebugEnabled()) m_logger.debug("Decompressing final: " + nDecomp); } } } else { // not compressed if(m_lblock != null) // second block is first to be written m_outStream.write(m_lblock); if(nBlockType == DENC_BLOCK_LAST && decdata != null) // write also last block m_outStream.write(decdata); } m_totalDecrypted += decdata.length; } // keep last block for possible padding check m_lblock = decdata; } catch (Exception ex) { DigiDocException de = new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Error decrypting: " + ex, ex); SAXDigiDocException.handleException(de); } } private boolean checkPadding(byte[] data, int nPadLen) { boolean bPadOk = true; if(m_logger.isDebugEnabled()) m_logger.debug("Checking padding: " + nPadLen + " bytes"); if(nPadLen < 0 || nPadLen > 16 || data == null || data.length < nPadLen) return false; for (int i = data.length - nPadLen; nPadLen > 0 && i < data.length - 1; i++) { if (m_logger.isDebugEnabled()) m_logger.debug("Data at: " + i + " = " + data[i]); if ((data[i] != 0 && nPadLen != 16) || (nPadLen == 16 && data[i] != 16 && data[i] != 0)) { if (m_logger.isDebugEnabled()) m_logger.debug("Data at: " + i + " = " + data[i] + " cancel padding"); bPadOk = false; break; } } return bPadOk; } private byte[] removePadding(byte[] data, int nPadLen) { if(m_logger.isDebugEnabled()) m_logger.debug("Removing padding: " + nPadLen + " bytes"); if(nPadLen < 0 || nPadLen > 16 || data == null || data.length < nPadLen) return data; byte[] data2 = new byte[data.length - nPadLen]; System.arraycopy(data, 0, data2, 0, data.length - nPadLen); return data2; } /** * SAX characters event handler * * @param buf received bytes array * @param offset offset to the array * @param len length of data */ public void characters(char buf[], int offset, int len) throws SAXException { String s = new String(buf, offset, len); // just collect the data since it could // be on many lines and be processed in many events if (s != null) { if (m_sbCollectChars != null) { m_sbCollectChars.append(s); } if (m_bDecrypting) { decryptBlock(s, m_nBlockType); if (m_nBlockType == DENC_BLOCK_FIRST) { m_nBlockType = DENC_BLOCK_MIDDLE; } } } } }