Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
src/main/resources/generated/
target/
*~

View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-totp</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-totp</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.6.0</version>
<relativePath>../</relativePath>
</parent>
<build>
<plugins>
<!-- Pre-cache Angular templates with maven-angular-plugin -->
<plugin>
<groupId>com.keithbranton.mojo</groupId>
<artifactId>angular-maven-plugin</artifactId>
<version>0.3.4</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>html2js</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDir>${basedir}/src/main/resources</sourceDir>
<include>**/*.html</include>
<target>${basedir}/src/main/resources/generated/templates-main/templates.js</target>
<prefix>app/ext/totp</prefix>
</configuration>
</plugin>
<!-- JS/CSS Minification Plugin -->
<plugin>
<groupId>com.github.buckelieg</groupId>
<artifactId>minify-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<charset>UTF-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<cssSourceDir>/</cssSourceDir>
<cssTargetDir>/</cssTargetDir>
<cssFinalFile>totp.css</cssFinalFile>
<cssSourceFiles>
<cssSourceFile>license.txt</cssSourceFile>
</cssSourceFiles>
<cssSourceIncludes>
<cssSourceInclude>**/*.css</cssSourceInclude>
</cssSourceIncludes>
<jsSourceDir>/</jsSourceDir>
<jsTargetDir>/</jsTargetDir>
<jsFinalFile>totp.js</jsFinalFile>
<jsSourceFiles>
<jsSourceFile>license.txt</jsSourceFile>
</jsSourceFiles>
<jsSourceIncludes>
<jsSourceInclude>**/*.js</jsSourceInclude>
</jsSourceIncludes>
<!-- Do not minify and include tests -->
<jsSourceExcludes>
<jsSourceExclude>**/*.test.js</jsSourceExclude>
</jsSourceExcludes>
<jsEngine>CLOSURE</jsEngine>
<!-- Disable warnings for JSDoc annotations -->
<closureWarningLevels>
<misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
<nonStandardJsDocs>OFF</nonStandardJsDocs>
</closureWarningLevels>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<!-- Guava - Utility Library -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- ZXing - Barcode library -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.3</version>
</dependency>
<!-- Guacamole depends on an implementation of JAX-WS -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
<scope>provided</scope>
</dependency>
<!-- Library for unified IPv4/6 parsing and validation -->
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,4 @@
Apache Guacamole includes a number of subcomponents with separate copyright
notices and license terms. Your use of these subcomponents is subject to the
terms and conditions of their respective licenses, included within this
directory for reference.

View File

