001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on February 12, 2004, 10:34 AM
035 */
036
037package com.kitfox.svg.xml.cpx;
038
039import com.kitfox.svg.SVGConst;
040import java.io.*;
041import java.util.zip.*;
042import java.security.*;
043import java.util.logging.Level;
044import java.util.logging.Logger;
045
046/**
047 * This class reads/decodes the CPX file format.  This format is a simple
048 * compression/encryption transformer for XML data.  This stream takes in
049 * encrypted XML and outputs decrypted.  It does this by checking for a magic
050 * number at the start of the stream.  If absent, it treats the stream as
051 * raw XML data and passes it through unaltered.  This is to aid development
052 * in debugging versions, where the XML files will not be in CPX format.
053 *
054 * See http://java.sun.com/developer/technicalArticles/Security/Crypto/
055 *
056 * @author Mark McKay
057 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
058 */
059public class CPXInputStream extends FilterInputStream implements CPXConsts {
060
061
062    SecureRandom sec = new SecureRandom();
063
064    Inflater inflater = new Inflater();
065
066    int xlateMode;
067
068    //Keep header bytes in case this stream turns out to be plain text
069    byte[] head = new byte[4];
070    int headSize = 0;
071    int headPtr = 0;
072
073    boolean reachedEOF = false;
074    byte[] inBuffer = new byte[2048];
075    byte[] decryptBuffer = new byte[2048];
076
077    /** 
078     * Creates a new instance of CPXInputStream
079     * @param in
080     * @throws java.io.IOException
081     */
082    public CPXInputStream(InputStream in) throws IOException {
083        super(in);
084
085        //Determine processing type
086        for (int i = 0; i < 4; i++)
087        {
088            int val = in.read();
089            head[i] = (byte)val;
090            if (val == -1 || head[i] != MAGIC_NUMBER[i])
091            {
092                headSize = i + 1;
093                xlateMode = XL_PLAIN;
094                return;
095            }
096        }
097
098        xlateMode = XL_ZIP_CRYPT;
099    }
100
101    /**
102     * We do not allow marking
103     */
104    @Override
105    public boolean markSupported() { return false; }
106
107    /**
108     * Closes this input stream and releases any system resources
109     * associated with the stream.
110     * This
111     * method simply performs <code>in.close()</code>.
112     *
113     * @exception  IOException  if an I/O error occurs.
114     * @see        java.io.FilterInputStream#in
115     */
116    @Override
117    public void close() throws IOException {
118        reachedEOF = true;
119        in.close();
120    }
121
122    /**
123     * Reads the next byte of data from this input stream. The value
124     * byte is returned as an <code>int</code> in the range
125     * <code>0</code> to <code>255</code>. If no byte is available
126     * because the end of the stream has been reached, the value
127     * <code>-1</code> is returned. This method blocks until input data
128     * is available, the end of the stream is detected, or an exception
129     * is thrown.
130     * <p>
131     * This method
132     * simply performs <code>in.read()</code> and returns the result.
133     *
134     * @return     the next byte of data, or <code>-1</code> if the end of the
135     *             stream is reached.
136     * @exception  IOException  if an I/O error occurs.
137     * @see        java.io.FilterInputStream#in
138     */
139    @Override
140    public int read() throws IOException
141    {
142        final byte[] b = new byte[1];
143        int retVal = read(b, 0, 1);
144        if (retVal == -1) return -1;
145        return b[0];
146    }
147
148    /**
149     * Reads up to <code>byte.length</code> bytes of data from this
150     * input stream into an array of bytes. This method blocks until some
151     * input is available.
152     * <p>
153     * This method simply performs the call
154     * <code>read(b, 0, b.length)</code> and returns
155     * the  result. It is important that it does
156     * <i>not</i> do <code>in.read(b)</code> instead;
157     * certain subclasses of  <code>FilterInputStream</code>
158     * depend on the implementation strategy actually
159     * used.
160     *
161     * @param      b   the buffer into which the data is read.
162     * @return     the total number of bytes read into the buffer, or
163     *             <code>-1</code> if there is no more data because the end of
164     *             the stream has been reached.
165     * @exception  IOException  if an I/O error occurs.
166     * @see        java.io.FilterInputStream#read(byte[], int, int)
167     */
168    @Override
169    public int read(byte[] b) throws IOException
170    {
171        return read(b, 0, b.length);
172    }
173
174    /**
175     * Reads up to <code>len</code> bytes of data from this input stream
176     * into an array of bytes. This method blocks until some input is
177     * available.
178     * <p>
179     * This method simply performs <code>in.read(b, off, len)</code>
180     * and returns the result.
181     *
182     * @param      b     the buffer into which the data is read.
183     * @param      off   the start offset of the data.
184     * @param      len   the maximum number of bytes read.
185     * @return     the total number of bytes read into the buffer, or
186     *             <code>-1</code> if there is no more data because the end of
187     *             the stream has been reached.
188     * @exception  IOException  if an I/O error occurs.
189     * @see        java.io.FilterInputStream#in
190     */
191    @Override
192    public int read(byte[] b, int off, int len) throws IOException
193    {
194        if (reachedEOF) return -1;
195
196        if (xlateMode == XL_PLAIN)
197        {
198            int count = 0;
199            //Write header if appropriate
200            while (headPtr < headSize && len > 0)
201            {
202                b[off++] = head[headPtr++];
203                count++;
204                len--;
205            }
206
207            return (len == 0) ? count : count + in.read(b, off, len);
208        }
209
210        //Decrypt and inflate
211        if (inflater.needsInput() && !decryptChunk())
212        {
213            reachedEOF = true;
214
215            //Read remaining bytes
216            int numRead;
217            try {
218                numRead = inflater.inflate(b, off, len);
219            }
220            catch (Exception e)
221            {
222                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
223                return -1;
224            }
225
226            if (!inflater.finished())
227            {
228                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING,
229                    "Inflation imncomplete");
230            }
231
232            return numRead == 0 ? -1 : numRead;
233        }
234
235        try
236        {
237            return inflater.inflate(b, off, len);
238        }
239        catch (DataFormatException e)
240        {
241            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
242            return -1;
243        }
244    }
245
246
247    /**
248     * Call when inflater indicates that it needs more bytes.
249     * @return - true if we decrypted more bytes to deflate, false if we
250     * encountered the end of stream
251     * @throws java.io.IOException
252     */
253    protected boolean decryptChunk() throws IOException
254    {
255        while (inflater.needsInput())
256        {
257            int numInBytes = in.read(inBuffer);
258            if (numInBytes == -1) return false;
259//            int numDecryptBytes = cipher.update(inBuffer, 0, numInBytes, decryptBuffer);
260//            inflater.setInput(decryptBuffer, 0, numDecryptBytes);
261inflater.setInput(inBuffer, 0, numInBytes);
262        }
263
264        return true;
265    }
266
267    /**
268     * This method returns 1 if we've not reached EOF, 0 if we have.  Programs
269     * should not rely on this to determine the number of bytes that can be
270     * read without blocking.
271     */
272    @Override
273    public int available() { return reachedEOF ? 0 : 1; }
274
275    /**
276     * Skips bytes by reading them into a cached buffer
277     */
278    @Override
279    public long skip(long n) throws IOException
280    {
281        int skipSize = (int)n;
282        if (skipSize > inBuffer.length) skipSize = inBuffer.length;
283        return read(inBuffer, 0, skipSize);
284    }
285
286}
287
288/*
289 import java.security.KeyPairGenerator;
290  import java.security.KeyPair;
291  import java.security.KeyPairGenerator;
292  import java.security.PrivateKey;
293  import java.security.PublicKey;
294  import java.security.SecureRandom;
295  import java.security.Cipher;
296
297  ....
298
299  java.security.Security.addProvider(new cryptix.provider.Cryptix());
300
301  SecureRandom random = new SecureRandom(SecureRandom.getSeed(30));
302  KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
303  keygen.initialize(1024, random);
304  keypair = keygen.generateKeyPair();
305
306  PublicKey  pubkey  = keypair.getPublic();
307  PrivateKey privkey = keypair.getPrivate();
308 */
309
310/*
311 *
312 *Generate key pairs
313KeyPairGenerator keyGen =
314             KeyPairGenerator.getInstance("DSA");
315KeyGen.initialize(1024, new SecureRandom(userSeed));
316KeyPair pair = KeyGen.generateKeyPair();
317 */