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 */
018
019package org.apache.hadoop.hbase.security.provider.example;
020
021import java.nio.charset.StandardCharsets;
022import java.security.Provider;
023import java.util.Map;
024
025import javax.security.auth.callback.Callback;
026import javax.security.auth.callback.CallbackHandler;
027import javax.security.auth.callback.NameCallback;
028import javax.security.auth.callback.PasswordCallback;
029import javax.security.sasl.AuthorizeCallback;
030import javax.security.sasl.Sasl;
031import javax.security.sasl.SaslException;
032import javax.security.sasl.SaslServer;
033import javax.security.sasl.SaslServerFactory;
034
035import org.apache.yetus.audience.InterfaceAudience;
036
037/**
038 * This class was copied from Hadoop Common (3.1.2) and subsequently modified.
039 */
040@InterfaceAudience.Private
041public class SaslPlainServer implements SaslServer {
042  @SuppressWarnings("serial")
043  public static class SecurityProvider extends Provider {
044    public SecurityProvider() {
045      super("SaslPlainServer", 1.0, "SASL PLAIN Authentication Server");
046      put("SaslServerFactory.PLAIN",
047          SaslPlainServerFactory.class.getName());
048    }
049  }
050
051  public static class SaslPlainServerFactory implements SaslServerFactory {
052    @Override
053    public SaslServer createSaslServer(String mechanism, String protocol,
054        String serverName, Map<String,?> props, CallbackHandler cbh)
055            throws SaslException {
056      return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null;
057    }
058    @Override
059    public String[] getMechanismNames(Map<String,?> props){
060      return (props == null) || "false".equals(props.get(Sasl.POLICY_NOPLAINTEXT))
061          ? new String[]{"PLAIN"}
062          : new String[0];
063    }
064  }
065
066  private CallbackHandler cbh;
067  private boolean completed;
068  private String authz;
069
070  SaslPlainServer(CallbackHandler callback) {
071    this.cbh = callback;
072  }
073
074  @Override
075  public String getMechanismName() {
076    return "PLAIN";
077  }
078
079  @Override
080  public byte[] evaluateResponse(byte[] response) throws SaslException {
081    if (completed) {
082      throw new IllegalStateException("PLAIN authentication has completed");
083    }
084    if (response == null) {
085      throw new IllegalArgumentException("Received null response");
086    }
087    try {
088      String payload;
089      try {
090        payload = new String(response, StandardCharsets.UTF_8);
091      } catch (Exception e) {
092        throw new IllegalArgumentException("Received corrupt response", e);
093      }
094      // [ authz, authn, password ]
095      String[] parts = payload.split("\u0000", 3);
096      if (parts.length != 3) {
097        throw new IllegalArgumentException("Received corrupt response");
098      }
099      if (parts[0].isEmpty()) { // authz = authn
100        parts[0] = parts[1];
101      }
102
103      NameCallback nc = new NameCallback("SASL PLAIN");
104      nc.setName(parts[1]);
105      PasswordCallback pc = new PasswordCallback("SASL PLAIN", false);
106      pc.setPassword(parts[2].toCharArray());
107      AuthorizeCallback ac = new AuthorizeCallback(parts[1], parts[0]);
108      cbh.handle(new Callback[]{nc, pc, ac});
109      if (ac.isAuthorized()) {
110        authz = ac.getAuthorizedID();
111      }
112    } catch (Exception e) {
113      throw new SaslException("PLAIN auth failed: " + e.toString(), e);
114    } finally {
115      completed = true;
116    }
117    return null;
118  }
119
120  private void throwIfNotComplete() {
121    if (!completed) {
122      throw new IllegalStateException("PLAIN authentication not completed");
123    }
124  }
125
126  @Override
127  public boolean isComplete() {
128    return completed;
129  }
130
131  @Override
132  public String getAuthorizationID() {
133    throwIfNotComplete();
134    return authz;
135  }
136
137  @Override
138  public Object getNegotiatedProperty(String propName) {
139    throwIfNotComplete();
140    return Sasl.QOP.equals(propName) ? "auth" : null;
141  }
142
143  @Override
144  public byte[] wrap(byte[] outgoing, int offset, int len)
145      throws SaslException {
146    throwIfNotComplete();
147    throw new IllegalStateException(
148        "PLAIN supports neither integrity nor privacy");
149  }
150
151  @Override
152  public byte[] unwrap(byte[] incoming, int offset, int len)
153      throws SaslException {
154    throwIfNotComplete();
155    throw new IllegalStateException(
156        "PLAIN supports neither integrity nor privacy");
157  }
158
159  @Override
160  public void dispose() throws SaslException {
161    cbh = null;
162    authz = null;
163  }
164}