@@ -0,0 +1,8 @@
TOTP Reference Implementation (https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl)
-------------------------------------------------------------------------------
Verson: 07
From: 'IETF Trust' (http://trustee.ietf.org/license-info)
License(s):
BSD 3-clause (bundled/totp-reference-impl-07/license.txt)

View File

@@ -0,0 +1,28 @@
Copyright (c) 2011 IETF Trust and the persons identified as authors
of the code. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the names
of specific contributors, may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<!-- Output tar.gz -->
<formats>
<format>tar.gz</format>
</formats>
<!-- Include licenses and extension .jar -->
<fileSets>
<!-- Include licenses -->
<fileSet>
<outputDirectory></outputDirectory>
<directory>target/licenses</directory>
</fileSet>
<!-- Include extension .jar -->
<fileSet>
<directory>target</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp;
import org.apache.guacamole.auth.totp.user.UserVerificationService;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService;
import org.apache.guacamole.auth.totp.user.TOTPUserContext;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
/**
* AuthenticationProvider implementation which uses TOTP as an additional
* authentication factor for users which have already been authenticated by
* some other AuthenticationProvider.
*/
public class TOTPAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* Injector which will manage the object graph of this authentication
* provider.
*/
private final Injector injector;
/**
* Creates a new TOTPAuthenticationProvider that verifies users using TOTP.
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public TOTPAuthenticationProvider() throws GuacamoleException {
// Set up Guice injector.
injector = Guice.createInjector(
new TOTPAuthenticationProviderModule(this)
);
}
@Override
public String getIdentifier() {
return "totp";
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
UserVerificationService verificationService =
injector.getInstance(UserVerificationService.class);
// Verify identity of user
verificationService.verifyIdentity(context, authenticatedUser);
// User has been verified, and authentication should be allowed to
// continue
return new TOTPUserContext(context);
}
@Override
public UserContext redecorate(UserContext decorated, UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return new TOTPUserContext(context);
}
@Override
public void shutdown() {
injector.getInstance(CodeUsageTrackingService.class).shutdown();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp;
import org.apache.guacamole.auth.totp.user.UserVerificationService;
import com.google.inject.AbstractModule;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.AuthenticationProvider;
/**
* Guice module which configures TOTP-specific injections.
*/
public class TOTPAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* A reference to the TOTPAuthenticationProvider on behalf of which this
* module has configured injection.
*/
private final AuthenticationProvider authProvider;
/**
* Creates a new TOTP authentication provider module which configures
* injection for the TOTPAuthenticationProvider.
*
* @param authProvider
* The AuthenticationProvider for which injection is being configured.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public TOTPAuthenticationProviderModule(AuthenticationProvider authProvider)
throws GuacamoleException {
// Get local environment
this.environment = LocalEnvironment.getInstance();
// Store associated auth provider
this.authProvider = authProvider;
}
@Override
protected void configure() {
// Bind core implementations of guacamole-ext classes
bind(AuthenticationProvider.class).toInstance(authProvider);
bind(Environment.class).toInstance(environment);
// Bind TOTP-specific services
bind(CodeUsageTrackingService.class);
bind(ConfigurationService.class);
bind(UserVerificationService.class);
}
}

View File

@@ -0,0 +1,230 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.conf;
import com.google.inject.Inject;
import inet.ipaddr.IPAddress;
import java.util.Collections;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.EnumGuacamoleProperty;
import org.apache.guacamole.properties.IPAddressListProperty;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.totp.TOTPGenerator;
/**
* Service for retrieving configuration information regarding the TOTP
* authentication extension.
*/
public class ConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* The human-readable name of the entity issuing user accounts. By default,
* this will be "Apache Guacamole".
*/
private static final StringGuacamoleProperty TOTP_ISSUER =
new StringGuacamoleProperty() {
@Override
public String getName() { return "totp-issuer"; }
};
/**
* The number of digits which should be included in each generated TOTP
* code. By default, this will be 6.
*/
private static final IntegerGuacamoleProperty TOTP_DIGITS=
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "totp-digits"; }
};
/**
* The duration that each generated code should remain valid, in seconds.
* By default, this will be 30.
*/
private static final IntegerGuacamoleProperty TOTP_PERIOD =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "totp-period"; }
};
/**
* The hash algorithm that should be used to generate TOTP codes. By
* default, this will be "sha1". Legal values are "sha1", "sha256", and
* "sha512".
*/
private static final EnumGuacamoleProperty<TOTPGenerator.Mode> TOTP_MODE =
new EnumGuacamoleProperty<TOTPGenerator.Mode>(TOTPGenerator.Mode.class) {
@Override
public String getName() { return "totp-mode"; }
};
/**
* A property that contains a list of IP addresses and/or subnets for which
* MFA via the TOTP module should be bypassed. Users logging in from addresses
* contained in this list will not be prompted for a second authentication
* factor. If this property is empty or not defined, and the TOTP module
* is installed, all users will be prompted for MFA.
*/
private static final IPAddressListProperty TOTP_BYPASS_HOSTS =
new IPAddressListProperty() {
@Override
public String getName() { return "totp-bypass-hosts"; }
};
/**
* A property that contains a list of IP addresses and/or subnets for which
* MFA via the TOTP module should explicitly be enabled. If this property is defined,
* and the TOTP module is installed, users logging in from hosts contained
* in this list will be prompted for MFA, and users logging in from all
* other hosts will not be prompted for MFA.
*/
private static final IPAddressListProperty TOTP_ENFORCE_HOSTS =
new IPAddressListProperty() {
@Override
public String getName() { return "totp-enforce-hosts"; }
};
/**
* Returns the human-readable name of the entity issuing user accounts. If
* not specified, "Apache Guacamole" will be used by default.
*
* @return
* The human-readable name of the entity issuing user accounts.
*
* @throws GuacamoleException
* If the "totp-issuer" property cannot be read from
* guacamole.properties.
*/
public String getIssuer() throws GuacamoleException {
return environment.getProperty(TOTP_ISSUER, "Apache Guacamole");
}
/**
* Returns the number of digits which should be included in each generated
* TOTP code. If not specified, 6 will be used by default.
*
* @return
* The number of digits which should be included in each generated
* TOTP code.
*
* @throws GuacamoleException
* If the "totp-digits" property cannot be read from
* guacamole.properties.
*/
public int getDigits() throws GuacamoleException {
// Validate legal number of digits
int digits = environment.getProperty(TOTP_DIGITS, 6);
if (digits < 6 || digits > 8)
throw new GuacamoleServerException("TOTP codes may have no fewer "
+ "than 6 digits and no more than 8 digits.");
return digits;
}
/**
* Returns the duration that each generated code should remain valid, in
* seconds. If not specified, 30 will be used by default.
*
* @return
* The duration that each generated code should remain valid, in
* seconds.
*
* @throws GuacamoleException
* If the "totp-period" property cannot be read from
* guacamole.properties.
*/
public int getPeriod() throws GuacamoleException {
return environment.getProperty(TOTP_PERIOD, 30);
}
/**
* Returns the hash algorithm that should be used to generate TOTP codes. If
* not specified, SHA1 will be used by default.
*
* @return
* The hash algorithm that should be used to generate TOTP codes.
*
* @throws GuacamoleException
* If the "totp-mode" property cannot be read from
* guacamole.properties.
*/
public TOTPGenerator.Mode getMode() throws GuacamoleException {
return environment.getProperty(TOTP_MODE, TOTPGenerator.Mode.SHA1);
}
/**
* Return the list of IP addresses and/or subnets for which MFA authentication via the
* TOTP module should be bypassed, allowing users from those addresses to log in
* without the MFA requirement.
*
* @return
* A list of IP addresses and/or subnets for which MFA authentication
* should be bypassed.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getBypassHosts() throws GuacamoleException {
return environment.getProperty(TOTP_BYPASS_HOSTS, Collections.emptyList());
}
/**
* Return the list of IP addresses and/or subnets for which MFA authentication via the TOTP
* module should be explicitly enabled, requiring users logging in from hosts specified in
* the list to complete MFA.
*
* @return
* A list of IP addresses and/or subnets for which MFA authentication
* should be explicitly enabled.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getEnforceHosts() throws GuacamoleException {
return environment.getProperty(TOTP_ENFORCE_HOSTS, Collections.emptyList());
}
}

View File

@@ -0,0 +1,312 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.form;
import com.google.common.io.BaseEncoding;
import com.google.inject.Inject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.user.UserTOTPKey;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.totp.TOTPGenerator;
/**
* Field which prompts the user for an authentication code generated via TOTP.
*/
public class AuthenticationCodeField extends Field {
/**
* The name of the HTTP parameter which will contain the TOTP code provided
* by the user to verify their identity.
*/
public static final String PARAMETER_NAME = "guac-totp";
/**
* The unique name associated with this field type.
*/
private static final String FIELD_TYPE_NAME = "GUAC_TOTP_CODE";
/**
* The width of QR codes to generate, in pixels.
*/
private static final int QR_CODE_WIDTH = 256;
/**
* The height of QR codes to generate, in pixels.
*/
private static final int QR_CODE_HEIGHT = 256;
/**
* BaseEncoding which encodes/decodes base32.
*/
private static final BaseEncoding BASE32 = BaseEncoding.base32();
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* The TOTP key to expose to the user for the sake of enrollment, if any.
* If no such key should be exposed to the user, this will be null.
*/
private UserTOTPKey key;
/**
* Creates a new field which prompts the user for an authentication code
* generated via TOTP. The user's TOTP key is not exposed for enrollment.
*/
public AuthenticationCodeField() {
super(PARAMETER_NAME, FIELD_TYPE_NAME);
}
/**
* Exposes the given key to facilitate enrollment.
*
* @param key
* The TOTP key to expose to the user for the sake of enrollment.
*/
public void exposeKey(UserTOTPKey key) {
this.key = key;
}
/**
* Returns the username of the user associated with the key being used to
* generate TOTP codes. If the user's key is not being exposed to facilitate
* enrollment, this value will not be exposed either.
*
* @return
* The username of the user associated with the key being used to
* generate TOTP codes, or null if the user's key is not being exposed
* to facilitate enrollment.
*/
public String getUsername() {
// Do not reveal TOTP mode unless enrollment is in progress
if (key == null)
return null;
return key.getUsername();
}
/**
* Returns the base32-encoded secret key that is being used to generate TOTP
* codes for the authenticating user. If the user's key is not being exposed
* to facilitate enrollment, this value will not be exposed either.
*
* @return
* The base32-encoded secret key that is being used to generate TOTP
* codes for the authenticating user, or null if the user's key is not
* being exposed to facilitate enrollment.
*/
public String getSecret() {
// Do not reveal TOTP mode unless enrollment is in progress
if (key == null)
return null;
return BASE32.encode(key.getSecret());
}
/**
* Returns the number of digits used for each TOTP code. If the user's key
* is not being exposed to facilitate enrollment, this value will not be
* exposed either.
*
* @return
* The number of digits used for each TOTP code, or null if the user's
* key is not being exposed to facilitate enrollment.
*
* @throws GuacamoleException
* If the number of digits cannot be read from guacamole.properties.
*/
public Integer getDigits() throws GuacamoleException {
// Do not reveal code size unless enrollment is in progress
if (key == null)
return null;
return confService.getDigits();
}
/**
* Returns the human-readable name of the entity issuing user accounts. If
* the user's key is not being exposed to facilitate enrollment, this value
* will not be exposed either.
*
* @return
* The human-readable name of the entity issuing user accounts, or null
* if the user's key is not being exposed to facilitate enrollment.
*
* @throws GuacamoleException
* If the issuer cannot be read from guacamole.properties.
*/
public String getIssuer() throws GuacamoleException {
// Do not reveal code issuer unless enrollment is in progress
if (key == null)
return null;
return confService.getIssuer();
}
/**
* Returns the mode that TOTP code generation is operating in. This value
* will be one of "SHA1", "SHA256", or "SHA512". If the user's key is not
* being exposed to facilitate enrollment, this value will not be exposed
* either.
*
* @return
* The mode that TOTP code generation is operating in, such as "SHA1",
* "SHA256", or "SHA512", or null if the user's key is not being
* exposed to facilitate enrollment.
*
* @throws GuacamoleException
* If the TOTP mode cannot be read from guacamole.properties.
*/
public TOTPGenerator.Mode getMode() throws GuacamoleException {
// Do not reveal TOTP mode unless enrollment is in progress
if (key == null)
return null;
return confService.getMode();
}
/**
* Returns the number of seconds that each TOTP code remains valid. If the
* user's key is not being exposed to facilitate enrollment, this value will
* not be exposed either.
*
* @return
* The number of seconds that each TOTP code remains valid, or null if
* the user's key is not being exposed to facilitate enrollment.
*
* @throws GuacamoleException
* If the period cannot be read from guacamole.properties.
*/
public Integer getPeriod() throws GuacamoleException {
// Do not reveal code period unless enrollment is in progress
if (key == null)
return null;
return confService.getPeriod();
}
/**
* Returns the "otpauth" URI for the secret key used to generate TOTP codes
* for the current user. If the secret key is not being exposed to
* facilitate enrollment, null is returned.
*
* @return
* The "otpauth" URI for the secret key used to generate TOTP codes
* for the current user, or null is the secret ket is not being exposed
* to facilitate enrollment.
*
* @throws GuacamoleException
* If the configuration information required for generating the key URI
* cannot be read from guacamole.properties.
*/
public URI getKeyUri() throws GuacamoleException {
// Do not generate a key URI if no key is being exposed
if (key == null)
return null;
// Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
String issuer = confService.getIssuer();
return UriBuilder.fromUri("otpauth://totp/")
.path(issuer + ":" + key.getUsername())
.queryParam("secret", BASE32.encode(key.getSecret()))
.queryParam("issuer", issuer)
.queryParam("algorithm", confService.getMode())
.queryParam("digits", confService.getDigits())
.queryParam("period", confService.getPeriod())
.build();
}
/**
* Returns the URL of a QR code describing the user's TOTP key and
* configuration. If the key is not being exposed for enrollment, null is
* returned.
*
* @return
* The URL of a QR code describing the user's TOTP key and
* configuration, or null if the key is not being exposed for
* enrollment.
*
* @throws GuacamoleException
* If the configuration information required for generating the QR code
* cannot be read from guacamole.properties.
*/
public String getQrCode() throws GuacamoleException {
// Do not generate a QR code if no key is being exposed
URI keyURI = getKeyUri();
if (keyURI == null)
return null;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
// Create QR code writer
QRCodeWriter writer = new QRCodeWriter();
BitMatrix matrix = writer.encode(keyURI.toString(),
BarcodeFormat.QR_CODE, QR_CODE_WIDTH, QR_CODE_HEIGHT);
// Produce PNG image of TOTP key text
MatrixToImageWriter.writeToStream(matrix, "PNG", stream);
}
catch (WriterException e) {
throw new IllegalArgumentException("QR code could not be "
+ "generated for TOTP key.", e);
}
catch (IOException e) {
throw new IllegalStateException("Image stream of QR code could "
+ "not be written.", e);
}
// Return data URI for generated image
return "data:image/png;base64,"
+ BaseEncoding.base64().encode(stream.toByteArray());
}
}

View File

