001: /*
002: * Copyright 2007 The Kuali Foundation
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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: */
016: package edu.iu.uis.eden.messaging;
017:
018: import java.io.ByteArrayOutputStream;
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.security.GeneralSecurityException;
022: import java.security.Signature;
023:
024: import org.apache.commons.codec.binary.Base64;
025: import org.apache.commons.httpclient.Header;
026: import org.apache.commons.httpclient.HttpClient;
027: import org.apache.commons.httpclient.methods.PostMethod;
028: import org.apache.commons.lang.StringUtils;
029: import org.kuali.bus.services.KSBServiceLocator;
030: import org.kuali.rice.RiceConstants;
031: import org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor;
032: import org.springframework.remoting.httpinvoker.HttpInvokerClientConfiguration;
033:
034: import edu.iu.uis.eden.security.HttpClientHeaderDigitalSigner;
035: import edu.iu.uis.eden.security.SignatureVerifyingInputStream;
036:
037: /**
038: * At HttpInvokerRequestExecutor which is capable of digitally signing and verifying messages. It's capabilities
039: * to execute the signing and verification can be turned on or off via an application constant.
040: *
041: * @author Kuali Rice Team (kuali-rice@googlegroups.com)
042: */
043: public class KEWHttpInvokerRequestExecutor extends
044: CommonsHttpInvokerRequestExecutor {
045:
046: private Boolean secure = Boolean.TRUE;
047:
048: public KEWHttpInvokerRequestExecutor() {
049: super ();
050: }
051:
052: public KEWHttpInvokerRequestExecutor(Boolean secure) {
053: super ();
054: this .secure = secure;
055: }
056:
057: public KEWHttpInvokerRequestExecutor(HttpClient httpClient) {
058: super (httpClient);
059: }
060:
061: /**
062: * Signs the outgoing request by generating a digital signature from the bytes in the ByteArrayOutputStream and attaching the
063: * signature and our alias to the headers of the PostMethod.
064: */
065: @Override
066: protected void setRequestBody(
067: HttpInvokerClientConfiguration config,
068: PostMethod postMethod, ByteArrayOutputStream baos)
069: throws IOException {
070: if (isSecure()) {
071: try {
072: signRequest(postMethod, baos);
073: } catch (Exception e) {
074: throw new RuntimeException(
075: "Failed to sign the outgoing message.", e);
076: }
077: }
078: super .setRequestBody(config, postMethod, baos);
079: }
080:
081: /**
082: * Returns a wrapped InputStream which is responsible for verifying the digital signature on the response after all
083: * data has been read.
084: */
085: @Override
086: protected InputStream getResponseBody(
087: HttpInvokerClientConfiguration config, PostMethod postMethod)
088: throws IOException {
089: if (isSecure()) {
090: // extract and validate the headers
091: Header digitalSignatureHeader = postMethod
092: .getResponseHeader(RiceConstants.DIGITAL_SIGNATURE_HEADER);
093: Header keyStoreAliasHeader = postMethod
094: .getResponseHeader(RiceConstants.KEYSTORE_ALIAS_HEADER);
095: if (digitalSignatureHeader == null
096: || StringUtils.isEmpty(digitalSignatureHeader
097: .getValue())) {
098: throw new RuntimeException(
099: "A digital signature header was required on the response but none was found.");
100: }
101: if (keyStoreAliasHeader == null
102: || StringUtils.isEmpty(keyStoreAliasHeader
103: .getValue())) {
104: throw new RuntimeException(
105: "A key store alias header was required on the response but none was found.");
106: }
107: // decode the digital signature from the header into binary
108: byte[] digitalSignature = Base64
109: .decodeBase64(digitalSignatureHeader.getValue()
110: .getBytes("UTF-8"));
111: String keystoreAlias = keyStoreAliasHeader.getValue();
112: try {
113: // get the Signature for verification based on the alias that was sent to us
114: Signature signature = KSBServiceLocator
115: .getDigitalSignatureService()
116: .getSignatureForVerification(keystoreAlias);
117: // wrap the InputStream in an input stream that will verify the signature
118: return new SignatureVerifyingInputStream(
119: digitalSignature, signature, super
120: .getResponseBody(config, postMethod));
121: } catch (GeneralSecurityException e) {
122: throw new RuntimeException(e);
123: }
124: }
125: return super .getResponseBody(config, postMethod);
126: }
127:
128: @Override
129: protected void validateResponse(
130: HttpInvokerClientConfiguration config, PostMethod postMethod)
131: throws IOException {
132: if (postMethod.getStatusCode() >= 300) {
133: throw new HttpException(postMethod.getStatusCode(),
134: "Did not receive successful HTTP response: status code = "
135: + postMethod.getStatusCode()
136: + ", status message = ["
137: + postMethod.getStatusText() + "]");
138: }
139: }
140:
141: /**
142: * Signs the request by adding headers to the PostMethod.
143: */
144: protected void signRequest(PostMethod postMethod,
145: ByteArrayOutputStream baos) throws Exception {
146: Signature signature = KSBServiceLocator
147: .getDigitalSignatureService().getSignatureForSigning();
148: HttpClientHeaderDigitalSigner signer = new HttpClientHeaderDigitalSigner(
149: signature, postMethod, KSBServiceLocator
150: .getDigitalSignatureService()
151: .getKeyStoreAlias());
152: signer.getSignature().update(baos.toByteArray());
153: signer.sign();
154: }
155:
156: protected boolean isSecure() {
157: return getSecure();// && Utilities.getBooleanConstant(EdenConstants.SECURITY_HTTP_INVOKER_SIGN_MESSAGES, false);
158: }
159:
160: public Boolean getSecure() {
161: return this .secure;
162: }
163:
164: public void setSecure(Boolean secure) {
165: this.secure = secure;
166: }
167: }
|