001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.provider.example;
019
020import java.nio.charset.StandardCharsets;
021import java.security.Provider;
022import java.util.Map;
023import javax.security.auth.callback.Callback;
024import javax.security.auth.callback.CallbackHandler;
025import javax.security.auth.callback.NameCallback;
026import javax.security.auth.callback.PasswordCallback;
027import javax.security.sasl.AuthorizeCallback;
028import javax.security.sasl.Sasl;
029import javax.security.sasl.SaslException;
030import javax.security.sasl.SaslServer;
031import javax.security.sasl.SaslServerFactory;
032import org.apache.yetus.audience.InterfaceAudience;
033
034/**
035 * This class was copied from Hadoop Common (3.1.2) and subsequently modified.
036 */
037@InterfaceAudience.Private
038public class SaslPlainServer implements SaslServer {
039  @SuppressWarnings("serial")
040  public static class SecurityProvider extends Provider {
041    public SecurityProvider() {
042      super("SaslPlainServer", 1.0, "SASL PLAIN Authentication Server");
043      put("SaslServerFactory.PLAIN", SaslPlainServerFactory.class.getName());
044    }
045  }
046
047  public static class SaslPlainServerFactory implements SaslServerFactory {
048    @Override
049    public SaslServer createSaslServer(String mechanism, String protocol, String serverName,
050      Map<String, ?> props, CallbackHandler cbh) throws SaslException {
051      return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null;
052    }
053
054    @Override
055    public String[] getMechanismNames(Map<String, ?> props) {
056      return (props == null) || "false".equals(props.get(Sasl.POLICY_NOPLAINTEXT))
057        ? new String[] { "PLAIN" }
058        : new String[0];
059    }
060  }
061
062  private CallbackHandler cbh;
063  private boolean completed;
064  private String authz;
065
066  SaslPlainServer(CallbackHandler callback) {
067    this.cbh = callback;
068  }
069
070  @Override
071  public String getMechanismName() {
072    return "PLAIN";
073  }
074
075  @Override
076  public byte[] evaluateResponse(byte[] response) throws SaslException {
077    if (completed) {
078      throw new IllegalStateException("PLAIN authentication has completed");
079    }
080    if (response == null) {
081      throw new IllegalArgumentException("Received null response");
082    }
083    try {
084      String payload;
085      try {
086        payload = new String(response, StandardCharsets.UTF_8);
087      } catch (Exception e) {
088        throw new IllegalArgumentException("Received corrupt response", e);
089      }
090      // [ authz, authn, password ]
091      String[] parts = payload.split("\u0000", 3);
092      if (parts.length != 3) {
093        throw new IllegalArgumentException("Received corrupt response");
094      }
095      if (parts[0].isEmpty()) { // authz = authn
096        parts[0] = parts[1];
097      }
098
099      NameCallback nc = new NameCallback("SASL PLAIN");
100      nc.setName(parts[1]);
101      PasswordCallback pc = new PasswordCallback("SASL PLAIN", false);
102      pc.setPassword(parts[2].toCharArray());
103      AuthorizeCallback ac = new AuthorizeCallback(parts[1], parts[0]);
104      cbh.handle(new Callback[] { nc, pc, ac });
105      if (ac.isAuthorized()) {
106        authz = ac.getAuthorizedID();
107      }
108    } catch (Exception e) {
109      throw new SaslException("PLAIN auth failed: " + e.toString(), e);
110    } finally {
111      completed = true;
112    }
113    return null;
114  }
115
116  private void throwIfNotComplete() {
117    if (!completed) {
118      throw new IllegalStateException("PLAIN authentication not completed");
119    }
120  }
121
122  @Override
123  public boolean isComplete() {
124    return completed;
125  }
126
127  @Override
128  public String getAuthorizationID() {
129    throwIfNotComplete();
130    return authz;
131  }
132
133  @Override
134  public Object getNegotiatedProperty(String propName) {
135    throwIfNotComplete();
136    return Sasl.QOP.equals(propName) ? "auth" : null;
137  }
138
139  @Override
140  public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
141    throwIfNotComplete();
142    throw new IllegalStateException("PLAIN supports neither integrity nor privacy");
143  }
144
145  @Override
146  public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException {
147    throwIfNotComplete();
148    throw new IllegalStateException("PLAIN supports neither integrity nor privacy");
149  }
150
151  @Override
152  public void dispose() throws SaslException {
153    cbh = null;
154    authz = null;
155  }
156}