@@ -0,0 +1,264 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.user;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for tracking past valid uses of TOTP codes. An internal thread
* periodically walks through records of past codes, removing records which
* should be invalid by their own nature (no longer matching codes generated by
* the secret key).
*/
@Singleton
public class CodeUsageTrackingService {
/**
* The number of periods during which a previously-used code should remain
* unusable. Once this period has elapsed, the code can be reused again if
* it is otherwise valid.
*/
private static final int INVALID_INTERVAL = 2;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(CodeUsageTrackingService.class);
/**
* Executor service which runs the cleanup task.
*/
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Map of previously-used codes to the timestamp after which the code can
* be used again, providing the TOTP key legitimately generates that code.
*/
private final ConcurrentMap<UsedCode, Long> invalidCodes =
new ConcurrentHashMap<UsedCode, Long>();
/**
* Creates a new CodeUsageTrackingService which tracks past valid uses of
* TOTP codes on a per-user basis.
*/
public CodeUsageTrackingService() {
executor.scheduleAtFixedRate(new CodeEvictionTask(), 1, 1, TimeUnit.MINUTES);
}
/**
* Task which iterates through all explicitly-invalidated codes, evicting
* those codes which are old enough that they would fail validation against
* the secret key anyway.
*/
private class CodeEvictionTask implements Runnable {
@Override
public void run() {
// Get start time of cleanup check
long checkStart = System.currentTimeMillis();
// For each code still being tracked, remove those which are old
// enough that they would fail validation against the secret key
Iterator<Map.Entry<UsedCode, Long>> entries = invalidCodes.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<UsedCode, Long> entry = entries.next();
long invalidUntil = entry.getValue();
// If code is sufficiently old, evict it and check the next one
if (checkStart >= invalidUntil)
entries.remove();
}
// Log completion and duration
logger.debug("TOTP tracking cleanup check completed in {} ms.",
System.currentTimeMillis() - checkStart);
}
}
/**
* A valid TOTP code which was previously used by a particular user.
*/
private class UsedCode {
/**
* The username of the user which previously used this code.
*/
private final String username;
/**
* The valid code given by the user.
*/
private final String code;
/**
* Creates a new UsedCode which records the given code as having been
* used by the given user.
*
* @param username
* The username of the user which previously used the given code.
*
* @param code
* The valid code given by the user.
*/
public UsedCode(String username, String code) {
this.username = username;
this.code = code;
}
/**
* Returns the username of the user which previously used the code
* associated with this UsedCode.
*
* @return
* The username of the user which previously used this code.
*/
public String getUsername() {
return username;
}
/**
* Returns the valid code given by the user when this UsedCode was
* created.
*
* @return
* The valid code given by the user.
*/
public String getCode() {
return code;
}
@Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + this.username.hashCode();
hash = 79 * hash + this.code.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final UsedCode other = (UsedCode) obj;
return username.equals(other.username) && code.equals(other.code);
}
}
/**
* Attempts to mark the given code as used. The code MUST have already been
* validated against the user's secret key, as this function only verifies
* whether the code has been previously used, not whether it is actually
* valid. If the code has not previously been used, the code is stored as
* having been used by the given user at the current time.
*
* @param username
* The username of the user who has attempted to use the given valid
* code.
*
* @param code
* The otherwise-valid code given by the user.
*
* @return
* true if the code has not previously been used by the given user and
* has now been marked as previously used, false otherwise.
*
* @throws GuacamoleException
* If configuration information necessary to determine the length of
* time a code should be marked as invalid cannot be read from
* guacamole.properties.
*/
public boolean useCode(String username, String code)
throws GuacamoleException {
// Repeatedly attempt to use the given code until an explicit success
// or failure has occurred
UsedCode usedCode = new UsedCode(username, code);
for (;;) {
// Explicitly invalidate each used code for two periods after its
// first successful use
long current = System.currentTimeMillis();
long invalidUntil = current + confService.getPeriod() * 1000 * INVALID_INTERVAL;
// Try to use the given code, marking it as used within the map of
// now-invalidated codes
Long expires = invalidCodes.putIfAbsent(usedCode, invalidUntil);
if (expires == null)
return true;
// If the code was already used, fail to use the code if
// insufficient time has elapsed since it was last used
// successfully
if (expires > current)
return false;
// Otherwise, the code is actually valid - remove the invalidated
// code only if it still has the expected expiration time, and
// retry using the code
invalidCodes.remove(usedCode, expires);
}
}
/**
* Cleans up resources which may be in use by this service in the
* background, such as other threads. This function MUST be invoked during
* webapp shutdown to avoid leaking these resources.
*/
public void shutdown() {
executor.shutdownNow();
}
}

View File

@@ -0,0 +1,139 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.user;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.guacamole.form.BooleanField;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.net.auth.DelegatingUser;
import org.apache.guacamole.net.auth.User;
/**
* TOTP-specific User implementation which wraps a User from another extension,
* hiding and blocking access to the core attributes used by TOTP.
*/
public class TOTPUser extends DelegatingUser {
/**
* The name of the user attribute which disables the TOTP requirement
* for that specific user.
*/
public static final String TOTP_KEY_DISABLED_ATTRIBUTE_NAME = "guac-totp-disabled";
/**
* The name of the user attribute which stores the TOTP key.
*/
public static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
/**
* The name of the user attribute defines whether the TOTP key has been
* confirmed by the user, and the user is thus fully enrolled.
*/
public static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
/**
* The name of the user attribute defines whether the TOTP key has been
* generated for the user, regardless of whether that key has been
* confirmed. This attribute is not stored, but is instead exposed
* dynamically in lieu of exposing the actual TOTP key.
*/
public static final String TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME = "guac-totp-key-generated";
/**
* The string value used by TOTP user attributes to represent the boolean
* value "true".
*/
public static final String TRUTH_VALUE = "true";
/**
* The form which contains all configurable properties for this user.
*/
public static final Form TOTP_ENROLLMENT_STATUS = new Form("totp-enrollment-status",
Arrays.asList(
new BooleanField(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, TRUTH_VALUE),
new BooleanField(TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME, TRUTH_VALUE),
new BooleanField(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, TRUTH_VALUE)
)
);
/**
* Wraps the given User object, hiding and blocking access to the core
* attributes used by TOTP.
*
* @param user
* The User object to wrap.
*/
public TOTPUser(User user) {
super(user);
}
/**
* Returns the User object wrapped by this TOTPUser.
*
* @return
* The wrapped User object.
*/
public User getUndecorated() {
return getDelegateUser();
}
@Override
public Map<String, String> getAttributes() {
// Create independent, mutable copy of attributes
Map<String, String> attributes = new HashMap<>(super.getAttributes());
if (!attributes.containsKey(TOTP_KEY_DISABLED_ATTRIBUTE_NAME))
attributes.put(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, null);
// Replace secret key with simple boolean attribute representing
// whether a key has been generated at all
String secret = attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
if (secret != null && !secret.isEmpty())
attributes.put(TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME, TRUTH_VALUE);
return attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
// Create independent, mutable copy of attributes
attributes = new HashMap<>(attributes);
// Do not allow TOTP secret to be directly manipulated
attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
// Reset TOTP status entirely if requested
String generated = attributes.remove(TOTP_KEY_SECRET_GENERATED_ATTRIBUTE_NAME);
if (generated != null && !generated.equals(TRUTH_VALUE)) {
attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, null);
attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, null);
}
super.setAttributes(attributes);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.user;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.usergroup.TOTPUserGroup;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.net.auth.DecoratingDirectory;
import org.apache.guacamole.net.auth.DelegatingUserContext;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.UserGroup;
/**
* TOTP-specific UserContext implementation which wraps the UserContext of
* some other extension, providing (or hiding) additional data.
*/
public class TOTPUserContext extends DelegatingUserContext {
/**
* Creates a new TOTPUserContext which wraps the given UserContext,
* providing (or hiding) additional TOTP-specific data.
*
* @param userContext
* The UserContext to wrap.
*/
public TOTPUserContext(UserContext userContext) {
super(userContext);
}
@Override
public Directory<User> getUserDirectory() throws GuacamoleException {
return new DecoratingDirectory<User>(super.getUserDirectory()) {
@Override
protected User decorate(User object) {
return new TOTPUser(object);
}
@Override
protected User undecorate(User object) {
assert(object instanceof TOTPUser);
return ((TOTPUser) object).getUndecorated();
}
};
}
@Override
public Directory<UserGroup> getUserGroupDirectory() throws GuacamoleException {
return new DecoratingDirectory<UserGroup>(super.getUserGroupDirectory()) {
@Override
protected UserGroup decorate(UserGroup object) {
return new TOTPUserGroup(object);
}
@Override
protected UserGroup undecorate(UserGroup object) {
assert(object instanceof TOTPUserGroup);
return ((TOTPUserGroup) object).getUndecorated();
}
};
}
@Override
public Collection<Form> getUserAttributes() {
Collection<Form> userAttrs = new HashSet<>(super.getUserAttributes());
userAttrs.add(TOTPUser.TOTP_ENROLLMENT_STATUS);
return Collections.unmodifiableCollection(userAttrs);
}
@Override
public Collection<Form> getUserGroupAttributes() {
Collection<Form> userGroupAttrs = new HashSet<>(super.getUserGroupAttributes());
userGroupAttrs.add(TOTPUserGroup.TOTP_USER_GROUP_CONFIG);
return Collections.unmodifiableCollection(userGroupAttrs);
}
}

View File

@@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.user;
import java.security.SecureRandom;
import java.util.Random;
/**
* The key used to generate TOTP codes for a particular user.
*/
public class UserTOTPKey {
/**
* Secure source of random bytes.
*/
private static final Random RANDOM = new SecureRandom();
/**
* The username of the user associated with this key.
*/
private final String username;
/**
* Whether the associated secret key has been confirmed by the user. A key
* is confirmed once the user has successfully entered a valid TOTP
* derived from that key.
*/
private boolean confirmed;
/**
* The base32-encoded TOTP key associated with the user.
*/
private byte[] secret;
/**
* Generates the given number of random bytes.
*
* @param length
* The number of random bytes to generate.
*
* @return
* A new array of exactly the given number of random bytes.
*/
private static byte[] generateBytes(int length) {
byte[] bytes = new byte[length];
RANDOM.nextBytes(bytes);
return bytes;
}
/**
* Creates a new, unconfirmed, randomly-generated TOTP key having the given
* length.
*
* @param username
* The username of the user associated with this key.
*
* @param length
* The length of the key to generate, in bytes.
*/
public UserTOTPKey(String username, int length) {
this(username, generateBytes(length), false);
}
/**
* Creates a new UserTOTPKey containing the given key and having the given
* confirmed state.
*
* @param username
* The username of the user associated with this key.
*
* @param secret
* The raw binary secret key to be used to generate TOTP codes.
*
* @param confirmed
* true if the user associated with the key has confirmed that they can
* successfully generate the corresponding TOTP codes (the user has
* been "enrolled"), false otherwise.
*/
public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
this.username = username;
this.confirmed = confirmed;
this.secret = secret;
}
/**
* Returns the username of the user associated with this key.
*
* @return
* The username of the user associated with this key.
*/
public String getUsername() {
return username;
}
/**
* Returns the raw binary secret key to be used to generate TOTP codes.
*
* @return
* The raw binary secret key to be used to generate TOTP codes.
*/
public byte[] getSecret() {
return secret;
}
/**
* Returns whether the user associated with the key has confirmed that they
* can successfully generate the corresponding TOTP codes (the user has
* been "enrolled").
*
* @return
* true if the user has confirmed that they can successfully generate
* the TOTP codes generated by this key, false otherwise.
*/
public boolean isConfirmed() {
return confirmed;
}
/**
* Sets whether the user associated with the key has confirmed that they
* can successfully generate the corresponding TOTP codes (the user has
* been "enrolled").
*
* @param confirmed
* true if the user has confirmed that they can successfully generate
* the TOTP codes generated by this key, false otherwise.
*/
public void setConfirmed(boolean confirmed) {
this.confirmed = confirmed;
}
}

