implemented DIGEST authentication, which is for remote login more secure

as BASIC were pwd is transmitted near clear text (B64enc).
This has some implication as RFC 2617 requires and recommends a password hash MD5(user:realm:pwd) for DIGEST.

!!! before activating DIGEST you have to reassign all passwords !!! to allow new calculation of the hash
- default authentication is still BASIC
- configuration at this time only manually in (DATA/settings) or  defaults/web.xml  (<auth-method>
- the realmname is in defaults/yacy.init  adminRealm=YaCy-AdminUI
- fyi: the realmname is shown on login screen
- changing the realm name invalidates all passwords - but for security you are encouraged to do so (as localhostadmin)
- implemented to support both, old hashes for BASIC and new hashes for BASIC and DIGEST
- to differentiate old / new hash the in Jetty used hash-prefix "MD5:" is used for new pwd-hashes (  "MD5:hash" )
pull/1/head
reger 11 years ago
parent ba44eb1160
commit 0c754dd794

@ -68,18 +68,19 @@
<!-- security configuration -->
<!-- authentication method and default roles are preconfigured by the application (provided only for completness) -->
<!--
<login-config>
<!-- supported BASIC or DIGEST -->
<!-- Note: with DIGEST the name of this peer is part of password hashes
changing the peer name will invalidate all passwords
!!! Attention !!! prior to change this to DIGEST you have to reenter your password
to calculate a correct password hash -->
<auth-method>BASIC</auth-method>
<realm-name>YaCy Admin Interface</realm-name>
</login-config>
-->
<!-- Roles -->
<!--
<security-role>
<role-name>adminRight</role-name>
<description>Administrator</description>
</security-role>
-->
</web-app>

@ -400,6 +400,16 @@ adminAccountUserName=admin
# inaccessibility for installations on headless servers.
adminAccountForLocalhost=true
# adminRealm: a internal name (like a group name) for the login setting of the admin frontend
# ATTENTION: changing this name will invalidate all currently password hashes
# - With DIGEST authentication mode is this realm name of generated password hashes
# (RFC 2617 standard and recommendation). If you want to share password configuration
# with additional machines they have to belong to the same realm
# - authentication defaults to BASIC
# - and can be configured in defaults/web.xml , tag <auth-method>
adminRealm=YaCy-AdminUI
# if you are running a principal peer, you must update the following variables
# The upload method that should be used to upload the seed-list file to
# a public accessible webserver where it can be loaded by other peers.

@ -33,8 +33,6 @@ import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.yacy.cora.order.Base64Order;
import net.yacy.cora.order.Digest;
import net.yacy.cora.protocol.Domains;
import net.yacy.cora.protocol.RequestHeader;
@ -65,7 +63,8 @@ public class ConfigAccounts_p {
// may be overwritten if new password is given
if (user.length() > 0 && pw1.length() > 3 && pw1.equals(pw2)) {
// check passed. set account:
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1)));
// old: // env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1)));
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "MD5:"+Digest.encodeMD5Hex(user + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":"+ pw1));
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT, "");
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME,user);
} else {
@ -193,7 +192,9 @@ public class ConfigAccounts_p {
if( "newuser".equals(post.get("current_user"))){ //new user
if (!"".equals(pw1)) { //change only if set
mem.put(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(username + ":" + pw1));
// MD5 according to HTTP Digest RFC 2617 (3.2.2) name:realm:pwd (use seed.hash as realm)
// with prefix of encoding method (supported MD5: )
mem.put(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:"+Digest.encodeMD5Hex(username + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":"+pw1));
}
mem.put(UserDB.Entry.USER_FIRSTNAME, firstName);
@ -222,7 +223,8 @@ public class ConfigAccounts_p {
if (entry != null) {
try{
if (!"".equals(pw1)) {
entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(username+":"+pw1));
// with prefix of encoding method (supported MD5: )
entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:"+Digest.encodeMD5Hex(username+ ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":"+pw1));
}
entry.setProperty(UserDB.Entry.USER_FIRSTNAME, firstName);

@ -92,7 +92,7 @@ public class SettingsAck_p {
return prop;
}
// check passed. set account:
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(user + ":" + pw1)));
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "MD5:"+Digest.encodeMD5Hex(user + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + pw1));
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT, "");
env.setConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, user);
prop.put("info", "5");//admin account changed

@ -125,28 +125,31 @@ public class User{
}
}
if(post!= null && entry != null){
if(post.containsKey("changepass")){
prop.put("status", "1"); //password
if(entry.getMD5EncodedUserPwd().equals(Digest.encodeMD5Hex(entry.getUserName()+":"+post.get("oldpass", "")))){
if(post.get("newpass").equals(post.get("newpass2"))){
if(!post.get("newpass", "").equals("")){
try {
entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, Digest.encodeMD5Hex(entry.getUserName()+":"+post.get("newpass", "")));
prop.put("status_password", "0"); //changes
} catch (final Exception e) {
ConcurrentLog.logException(e);
}
}else{
prop.put("status_password", "3"); //empty
}
}else{
prop.put("status_password", "2"); //pws do not match
}
}else{
prop.put("status_password", "1"); //old pw wrong
}
}
if (post != null && entry != null) {
if (post.containsKey("changepass")) {
prop.put("status", "1"); //password
if (entry.getMD5EncodedUserPwd().startsWith("MD5:") ?
entry.getMD5EncodedUserPwd().equals("MD5:"+Digest.encodeMD5Hex(entry.getUserName() + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + post.get("oldpass", ""))) :
entry.getMD5EncodedUserPwd().equals(Digest.encodeMD5Hex(entry.getUserName() + ":" + post.get("oldpass", "")))) {
if (post.get("newpass").equals(post.get("newpass2"))) {
if (!post.get("newpass", "").equals("")) {
try {
entry.setProperty(UserDB.Entry.MD5ENCODED_USERPWD_STRING, "MD5:" + Digest.encodeMD5Hex(entry.getUserName() + ":" + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + post.get("newpass", "")));
prop.put("status_password", "0"); //changes
} catch (final Exception e) {
ConcurrentLog.logException(e);
}
} else {
prop.put("status_password", "3"); //empty
}
} else {
prop.put("status_password", "2"); //pws do not match
}
} else {
prop.put("status_password", "1"); //old pw wrong
}
}
}
if(post!=null && post.containsKey("logout")){
prop.put("logged-in", "0");

@ -47,6 +47,8 @@ import net.yacy.cora.util.SpaceExceededException;
import net.yacy.kelondro.blob.MapHeap;
import net.yacy.kelondro.util.FileUtils;
import net.yacy.kelondro.util.kelondroException;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
public final class UserDB {
@ -126,16 +128,19 @@ public final class UserDB {
}
/**
* Use a ProxyAuth String to authenticate user.
* Use a ProxyAuth Value to authenticate user.
* @param auth base64 Encoded String, which contains "username:pw".
*/
public Entry proxyAuth(final String auth) {
Entry entry = null;
if (auth != null) {
final String[] tmp = Base64Order.standardCoder.decodeString(auth.trim().substring(6)).split(":");
final String[] tmp = Base64Order.standardCoder.decodeString(auth.trim()).split(":");
if (tmp.length == 2) {
entry = this.passwordAuth(tmp[0], tmp[1]);
if (entry == null) {
entry = this.md5Auth(tmp[0], tmp[1]);
}
}
}
return entry;
@ -199,24 +204,26 @@ public final class UserDB {
}
return null;
}
public Entry passwordAuth(final String user, final String password) {
final Entry entry = this.getEntry(user);
final String md5pwd;
if (entry != null && (md5pwd = entry.getMD5EncodedUserPwd()) != null && md5pwd.equals(Digest.encodeMD5Hex(user+":"+password))) {
if (entry.isLoggedOut()){
try {
entry.setProperty(Entry.LOGGED_OUT, "false");
} catch (final Exception e) {
ConcurrentLog.logException(e);
}
if (entry != null && (md5pwd = entry.getMD5EncodedUserPwd()) != null) {
boolean authok;
if (md5pwd.startsWith("MD5:")) { // DIGEST style
authok = md5pwd.equals("MD5:" + Digest.encodeMD5Hex(user + ":"
+ Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_REALM, "YaCy") + ":" + password));
} else {
authok = md5pwd.equals(Digest.encodeMD5Hex(user + ":" + password));
}
if (authok) {
return entry;
}
return entry;
}
return null;
}
public Entry passwordAuth(final String user, final String password, final String ip){
final Entry entry = passwordAuth(user, password);
if (entry != null) {
@ -228,17 +235,9 @@ public final class UserDB {
public Entry md5Auth(final String user, final String md5) {
final Entry entry = this.getEntry(user);
if (entry != null && entry.getMD5EncodedUserPwd().equals(md5)) {
if (entry.isLoggedOut()){
try {
entry.setProperty(Entry.LOGGED_OUT, "false");
} catch (final Exception e) {
ConcurrentLog.logException(e);
}
return null;
}
if (entry != null && entry.getMD5EncodedUserPwd().endsWith(md5)) { // user pwd migth have prefix "MD5:"
return entry;
}
}
return null;
}
@ -328,8 +327,7 @@ public final class UserDB {
public class Entry {
public static final String MD5ENCODED_USERPWD_STRING = "MD5_user:pwd";
public static final String AUTHENTICATION_METHOD = "auth_method";
public static final String LOGGED_OUT = "loggedOut";
public static final String USER_FIRSTNAME = "firstName";
public static final String USER_LASTNAME = "lastName";
public static final String USER_ADDRESS = "address";
@ -364,10 +362,6 @@ public final class UserDB {
this.mem = (mem == null) ? new HashMap<String, String>() : mem;
if (mem == null || !mem.containsKey(AUTHENTICATION_METHOD)) {
this.mem.put(AUTHENTICATION_METHOD,"yacy");
}
this.oldDate=Calendar.getInstance();
this.newDate=Calendar.getInstance();
}
@ -550,10 +544,6 @@ public final class UserDB {
return (this.mem.containsKey(accessRight.toString())) ? this.mem.get(accessRight.toString()).equals("true") : false;
}
public boolean isLoggedOut(){
return (this.mem.containsKey(LOGGED_OUT) ? this.mem.get(LOGGED_OUT).equals("true") : false);
}
public void logout(final String ip, final String logintoken){
logout(ip);
if(cookieUsers.containsKey(logintoken)){
@ -563,7 +553,6 @@ public final class UserDB {
public void logout(final String ip) {
try {
setProperty(LOGGED_OUT, "true");
if (ipUsers.containsKey(ip)){
ipUsers.remove(ip);
}
@ -572,10 +561,6 @@ public final class UserDB {
}
}
public void logout(){
logout("xxxxxx");
}
@Override
public String toString() {
final StringBuilder str = new StringBuilder();

@ -45,9 +45,8 @@ import net.yacy.http.servlets.SolrServlet;
import net.yacy.http.servlets.YaCyDefaultServlet;
import net.yacy.http.servlets.YaCyProxyServlet;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
import net.yacy.utils.PKCS12Tool;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@ -160,14 +159,17 @@ public class Jetty8HttpServerImpl implements YaCyHttpServer {
allrequesthandlers.addHandler(htrootContext);
allrequesthandlers.addHandler(new DefaultHandler()); // if not handled by other handler
// wrap all handlers by security handler
YaCyLoginService loginService = new YaCyLoginService();
// this is very important (as it is part of the user password hash)
// changes will ivalidate all current existing user-password-hashes (from userDB)
loginService.setName(sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy"));
Jetty8YaCySecurityHandler securityHandler = new Jetty8YaCySecurityHandler();
LoginService loginService = new YaCyLoginService();
securityHandler.setLoginService(loginService);
securityHandler.setRealmName(loginService.getName());
htrootContext.setSecurityHandler(securityHandler);
// wrap all handlers
Handler crashHandler = new CrashProtectionHandler(allrequesthandlers);
// check server access restriction and add IPAccessHandler if restrictions are needed
// otherwise don't (to save performance)

@ -26,6 +26,8 @@ package net.yacy.http;
import net.yacy.cora.order.Base64Order;
import net.yacy.cora.order.Digest;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
import net.yacy.server.serverAccessTracker;
import org.eclipse.jetty.util.security.Credential;
@ -34,27 +36,39 @@ import org.eclipse.jetty.util.security.Credential;
/**
* implementation of YaCy's old admin password as jetty Credential
* supporting BASIC and DIGEST authentication
* and using MD5 encryptet passwords/credentials. Following RFC recommendation (to use the realm in MD5 hash)
* expecting a MD5 hash in format MD5( username:realm:password ), realm configured in yacy.init adminRealm
* (exception: old style credential MD5( username:password ) still accepted with BASIC auth)
*
*/
public class YaCyLegacyCredential extends Credential {
private static final long serialVersionUID = -3527894085562480001L;
private String hash;
private String hash; // remember password hash (for new style with prefix of used encryption supported "MD5:" )
private String foruser; // remember the user as YaCy credential is username:pwd (not just pwd)
private boolean isBase64enc; // remember hash encoding false = encodeMD5Hex(usr:pwd) ; true = encodeMD5Hex(Base64Order.standardCoder.encodeString(usr:pw))
private Credential c;
/**
* internal hash function
* internal hash function for admin account
*
* @param clear password
* @param pw clear password
* @return hash string
*/
public static String calcHash(String pw) {
public static String calcHash(String pw) { // old style hash
return Digest.encodeMD5Hex(Base64Order.standardCoder.encodeString(pw));
}
@Override
public boolean check(Object credentials) {
if (credentials instanceof String) {
if (credentials instanceof Credential) { // for DIGEST auth
return ((Credential) credentials).check(c);
}
if (credentials instanceof String) { // for BASIC auth
final String pw = (String) credentials;
if (isBase64enc) {
if (serverAccessTracker.timeSinceAccessFromLocalhost() < 100) {
@ -63,42 +77,55 @@ public class YaCyLegacyCredential extends Credential {
// the cleartext password is not stored anywhere, but we must find a way to allow scripts to steer a peer.
// this is the exception that makes that possible.
// TODO: it should be better to check the actual access IP here, but that is not handed over to Credential classes :(
if (pw.equals(this.hash)) return true;
if ((pw).equals(this.hash)) return true;
}
// exception for admin use old style MD5hash (user:password)
return calcHash(foruser + ":" + pw).equals(this.hash); // for admin user
}
// normal users
return Digest.encodeMD5Hex(foruser + ":" + pw).equals(this.hash);
// normal users (and new admin pwd)
if (hash.startsWith(MD5.__TYPE) && hash != null) {
return (Digest.encodeMD5Hex(foruser + ":" + Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy")+":" + pw).equals(hash.substring(4)));
} else { // special check for old style password hash (prior v1.67/9501 15.1.2014)
return Digest.encodeMD5Hex(foruser + ":" + pw).equals(hash);
}
}
throw new UnsupportedOperationException();
}
/**
* create Credential object from config file hash
* @param configHash hash as in config file hash(adminuser:pwd)
* @return
*/
public static Credential getCredentialsFromConfig(String username, String configHash) {
YaCyLegacyCredential c = new YaCyLegacyCredential();
c.foruser = username;
c.isBase64enc = true;
c.hash = configHash;
return c;
}
/**
* create Credential object from password
* @param username
* @param configHash encodeMD5Hex("user:pwd") as stored in UserDB
* @return
*/
public static Credential getCredentials(String username, String configHash) {
YaCyLegacyCredential c = new YaCyLegacyCredential();
c.foruser = username;
c.isBase64enc = false;
c.hash = configHash;
//c.hash = calcHash(user + ":" + password);
return c;
}
/**
* create Credential object from config file hash
*
* @param configHash hash as in config file hash(adminuser:pwd)
* @return
*/
public static Credential getCredentialForAdmin(String username, String configHash) {
YaCyLegacyCredential yc = new YaCyLegacyCredential();
if (configHash.startsWith("MD5:")) {
yc.isBase64enc = false;
yc.c = Credential.getCredential(configHash);
} else {
yc.isBase64enc = true;
}
yc.foruser = username;
yc.hash = configHash;
return yc;
}
/**
* create Credential object from password
*
* @param username
* @param configHash encodeMD5Hex("user:realm:pwd") as stored in UserDB
* @return
*/
public static Credential getCredentialForUserDB(String username, String configHash) {
YaCyLegacyCredential yc = new YaCyLegacyCredential();
yc.c = Credential.getCredential(configHash); // creates a MD5 hash credential
yc.foruser = username;
yc.isBase64enc = false;
yc.hash = configHash;
return yc;
}
}

@ -25,80 +25,68 @@
package net.yacy.http;
import java.io.IOException;
import java.security.Principal;
import javax.security.auth.Subject;
import java.util.ArrayList;
import net.yacy.data.UserDB.AccessRight;
import net.yacy.data.UserDB.Entry;
import net.yacy.search.Switchboard;
import net.yacy.search.SwitchboardConstants;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.MappedLoginService;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Credential;
/**
* jetty login service, provides one admin user
* jetty login service, provides admin and YaCy.UserDB users with role assignment
* with DIGEST auth by default Jetty uses the name of the loginSevice as realmname (which is part of all password hashes)
*/
public class YaCyLoginService extends MappedLoginService implements LoginService {
@Override
public String getName() {
return "YaCy 'admin' Account (reset your password with bin/passwd.sh <new password>)";
}
@Override
protected UserIdentity loadUser(String username) {
if (username == null || username.isEmpty()) return null; // quick exit
final Switchboard sb = Switchboard.getSwitchboard();
String adminuser = sb.getConfig(SwitchboardConstants.ADMIN_ACCOUNT_USER_NAME, "admin");
Credential credential = null;
String[] roles = null;
if (username.equals(adminuser)) {
final String adminAccountBase64MD5 = sb.getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, "");
// in YaCy the credential hash is composed of username:pwd so the username is needed to create valid credential
// not just the password (as usually in Jetty). As the accountname for the std. adminuser is not stored a useridentity
// is created for current user (and the pwd checked against the stored username:pwd setting)
Credential credential = YaCyLegacyCredential.getCredentialsFromConfig(username, adminAccountBase64MD5);
// in YaCy the credential hash is composed of username:pwd so the username is needed to create valid credential
// not just the password (as usually in Jetty). As the accountname for the std. adminuser is not stored a useridentity
// is created for current user (and the pwd checked against the stored username:pwd setting)
credential = YaCyLegacyCredential.getCredentialForAdmin(username, adminAccountBase64MD5);
// TODO: YaCy user:pwd hashes should longterm likely be switched to separable username + pwd-hash entries
// and/or the standard admin account username shuld be fix = "admin"
Principal userPrincipal = new MappedLoginService.KnownUser(username, credential);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(credential);
subject.setReadOnly();
IdentityService is = getIdentityService();
return is.newUserIdentity(subject, userPrincipal, new String[]{AccessRight.ADMIN_RIGHT.toString()});
}
Entry user = sb.userDB.getEntry(username);
if (user != null) {
// assigning roles from userDB
String[] role = new String[AccessRight.values().length];
int i = 0;
for (final AccessRight right : AccessRight.values()) {
if (user.hasRight(right)) {
role[i] = right.toString();
i++;
roles = new String[]{AccessRight.ADMIN_RIGHT.toString()};
} else {
Entry user = sb.userDB.getEntry(username);
if (user != null && user.getMD5EncodedUserPwd() != null) {
// assigning roles from userDB
ArrayList<String> roletmp = new ArrayList<String>();
for (final AccessRight right : AccessRight.values()) {
if (user.hasRight(right)) {
roletmp.add(right.toString());
}
}
if (roletmp.size() > 0) roles = roletmp.toArray(new String[roletmp.size()]);
credential = YaCyLegacyCredential.getCredentialForUserDB(username, user.getMD5EncodedUserPwd());
}
}
if (credential != null) {
if (roles != null) {
return putUser(username, credential, roles);
} else {
return putUser(username, credential); // w/o role makes not much sense, but succeeds login....
}
Credential credential = YaCyLegacyCredential.getCredentials(username, user.getMD5EncodedUserPwd());
Principal userPrincipal = new MappedLoginService.KnownUser(username, credential);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(credential);
subject.setReadOnly();
IdentityService is = getIdentityService();
return is.newUserIdentity(subject, userPrincipal, role);
}
return null;
}
@Override
protected void loadUsers() throws IOException {
// don't load any users into MappedLoginService on startup
// we use loadUser for dynamic checking
}
@Override
protected void loadUsers() throws IOException {
// don't load any users into MappedLoginService on startup
// we use loadUser for dynamic checking
}
}

@ -48,9 +48,14 @@ import javax.servlet.http.HttpServletResponse;
import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.analysis.Classification;
import net.yacy.cora.order.Base64Order;
import net.yacy.cora.protocol.Domains;
import net.yacy.cora.protocol.HeaderFramework;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.data.UserDB;
import net.yacy.data.UserDB.AccessRight;
import net.yacy.data.UserDB.Entry;
import net.yacy.http.ProxyHandler;
import net.yacy.kelondro.util.FileUtils;
import net.yacy.kelondro.util.MemoryControl;
@ -644,11 +649,28 @@ public class YaCyDefaultServlet extends HttpServlet {
legacyRequestHeader.put(HeaderFramework.CONNECTION_PROP_PATH, target);
legacyRequestHeader.put(HeaderFramework.CONNECTION_PROP_EXT, targetExt);
// for userDB user legacyRequest expect login in Cookie (add one)
if (request.getUserPrincipal() != null) {
String userpassEncoded = request.getHeader("Authorization"); // e.g. "Basic xxXXxxXXxxXX"
if (userpassEncoded != null) {
legacyRequestHeader.setCookie("login", userpassEncoded);
if (legacyRequestHeader.containsKey(RequestHeader.AUTHORIZATION)) {
if (HttpServletRequest.BASIC_AUTH.equalsIgnoreCase(request.getAuthType())) {
} else {
// handle DIGEST auth for legacyHeader (create username:md5pwdhash
if (request.getUserPrincipal() != null) {
String userpassEncoded = request.getHeader(RequestHeader.AUTHORIZATION); // e.g. "Basic AdminMD5hash"
if (userpassEncoded != null) {
if (request.isUserInRole(AccessRight.ADMIN_RIGHT.toString()) && !Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5,"").isEmpty()) {
// fake admin authentication for legacyRequestHeader (as e.g. DIGEST is not supported by legacyRequestHeader)
legacyRequestHeader.put(RequestHeader.AUTHORIZATION, HttpServletRequest.BASIC_AUTH + " "
+ Switchboard.getSwitchboard().getConfig(SwitchboardConstants.ADMIN_ACCOUNT_B64MD5, ""));
} else {
// fake Basic auth header for Digest auth (Basic username:md5pwdhash)
String username = request.getRemoteUser();
Entry user = Switchboard.getSwitchboard().userDB.getEntry(username);
if (user != null) {
legacyRequestHeader.put(RequestHeader.AUTHORIZATION, HttpServletRequest.BASIC_AUTH + " "
+ username + ":" + user.getMD5EncodedUserPwd());
}
}
}
}
}
}
return legacyRequestHeader;
@ -842,7 +864,7 @@ public class YaCyDefaultServlet extends HttpServlet {
// handle action auth: check if the servlets requests authentication
if (templatePatterns.containsKey(serverObjects.ACTION_AUTHENTICATE)) {
if (!request.authenticate(response)) {
return;
return;
}
//handle action forward
} else if (templatePatterns.containsKey(serverObjects.ACTION_LOCATION)) {

@ -3233,6 +3233,8 @@ public final class Switchboard extends serverSwitch {
* http-authentify: auth-level 4
*
* @param requestHeader
* - requestHeader..AUTHORIZATION = B64encode("adminname:password") or = B64encode("adminname:valueOf_Base64MD5cft")
* - adminAccountBase64MD5 = MD5(B64encode("adminname:password") or = "MD5:"+MD5("adminname:peername:password")
* @return the auth-level as described above or 1 which means 'not authorized'. a 0 is returned in case of
* fraud attempts
*/
@ -3254,8 +3256,13 @@ public final class Switchboard extends serverSwitch {
}
// get the authorization string from the header
final String realmProp = (requestHeader.get(RequestHeader.AUTHORIZATION, "xxxxxx")).trim();
final String realmValue = realmProp.substring(6);
final String realmProp = (requestHeader.get(RequestHeader.AUTHORIZATION, "")).trim();
String realmValue = realmProp.isEmpty() ? null : realmProp.substring(6); // take out "BASIC "
// authorization with admin keyword in configuration
if ( realmValue == null || realmValue.isEmpty() ) {
return 1;
}
// security check against too long authorization strings
if ( realmValue.length() > 256 ) {
@ -3264,24 +3271,42 @@ public final class Switchboard extends serverSwitch {
// authorization by encoded password, only for localhost access
String pass = Base64Order.standardCoder.encodeString(adminAccountUserName + ":" + adminAccountBase64MD5);
if ( accessFromLocalhost && (pass.equals(realmValue)) ) {
if ( accessFromLocalhost && (pass.equals(realmValue)) ) { // assume realmValue as is in cfg
adminAuthenticationLastAccess = System.currentTimeMillis();
return 3; // soft-authenticated for localhost
}
// authorization by hit in userDB
if ( this.userDB.hasAdminRight(realmProp, requestHeader.getHeaderCookies()) ) {
// authorization by hit in userDB (realm username:encodedpassword - handed over by DefaultServlet)
if ( this.userDB.hasAdminRight(realmValue, requestHeader.getHeaderCookies()) ) {
adminAuthenticationLastAccess = System.currentTimeMillis();
return 4; //return, because 4=max
}
// authorization with admin keyword in configuration
if ( realmValue == null || realmValue.isEmpty() ) {
return 1;
// athorization by BASIC auth (realmValue = "adminname:password")
if (adminAccountBase64MD5.startsWith("MD5:")) {
// handle new option adminAccountBase64MD5="MD5:xxxxxxx" = encodeMD5Hex ("adminname:peername:password")
String realmtmp = Base64Order.standardCoder.decodeString(realmValue); //decode to clear text
int i = realmtmp.indexOf(':');
if (i > 4) { // put peer name in realmValue (>4 is correct to scipt "MD5:" and usernames are min 4 characters)
realmtmp = realmtmp.substring(0, i + 1) + sb.getConfig(SwitchboardConstants.ADMIN_REALM,"YaCy") + ":" + realmtmp.substring(i + 1);
if (adminAccountBase64MD5.substring(4).equals(Digest.encodeMD5Hex(realmtmp))) {
adminAuthenticationLastAccess = System.currentTimeMillis();
return 4; // hard-authenticated, all ok
}
if ( adminAccountBase64MD5.equals(Digest.encodeMD5Hex(realmValue)) ) {
} else {
// handle DIGEST auth (realmValue = adminAccountBase (set for lecacyHeader in DefaultServlet for authenticated requests)
if (adminAccountBase64MD5.equals(realmValue)) {
adminAuthenticationLastAccess = System.currentTimeMillis();
return 4; // hard-authenticated, all ok
}
}
} else {
// handle old option adminAccountBase64MD5="xxxxxxx" = encodeMD55Hex(encodeB64("adminname:password")
if (adminAccountBase64MD5.equals(Digest.encodeMD5Hex(realmValue))) {
adminAuthenticationLastAccess = System.currentTimeMillis();
return 4; // hard-authenticated, all ok
}
}
return 1;
}

@ -40,11 +40,14 @@ public final class SwitchboardConstants {
* <p><code>public static final String <strong>ADMIN_ACCOUNT_B64MD5</strong> = "adminAccountBase64MD5"</code></p>
* <p>Name of the setting holding the authentication hash for the static <code>admin</code>-account. It is calculated
* by first encoding <code>username:password</code> as Base64 and hashing it using {@link MapTools#encodeMD5Hex(String)}.</p>
* With introduction of DIGEST authentication all passwords are MD5 encoded and calculatd as <code>username:adminrealm:password</code>
* To differentiate old and new admin passwords, use the new calculated passwords a "MD5:" prefix.
*/
public static final String ADMIN_ACCOUNT = "adminAccount";
public static final String ADMIN_ACCOUNT_B64MD5 = "adminAccountBase64MD5";
public static final String ADMIN_ACCOUNT_USER_NAME = "adminAccountUserName"; // by default 'admin'
public static final String ADMIN_ACCOUNT_FOR_LOCALHOST = "adminAccountForLocalhost";
public static final String ADMIN_REALM = "adminRealm";
public static final int CRAWLJOB_SYNC = 0;
public static final int CRAWLJOB_STATUS = 1;

Loading…
Cancel
Save