/**
* Copyright 2013-2015 Seagate Technology LLC.
*
* This Source Code Form is subject to the terms of the Mozilla
* Public License, v. 2.0. If a copy of the MPL was not
* distributed with this file, You can obtain one at
* https://mozilla.org/MP:/2.0/.
*
* This program is distributed in the hope that it will be useful,
* but is provided AS-IS, WITHOUT ANY WARRANTY; including without
* the implied warranty of MERCHANTABILITY, NON-INFRINGEMENT or
* FITNESS FOR A PARTICULAR PURPOSE. See the Mozilla Public
* License for more details.
*
* See www.openkinetic.org for more project information
*/
package com.seagate.kinetic.simulator.internal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import kinetic.client.KineticException;
import com.google.protobuf.ByteString;
import com.seagate.kinetic.common.lib.HMACAlgorithmUtil;
import com.seagate.kinetic.common.lib.KineticMessage;
import com.seagate.kinetic.common.lib.RoleUtil;
import com.seagate.kinetic.proto.Kinetic.Command;
import com.seagate.kinetic.proto.Kinetic.Command.MessageType;
import com.seagate.kinetic.proto.Kinetic.Command.Security;
import com.seagate.kinetic.proto.Kinetic.Command.Security.ACL;
import com.seagate.kinetic.proto.Kinetic.Command.Security.ACL.Permission;
import com.seagate.kinetic.proto.Kinetic.Command.Status.StatusCode;
import com.seagate.kinetic.simulator.lib.HmacStore;
/**
* Security handler prototype.
*
* @author chiaming
*
*/
public abstract class SecurityHandler {
private final static Logger logger = Logger.getLogger(SecurityHandler.class
.getName());
public static boolean checkPermission(KineticMessage request,
KineticMessage respond, Map<Long, ACL> currentMap) {
boolean hasPermission = false;
Command.Builder commandBuilder = (Command.Builder) respond.getCommand();
// set reply type
commandBuilder.getHeaderBuilder()
.setMessageType(MessageType.SECURITY_RESPONSE);
// set ack sequence
commandBuilder.getHeaderBuilder()
.setAckSequence(request.getCommand().getHeader().getSequence());
// check if has permission to set security
if (currentMap == null) {
hasPermission = true;
} else {
try {
// check if client has permission
Authorizer.checkPermission(currentMap, request.getMessage().getHmacAuth().getIdentity(),
Permission.SECURITY);
hasPermission = true;
} catch (KVSecurityException e) {
commandBuilder.getStatusBuilder()
.setCode(StatusCode.NOT_AUTHORIZED);
commandBuilder.getStatusBuilder()
.setStatusMessage(e.getMessage());
}
}
return hasPermission;
}
public static synchronized void handleSecurity(
KineticMessage request, KineticMessage response,
SimulatorEngine engine)
throws KVStoreException, IOException {
Command.Builder commandBuilder = (Command.Builder) response
.getCommand();
commandBuilder.getHeaderBuilder().setMessageType(MessageType.SECURITY_RESPONSE);
if (request.getIsSecureChannel() == false) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.INVALID_REQUEST);
commandBuilder.getStatusBuilder().setStatusMessage(
"TLS channel is required for Security operation");
return;
}
// check if only contains one security component change (ACL or ICE or Lock Pin)
if (isSecurityMessageValid(request) == false) {
//logger.warning("security not enforced at this time, should not set ACL/ICE/LOCL at the same time ...");
commandBuilder.getStatusBuilder().setCode(
StatusCode.INVALID_REQUEST);
commandBuilder
.getStatusBuilder()
.setStatusMessage(
"contains more than one security component change (ACL or ICE or Lock Pin");
return;
}
// get ACL list
List<ACL> requestAclList = request.getCommand().getBody().getSecurity()
.getAclList();
// Validate input
for (ACL acl : requestAclList) {
// add algorithm check
if (!acl.hasHmacAlgorithm()
|| !HMACAlgorithmUtil.isSupported(acl.getHmacAlgorithm())) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.NO_SUCH_HMAC_ALGORITHM);
return;
}
for (ACL.Scope domain : acl.getScopeList()) {
if (domain.hasOffset() && domain.getOffset() < 0) {
// Negative offsets are not allowed
commandBuilder.getStatusBuilder().setCode(
StatusCode.INVALID_REQUEST);
commandBuilder.getStatusBuilder().setStatusMessage(
"Offset in domain is less than 0.");
return;
}
List<Permission> roleOfList = domain.getPermissionList();
if (null == roleOfList || roleOfList.isEmpty()) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.INVALID_REQUEST);
commandBuilder.getStatusBuilder().setStatusMessage(
"No role set in acl");
return;
}
for (Permission role : roleOfList) {
if (!RoleUtil.isValid(role)) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.INVALID_REQUEST);
commandBuilder.getStatusBuilder().setStatusMessage(
"Role is invalid in acl. Role is: "
+ role.toString());
return;
}
}
}
}
// construct new security builder to persist
Security.Builder securityBuilder = Security.newBuilder();
// request security
Security requestSecurity = request.getCommand().getBody().getSecurity();
// init pins
initSecuirtyBuilderPins (securityBuilder, engine);
// check if this is an ACL update
if (requestAclList.isEmpty()) {
// not an ACL update, add/preserve current acl list
securityBuilder.addAllAcl(engine.getAclMap().values());
} else {
// this is an ACL update, add request ack list
securityBuilder.addAllAcl(requestAclList);
// update engine cache ACL list
for (ACL acl : requestAclList) {
engine.getAclMap().put(acl.getIdentity(), acl);
}
}
// check if request has ICE pin
if (requestSecurity.hasNewErasePIN() || requestSecurity.hasOldErasePIN()) {
// get current erase pin
ByteString currentErasePin = engine.getSecurityPin().getErasePin();
// need to compare if we need the old pin
if ((currentErasePin != null) && (currentErasePin.isEmpty() == false)) {
// get old erase pin
ByteString oldErasePin = requestSecurity.getOldErasePIN();
// compare old with current
if (currentErasePin.equals(oldErasePin) == false) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.NOT_AUTHORIZED);
commandBuilder.getStatusBuilder().setStatusMessage(
"Invalid old erase pin: " + oldErasePin);
return;
}
}
// set to builder to be persisted
securityBuilder.setNewErasePIN(requestSecurity.getNewErasePIN());
// set ICE pin to cache
engine.getSecurityPin().setErasePin(requestSecurity.getNewErasePIN());
}
if (requestSecurity.hasNewLockPIN() || requestSecurity.hasOldLockPIN()) {
// get current lock pin
ByteString currentLockPin = engine.getSecurityPin().getLockPin();
// need to compare if we need the old pin
if ((currentLockPin != null) && (currentLockPin.isEmpty() == false)) {
// get old lock pin
ByteString oldLockPin = requestSecurity.getOldLockPIN();
// compare old with current
if (currentLockPin.equals(oldLockPin) == false) {
commandBuilder.getStatusBuilder().setCode(
StatusCode.NOT_AUTHORIZED);
commandBuilder.getStatusBuilder().setStatusMessage(
"Invalid old lock pin: " + oldLockPin);
return;
}
}
// set new LOCK pin to builder to be persisted
securityBuilder.setNewLockPIN (requestSecurity.getNewLockPIN());
// set LOCK pin to cache
engine.getSecurityPin().setLockPin(requestSecurity.getNewLockPIN());
}
// persist to acl file
SecurityHandler.persistAcl(securityBuilder.build().toByteArray(), engine.getKineticHome());
// set status code
commandBuilder.getStatusBuilder().setCode(StatusCode.SUCCESS);
return;
}
private static void persistAcl(byte[] contents, String kineticHome)
throws IOException {
String aclPersistFilePath = kineticHome + File.separator + ".acl";
String aclPersistBakFilePath = aclPersistFilePath + ".bak";
// delete backup file
File aclBakFile = new File(aclPersistBakFilePath);
if (aclBakFile.exists()) {
aclBakFile.delete();
}
// backup file
File aclFile = new File(aclPersistFilePath);
aclFile.renameTo(aclBakFile);
// save new file
aclFile = new File(aclPersistFilePath);
FileOutputStream out = new FileOutputStream(aclFile);
out.write(contents);
out.close();
}
public static void loadACL(SimulatorEngine engine) throws IOException, KineticException {
String aclPersistFilePath = engine.getKineticHome() + File.separator + ".acl";
File aclFile = new File(aclPersistFilePath);
Map<Long, ACL> aclmap = new HashMap<Long, ACL>();
Security security = null;
if (aclFile.exists()) {
Long fileLength = aclFile.length();
if (fileLength != 0) {
byte[] fileContent = new byte[fileLength.intValue()];
FileInputStream in = new FileInputStream(aclFile);
in.read(fileContent);
in.close();
security = Security.parseFrom(fileContent);
List<ACL> aclList = security.getAclList();
for (ACL acl : aclList) {
aclmap.put(acl.getIdentity(), acl);
}
// set erase pin in cache
engine.getSecurityPin().setErasePin(security.getNewErasePIN());
// set lock pin in cache
engine.getSecurityPin().setLockPin(security.getNewLockPIN());
// lock the device since it was lock enabled
if (engine.getSecurityPin().getLockPin().isEmpty() == false) {
engine.setDeviceLocked(true);
logger.warning ("******* Device is locked ********");
}
}
}
if (aclmap.size() == 0) {
// get default acl map
aclmap = HmacStore.getAclMap();
}
// set to engine
engine.setAclMap(aclmap);
// set default hmac key map
engine.setHmacKeyMap(HmacStore.getHmacKeyMap(aclmap));
}
/**
* Reset security ACL and pins to default.
*
* @param kineticHome
* @param securityPin
* @param aclmap
* @param hmacKeyMap
* @throws KineticException
*/
public static void resetSecurity (SimulatorEngine engine) throws KineticException {
String aclPersistFilePath = engine.getKineticHome() + File.separator + ".acl";
File aclFile = new File(aclPersistFilePath);
// delete security file
boolean deleted = aclFile.delete();
if (deleted) {
logger.info("removed security data ....");
}
// clear erase pin
engine.getSecurityPin().setErasePin(null);
// clear lock pin
engine.getSecurityPin().setLockPin(null);
// clear acl map
engine.getAclMap().clear();
// clear key map
engine.getHmacKeyMap().clear();
Map <Long, ACL> aclmap = HmacStore.getAclMap();
// set default ack map
engine.setAclMap(aclmap);
// set default key map
engine.setHmacKeyMap(HmacStore.getHmacKeyMap(aclmap));
logger.info("reset security data to its factory defaults ...");
}
/**
* Check if set Security contains only ACL, or ISE, or Lock pin change.
*
* Drive only support to change one at a time.
*
* @param request
* @return true if only one
*/
private static boolean isSecurityMessageValid (KineticMessage request) {
boolean isValid = false;
int count = 0;
Security security = request.getCommand().getBody().getSecurity();
// if acl has value
if (security.getAclCount() > 0) {
count ++;
}
// if erase pin has value
if (security.hasNewErasePIN() || security.hasOldErasePIN()) {
count ++;
}
// if lock pin has value
if (security.hasNewLockPIN() || security.hasOldLockPIN()) {
count ++;
}
// we only allow to set one at a time
if (count <= 1) {
isValid = true;
}
return isValid;
}
private static void initSecuirtyBuilderPins (Security.Builder securityBuilder, SimulatorEngine engine) {
//preserv current lock pin
if (engine.getSecurityPin().getLockPin() != null) {
securityBuilder.setOldLockPIN(engine.getSecurityPin().getLockPin());
securityBuilder.setNewLockPIN(engine.getSecurityPin().getLockPin());
}
// preserv current ICE pin
if (engine.getSecurityPin().getErasePin() != null) {
securityBuilder.setOldErasePIN (engine.getSecurityPin().getErasePin());
securityBuilder.setNewErasePIN(engine.getSecurityPin().getErasePin());
}
}
}