View File

@@ -0,0 +1,435 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.user;
import com.google.common.io.BaseEncoding;
import com.google.inject.Inject;
import com.google.inject.Provider;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import java.security.InvalidKeyException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
import org.apache.guacamole.auth.totp.usergroup.TOTPUserGroup;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.language.TranslatableGuacamoleClientException;
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.UserGroup;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.properties.IPAddressListProperty;
import org.apache.guacamole.totp.TOTPGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for verifying the identity of a user using TOTP.
*/
public class UserVerificationService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
/**
* BaseEncoding instance which decoded/encodes base32.
*/
private static final BaseEncoding BASE32 = BaseEncoding.base32();
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Service for tracking whether TOTP codes have been used.
*/
@Inject
private CodeUsageTrackingService codeService;
/**
* Provider for AuthenticationCodeField instances.
*/
@Inject
private Provider<AuthenticationCodeField> codeFieldProvider;
/**
* Retrieves and decodes the base32-encoded TOTP key associated with user
* having the given UserContext. If no TOTP key is associated with the user,
* a random key is generated and associated with the user. If the extension
* storing the user does not support storage of the TOTP key, null is
* returned.
*
* @param context
* The UserContext of the user whose TOTP key should be retrieved.
*
* @param username
* The username of the user associated with the given UserContext.
*
* @return
* The TOTP key associated with the user having the given UserContext,
* or null if the extension storing the user does not support storage
* of the TOTP key.
*
* @throws GuacamoleException
* If a new key is generated, but the extension storing the associated
* user fails while updating the user account.
*/
private UserTOTPKey getKey(UserContext context,
String username) throws GuacamoleException {
// Retrieve attributes from current user
User self = context.self();
Map<String, String> attributes = context.self().getAttributes();
// If no key is defined, attempt to generate a new key
String secret = attributes.get(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME);
if (secret == null || secret.isEmpty())
return generateKey(context, username);
// Parse retrieved base32 key value
byte[] key;
try {
key = BASE32.decode(secret);
}
// If key is not valid base32, warn but otherwise pretend the key does
// not exist
catch (IllegalArgumentException e) {
logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
logger.debug("TOTP key is not valid base32.", e);
return null;
}
// Otherwise, parse value from attributes
boolean confirmed = "true".equals(attributes.get(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
return new UserTOTPKey(username, key, confirmed);
}
/**
* Generate and set a new key for the specified user and context, returning
* the key if the set successfully or null if it fails.
*
* @param context
* The UserContext of the user whose TOTP key should be generated and set.
*
* @param username
* The username of the user associated with the given UserContext.
*
* @return
* The generated and set key, or null if the operation failed.
*
* @throws GuacamoleException
* If a new key is generated, but the extension storing the associated
* user fails while updating the user account, or if the configuration
* cannot be retrieved.
*/
private UserTOTPKey generateKey(UserContext context, String username)
throws GuacamoleException {
// Generate random key for user
TOTPGenerator.Mode mode = confService.getMode();
UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
if (setKey(context, generated))
return generated;
// Fail if key cannot be set
return null;
}
/**
* Attempts to store the given TOTP key within the user account of the user
* having the given UserContext. As not all extensions will support storage
* of arbitrary attributes, this operation may fail.
*
* @param context
* The UserContext associated with the user whose TOTP key is to be
* stored.
*
* @param key
* The TOTP key to store.
*
* @return
* true if the TOTP key was successfully stored, false if the extension
* handling storage does not support storage of the key.
*
* @throws GuacamoleException
* If the extension handling storage fails internally while attempting
* to update the user.
*/
private boolean setKey(UserContext context, UserTOTPKey key)
throws GuacamoleException {
// Get mutable set of attributes
User self = context.self();
Map<String, String> attributes = new HashMap<>();
// Set/overwrite current TOTP key state
attributes.put(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
attributes.put(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
self.setAttributes(attributes);
// Confirm that attributes have actually been set
Map<String, String> setAttributes = self.getAttributes();
if (!setAttributes.containsKey(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME)
|| !setAttributes.containsKey(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
return false;
// Update user object
try {
context.getPrivileged().getUserDirectory().update(self);
}
catch (GuacamoleSecurityException e) {
logger.info("User \"{}\" cannot store their TOTP key as they "
+ "lack permission to update their own account and the "
+ "TOTP extension was unable to obtain privileged access. "
+ "TOTP will be disabled for this user.",
self.getIdentifier());
logger.debug("Permission denied to set TOTP key of user "
+ "account.", e);
return false;
}
catch (GuacamoleUnsupportedException e) {
logger.debug("Extension storage for user is explicitly read-only. "
+ "Cannot update attributes to store TOTP key.", e);
return false;
}
// TOTP key successfully stored/updated
return true;
}
/**
* Checks the user in question, via both UserContext and AuthenticatedUser,
* to see if TOTP has been disabled for this user, either directly or via
* membership in a group that has had TOTP marked as disabled.
*
* @param context
* The UserContext for the user being verified.
*
* @param authenticatedUser
* The AuthenticatedUser for the user being verified.
*
* @return
* True if TOTP access has been disabled for the user, otherwise
* false.
*
* @throws GuacamoleException
* If the extension handling storage fails internally while attempting
* to update the user.
*/
private boolean totpDisabled(UserContext context,
AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// If TOTP is disabled for this user, return, allowing login to continue
Map<String, String> myAttributes = context.self().getAttributes();
if (myAttributes != null
&& TOTPUser.TRUTH_VALUE.equals(myAttributes.get(TOTPUser.TOTP_KEY_DISABLED_ATTRIBUTE_NAME))) {
logger.warn("TOTP validation has been disabled for user \"{}\"",
context.self().getIdentifier());
return true;
}
// Check if any effective user groups have TOTP marked as disabled
Set<String> userGroups = authenticatedUser.getEffectiveUserGroups();
Directory<UserGroup> directoryGroups = context.getPrivileged().getUserGroupDirectory();
for (String userGroup : userGroups) {
UserGroup thisGroup = directoryGroups.get(userGroup);
if (thisGroup == null)
continue;
Map<String, String> grpAttributes = thisGroup.getAttributes();
if (grpAttributes != null
&& TOTPUserGroup.TRUTH_VALUE.equals(grpAttributes.get(TOTPUserGroup.TOTP_KEY_DISABLED_ATTRIBUTE_NAME))) {
logger.warn("TOTP validation will be bypassed for user \"{}\""
+ " because it has been disabled for group \"{}\"",
context.self().getIdentifier(), userGroup);
return true;
}
}
// TOTP has not been disabled
return false;
}
/**
* Verifies the identity of the given user using TOTP. If a authentication
* code from the user's TOTP device has not already been provided, a code is
* requested in the form of additional expected credentials. Any provided
* code is cryptographically verified. If no code is present, or the
* received code is invalid, an exception is thrown.
*
* @param context
* The UserContext provided for the user by another authentication
* extension.
*
* @param authenticatedUser
* The user whose identity should be verified using TOTP.
*
* @throws GuacamoleException
* If required TOTP-specific configuration options are missing or
* malformed, or if the user's identity cannot be verified.
*/
public void verifyIdentity(UserContext context,
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials();
// Get the current client address
IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
// Ignore anonymous users
if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
return;
// Pull address lists to check from configuration. Note that the enforce
// list will override the bypass list, which means that, if the client
// address happens to be in both lists, Duo MFA will be enforced.
List<IPAddress> bypassAddresses = confService.getBypassHosts();
List<IPAddress> enforceAddresses = confService.getEnforceHosts();
// Check the bypass list for the client address, and set the enforce
// flag to the opposite.
boolean enforceHost = !(IPAddressListProperty.addressListContains(bypassAddresses, clientAddr));
// Only continue processing if the list is not empty
if (!enforceAddresses.isEmpty()) {
// If client address is not available or invalid, MFA will
// be enforced.
if (clientAddr == null || !clientAddr.isIPAddress())
enforceHost = true;
// Check the enforce list and set the flag if the client address
// is found in the list.
else
enforceHost = IPAddressListProperty.addressListContains(enforceAddresses, clientAddr);
}
// If the enforce flag is not true, bypass TOTP MFA.
if (!enforceHost)
return;
// Ignore anonymous users
String username = authenticatedUser.getIdentifier();
if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
return;
// Check if TOTP has been disabled for this user
if (totpDisabled(context, authenticatedUser))
return;
// Ignore users which do not have an associated key
UserTOTPKey key = getKey(context, username);
if (key == null)
return;
// Retrieve TOTP from request
String code = credentials.getParameter(AuthenticationCodeField.PARAMETER_NAME);
// If no TOTP provided, request one
if (code == null) {
AuthenticationCodeField field = codeFieldProvider.get();
// If the user hasn't completed enrollment, request that they do
if (!key.isConfirmed()) {
// If the key has not yet been confirmed, generate a new one.
key = generateKey(context, username);
field.exposeKey(key);
throw new TranslatableGuacamoleInsufficientCredentialsException(
"TOTP enrollment must be completed before "
+ "authentication can continue",
"TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
Collections.<Field>singletonList(field)
));
}
// Otherwise simply request the user's authentication code
throw new TranslatableGuacamoleInsufficientCredentialsException(
"A TOTP authentication code is required before login can "
+ "continue", "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
Collections.<Field>singletonList(field)
));
}
try {
// Get generator based on user's key and provided configuration
TOTPGenerator totp = new TOTPGenerator(key.getSecret(),
confService.getMode(), confService.getDigits(),
TOTPGenerator.DEFAULT_START_TIME, confService.getPeriod());
// Verify provided TOTP against value produced by generator
if ((code.equals(totp.generate()) || code.equals(totp.previous()))
&& codeService.useCode(username, code)) {
// Record key as confirmed, if it hasn't already been so recorded
if (!key.isConfirmed()) {
key.setConfirmed(true);
setKey(context, key);
}
// User has been verified
return;
}
}
catch (InvalidKeyException e) {
logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
logger.debug("TOTP key is not valid.", e);
}
// Provided code is not valid
throw new TranslatableGuacamoleClientException("Provided TOTP code "
+ "is not valid.", "TOTP.INFO_VERIFICATION_FAILED");
}
}

View File

@@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.totp.usergroup;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.guacamole.form.BooleanField;
import org.apache.guacamole.form.Form;
import org.apache.guacamole.net.auth.DelegatingUserGroup;
import org.apache.guacamole.net.auth.UserGroup;
/**
* A UserGroup that wraps another UserGroup implementation, decorating it with
* attributes that control TOTP configuration for users that are members of that
* group.
*/
public class TOTPUserGroup extends DelegatingUserGroup {
/**
* The attribute associated with a group that disables the TOTP requirement
* for any users that are a member of that group, or are members of any
* groups that are members of this group.
*/
public static final String TOTP_KEY_DISABLED_ATTRIBUTE_NAME = "guac-totp-disabled";
/**
* The string value used by TOTP user attributes to represent the boolean
* value "true".
*/
public static final String TRUTH_VALUE = "true";
/**
* The form that contains fields for configuring TOTP for members of this
* group.
*/
public static final Form TOTP_USER_GROUP_CONFIG = new Form("totp-user-group-config",
Arrays.asList(
new BooleanField(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, TRUTH_VALUE)
)
);
/**
* Create a new instance of this user group implementation, wrapping the
* provided UserGroup.
*
* @param userGroup
* The UserGroup to be wrapped.
*/
public TOTPUserGroup(UserGroup userGroup) {
super(userGroup);
}
/**
* Return the original UserGroup that this implementation is wrapping.
*
* @return
* The original UserGroup that this implementation wraps.
*/
public UserGroup getUndecorated() {
return getDelegateUserGroupGroup();
}
/**
* Returns whether or not TOTP has been disabled for members of this group.
*
* @return
* True if TOTP has been disabled for members of this group, otherwise
* false.
*/
public boolean totpDisabled() {
return (TRUTH_VALUE.equals(getAttributes().get(TOTP_KEY_DISABLED_ATTRIBUTE_NAME)));
}
@Override
public Map<String, String> getAttributes() {
// Create a mutable copy of the attributes
Map<String, String> attributes = new HashMap<>(super.getAttributes());
if (!attributes.containsKey(TOTP_KEY_DISABLED_ATTRIBUTE_NAME))
attributes.put(TOTP_KEY_DISABLED_ATTRIBUTE_NAME, null);
return attributes;
}
}

View File

@@ -0,0 +1,459 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.totp;
import com.google.common.primitives.Longs;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue;
/*
* NOTE: This TOTP implementation is based on the TOTP reference implementation
* provided by the IETF Trust at:
*
* https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl
*/
/*
* Copyright (c) 2011 IETF Trust and the persons identified as authors
* of the code. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Internet Society, IETF or IETF Trust, nor the names
* of specific contributors, may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Generator which uses the TOTP algorithm to generate authentication codes.
*/
public class TOTPGenerator {
/**
* The default time to use as the basis for comparison when transforming
* provided TOTP timestamps into counter values required for HOTP, in
* seconds since midnight, 1970-01-01, UTC (UNIX epoch).
*/
public static final long DEFAULT_START_TIME = 0;
/**
* The default frequency at which new TOTP codes should be generated (and
* old codes invalidated), in seconds.
*/
public static final long DEFAULT_TIME_STEP = 30;
/**
* The TOTP generation mode. The mode dictates the hash function which
* should be used to generate authentication codes, as well as the required
* key size.
*/
private final Mode mode;
/**
* The shared key to use to generate authentication codes. The size
* required for this key depends on the generation mode.
*/
private final Key key;
/**
* The length of codes to generate, in digits.
*/
private final int length;
/**
* The base time against which the timestamp specified for each TOTP
* should be compared to produce the corresponding HOTP counter value, in
* seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the value
* value referred to as "T0" in the TOTP specification.
*/
private final long startTime;
/**
* The frequency that new TOTP codes should be generated and invalidated,
* in seconds. This is the value referred to as "X" in the TOTP
* specification.
*/
private final long timeStep;
/**
* The operating mode for TOTP, defining the hash algorithm to be used.
*/
public enum Mode {
/**
* TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode
* requires 160-bit keys.
*/
@PropertyValue("sha1")
SHA1("HmacSHA1", 20),
/**
* TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode
* requires 256-bit keys.
*/
@PropertyValue("sha256")
SHA256("HmacSHA256", 32),
/**
* TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode
* requires 512-bit keys.
*/
@PropertyValue("sha512")
SHA512("HmacSHA512", 64);
/**
* The name of the HMAC algorithm which the TOTP implementation should
* use when operating in this mode, in the format required by
* Mac.getInstance().
*/
private final String algorithmName;
/**
* The recommended length of keys generated for TOTP in this mode, in
* bytes. Keys are recommended to be the same length as the hash
* involved.
*/
private final int recommendedKeyLength;
/**
* Creates a new TOTP operating mode which is associated with the
* given HMAC algorithm.
*
* @param algorithmName
* The name of the HMAC algorithm which the TOTP implementation
* should use when operating in this mode, in the format required
* by Mac.getInstance().
*
* @param recommendedKeyLength
* The recommended length of keys generated for TOTP in this mode,
* in bytes.
*/
private Mode(String algorithmName, int recommendedKeyLength) {
this.algorithmName = algorithmName;
this.recommendedKeyLength = recommendedKeyLength;
}
/**
* Returns the name of the HMAC algorithm which the TOTP implementation
* should use when operating in this mode. The name returned will be
* in the format required by Mac.getInstance().
*
* @return
* The name of the HMAC algorithm which the TOTP implementation
* should use.
*/
public String getAlgorithmName() {
return algorithmName;
}
/**
* Returns the recommended length of keys generated for TOTP in this
* mode, in bytes. Keys are recommended to be the same length as the
* hash involved.
*
* @return
* The recommended length of keys generated for TOTP in this mode,
* in bytes.
*/
public int getRecommendedKeyLength() {
return recommendedKeyLength;
}
}
/**
* Creates a new TOTP generator which uses the given shared key to generate
* authentication codes. The provided generation mode dictates the size of
* the key required, while the given start time and time step dictate how
* timestamps provided for code generation are converted to the counter
* value used by HOTP (the algorithm which forms the basis of TOTP).
*
* @param key
* The shared key to use to generate authentication codes.
*
* @param mode
* The mode in which the TOTP algorithm should operate.
*
* @param length
* The length of the codes to generate, in digits. As required
* by the specification, this value MUST be at least 6 but no greater
* than 8.
*
* @param startTime
* The base time against which the timestamp specified for each TOTP
* should be compared to produce the corresponding HOTP counter value,
* in seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the
* value referred to as "T0" in the TOTP specification.
*
* @param timeStep
* The frequency that new TOTP codes should be generated and
* invalidated, in seconds. This is the value referred to as "X" in the
* TOTP specification.
*
* @throws InvalidKeyException
* If the provided key is invalid for the requested TOTP mode.
*/
public TOTPGenerator(byte[] key, Mode mode, int length, long startTime,
long timeStep) throws InvalidKeyException {
// Validate length is within spec
if (length < 6 || length > 8)
throw new IllegalArgumentException("TOTP codes must be at least 6 "
+ "digits and no more than 8 digits.");
this.key = new SecretKeySpec(key, "RAW");
this.mode = mode;
this.length = length;
this.startTime = startTime;
this.timeStep = timeStep;
// Verify key validity
getMacInstance(this.mode, this.key);
}
/**
* Creates a new TOTP generator which uses the given shared key to generate
* authentication codes. The provided generation mode dictates the size of
* the key required. The start time and time step used to produce the
* counter value used by HOTP (the algorithm which forms the basis of TOTP)
* are set to the default values recommended by the TOTP specification (0
* and 30 respectively).
*
* @param key
* The shared key to use to generate authentication codes.
*
* @param mode
* The mode in which the TOTP algorithm should operate.
*
* @param length
* The length of the codes to generate, in digits. As required
* by the specification, this value MUST be at least 6 but no greater
* than 8.
*
* @throws InvalidKeyException
* If the provided key is invalid for the requested TOTP mode.
*/
public TOTPGenerator(byte[] key, Mode mode, int length)
throws InvalidKeyException {
this(key, mode, length, DEFAULT_START_TIME, DEFAULT_TIME_STEP);
}
/**
* Returns a new Mac instance which produces message authentication codes
* using the given secret key and the algorithm required by the given TOTP
* mode.
*
* @param mode
* The TOTP mode which dictates the HMAC algorithm to be used.
*
* @param key
* The secret key to use to produce message authentication codes.
*
* @return
* A new Mac instance which produces message authentication codes
* using the given secret key and the algorithm required by the given
* TOTP mode.
*
* @throws InvalidKeyException
* If the provided key is invalid for the requested TOTP mode.
*/
private static Mac getMacInstance(Mode mode, Key key)
throws InvalidKeyException {
try {
Mac hmac = Mac.getInstance(mode.getAlgorithmName());
hmac.init(key);
return hmac;
}
catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("Support for the HMAC "
+ "algorithm required for TOTP in " + mode + " mode is "
+ "missing.", e);
}
}
/**
* Calculates the HMAC for the given message using the key and algorithm
* provided when this TOTPGenerator was created.
*
* @param message
* The message to calculate the HMAC of.
*
* @return
* The HMAC of the given message.
*/
private byte[] getHMAC(byte[] message) {
try {
return getMacInstance(mode, key).doFinal(message);
}
catch (InvalidKeyException e) {
// As the key is verified during construction of the TOTPGenerator,
// this should never happen
throw new IllegalStateException("Provided key became invalid after "
+ "passing validation.", e);
}
}
/**
* Given an arbitrary integer value, returns a code containing the decimal
* representation of that value and exactly the given number of digits. If
* the given value has more than the desired number of digits, leading
* digits will be truncated to reduce the length. If the given value has
* fewer than the desired number of digits, leading zeroes will be added to
* increase the length.
*
* @param value
* The value to convert into a decimal code of the given length.
*
* @param length
* The number of digits to include in the code.
*
* @return
* A code containing the decimal value of the given integer, truncated
* or padded such that exactly the given number of digits are present.
*/
private String toCode(int value, int length) {
// Convert value to simple integer string
String valueString = Integer.toString(value);
// If the resulting string is too long, truncate to the last N digits
if (valueString.length() > length)
return valueString.substring(valueString.length() - length);
// Otherwise, add zeroes until the desired length is reached
StringBuilder builder = new StringBuilder(length);
for (int i = valueString.length(); i < length; i++)
builder.append('0');
// Return the padded integer string
builder.append(valueString);
return builder.toString();
}
/**
* Generates a TOTP code of the given length using the given absolute
* timestamp rather than the current system time.
*
* @param time
* The absolute timestamp to use to generate the TOTP code, in seconds
* since midnight, 1970-01-01, UTC (UNIX epoch).
*
* @return
* The TOTP code which corresponds to the given timestamp, having
* exactly the given length.
*
* @throws IllegalArgumentException
* If the given length is invalid as defined by the TOTP specification.
*/
public String generate(long time) {
// Calculate HOTP counter value based on provided time
long counter = (time - startTime) / timeStep;
byte[] hash = getHMAC(Longs.toByteArray(counter));
// Calculate HOTP value as defined by section 5.2 of RFC 4226:
// https://tools.ietf.org/html/rfc4226#section-5.2
int offset = hash[hash.length - 1] & 0xF;
int binary
= ((hash[offset] & 0x7F) << 24)
| ((hash[offset + 1] & 0xFF) << 16)
| ((hash[offset + 2] & 0xFF) << 8)
| (hash[offset + 3] & 0xFF);
// Truncate or pad the value accordingly
return toCode(binary, length);
}
/**
* Generates a TOTP code of the given length using the current system time.
*
* @return
* The TOTP code which corresponds to the current system time, having
* exactly the given length.
*
* @throws IllegalArgumentException
* If the given length is invalid as defined by the TOTP specification.
*/
public String generate() {
return generate(System.currentTimeMillis() / 1000);
}
/**
* Returns the TOTP code which would have been generated immediately prior
* to the code returned by invoking generate() with the given timestamp.
*
* @param time
* The absolute timestamp to use to generate the TOTP code, in seconds
* since midnight, 1970-01-01, UTC (UNIX epoch).
*
* @return
* The TOTP code which would have been generated immediately prior to
* the code returned by invoking generate() with the given timestamp.
*/
public String previous(long time) {
return generate(Math.max(startTime, time - timeStep));
}
/**
* Returns the TOTP code which would have been generated immediately prior
* to the code currently being returned by generate().
*
* @return
* The TOTP code which would have been generated immediately prior to
* the code currently being returned by generate().
*/
public String previous() {
return previous(System.currentTimeMillis() / 1000);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Config block which registers TOTP-specific field types.
*/
angular.module('guacTOTP').config(['formServiceProvider',
function guacTOTPConfig(formServiceProvider) {
// Define field for the TOTP code provided by the user
formServiceProvider.registerFieldType('GUAC_TOTP_CODE', {
module : 'guacTOTP',
controller : 'authenticationCodeFieldController',
templateUrl : 'app/ext/totp/templates/authenticationCodeField.html'
});
}]);

View File

@@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter
* the code generated by their authentication device.
*/
angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$window',
function authenticationCodeFieldController($scope, $window) {
/**
* The secret key split into groups of at most four characters each, or
* null if the secret key is not exposed.
*
* @type String[]
*/
$scope.groupedSecret = $scope.field.secret && $scope.field.secret.match(/.{1,4}/g);
/**
* Whether the raw details of the secret key and TOTP configuration should
* be shown. By default, such details are hidden. If the secret key is not
* exposed, this property has no effect.
*/
$scope.detailsShown = false;
/**
* Shows the raw details of the secret key and TOTP configuration. If the
* secret key is not exposed, or the details are already shown, this
* function has no effect.
*/
$scope.showDetails = function showDetails() {
$scope.detailsShown = true;
};
/**
* Hides the raw details of the secret key and TOTP configuration. If the
* details are already hidden, this function has no effect.
*/
$scope.hideDetails = function hideDetails() {
$scope.detailsShown = false;
};
/**
* Attempts to open the "otpauth" URI containing the user's TOTP key,
* invoking whichever application may be installed locally for handling
* multi-factor authentication.
*/
$scope.openKeyURI = function openKeyURI() {
$window.open($scope.field.keyUri);
};
}]);

View File

@@ -0,0 +1,37 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "TOTP TFA Authentication Backend",
"namespace" : "totp",
"authProviders" : [
"org.apache.guacamole.auth.totp.TOTPAuthenticationProvider"
],
"translations" : [
"translations/ca.json",
"translations/de.json",
"translations/en.json",
"translations/fr.json",
"translations/ja.json",
"translations/ko.json",
"translations/pl.json",
"translations/pt.json",
"translations/ru.json",
"translations/zh.json"
],
"js" : [
"totp.min.js"
],
"css" : [
"totp.min.css"
],
"resources" : {
"templates/authenticationCodeField.html" : "text/html"
}
}

View File

@@ -0,0 +1,18 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

View File

@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.totp-enroll p, .totp-details {
font-size: 0.8em;
}
.totp-qr-code {
text-align: center;
}
.totp-qr-code img {
margin: 1em;
border: 1px solid rgba(0,0,0,0.25);
box-shadow: 1px 1px 2px rgba(0,0,0,0.25);
cursor: pointer;
}
h3.totp-details-header {
font-size: 0.8em;
}
h3.totp-details-header::before {
content: '▸ ';
}
.totp-details-visible h3.totp-details-header::before {
content: '▾ ';
}
.totp-details,
.totp-hide-details {
display: none;
}
.totp-details-visible .totp-details {
display: table;
}
.totp-details-visible .totp-hide-details {
display: inline;
}
.totp-details-visible .totp-show-details {
display: none;
}
.totp-hide-details, .totp-show-details {
color: blue;
text-decoration: underline;
cursor: pointer;
margin: 0 0.25em;
font-weight: normal;
}
.totp-details {
margin: 0 auto;
}
.totp-details th {
padding-right: 0.25em;
text-align: left;
}
.totp-details td {
font-family: monospace;
}
.totp-detail {
display: inline-block;
margin: 0 0.25em;
}

View File

@@ -0,0 +1,48 @@
<div class="totp-code-field" ng-class="{ 'totp-details-visible' : detailsShown }">
<!-- Enroll user if necessary -->
<div class="totp-enroll" ng-show="field.qrCode">
<p translate="TOTP.HELP_ENROLL_BARCODE"></p>
<!-- Barcode and key details -->
<div class="totp-qr-code"><img ng-src="{{field.qrCode}}" ng-click="openKeyURI()"></div>
<h3 class="totp-details-header">
{{'TOTP.SECTION_HEADER_DETAILS' | translate}}
<a class="totp-show-details" ng-click="showDetails()">{{'TOTP.ACTION_SHOW_DETAILS' | translate}}</a>
<a class="totp-hide-details" ng-click="hideDetails()">{{'TOTP.ACTION_HIDE_DETAILS' | translate}}</a>
</h3>
<table class="totp-details">
<tr>
<th>{{'TOTP.FIELD_HEADER_SECRET_KEY' | translate}}</th>
<td><span ng-repeat="group in groupedSecret"
class="totp-detail">{{ group }}</span></td>
</tr>
<tr>
<th>{{'TOTP.FIELD_HEADER_DIGITS' | translate}}</th>
<td><span class="totp-detail">{{ field.digits }}</span></td>
</tr>
<tr>
<th>{{'TOTP.FIELD_HEADER_ALGORITHM' | translate}}</th>
<td><span class="totp-detail">{{ field.mode }}</span></td>
</tr>
<tr>
<th>{{'TOTP.FIELD_HEADER_INTERVAL' | translate}}</th>
<td><span class="totp-detail">{{ field.period }}</span></td>
</tr>
</table>
<p translate="TOTP.HELP_ENROLL_VERIFY"
translate-values="{ DIGITS : field.digits }"></p>
</div>
<!-- Field for entry of the current TOTP code -->
<div class="totp-code">
<input type="text"
placeholder="{{'TOTP.FIELD_PLACEHOLDER_CODE' |translate}}"
ng-attr-name="{{ field.name }}"
ng-model="model" autocomplete="off" autocorrect="off" autocapitalize="off" autofocus>
</div>
</div>

View File

@@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Module which provides handling for TOTP multi-factor authentication.
*/
angular.module('guacTOTP', [
'form'
]);
// Ensure the guacTOTP module is loaded along with the rest of the app
angular.module('index').requires.push('guacTOTP');

View File

@@ -0,0 +1,34 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Amaga",
"ACTION_SHOW_DETAILS" : "Mostra",
"FIELD_HEADER_ALGORITHM" : "Algoritme:",
"FIELD_HEADER_DIGITS" : "Digits:",
"FIELD_HEADER_INTERVAL" : "Interval:",
"FIELD_HEADER_SECRET_KEY" : "Clau secreta:",
"FIELD_PLACEHOLDER_CODE" : "Codi d'autenticació",
"INFO_CODE_REQUIRED" : "Introduïu el vostre codi d'autenticació per verificar la vostra identitat.",
"INFO_ENROLL_REQUIRED" : "S'ha activat l'autenticació multi-factor al vostre compte.",
"INFO_VERIFICATION_FAILED" : "Ha fallat la verificació. Torna-ho a provar.",
"HELP_ENROLL_BARCODE" : "Per completar el procés d'inscripció, busqueu el codi de barres següent amb l'aplicació d'autenticació de dos factors al vostre telèfon o dispositiu.",
"HELP_ENROLL_VERIFY" : "Després d'escanejar el codi de barres, introduïu el codi d'autentificació de {DIGITS} que apareix per comprovar que la inscripció ha tingut èxit.",
"SECTION_HEADER_DETAILS" : "Detalls:"
}
}

