org.ejbca.ui.web.LimitLengthASN1Reader.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.ui.web.LimitLengthASN1Reader.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: The OpenSource Certificate Authority                *
 *                                                                       *
 *  This software 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 any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.ejbca.ui.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.cesecore.certificates.ocsp.exception.MalformedRequestException;
import org.ejbca.core.model.InternalEjbcaResources;

/** Helper class used all bytes for the first ASN.1 object in the stream. 
 * Limits the size that is ever read to MAX_REQUEST_SIZE. This class is used for example to read requests from POSTs to servlets, 
 * when you want to make sure that you never read too many bytes that might have been sent by an attacker.
 * Can only be used to read ASN.1 sequences, will throw MalformedException if first tag is not a sequence.
 * Example usage:
 * <pre>
 * final ServletInputStream in = request.getInputStream(); // ServletInputStream does not have to be closed, container handles this
 * ret = new LimitLengthASN1Reader(in, n).readFirstASN1Object();
 * </pre>
 * 
 * @version $Id: LimitLengthASN1Reader.java 19901 2014-09-30 14:29:38Z anatom $
 */
public class LimitLengthASN1Reader extends ASN1InputStream {

    private static final Logger m_log = Logger.getLogger(LimitLengthASN1Reader.class);
    /** Internal localization of logs and errors */
    private static final InternalEjbcaResources intres = InternalEjbcaResources.getInstance();

    /** Max size of a request is 100000 bytes */
    public static final int MAX_REQUEST_SIZE = 100000;

    private ByteArrayOutputStream baos;

    final private int contentLength;

    /**
     * 
     * @param input
     * @param contentLength the provided contentLength, we do not trust it but will use it if given
     */
    public LimitLengthASN1Reader(final InputStream input, final int contentLength) {
        super(input, MAX_REQUEST_SIZE);
        this.baos = new ByteArrayOutputStream();
        this.contentLength = contentLength;
    }

    /* (non-Javadoc)
     * @see java.io.FilterInputStream#read()
     * This method is used in #readLeangth()
     */
    @Override
    public int read() throws IOException {
        final int result = super.read();
        this.baos.write(result);
        return result;
    }

    /**
     * Read the 'value' of the top ASN1 object and append it to the already read 'tag' and 'value'
     * @param length nr of value bytes that we should read
     * @return the top ASN1 object
     * @throws IOException
     * @throws MalformedRequestException if the number of bytes read is less than length, i.e. asn.1 length tag was invalid
     */
    private byte[] readTopASN1(final int length) throws IOException, MalformedRequestException {
        // This small code snippet is inspired/copied from apache IO utils by Tomas Gustavsson...
        final byte[] buf = new byte[length]; // buf of length length, normal optimal case is only one read operation below
        int n = 0;
        int bytesRead = 0;
        // We must always read until it returns -1, make sure we read maximum length bytes
        while (-1 != (n = read(buf, 0, length - bytesRead))) {
            bytesRead += n;
            this.baos.write(buf, 0, n);
            if (bytesRead >= length) {
                // We read as much as we should, stop reading
                break;
            }
        }
        if (bytesRead != length) {
            // If we have read less bytes than we should have, the asn.1 was incorrect and this might be some type of attempt to perform buffer overflow
            final String msg = intres.getLocalizedMessage("request.notcorrectasn1length", Integer.valueOf(length),
                    Integer.valueOf(bytesRead));
            m_log.info(msg);
            throw new MalformedRequestException(msg);
        }
        this.baos.flush();
        return this.baos.toByteArray();
    }

    /** Reads all bytes for the first ASN.1 object in the stream. Limits the size that is ever read to MAX_REQUEST_SIZE.
     * @return all bytes for the first ASN.1 object in the stream. 
     * @throws IOException
     * @throws MalformedRequestException if the request is too large or not correctly GET encoded.
     */
    public byte[] readFirstASN1Object() throws IOException, MalformedRequestException {
        final int tag = read() & 0x1f;
        if (tag != SEQUENCE) {
            final String msg = intres.getLocalizedMessage("request.notasequence", Integer.valueOf(tag));
            m_log.info(msg);
            throw new MalformedRequestException(msg);
        }
        final int length = readLength();
        if (length > MAX_REQUEST_SIZE) {
            final String msg = intres.getLocalizedMessage("request.toolarge", Integer.valueOf(MAX_REQUEST_SIZE),
                    Integer.valueOf(length));
            m_log.info(msg);
            throw new MalformedRequestException(msg);
        }
        // If there was an asn.1 stream of undefined length we will try to read it the classic way, limiting the size of bytes read.
        if (length < 0) {// undefined length
            if (this.contentLength > MAX_REQUEST_SIZE) {
                final String msg = intres.getLocalizedMessage("request.toolarge", Integer.valueOf(MAX_REQUEST_SIZE),
                        Integer.valueOf(this.baos.size()));
                m_log.info(msg);
                throw new MalformedRequestException(msg);
            }
            final int tlByteLength = this.baos.toByteArray().length;
            if (this.contentLength < tlByteLength) { // Content-length invalid. Try to read although.
                if (m_log.isTraceEnabled()) {
                    m_log.trace("No content-length, reading as much as we have (<MAX_REQUEST_SIZE)");
                }
                final byte[] t = new byte[10240];
                int r = 0;
                int len = 0;
                while (((len = read(t)) != -1) && (r < LimitLengthASN1Reader.MAX_REQUEST_SIZE)) { // never read more than MAX_OCSP_REQUEST_SIZE bytes
                    this.baos.write(t, 0, len);
                    r = r + len;
                }
                return this.baos.toByteArray();
            }
            // Read content-length bytes from stream
            if (m_log.isTraceEnabled()) {
                m_log.trace("Got content-length: " + this.contentLength);
            }
            return readTopASN1(this.contentLength - tlByteLength); // 'tlByteLength' bytes already read. 'this.contentLength <= MAX_REQUEST_SIZE' tested above.
        }
        // defined length, just read as many bytes as the length tag says
        if (m_log.isTraceEnabled()) {
            m_log.trace("Got ASN1 length: " + length);
        }
        return readTopASN1(length);
    }
}