001/**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.ksb.security;
017
018import java.io.BufferedOutputStream;
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.security.GeneralSecurityException;
023
024import javax.servlet.ServletOutputStream;
025
026/**
027 * An OutputStream which decorates another OutputStream with a wrapper that digitally
028 * signs the data when the OutputStream is closed.  Since this class does not know where
029 * the resulting digital signature will reside, a DigitalSigner will be invoked to
030 * execute the actual signing of the message (i.e. put it in a header).
031 * 
032 * @author Kuali Rice Team (rice.collab@kuali.org)
033 */
034public class SignatureSigningOutputStream extends ServletOutputStream {
035
036        private boolean delayWrite;
037        private DigitalSigner signer;
038        private BufferedOutputStream bufferedDataHoldingStream;
039        private ByteArrayOutputStream dataHoldingStream;
040        private OutputStream wrappedOutputStream;
041        
042        /**
043         * Constructs a SignatureSigningOutputStream with the given DigitalSigner and underlying OutputStream.
044         * If true, the delayWrite boolean indicates that the stream should store all data internally until the
045         * stream is closed, at which point it should forward all data to the wrapped OutputStream.  If delayWrite
046         * is false, then the data will be forwarded immediately.
047         */
048        public SignatureSigningOutputStream(DigitalSigner signer, OutputStream wrappedOutputStream, boolean delayWrite) {
049                super();
050                this.delayWrite = delayWrite;
051                if (delayWrite) {
052                    this.dataHoldingStream = new ByteArrayOutputStream();
053                    this.bufferedDataHoldingStream = new BufferedOutputStream(this.dataHoldingStream);
054                }
055                this.wrappedOutputStream = wrappedOutputStream;
056                this.signer = signer;
057        }
058
059        public void write(int data) throws IOException {
060                if (this.delayWrite) {
061                    this.bufferedDataHoldingStream.write(data);
062                } else {
063                    this.wrappedOutputStream.write(data);
064                }
065                try {
066                    this.signer.getSignature().update((byte)data);      
067                } catch (GeneralSecurityException e) {
068                        IOException exception = new IOException("Error updating signature.");
069                        exception.initCause(e);
070                        throw exception;
071                }
072        }
073        
074        @Override
075        public void close() throws IOException {
076                // before we close, sign the message
077                try {
078                    this.signer.sign();
079                        if (this.delayWrite) {
080                            this.bufferedDataHoldingStream.close();
081                                byte[] data = this.dataHoldingStream.toByteArray();
082                                for (int index = 0; index < data.length; index++) {
083                                    this.wrappedOutputStream.write(data[index]);
084                                }
085                        }
086                        this.wrappedOutputStream.close();
087                } catch (Exception e) {
088                        IOException exception = new IOException("Error attaching digital signature to outbound response.");
089                        exception.initCause(e);
090                        throw exception;
091                } finally {
092                        super.close();
093                }
094        }
095
096}