View File

@@ -0,0 +1,52 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Details ausblenden",
"ACTION_SHOW_DETAILS" : "Details anzeigen",
"FIELD_HEADER_ALGORITHM" : "Algorithmus:",
"FIELD_HEADER_DIGITS" : "Anzahl Zeichen:",
"FIELD_HEADER_INTERVAL" : "Intervall:",
"FIELD_HEADER_SECRET_KEY" : "Privater Schlüssel:",
"FIELD_PLACEHOLDER_CODE" : "Authentifizierungs-Code",
"INFO_CODE_REQUIRED" : "Bitte geben Sie Ihren Authentifizierungs-Code ein, um Ihre Identität zu bestätigen.",
"INFO_ENROLL_REQUIRED" : "Die Zweifaktor-Authentifizierung wurde für Ihren Account aktiviert.",
"INFO_VERIFICATION_FAILED" : "Verifikation fehlgeschlagen. Bitte nochmals versuchen.",
"HELP_ENROLL_BARCODE" : "Bitte scannen Sie den Barcode mittels einer Zweifaktor-Authentifizierungs-App auf Ihrem Smartphone oder einem anderen Gerät, um den Prozess fortzusetzen.",
"HELP_ENROLL_VERIFY" : "Nach dem Scannen des Barcodes wird Ihnen ein {DIGITS}-stelliger Authentifizierungscode angezeigt. Bitte geben Sie diesen Code ein um die Aktivierung des Zweifaktor-Verfahrens abzuschliessen.",
"SECTION_HEADER_DETAILS" : "Details:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "TOTP deaktivieren:",
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Geheimer Schlüssel erstellt:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Authentifizierungs-Gerät bestätigt:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "TOTP Ausroll-Status"
},
"USER_GROUP_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "TOTP deaktivieren:",
"SECTION_HEADER_TOTP_USER_GROUP_CONFIG" : "TOTP Konfiguration"
}
}

