package org.apereo.cas.adaptors.yubikey; import com.yubico.client.v2.ResponseStatus; import com.yubico.client.v2.VerificationResponse; import com.yubico.client.v2.YubicoClient; import com.yubico.client.v2.exceptions.YubicoValidationFailure; import com.yubico.client.v2.exceptions.YubicoVerificationException; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.authentication.Credential; import org.apereo.cas.authentication.HandlerResult; import org.apereo.cas.authentication.PreventedException; import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.web.support.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.FailedLoginException; import java.security.GeneralSecurityException; /** * An authentication handler that uses the Yubico cloud validation * platform to authenticate one-time password tokens that are * issued by a YubiKey device. To use YubiCloud you need a * client id and an API key which must be obtained from Yubico. * <p>For more info, please visit * <a href="http://yubico.github.io/yubico-java-client/">this link</a></p> * * @author Misagh Moayyed * @since 4.1 */ public class YubiKeyAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { private static final Logger LOGGER = LoggerFactory.getLogger(YubiKeyAuthenticationHandler.class); private final YubiKeyAccountRegistry registry; private final YubicoClient client; /** * Prepares the Yubico client with the received clientId and secretKey. If you wish to * limit the usage of this handler only to a particular set of yubikey accounts for a special * group of users, you may verify an compliant implementation of {@link YubiKeyAccountRegistry}. * By default, all accounts are allowed. * * @param name the name * @param servicesManager the services manager * @param principalFactory the principal factory * @param clientId the client id * @param secretKey the secret key * @param registry the account registry which holds registrations. */ public YubiKeyAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory, final Integer clientId, final String secretKey, final YubiKeyAccountRegistry registry) { super(name, servicesManager, principalFactory, null); this.registry = registry; this.client = YubicoClient.getClient(clientId, secretKey); if (this.registry == null) { LOGGER.warn("No YubiKey account registry is defined. All credentials are considered " + "eligible for YubiKey authentication. Consider providing an account registry implementation via [{}]", YubiKeyAccountRegistry.class.getName()); } } public YubiKeyAuthenticationHandler(final Integer clientId, final String secretKey) { this(StringUtils.EMPTY, null, null, clientId, secretKey, null); } @Override protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException { final YubiKeyCredential yubiKeyCredential = (YubiKeyCredential) credential; final String otp = yubiKeyCredential.getToken(); if (!YubicoClient.isValidOTPFormat(otp)) { LOGGER.debug("Invalid OTP format [{}]", otp); throw new AccountNotFoundException("OTP format is invalid"); } final RequestContext context = RequestContextHolder.getRequestContext(); final String uid = WebUtils.getAuthentication(context).getPrincipal().getId(); final String publicId = YubicoClient.getPublicId(otp); if (this.registry != null && !this.registry.isYubiKeyRegisteredFor(uid, publicId)) { LOGGER.debug("YubiKey public id [{}] is not registered for user [{}]", publicId, uid); throw new AccountNotFoundException("YubiKey id is not recognized in registry"); } try { final VerificationResponse response = this.client.verify(otp); final ResponseStatus status = response.getStatus(); if (status.compareTo(ResponseStatus.OK) == 0) { LOGGER.debug("YubiKey response status [{}] at [{}]", status, response.getTimestamp()); return createHandlerResult(yubiKeyCredential, this.principalFactory.createPrincipal(uid), null); } throw new FailedLoginException("Authentication failed with status: " + status); } catch (final YubicoVerificationException | YubicoValidationFailure e) { LOGGER.error(e.getMessage(), e); throw new FailedLoginException("YubiKey validation failed: " + e.getMessage()); } } public YubiKeyAccountRegistry getRegistry() { return this.registry; } public YubicoClient getClient() { return this.client; } @Override public boolean supports(final Credential credential) { return YubiKeyCredential.class.isAssignableFrom(credential.getClass()); } }