View Javadoc
1   /*
2    * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.security.sasl;
27  
28  import javax.security.sasl.*;
29  
30  /**
31    * Implements the PLAIN SASL client mechanism.
32    * (<A
33    * HREF="http://ftp.isi.edu/in-notes/rfc2595.txt">RFC 2595</A>)
34    *
35    * @author Rosanna Lee
36    */
37  final class PlainClient implements SaslClient {
38      private boolean completed = false;
39      private byte[] pw;
40      private String authorizationID;
41      private String authenticationID;
42      private static byte SEP = 0; // US-ASCII <NUL>
43  
44      /**
45       * Creates a SASL mechanism with client credentials that it needs
46       * to participate in Plain authentication exchange with the server.
47       *
48       * @param authorizationID A possibly null string representing the principal
49       *  for which authorization is being granted; if null, same as
50       *  authenticationID
51       * @param authenticationID A non-null string representing the principal
52       * being authenticated. pw is associated with with this principal.
53       * @param pw A non-null byte[] containing the password.
54       */
55      PlainClient(String authorizationID, String authenticationID, byte[] pw)
56      throws SaslException {
57          if (authenticationID == null || pw == null) {
58              throw new SaslException(
59                  "PLAIN: authorization ID and password must be specified");
60          }
61  
62          this.authorizationID = authorizationID;
63          this.authenticationID = authenticationID;
64          this.pw = pw;  // caller should have already cloned
65      }
66  
67      /**
68       * Retrieves this mechanism's name for to initiate the PLAIN protocol
69       * exchange.
70       *
71       * @return  The string "PLAIN".
72       */
73      public String getMechanismName() {
74          return "PLAIN";
75      }
76  
77      public boolean hasInitialResponse() {
78          return true;
79      }
80  
81      public void dispose() throws SaslException {
82          clearPassword();
83      }
84  
85      /**
86       * Retrieves the initial response for the SASL command, which for
87       * PLAIN is the concatenation of authorization ID, authentication ID
88       * and password, with each component separated by the US-ASCII <NUL> byte.
89       *
90       * @param challengeData Ignored
91       * @return A non-null byte array containing the response to be sent to the server.
92       * @throws SaslException If cannot encode ids in UTF-8
93       * @throw IllegalStateException if authentication already completed
94       */
95      public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
96          if (completed) {
97              throw new IllegalStateException(
98                  "PLAIN authentication already completed");
99          }
100         completed = true;
101 
102         try {
103             byte[] authz = (authorizationID != null)?
104                 authorizationID.getBytes("UTF8") :
105                 null;
106             byte[] auth = authenticationID.getBytes("UTF8");
107 
108             byte[] answer = new byte[pw.length + auth.length + 2 +
109                 (authz == null ? 0 : authz.length)];
110 
111             int pos = 0;
112             if (authz != null) {
113                 System.arraycopy(authz, 0, answer, 0, authz.length);
114                 pos = authz.length;
115             }
116             answer[pos++] = SEP;
117             System.arraycopy(auth, 0, answer, pos, auth.length);
118 
119             pos += auth.length;
120             answer[pos++] = SEP;
121 
122             System.arraycopy(pw, 0, answer, pos, pw.length);
123 
124             clearPassword();
125             return answer;
126         } catch (java.io.UnsupportedEncodingException e) {
127             throw new SaslException("Cannot get UTF-8 encoding of ids", e);
128         }
129     }
130 
131     /**
132      * Determines whether this mechanism has completed.
133      * Plain completes after returning one response.
134      *
135      * @return true if has completed; false otherwise;
136      */
137     public boolean isComplete() {
138         return completed;
139     }
140 
141     /**
142       * Unwraps the incoming buffer.
143       *
144       * @throws SaslException Not applicable to this mechanism.
145       */
146     public byte[] unwrap(byte[] incoming, int offset, int len)
147         throws SaslException {
148         if (completed) {
149             throw new SaslException(
150                 "PLAIN supports neither integrity nor privacy");
151         } else {
152             throw new IllegalStateException("PLAIN authentication not completed");
153         }
154     }
155 
156     /**
157       * Wraps the outgoing buffer.
158       *
159       * @throws SaslException Not applicable to this mechanism.
160       */
161     public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
162         if (completed) {
163             throw new SaslException(
164                 "PLAIN supports neither integrity nor privacy");
165         } else {
166             throw new IllegalStateException("PLAIN authentication not completed");
167         }
168     }
169 
170     /**
171      * Retrieves the negotiated property.
172      * This method can be called only after the authentication exchange has
173      * completed (i.e., when <tt>isComplete()</tt> returns true); otherwise, a
174      * <tt>SaslException</tt> is thrown.
175      *
176      * @return value of property; only QOP is applicable to PLAIN.
177      * @exception IllegalStateException if this authentication exchange
178      *     has not completed
179      */
180     public Object getNegotiatedProperty(String propName) {
181         if (completed) {
182             if (propName.equals(Sasl.QOP)) {
183                 return "auth";
184             } else {
185                 return null;
186             }
187         } else {
188             throw new IllegalStateException("PLAIN authentication not completed");
189         }
190     }
191 
192     private void clearPassword() {
193         if (pw != null) {
194             // zero out password
195             for (int i = 0; i < pw.length; i++) {
196                 pw[i] = (byte)0;
197             }
198             pw = null;
199         }
200     }
201 
202     protected void finalize() {
203         clearPassword();
204     }
205 }