View File

@@ -0,0 +1,52 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Hide",
"ACTION_SHOW_DETAILS" : "Show",
"FIELD_HEADER_ALGORITHM" : "Algorithm:",
"FIELD_HEADER_DIGITS" : "Digits:",
"FIELD_HEADER_INTERVAL" : "Interval:",
"FIELD_HEADER_SECRET_KEY" : "Secret Key:",
"FIELD_PLACEHOLDER_CODE" : "Authentication Code",
"INFO_CODE_REQUIRED" : "Please enter your authentication code to verify your identity.",
"INFO_ENROLL_REQUIRED" : "Multi-factor authentication has been enabled on your account.",
"INFO_VERIFICATION_FAILED" : "Verification failed. Please try again.",
"HELP_ENROLL_BARCODE" : "To complete the enrollment process, scan the barcode below with the two-factor authentication app on your phone or device.",
"HELP_ENROLL_VERIFY" : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful.",
"SECTION_HEADER_DETAILS" : "Details:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Disable TOTP:",
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Secret key generated:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Authentication device confirmed:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "TOTP Enrollment Status"
},
"USER_GROUP_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Disable TOTP:",
"SECTION_HEADER_TOTP_USER_GROUP_CONFIG" : "TOTP Configuration"
}
}

View File

@@ -0,0 +1,52 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Masquer",
"ACTION_SHOW_DETAILS" : "Montrer",
"FIELD_HEADER_ALGORITHM" : "Algorithme:",
"FIELD_HEADER_DIGITS" : "Chiffres:",
"FIELD_HEADER_INTERVAL" : "Intervalle:",
"FIELD_HEADER_SECRET_KEY" : "Clé secrète:",
"FIELD_PLACEHOLDER_CODE" : "Code d'authentification",
"INFO_CODE_REQUIRED" : "Veuillez entrer le code d'authentification pour vérifier votre identité.",
"INFO_ENROLL_REQUIRED" : "L'authentification multi-facteurs a été activée pour votre compte.",
"INFO_VERIFICATION_FAILED" : "La vérification a échoué. Veuillez réessayer.",
"HELP_ENROLL_BARCODE" : "Pour terminer votre processus d'inscription, scannez le code-barre ci-dessous avec l'application deux-facteurs sur votre téléphone ou votre appareil",
"HELP_ENROLL_VERIFY" : "Après avoir scanné le code-barre, saisissez les {DIGITS} chiffres du code d'authentification affichés pour terminer votre inscription.",
"SECTION_HEADER_DETAILS" : "Détails:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Désactiver TOTP:",
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Clé secrète générée:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Appareil d'authentification confirmé:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "Statut d'inscription TOTP"
},
"USER_GROUP_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "Désactiver TOTP:",
"SECTION_HEADER_TOTP_USER_GROUP_CONFIG" : "Configuration TOTP"
}
}

