001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.iiop.csiv2;
023:
024: /***************************************
025: * *
026: * JBoss: The OpenSource J2EE WebOS *
027: * *
028: * Distributable under LGPL license. *
029: * See terms of license at gnu.org. *
030: * *
031: ***************************************/
032:
033: import java.security.Principal;
034:
035: import org.omg.CORBA.Any;
036: import org.omg.CORBA.BAD_PARAM;
037: import org.omg.CORBA.CompletionStatus;
038: import org.omg.CORBA.LocalObject;
039: import org.omg.CORBA.MARSHAL;
040: import org.omg.CORBA.NO_PERMISSION;
041: import org.omg.CORBA.ORB;
042: import org.omg.CSI.AuthorizationElement;
043: import org.omg.CSI.EstablishContext;
044: import org.omg.CSI.GSS_NT_ExportedNameHelper;
045: import org.omg.CSI.ITTAnonymous;
046: import org.omg.CSI.IdentityToken;
047: import org.omg.CSI.MTContextError;
048: import org.omg.CSI.SASContextBody;
049: import org.omg.CSI.SASContextBodyHelper;
050: import org.omg.IOP.Codec;
051: import org.omg.IOP.CodecPackage.FormatMismatch;
052: import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
053: import org.omg.IOP.CodecPackage.TypeMismatch;
054: import org.omg.IOP.ServiceContext;
055: import org.omg.IOP.TaggedComponent;
056: import org.omg.PortableInterceptor.ClientRequestInfo;
057: import org.omg.PortableInterceptor.ClientRequestInterceptor;
058: import org.omg.CSIIOP.CompoundSecMech;
059: import org.omg.CSIIOP.CompoundSecMechList;
060: import org.omg.CSIIOP.CompoundSecMechListHelper;
061: import org.omg.CSIIOP.EstablishTrustInClient;
062: import org.omg.CSIIOP.IdentityAssertion;
063: import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
064: import org.omg.GSSUP.InitialContextToken;
065: import org.jacorb.orb.MinorCodes;
066: import org.jboss.logging.Logger;
067: import org.jboss.security.SecurityAssociation;
068: import org.jboss.security.RunAsIdentity;
069:
070: /**
071: * This implementation of
072: * <code>org.omg.PortableInterceptor.ClientRequestInterceptor</code> inserts
073: * the security attribute service (SAS) context into outgoing IIOP requests
074: * and handles the SAS messages received from the target security service
075: * in the SAS context of incoming IIOP replies.
076: *
077: * @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
078: * @version $Revision: 57194 $
079: */
080: public class SASClientIdentityInterceptor extends LocalObject implements
081: ClientRequestInterceptor {
082: /** @since 4.0.1 */
083: static final long serialVersionUID = -3416778273722755220L;
084: private static final Logger log = Logger
085: .getLogger(SASClientIdentityInterceptor.class);
086: private static final boolean traceEnabled = log.isTraceEnabled();
087:
088: // Constants ------------------------------------------------------
089: private static final int sasContextId = org.omg.IOP.SecurityAttributeService.value;
090:
091: /*
092: * Pre-built empty tokens
093: */
094: private static final IdentityToken absentIdentityToken;
095: static {
096: absentIdentityToken = new IdentityToken();
097: absentIdentityToken.absent(true);
098: }
099: private static final AuthorizationElement[] noAuthorizationToken = {};
100: private static final byte[] noAuthenticationToken = {};
101:
102: // Fields ---------------------------------------------------------
103: private Codec codec;
104:
105: /*
106: * Username and password of this server, in case it does not use an
107: * SSL certificate to authenticate itself when acting as a client.
108: */
109: private static final String serverUsername = "j2ee"; // hardcoded (REVISIT!)
110: private static final String serverPassword = "j2ee"; // hardcoded (REVISIT!)
111:
112: // Constructor ----------------------------------------------------
113:
114: public SASClientIdentityInterceptor(Codec codec) {
115: this .codec = codec;
116: }
117:
118: // Methods -------------------------------------------------------
119:
120: // org.omg.PortableInterceptor.Interceptor operations ------------
121:
122: public String name() {
123: return "SASClientIdentityInterceptor";
124: }
125:
126: public void destroy() {
127: // do nothing
128: }
129:
130: // ClientRequestInterceptor operations ---------------------------
131:
132: public void send_request(ClientRequestInfo ri) {
133: try {
134: CompoundSecMech secMech = CSIv2Util
135: .getMatchingSecurityMech(
136: ri,
137: codec,
138: (short) (EstablishTrustInClient.value + IdentityAssertion.value), /* client supports */
139: (short) 0 /* client requires */);
140: if (secMech == null)
141: return;
142:
143: if (traceEnabled) {
144: StringBuffer tmp = new StringBuffer();
145: CSIv2Util.toString(secMech, tmp);
146: log.trace(tmp);
147: }
148: // these "null tokens" will be changed if needed
149: IdentityToken identityToken = absentIdentityToken;
150: byte[] encodedAuthenticationToken = noAuthenticationToken;
151:
152: if ((secMech.sas_context_mech.target_supports & IdentityAssertion.value) != 0) {
153: // will create identity token
154: Principal p = null;
155: RunAsIdentity runAs = SecurityAssociation
156: .peekRunAsIdentity();
157: if (runAs != null) {
158: // will use run-as identity
159: p = runAs;
160: } else {
161: // will use caller identity
162: p = SecurityAssociation.getPrincipal();
163: }
164:
165: if (p != null) {
166: // The name scope needs to be externalized
167: String name = p.getName();
168: if (name.indexOf('@') < 0)
169: name += "@default"; // hardcoded (REVISIT!)
170: byte[] principalName = name.getBytes("UTF-8");
171:
172: // encode the principal name as mandated by RFC2743
173: byte[] encodedName = CSIv2Util
174: .encodeGssExportedName(principalName);
175:
176: // encapsulate the encoded name
177: Any any = ORB.init().create_any();
178: byte[] encapsulatedEncodedName = null;
179: GSS_NT_ExportedNameHelper.insert(any, encodedName);
180: try {
181: encapsulatedEncodedName = codec
182: .encode_value(any);
183: } catch (InvalidTypeForEncoding e) {
184: throw new RuntimeException(
185: "Unexpected exception: " + e);
186: }
187:
188: // create identity token
189: identityToken = new IdentityToken();
190: identityToken
191: .principal_name(encapsulatedEncodedName);
192: } else if ((secMech.sas_context_mech.supported_identity_types & ITTAnonymous.value) != 0) {
193: // no run-as or caller identity and the target
194: // supports ITTAnonymous: use the anonymous identity
195: identityToken = new IdentityToken();
196: identityToken.anonymous(true);
197: }
198: }
199:
200: if ((secMech.as_context_mech.target_requires & EstablishTrustInClient.value) != 0) {
201: // will create authentication token with the
202: // configured pair serverUsername/serverPassword
203: byte[] encodedTargetName = secMech.as_context_mech.target_name;
204: String name = serverUsername;
205: if (name.indexOf('@') < 0) {
206: byte[] decodedTargetName = CSIv2Util
207: .decodeGssExportedName(encodedTargetName);
208: String targetName = new String(decodedTargetName,
209: "UTF-8");
210: name += "@" + targetName; // "@default"
211: }
212: byte[] username = name.getBytes("UTF-8");
213: // I don't know why there is not a better way
214: // to go from char[] -> byte[]
215: byte[] password = serverPassword.getBytes("UTF-8");
216:
217: // create authentication token
218: InitialContextToken authenticationToken = new InitialContextToken(
219: username, password, encodedTargetName);
220: // ASN.1-encode it, as defined in RFC 2743
221: encodedAuthenticationToken = CSIv2Util
222: .encodeInitialContextToken(authenticationToken,
223: codec);
224: }
225:
226: if (identityToken != absentIdentityToken
227: || encodedAuthenticationToken != noAuthenticationToken) {
228: // at least one non-null token was created,
229: // create EstablishContext message with it
230: EstablishContext message = new EstablishContext(
231: 0, // stateless ctx id
232: noAuthorizationToken, identityToken,
233: encodedAuthenticationToken);
234:
235: // create SAS context with the EstablishContext message
236: SASContextBody contextBody = new SASContextBody();
237: contextBody.establish_msg(message);
238:
239: // stuff the SAS context into the outgoing request
240: Any any = ORB.init().create_any();
241: SASContextBodyHelper.insert(any, contextBody);
242: ServiceContext sc = new ServiceContext(sasContextId,
243: codec.encode_value(any));
244: ri
245: .add_request_service_context(sc, true /*replace existing context*/);
246: }
247: } catch (java.io.UnsupportedEncodingException e) {
248: throw new MARSHAL("Unexpected exception: " + e);
249: } catch (InvalidTypeForEncoding e) {
250: throw new MARSHAL("Unexpected exception: " + e);
251: }
252: }
253:
254: public void send_poll(ClientRequestInfo ri) {
255: // do nothing
256: }
257:
258: public void receive_reply(ClientRequestInfo ri) {
259: try {
260: ServiceContext sc = ri
261: .get_reply_service_context(sasContextId);
262: Any msg = codec.decode_value(sc.context_data,
263: SASContextBodyHelper.type());
264: SASContextBody contextBody = SASContextBodyHelper
265: .extract(msg);
266:
267: // At this point contextBody should contain a
268: // CompleteEstablishContext message, which does not require any
269: // treatment. ContextError messages should arrive via
270: // receive_exception().
271:
272: if (traceEnabled)
273: log.trace("receive_reply: got SAS reply, type "
274: + contextBody.discriminator());
275:
276: if (contextBody.discriminator() == MTContextError.value) {
277: // should not happen
278: log.warn("Unexpected ContextError in SAS reply");
279: throw new NO_PERMISSION(
280: "Unexpected ContextError in SAS reply",
281: MinorCodes.SAS_CSS_FAILURE,
282: CompletionStatus.COMPLETED_YES);
283: }
284: } catch (BAD_PARAM e) {
285: // no service context with sasContextId: do nothing
286: } catch (FormatMismatch e) {
287: throw new MARSHAL("Could not parse SAS reply: " + e, 0,
288: CompletionStatus.COMPLETED_YES);
289: } catch (TypeMismatch e) {
290: throw new MARSHAL("Could not parse SAS reply: " + e, 0,
291: CompletionStatus.COMPLETED_YES);
292: }
293: }
294:
295: public void receive_exception(ClientRequestInfo ri) {
296: try {
297: ServiceContext sc = ri
298: .get_reply_service_context(sasContextId);
299: Any msg = codec.decode_value(sc.context_data,
300: SASContextBodyHelper.type());
301: SASContextBody contextBody = SASContextBodyHelper
302: .extract(msg);
303:
304: // At this point contextBody may contain a either a
305: // CompleteEstablishContext message or a ContextError message.
306: // Neither message requires any treatment. We decoded the context
307: // body just to check that it contains a well-formed message.
308:
309: if (traceEnabled)
310: log.trace("receive_exceptpion: got SAS reply, type "
311: + contextBody.discriminator());
312: } catch (BAD_PARAM e) {
313: // no service context with sasContextId: do nothing
314: } catch (FormatMismatch e) {
315: throw new MARSHAL("Could not parse SAS reply: " + e,
316: MinorCodes.SAS_CSS_FAILURE,
317: CompletionStatus.COMPLETED_MAYBE);
318: } catch (TypeMismatch e) {
319: throw new MARSHAL("Could not parse SAS reply: " + e,
320: MinorCodes.SAS_CSS_FAILURE,
321: CompletionStatus.COMPLETED_MAYBE);
322: }
323: }
324:
325: public void receive_other(ClientRequestInfo ri) {
326: // do nothing
327: }
328:
329: CompoundSecMech getSecurityMech(ClientRequestInfo ri) {
330: CompoundSecMechList csmList = null;
331: CompoundSecMech securityMech = null;
332: try {
333: TaggedComponent tc = ri
334: .get_effective_component(TAG_CSI_SEC_MECH_LIST.value);
335:
336: Any any = codec.decode_value(tc.component_data,
337: CompoundSecMechListHelper.type());
338:
339: csmList = CompoundSecMechListHelper.extract(any);
340:
341: // at this point you can inspect the fields csmList.stateful
342: // and csmList.mechanism_list. The latter is an array of
343: // org.omg.CSIIOP.CompoundSecMech instances, which in our IORs
344: // has length 1.
345: //
346: // The actual info you want is in csmList.mechanism_list[0].
347: securityMech = csmList.mechanism_list[0];
348: } catch (BAD_PARAM e) {
349: // no component with TAG_CSI_SEC_MECH_LIST was found
350: } catch (org.omg.IOP.CodecPackage.TypeMismatch tm) {
351: // unexpected exception in codec.decode_value
352: } catch (org.omg.IOP.CodecPackage.FormatMismatch tm) {
353: // unexpected exception in codec.decode_value
354: }
355: return securityMech;
356: }
357: }
|