1
+ /*
2
+ * Copyright (C) 2009-2014 MongoDB, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package org .mongodb .sasl ;
18
+
19
+ import org .ietf .jgss .GSSCredential ;
20
+ import org .ietf .jgss .GSSException ;
21
+ import org .ietf .jgss .GSSManager ;
22
+ import org .ietf .jgss .GSSName ;
23
+ import org .ietf .jgss .Oid ;
24
+ import org .jruby .Ruby ;
25
+ import org .jruby .RubyString ;
26
+ import org .jruby .RubyBoolean ;
27
+
28
+ import javax .security .sasl .Sasl ;
29
+ import javax .security .sasl .SaslClient ;
30
+ import javax .security .sasl .SaslException ;
31
+ import java .net .UnknownHostException ;
32
+ import java .net .InetAddress ;
33
+ import java .util .HashMap ;
34
+ import java .util .Map ;
35
+
36
+ /**
37
+ * A helper class for SASL authentication using GSSAPI (Kerberos)
38
+ */
39
+ public class GSSAPIAuthenticator {
40
+ private static final String GSSAPI_MECHANISM_NAME = "GSSAPI" ;
41
+ private static final String GSSAPI_OID = "1.2.840.113554.1.2.2" ;
42
+ public static final String CANONICALIZE_HOST_NAME_KEY = "CANONICALIZE_HOST_NAME" ;
43
+
44
+ private final Ruby runTime ;
45
+ private final String userName ;
46
+ private final String hostName ;
47
+ private final String serviceName ;
48
+ private final boolean canonicalizeHostName ;
49
+
50
+ private final SaslClient saslClient ;
51
+
52
+ /**
53
+ * Constructs a wrapper for a Sasl client that handles GSSAPI (Kerberos) mechanism authentication.
54
+ *
55
+ * @param runTime the Ruby run time
56
+ * @param userName the user name
57
+ * @param hostName the host name
58
+ * @param serviceName the service name
59
+ * @param canonicalizeHostName whether the hostname should be canonicalized
60
+ */
61
+ public GSSAPIAuthenticator (final Ruby runTime , final RubyString userName , final RubyString hostName , final RubyString serviceName , final RubyBoolean canonicalizeHostName ) {
62
+ this .runTime = runTime ;
63
+ this .userName = userName .decodeString ();
64
+ this .hostName = hostName .decodeString ();
65
+ this .serviceName = serviceName .decodeString ();
66
+ this .canonicalizeHostName = (Boolean ) canonicalizeHostName .toJava (Boolean .class );
67
+ this .saslClient = createSaslClient ();
68
+ }
69
+
70
+ /**
71
+ * If the mechanism has an initial response, evaluteChallenge() is called to get the challenge. Otherwise, null is returned.
72
+ *
73
+ * @return the initial challenge to send to the server or null if the mechanism doesn't have an initial response.
74
+ *
75
+ * @throws MongoSecurityException if there is no response to the challenge.
76
+ */
77
+ public RubyString initializeChallenge () {
78
+ try {
79
+ return saslClient .hasInitialResponse () ? RubyString .newString (runTime , saslClient .evaluateChallenge (new byte [0 ])) : null ;
80
+ } catch (SaslException e ) {
81
+ throw new MongoSecurityException ("SASL protocol error: no client response to challenge for credential" , e );
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Evaluate the next challenge, given the response from the server.
87
+ *
88
+ * @param rubyPayload the non-null challenge sent from the server.
89
+ *
90
+ * @return the response to the challenge
91
+ */
92
+ public RubyString evaluateChallenge (RubyString rubyPayload ) {
93
+ try {
94
+ return RubyString .newString (runTime , saslClient .evaluateChallenge (rubyPayload .getBytes ()));
95
+ } catch (SaslException e ) {
96
+ throw new MongoSecurityException ("SASL protocol error: no client response to challenge for credential" , e );
97
+ }
98
+ }
99
+
100
+ private SaslClient createSaslClient () {
101
+ try {
102
+ Map <String , Object > props = new HashMap <String , Object >();
103
+ props .put (Sasl .CREDENTIALS , getGSSCredential (userName ));
104
+ SaslClient saslClient = Sasl .createSaslClient (new String []{GSSAPI_MECHANISM_NAME }, userName ,
105
+ serviceName ,
106
+ getHostName (), props , null );
107
+ if (saslClient == null ) {
108
+ throw new MongoSecurityException (String .format ("No platform support for %s mechanism" , GSSAPI_MECHANISM_NAME ));
109
+ }
110
+ return saslClient ;
111
+ } catch (SaslException e ) {
112
+ throw new MongoSecurityException (e );
113
+ } catch (GSSException e ) {
114
+ throw new MongoSecurityException (e );
115
+ } catch (UnknownHostException e ) {
116
+ throw new MongoSecurityException (e );
117
+ } catch (SecurityException e ) {
118
+ throw new MongoSecurityException (e );
119
+ }
120
+ }
121
+
122
+ private GSSCredential getGSSCredential (final String userName ) throws GSSException {
123
+ Oid krb5Mechanism = new Oid (GSSAPI_OID );
124
+ GSSManager manager = GSSManager .getInstance ();
125
+ GSSName name = manager .createName (userName , GSSName .NT_USER_NAME );
126
+ return manager .createCredential (name , GSSCredential .INDEFINITE_LIFETIME , krb5Mechanism , GSSCredential .INITIATE_ONLY );
127
+ }
128
+
129
+ private String getHostName () throws UnknownHostException {
130
+ return canonicalizeHostName ? InetAddress .getByName (hostName ).getCanonicalHostName () : hostName ;
131
+ }
132
+ }
0 commit comments