View File

@@ -0,0 +1,39 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Nascondere",
"ACTION_SHOW_DETAILS" : "Mostrare",
"FIELD_HEADER_ALGORITHM" : "Algoritmo:",
"FIELD_HEADER_DIGITS" : "Cifre:",
"FIELD_HEADER_INTERVAL" : "Intervallo:",
"FIELD_HEADER_SECRET_KEY" : "Chiave segreta:",
"FIELD_PLACEHOLDER_CODE" : "Codice di Autenticazione",
"INFO_CODE_REQUIRED" : "Inserisci il tuo codice di autenticazione per verificare la tua identità.",
"INFO_ENROLL_REQUIRED" : "L'autenticazione a più fattori è stata abilitata sul tuo account.",
"INFO_VERIFICATION_FAILED" : "Verifica fallita. Per favore riprova.",
"HELP_ENROLL_BARCODE" : "Per completare il processo di registrazione, scansiona il codice a barre qui sotto con l'app di autenticazione a due fattori sul tuo telefono o dispositivo.",
"HELP_ENROLL_VERIFY" : "Dopo aver scansionato il codice a barre, inserire il codice di autenticazione a {DIGITS}-cifre visualizzato per verificare che la registrazione sia andata a buon fine.",
"SECTION_HEADER_DETAILS" : "Dettagli:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Chiave segreta generata:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Dispositivo di autenticazione confermato:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "Stato di iscrizione TOTP"
}
}

View File

@@ -0,0 +1,26 @@
{
"TOTP" : {
"ACTION_HIDE_DETAILS" : "非表示",
"ACTION_SHOW_DETAILS" : "表示",
"FIELD_HEADER_ALGORITHM" : "アルゴリズム:",
"FIELD_HEADER_DIGITS" : "認証コード桁数:",
"FIELD_HEADER_INTERVAL" : "認証コード利用可能時間(秒):",
"FIELD_HEADER_SECRET_KEY" : "秘密鍵:",
"FIELD_PLACEHOLDER_CODE" : "認証コード",
"INFO_CODE_REQUIRED" : "認証コードを入力してください。",
"INFO_ENROLL_REQUIRED" : "二要素認証システムが有効になっています。",
"INFO_VERIFICATION_FAILED" : "認証に失敗しました。もう一度やり直してください。",
"HELP_ENROLL_BARCODE" : "スマートフォンやタブレット等のデバイスの二要素認証アプリでQRコードを読み込んでください。",
"HELP_ENROLL_VERIFY" : "QRコードを読み込み、表示された {DIGITS}桁の認証コードを入力してください。",
"SECTION_HEADER_DETAILS" : "詳細:"
}
}

View File

