/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/
package org.xdi.oxauth.service.fido.u2f;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.inject.Named;
import org.gluu.site.ldap.persistence.LdapEntryManager;
import org.slf4j.Logger;
import org.xdi.oxauth.crypto.random.ChallengeGenerator;
import org.xdi.oxauth.exception.fido.u2f.DeviceCompromisedException;
import org.xdi.oxauth.model.config.StaticConfiguration;
import org.xdi.oxauth.model.fido.u2f.DeviceRegistration;
import org.xdi.oxauth.model.fido.u2f.DeviceRegistrationResult;
import org.xdi.oxauth.model.fido.u2f.DeviceRegistrationStatus;
import org.xdi.oxauth.model.fido.u2f.RegisterRequestMessageLdap;
import org.xdi.oxauth.model.fido.u2f.RequestMessageLdap;
import org.xdi.oxauth.model.fido.u2f.exception.BadInputException;
import org.xdi.oxauth.model.fido.u2f.message.RawRegisterResponse;
import org.xdi.oxauth.model.fido.u2f.protocol.AuthenticateRequest;
import org.xdi.oxauth.model.fido.u2f.protocol.ClientData;
import org.xdi.oxauth.model.fido.u2f.protocol.DeviceData;
import org.xdi.oxauth.model.fido.u2f.protocol.RegisterRequest;
import org.xdi.oxauth.model.fido.u2f.protocol.RegisterRequestMessage;
import org.xdi.oxauth.model.fido.u2f.protocol.RegisterResponse;
import org.xdi.oxauth.model.util.Base64Util;
import org.xdi.oxauth.service.UserService;
import org.xdi.oxauth.util.ServerUtil;
import org.xdi.util.StringHelper;
import com.unboundid.ldap.sdk.Filter;
/**
* Provides operations with U2F registration requests
*
* @author Yuriy Movchan Date: 05/19/2015
*/
@Stateless
@Named("u2fRegistrationService")
public class RegistrationService extends RequestService {
@Inject
private Logger log;
@Inject
private LdapEntryManager ldapEntryManager;
@Inject
private ApplicationService applicationService;
@Inject
private UserService userService;
@Inject
private AuthenticationService u2fAuthenticationService;
@Inject
private RawRegistrationService rawRegistrationService;
@Inject
private ClientDataValidationService clientDataValidationService;
@Inject
private DeviceRegistrationService deviceRegistrationService;
@Inject @Named("randomChallengeGenerator")
private ChallengeGenerator challengeGenerator;
@Inject
private StaticConfiguration staticConfiguration;
public RegisterRequestMessage builRegisterRequestMessage(String appId, String userInum) {
if (applicationService.isValidateApplication()) {
applicationService.checkIsValid(appId);
}
List<AuthenticateRequest> authenticateRequests = new ArrayList<AuthenticateRequest>();
List<RegisterRequest> registerRequests = new ArrayList<RegisterRequest>();
boolean twoStep = StringHelper.isNotEmpty(userInum);
if (twoStep) {
// In two steps we expects not empty userInum
List<DeviceRegistration> deviceRegistrations = deviceRegistrationService.findUserDeviceRegistrations(userInum, appId);
for (DeviceRegistration deviceRegistration : deviceRegistrations) {
if (!deviceRegistration.isCompromised()) {
try {
AuthenticateRequest authenticateRequest = u2fAuthenticationService.startAuthentication(appId, deviceRegistration);
authenticateRequests.add(authenticateRequest);
} catch (DeviceCompromisedException ex) {
log.error("Faield to authenticate device", ex);
}
}
}
}
RegisterRequest request = startRegistration(appId);
registerRequests.add(request);
return new RegisterRequestMessage(authenticateRequests, registerRequests);
}
public RegisterRequest startRegistration(String appId) {
return startRegistration(appId, challengeGenerator.generateChallenge());
}
public RegisterRequest startRegistration(String appId, byte[] challenge) {
return new RegisterRequest(Base64Util.base64urlencode(challenge), appId);
}
public DeviceRegistrationResult finishRegistration(RegisterRequestMessage requestMessage, RegisterResponse response, String userInum) throws BadInputException {
return finishRegistration(requestMessage, response, userInum, null);
}
public DeviceRegistrationResult finishRegistration(RegisterRequestMessage requestMessage, RegisterResponse response, String userInum, Set<String> facets)
throws BadInputException {
RegisterRequest request = requestMessage.getRegisterRequest();
String appId = request.getAppId();
ClientData clientData = response.getClientData();
clientDataValidationService.checkContent(clientData, RawRegistrationService.SUPPORTED_REGISTER_TYPES, request.getChallenge(), facets);
RawRegisterResponse rawRegisterResponse = rawRegistrationService.parseRawRegisterResponse(response.getRegistrationData());
rawRegistrationService.checkSignature(appId, clientData, rawRegisterResponse);
Date now = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTime();
DeviceRegistration deviceRegistration = rawRegistrationService.createDevice(rawRegisterResponse);
deviceRegistration.setStatus(DeviceRegistrationStatus.ACTIVE);
deviceRegistration.setApplication(appId);
deviceRegistration.setCreationDate(now);
int keyHandleHashCode = deviceRegistrationService.getKeyHandleHashCode(rawRegisterResponse.getKeyHandle());
deviceRegistration.setKeyHandleHashCode(keyHandleHashCode);
final String deviceRegistrationId = String.valueOf(System.currentTimeMillis());
deviceRegistration.setId(deviceRegistrationId);
String responseDeviceData = response.getDeviceData();
if (StringHelper.isNotEmpty(responseDeviceData)) {
try {
String responseDeviceDataDecoded = new String(Base64Util.base64urldecode(responseDeviceData));
DeviceData deviceData = ServerUtil.jsonMapperWithWrapRoot().readValue(responseDeviceDataDecoded, DeviceData.class);
deviceRegistration.setDeviceData(deviceData);
} catch (Exception ex) {
throw new BadInputException(String.format("Device data is invalid: %s", responseDeviceData), ex);
}
}
boolean approved = StringHelper.equals(RawRegistrationService.REGISTER_FINISH_TYPE, response.getClientData().getTyp());
if (!approved) {
log.debug("Registratio request with keyHandle '{}' was canceled", rawRegisterResponse.getKeyHandle());
return new DeviceRegistrationResult(deviceRegistration, DeviceRegistrationResult.Status.CANCELED);
}
boolean twoStep = StringHelper.isNotEmpty(userInum);
if (twoStep) {
deviceRegistration.setDn(deviceRegistrationService.getDnForU2fDevice(userInum, deviceRegistrationId));
// Check if there is device registration with keyHandle in LDAP already
List<DeviceRegistration> foundDeviceRegistrations = deviceRegistrationService.findDeviceRegistrationsByKeyHandle(appId, deviceRegistration.getKeyHandle(), "oxId");
if (foundDeviceRegistrations.size() != 0) {
throw new BadInputException(String.format("KeyHandle %s was compromised", deviceRegistration.getKeyHandle()));
}
deviceRegistrationService.addUserDeviceRegistration(userInum, deviceRegistration);
} else {
deviceRegistration.setDn(deviceRegistrationService.getDnForOneStepU2fDevice(deviceRegistrationId));
deviceRegistrationService.addOneStepDeviceRegistration(deviceRegistration);
}
return new DeviceRegistrationResult(deviceRegistration, DeviceRegistrationResult.Status.APPROVED);
}
public void storeRegisterRequestMessage(RegisterRequestMessage requestMessage, String userInum, String sessionState) {
Date now = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTime();
final String registerRequestMessageId = UUID.randomUUID().toString();
RequestMessageLdap registerRequestMessageLdap = new RegisterRequestMessageLdap(getDnForRegisterRequestMessage(registerRequestMessageId),
registerRequestMessageId, now, sessionState, userInum, requestMessage);
ldapEntryManager.persist(registerRequestMessageLdap);
}
public RegisterRequestMessage getRegisterRequestMessage(String oxId) {
String requestDn = getDnForRegisterRequestMessage(oxId);
RegisterRequestMessageLdap registerRequestMessageLdap = ldapEntryManager.find(RegisterRequestMessageLdap.class, requestDn);
if (registerRequestMessageLdap == null) {
return null;
}
return registerRequestMessageLdap.getRegisterRequestMessage();
}
public RegisterRequestMessageLdap getRegisterRequestMessageByRequestId(String requestId) {
String baseDn = getDnForRegisterRequestMessage(null);
Filter requestIdFilter = Filter.createEqualityFilter("oxRequestId", requestId);
List<RegisterRequestMessageLdap> registerRequestMessagesLdap = ldapEntryManager.findEntries(baseDn, RegisterRequestMessageLdap.class,
requestIdFilter);
if ((registerRequestMessagesLdap == null) || registerRequestMessagesLdap.isEmpty()) {
return null;
}
return registerRequestMessagesLdap.get(0);
}
public void removeRegisterRequestMessage(RequestMessageLdap registerRequestMessageLdap) {
removeRequestMessage(registerRequestMessageLdap);
}
/**
* Build DN string for U2F register request
*/
public String getDnForRegisterRequestMessage(String oxId) {
final String u2fBaseDn = staticConfiguration.getBaseDn().getU2fBase(); // ou=registration_requests,ou=u2f,o=@!1111,o=gluu
if (StringHelper.isEmpty(oxId)) {
return String.format("ou=registration_requests,%s", u2fBaseDn);
}
return String.format("oxid=%s,ou=registration_requests,%s", oxId, u2fBaseDn);
}
}