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.*;
042import java.util.zip.*;
043import java.security.*;
044import java.util.logging.Level;
045import java.util.logging.Logger;
046import javax.crypto.*;
047
048/**
049 * This class reads/decodes the CPX file format.  This format is a simple
050 * compression/encryption transformer for XML data.  This stream takes in
051 * encrypted XML and outputs decrypted.  It does this by checking for a magic
052 * number at the start of the stream.  If absent, it treats the stream as
053 * raw XML data and passes it through unaltered.  This is to aid development
054 * in debugging versions, where the XML files will not be in CPX format.
055 *
056 * See http://java.sun.com/developer/technicalArticles/Security/Crypto/
057 *
058 * @author Mark McKay
059 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
060 */
061public class CPXInputStream extends FilterInputStream implements CPXConsts {
062
063
064    SecureRandom sec = new SecureRandom();
065
066    Inflater inflater = new Inflater();
067
068    int xlateMode;
069
070    //Keep header bytes in case this stream turns out to be plain text
071    byte[] head = new byte[4];
072    int headSize = 0;
073    int headPtr = 0;
074
075    boolean reachedEOF = false;
076    byte[] inBuffer = new byte[2048];
077    byte[] decryptBuffer = new byte[2048];
078
079    /** Creates a new instance of CPXInputStream */
080    public CPXInputStream(InputStream in) throws IOException {
081        super(in);
082
083        //Determine processing type
084        for (int i = 0; i < 4; i++)
085        {
086            int val = in.read();
087            head[i] = (byte)val;
088            if (val == -1 || head[i] != MAGIC_NUMBER[i])
089            {
090                headSize = i + 1;
091                xlateMode = XL_PLAIN;
092                return;
093            }
094        }
095
096        xlateMode = XL_ZIP_CRYPT;
097    }
098
099    /**
100     * We do not allow marking
101     */
102    public boolean markSupported() { return false; }
103
104    /**
105     * Closes this input stream and releases any system resources
106     * associated with the stream.
107     * This
108     * method simply performs <code>in.close()</code>.
109     *
110     * @exception  IOException  if an I/O error occurs.
111     * @see        java.io.FilterInputStream#in
112     */
113    public void close() throws IOException {
114        reachedEOF = true;
115        in.close();
116    }
117
118    /**
119     * Reads the next byte of data from this input stream. The value
120     * byte is returned as an <code>int</code> in the range
121     * <code>0</code> to <code>255</code>. If no byte is available
122     * because the end of the stream has been reached, the value
123     * <code>-1</code> is returned. This method blocks until input data
124     * is available, the end of the stream is detected, or an exception
125     * is thrown.
126     * <p>
127     * This method
128     * simply performs <code>in.read()</code> and returns the result.
129     *
130     * @return     the next byte of data, or <code>-1</code> if the end of the
131     *             stream is reached.
132     * @exception  IOException  if an I/O error occurs.
133     * @see        java.io.FilterInputStream#in
134     */
135    public int read() throws IOException
136    {
137        final byte[] b = new byte[1];
138        int retVal = read(b, 0, 1);
139        if (retVal == -1) return -1;
140        return b[0];
141    }
142
143    /**
144     * Reads up to <code>byte.length</code> bytes of data from this
145     * input stream into an array of bytes. This method blocks until some
146     * input is available.
147     * <p>
148     * This method simply performs the call
149     * <code>read(b, 0, b.length)</code> and returns
150     * the  result. It is important that it does
151     * <i>not</i> do <code>in.read(b)</code> instead;
152     * certain subclasses of  <code>FilterInputStream</code>
153     * depend on the implementation strategy actually
154     * used.
155     *
156     * @param      b   the buffer into which the data is read.
157     * @return     the total number of bytes read into the buffer, or
158     *             <code>-1</code> if there is no more data because the end of
159     *             the stream has been reached.
160     * @exception  IOException  if an I/O error occurs.
161     * @see        java.io.FilterInputStream#read(byte[], int, int)
162     */
163    public int read(byte[] b) throws IOException
164    {
165        return read(b, 0, b.length);
166    }
167
168    /**
169     * Reads up to <code>len</code> bytes of data from this input stream
170     * into an array of bytes. This method blocks until some input is
171     * available.
172     * <p>
173     * This method simply performs <code>in.read(b, off, len)</code>
174     * and returns the result.
175     *
176     * @param      b     the buffer into which the data is read.
177     * @param      off   the start offset of the data.
178     * @param      len   the maximum number of bytes read.
179     * @return     the total number of bytes read into the buffer, or
180     *             <code>-1</code> if there is no more data because the end of
181     *             the stream has been reached.
182     * @exception  IOException  if an I/O error occurs.
183     * @see        java.io.FilterInputStream#in
184     */
185    public int read(byte[] b, int off, int len) throws IOException
186    {
187        if (reachedEOF) return -1;
188
189        if (xlateMode == XL_PLAIN)
190        {
191            int count = 0;
192            //Write header if appropriate
193            while (headPtr < headSize && len > 0)
194            {
195                b[off++] = head[headPtr++];
196                count++;
197                len--;
198            }
199
200            return (len == 0) ? count : count + in.read(b, off, len);
201        }
202
203        //Decrypt and inflate
204        if (inflater.needsInput() && !decryptChunk())
205        {
206            reachedEOF = true;
207
208            //Read remaining bytes
209            int numRead;
210            try {
211                numRead = inflater.inflate(b, off, len);
212            }
213            catch (Exception e)
214            {
215                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
216                return -1;
217            }
218
219            if (!inflater.finished())
220            {
221                Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING,
222                    "Inflation imncomplete");
223            }
224
225            return numRead == 0 ? -1 : numRead;
226        }
227
228        try
229        {
230            return inflater.inflate(b, off, len);
231        }
232        catch (DataFormatException e)
233        {
234            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
235            return -1;
236        }
237    }
238
239
240    /**
241     * Call when inflater indicates that it needs more bytes.
242     * @return - true if we decrypted more bytes to deflate, false if we
243     * encountered the end of stream
244     */
245    protected boolean decryptChunk() throws IOException
246    {
247        while (inflater.needsInput())
248        {
249            int numInBytes = in.read(inBuffer);
250            if (numInBytes == -1) return false;
251//            int numDecryptBytes = cipher.update(inBuffer, 0, numInBytes, decryptBuffer);
252//            inflater.setInput(decryptBuffer, 0, numDecryptBytes);
253inflater.setInput(inBuffer, 0, numInBytes);
254        }
255
256        return true;
257    }
258
259    /**
260     * This method returns 1 if we've not reached EOF, 0 if we have.  Programs
261     * should not rely on this to determine the number of bytes that can be
262     * read without blocking.
263     */
264    public int available() { return reachedEOF ? 0 : 1; }
265
266    /**
267     * Skips bytes by reading them into a cached buffer
268     */
269    public long skip(long n) throws IOException
270    {
271        int skipSize = (int)n;
272        if (skipSize > inBuffer.length) skipSize = inBuffer.length;
273        return read(inBuffer, 0, skipSize);
274    }
275
276}
277
278/*
279 import java.security.KeyPairGenerator;
280  import java.security.KeyPair;
281  import java.security.KeyPairGenerator;
282  import java.security.PrivateKey;
283  import java.security.PublicKey;
284  import java.security.SecureRandom;
285  import java.security.Cipher;
286
287  ....
288
289  java.security.Security.addProvider(new cryptix.provider.Cryptix());
290
291  SecureRandom random = new SecureRandom(SecureRandom.getSeed(30));
292  KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
293  keygen.initialize(1024, random);
294  keypair = keygen.generateKeyPair();
295
296  PublicKey  pubkey  = keypair.getPublic();
297  PrivateKey privkey = keypair.getPrivate();
298 */
299
300/*
301 *
302 *Generate key pairs
303KeyPairGenerator keyGen =
304             KeyPairGenerator.getInstance("DSA");
305KeyGen.initialize(1024, new SecureRandom(userSeed));
306KeyPair pair = KeyGen.generateKeyPair();
307 */