@@ -0,0 +1,36 @@
{
"TOTP" : {
"ACTION_HIDE_DETAILS" : "숨기기",
"ACTION_SHOW_DETAILS" : "보이기",
"FIELD_HEADER_ALGORITHM" : "알고리즘:",
"FIELD_HEADER_DIGITS" : "숫자:",
"FIELD_HEADER_INTERVAL" : "간격:",
"FIELD_HEADER_SECRET_KEY" : "비밀 키:",
"FIELD_PLACEHOLDER_CODE" : "인증 코드",
"INFO_CODE_REQUIRED" : "신원을 확인하려면 인증 코드를 입력하세요.",
"INFO_ENROLL_REQUIRED" : "계정에서 다단계 인증이 활성화되었습니다.",
"INFO_VERIFICATION_FAILED" : "확인에 실패했습니다. 다시 시도하십시오.",
"HELP_ENROLL_BARCODE" : "등록 절차를 완료하려면 휴대 전화 또는 기기의 이중 인증 앱으로 아래 바코드를 스캔하세요.",
"HELP_ENROLL_VERIFY" : "바코드를 스캔한 후 표시되는 {DIGITS} 자리 인증 코드를 입력하여 성공적으로 등록되었는지 확인하십시오.",
"SECTION_HEADER_DETAILS" : "세부 정보:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_RESET" : "TOTP 비밀 키 지우기:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "TOTP 키 확인 완료:",
"SECTION_HEADER_TOTP_CONFIG_FORM" : "TOTP 구성"
}
}

View File

@@ -0,0 +1,43 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Ukryj",
"ACTION_SHOW_DETAILS" : "Pokaż",
"FIELD_HEADER_ALGORITHM" : "Algorytm:",
"FIELD_HEADER_DIGITS" : "Cyfry:",
"FIELD_HEADER_INTERVAL" : "Interwał:",
"FIELD_HEADER_SECRET_KEY" : "Sekretny klucz:",
"FIELD_PLACEHOLDER_CODE" : "Kod weryfikacyjny",
"INFO_CODE_REQUIRED" : "Podaj kod weryfikacyjny, aby potwierdzić swoją tożsamość.",
"INFO_ENROLL_REQUIRED" : "Uwierzytelnianie wieloskładnikowe zostało włączone na Twoim koncie.",
"INFO_VERIFICATION_FAILED" : "Weryfikacja nie powiodła się. Spróbuj ponownie.",
"HELP_ENROLL_BARCODE" : "Aby zakończyć proces rejestracji, zeskanuj poniższy kod kreskowy za pomocą aplikacji do uwierzytelniania dwuskładnikowego na swoim telefonie lub urządzeniu.",
"HELP_ENROLL_VERIFY" : "Po zeskanowaniu kodu kreskowego, wprowadź {DIGITS}-cyfrowy kod weryfikacyjny aby potwierdzić, że proces rejestracji przebiegł pomyślnie.",
"SECTION_HEADER_DETAILS" : "Szczegóły:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "Wygenerowano klucz:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "Potwierdzono urządzenie uwierzytelniające:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "Status Rejestracji TOTP"
}
}

View File

@@ -0,0 +1,34 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Esconder",
"ACTION_SHOW_DETAILS" : "Exibir",
"FIELD_HEADER_ALGORITHM" : "Algoritmo:",
"FIELD_HEADER_DIGITS" : "Digitos:",
"FIELD_HEADER_INTERVAL" : "Intervalo:",
"FIELD_HEADER_SECRET_KEY" : "Chave Secreta:",
"FIELD_PLACEHOLDER_CODE" : "Código de autenticação",
"INFO_CODE_REQUIRED" : "Por favor digite seu código de autenticação para verificar sua identidade.",
"INFO_ENROLL_REQUIRED" : "Autenticação Multi-factor foi habilitada para sua conta.",
"INFO_VERIFICATION_FAILED" : "A verificação falhou. Por favor tente novamente.",
"HELP_ENROLL_BARCODE" : "Para completar o processo de inscrição, faça o scan do código de barras abaixo com o app de autenticação de dois fatores no seu telefone ou dispositivo.",
"HELP_ENROLL_VERIFY" : "Após escanear o código de barras, digite os {DIGITS}-digitos do código de autenticação exibidos para verificar se sua inscrição foi recebida com sucesso.",
"SECTION_HEADER_DETAILS" : "Detalhes:"
}
}

View File

@@ -0,0 +1,30 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "Бэкенд TOTP TFA"
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "Спрятать",
"ACTION_SHOW_DETAILS" : "Показать",
"FIELD_HEADER_ALGORITHM" : "Алгоритм:",
"FIELD_HEADER_DIGITS" : "Количество цифр:",
"FIELD_HEADER_INTERVAL" : "Время действия:",
"FIELD_HEADER_SECRET_KEY" : "Секретный ключ:",
"FIELD_PLACEHOLDER_CODE" : "Код доступа",
"INFO_CODE_REQUIRED" : "Пожалуйста, введите код доступа для подтверждения подлинности.",
"INFO_ENROLL_REQUIRED" : "Для вашего аккаунта была включена многофакторная аутентификация.",
"INFO_VERIFICATION_FAILED" : "Проверка кода не пройдена. Пожалуйста, попробуйте снова.",
"HELP_ENROLL_BARCODE" : "Для завершения создания учетной записи отсканируйте штрихкод вашей программой двухфакторной аутентификации на смартфоне или другом устройстве.",
"HELP_ENROLL_VERIFY" : "После сканирования штрихкода введите {DIGITS}-циферный код доступа, полученный на вашем устройстве, для проверки и завершения процедуры.",
"SECTION_HEADER_DETAILS" : "Дополнительно:"
}
}

View File

@@ -0,0 +1,52 @@
{
"DATA_SOURCE_TOTP" : {
"NAME" : "TOTP TFA 后端"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_TOTP" : ""
},
"TOTP" : {
"ACTION_HIDE_DETAILS" : "隐藏",
"ACTION_SHOW_DETAILS" : "显示",
"FIELD_HEADER_ALGORITHM" : "算法:",
"FIELD_HEADER_DIGITS" : "数字位数:",
"FIELD_HEADER_INTERVAL" : "时间间隔:",
"FIELD_HEADER_SECRET_KEY" : "密钥:",
"FIELD_PLACEHOLDER_CODE" : "认证码",
"INFO_CODE_REQUIRED" : "请输入您的认证码以验证您的身份。",
"INFO_ENROLL_REQUIRED" : "您的账户已启用多因素身份验证。",
"INFO_VERIFICATION_FAILED" : "验证失败,请重试。",
"HELP_ENROLL_BARCODE" : "为了完成注册流程,请使用您的手机或设备上的双因素身份验证应用程序扫描下面的条形码。",
"HELP_ENROLL_VERIFY" : "扫描条形码后,输入显示的 {DIGITS} 位认证码以验证注册是否成功。",
"SECTION_HEADER_DETAILS" : "详细信息:"
},
"USER_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "禁用 TOTP",
"FIELD_HEADER_GUAC_TOTP_KEY_GENERATED" : "已生成密钥:",
"FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "已确认身份验证设备:",
"SECTION_HEADER_TOTP_ENROLLMENT_STATUS" : "TOTP 注册状态"
},
"USER_GROUP_ATTRIBUTES" : {
"FIELD_HEADER_GUAC_TOTP_DISABLED" : "禁用 TOTP",
"SECTION_HEADER_TOTP_USER_GROUP_CONFIG" : "TOTP 配置"
}
}

View File

@@ -0,0 +1,168 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.totp;
import java.security.InvalidKeyException;
import org.junit.Test;
import static org.junit.Assert.*;
/*
* NOTE: The tests for this TOTP implementation is based on the TOTP reference
* implementation provided by the IETF Trust at:
*
* https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl
*/
/*
* Copyright (c) 2011 IETF Trust and the persons identified as authors
* of the code. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Internet Society, IETF or IETF Trust, nor the names
* of specific contributors, may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Test which verifies the correctness of the TOTPGenerator class against the
* test inputs and results provided in the IETF reference implementation and
* spec for TOTP:
*
* https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Test-Vectors
*/
public class TOTPGeneratorTest {
/**
* Verifies the results of generating authentication codes using the TOTP
* algorithm in SHA1 mode.
*/
@Test
public void testGenerateSHA1() {
// 160-bit key consisting of the bytes "12345678901234567890" repeated
// as necessary
final byte[] key = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
};
try {
final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 8);
assertEquals("94287082", totp.generate(59));
assertEquals("07081804", totp.generate(1111111109));
assertEquals("14050471", totp.generate(1111111111));
assertEquals("89005924", totp.generate(1234567890));
assertEquals("69279037", totp.generate(2000000000));
assertEquals("65353130", totp.generate(20000000000L));
}
catch (InvalidKeyException e) {
fail("SHA1 test key is invalid.");
}
}
/**
* Verifies the results of generating authentication codes using the TOTP
* algorithm in SHA256 mode.
*/
@Test
public void testGenerateSHA256() {
// 256-bit key consisting of the bytes "12345678901234567890" repeated
// as necessary
final byte[] key = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2'
};
try {
final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA256, 8);
assertEquals("46119246", totp.generate(59));
assertEquals("68084774", totp.generate(1111111109));
assertEquals("67062674", totp.generate(1111111111));
assertEquals("91819424", totp.generate(1234567890));
assertEquals("90698825", totp.generate(2000000000));
assertEquals("77737706", totp.generate(20000000000L));
}
catch (InvalidKeyException e) {
fail("SHA256 test key is invalid.");
}
}
/**
* Verifies the results of generating authentication codes using the TOTP
* algorithm in SHA512 mode.
*/
@Test
public void testGenerateSHA512() {
// 512-bit key consisting of the bytes "12345678901234567890" repeated
// as necessary
final byte[] key = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4'
};
try {
final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA512, 8);
assertEquals("90693936", totp.generate(59));
assertEquals("25091201", totp.generate(1111111109));
assertEquals("99943326", totp.generate(1111111111));
assertEquals("93441116", totp.generate(1234567890));
assertEquals("38618901", totp.generate(2000000000));
assertEquals("47863826", totp.generate(20000000000L));
}
catch (InvalidKeyException e) {
fail("SHA512 test key is invalid.");
}
}
}