├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── coverage.yml │ └── scan.yml ├── .gitignore ├── BLURB ├── COPYING ├── NEWS ├── README ├── README.adoc ├── demo-server ├── README ├── README.adoc ├── config.yml ├── pom.xml └── src │ └── main │ ├── java │ └── demo │ │ ├── App.java │ │ ├── Config.java │ │ └── Resource.java │ └── resources │ └── demo │ ├── loginIndex.html │ └── registerIndex.html ├── doc └── development.adoc ├── jaas ├── README ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── yubico │ │ └── jaas │ │ ├── HttpOathOtpLoginModule.java │ │ ├── HttpOathOtpPrincipal.java │ │ ├── MultiValuePasswordCallback.java │ │ ├── YubikeyLoginModule.java │ │ ├── YubikeyPrincipal.java │ │ ├── YubikeyToUserMap.java │ │ └── impl │ │ ├── YubikeyToUserLDAPMap.java │ │ └── YubikeyToUserMapImpl.java │ └── test │ └── java │ └── com │ └── yubico │ └── jaas │ └── HttpOathOtpLoginModuleTest.java ├── pom.xml └── v2client ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── yubico │ │ └── client │ │ └── v2 │ │ ├── Cmd.java │ │ ├── HttpUtils.java │ │ ├── ResponseStatus.java │ │ ├── Signature.java │ │ ├── VerificationRequester.java │ │ ├── VerificationResponse.java │ │ ├── Version.java │ │ ├── YubicoClient.java │ │ ├── exceptions │ │ ├── YubicoInvalidResponse.java │ │ ├── YubicoSignatureException.java │ │ ├── YubicoValidationFailure.java │ │ └── YubicoVerificationException.java │ │ └── impl │ │ ├── VerificationResponseImpl.java │ │ └── YubicoClientImpl.java └── resources │ ├── META-INF │ └── beans.xml │ └── version.properties └── test └── java └── com └── yubico └── client └── v2 ├── YubicoClientTest.java └── impl ├── TestYubicoClientImpl.java └── VerificationResponseImplTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto-detect text files 2 | * text=auto 3 | 4 | # Always treat these as LF 5 | gradlew text eol=lf 6 | *.sh text eol=lf 7 | 8 | # Always treat these as CRLF 9 | *.bat text eol=crlf 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: JDK ${{matrix.java}} 8 | 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | java: [7, 8, 11, 13] 13 | 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v1 17 | 18 | - name: Set up JDK ${{ matrix.java }} 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: ${{ matrix.java }} 22 | 23 | - name: Run tests 24 | run: mvn -B test 25 | 26 | - name: Build JAR 27 | run: mvn -B package 28 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | name: JDK ${{matrix.java}} 10 | 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | java: [13] 15 | 16 | steps: 17 | - name: Check out code 18 | uses: actions/checkout@v1 19 | 20 | - name: Set up JDK ${{ matrix.java }} 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: ${{ matrix.java }} 24 | 25 | - name: Report test coverage 26 | env: 27 | COVERALLS: true 28 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 29 | run: mvn -B -DrepoToken=${COVERALLS_REPO_TOKEN} clean test jacoco:report coveralls:report 30 | -------------------------------------------------------------------------------- /.github/workflows/scan.yml: -------------------------------------------------------------------------------- 1 | name: static code analysis 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 0 * * 1' 7 | 8 | env: 9 | SCAN_IMG: 10 | yubico-yes-docker-local.jfrog.io/static-code-analysis/java:v1 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@master 18 | 19 | - name: Prep scan 20 | run: | 21 | docker login yubico-yes-docker-local.jfrog.io/ \ 22 | -u svc-static-code-analysis-reader \ 23 | -p ${{ secrets.ARTIFACTORY_READER_TOKEN }} 24 | docker pull ${SCAN_IMG} 25 | 26 | - name: Scan and fail on warnings 27 | run: | 28 | docker run -v${PWD}:/k \ 29 | -e PROJECT_NAME=${GITHUB_REPOSITORY#Yubico/} -t ${SCAN_IMG} 30 | 31 | - uses: actions/upload-artifact@master 32 | if: failure() 33 | with: 34 | name: suppression_files 35 | path: suppression_files 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*~ 3 | target 4 | .settings 5 | .project 6 | .classpath 7 | bin/ 8 | pom.xml.versionsBackup 9 | 10 | ### Intellij ### 11 | # Covers IntelliJ and PyCharm 12 | 13 | *.iml 14 | .idea/ 15 | 16 | ### VSCode ### 17 | 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /BLURB: -------------------------------------------------------------------------------- 1 | Author: Yubico 2 | Basename: yubico-java-client 3 | Homepage: https://developers.yubico.com/yubico-java-client/ 4 | License: BSD-2-Clause 5 | Name: yubico-java-client 6 | Project: yubico-java-client 7 | Summary: Client library written in Java for verifying Yubikey one-time passwords (OTPs). 8 | Travis: https://travis-ci.org/Yubico/yubico-java-client 9 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, 2011 Yubico AB 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | yubico-java-client NEWS -- History of user-visible changes. -*- outline -*- 2 | 3 | * Version 3.1.0 (unreleased) 4 | ** Updates to work better with new YubiCloud 5 | *** Default YubiCloud URLs now include only `api.yubico.com` 6 | *** `YubicoClient` now retries requests a number of times configurable via `.setMaxRetries(int)` (default: 5) 7 | *** `YubicoClient` now logs warnings when deprecated URLs `api2.yubico.com ... api5.yubico.com` are used 8 | 9 | * Version 3.0.5 10 | ** Fixed runtime error on JRE 11 due to `javax.xml.bind.DatatypeConverter` not existing anymore 11 | *** Re-introduced `commons-codec` dependency. 12 | 13 | * Version 3.0.4 14 | ** v1client: 15 | *** Deleted all contents from class YubicoClient so that use attempts fail at compile time instead of runtime. 16 | 17 | * Version 3.0.3 18 | ** v1client: 19 | *** Added deprecation notes 20 | *** Starting on 2019-02-04, YubicoClient.verify will always throw an UnsupportedOperationException explaining that the YubiCloud v1 API has been dropped. 21 | ** v2client: 22 | *** YubicoClient and YubicoClientImpl will now log warnings whenever they see a naked http:// URL in the wsapi_urls field. 23 | 24 | * Version 3.0.2 25 | ** `commons-codec` dependency removed, except for jaas subproject which still uses it 26 | ** Fixed a bug in handling `BACKEND_ERROR` responses 27 | 28 | * Version 3.0.1 29 | ** Better logging of wsapi request exceptions. 30 | ** User-Agent now includes JRE version. 31 | 32 | * Version 3.0.0 33 | ** Request signing is now the default behavior. 34 | ** Changed names on a few classes. 35 | ** Added a demo server. 36 | ** Responses now have an isOk() method. 37 | 38 | * Version 2.0.1 (released 2013-01-31) 39 | ** YubicoClient.isValidOTPFormat() now returns false on null. 40 | ** YubicoClient.getPublicId() is more explicit in its error handling. 41 | 42 | * Version 2.0RC8 (released 2013-01-04) 43 | ** Initial release on github. 44 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | == yubico-java-client 2 | 3 | NOTE: This project is deprecated and is no longer being maintained. For more information and guidance on how to implement Yubico OTP support in applications, see https://status.yubico.com/2021/04/15/one-api-yubico-com-one-http-get/. 4 | 5 | image:https://github.com/Yubico/yubico-java-client/workflows/build/badge.svg["Build Status", link="https://github.com/Yubico/yubico-java-client/actions"] 6 | 7 | This repository contains a Java library with an accompanying demo server, as well as a 8 | https://github.com/Yubico/yubico-java-client/tree/master/jaas[JAAS module], 9 | to validate YubiKey OTPs (One-Time Passwords). 10 | 11 | By default, this library uses the Yubico YubiCloud validation platform, 12 | but it can be configured for another validation server. 13 | 14 | NOTE: For more details on how to use a YubiKey OTP library, visit 15 | https://developers.yubico.com/OTP[developers.yubico.com/OTP]. 16 | 17 | === Usage 18 | 19 | Add this to your pom.xml: 20 | 21 | [source,xml] 22 | 23 | com.yubico 24 | yubico-validation-client2 25 | 3.0.5 26 | 27 | 28 | [source,java] 29 | ---- 30 | // clientId and secretKey are retrieved from https://upgrade.yubico.com/getapikey 31 | YubicoClient client = YubicoClient.getClient(clientId, secretKey); 32 | 33 | // otp is the OTP from the YubiKey 34 | VerificationResponse response = client.verify(otp); 35 | assert response.isOk(); 36 | ---- 37 | 38 | After validating the OTP you should make sure that the publicId part belongs to 39 | the correct user. For example: 40 | 41 | [source,java] 42 | YubicoClient.getPublicId(otp) 43 | .equals(/* Yubikey ID associated with the user */); 44 | 45 | For a complete example, see the https://github.com/Yubico/yubico-java-client/tree/master/demo-server[demo server]. 46 | 47 | === Logging 48 | The validation client depends on slf4j-api for logging. To get the actual logs 49 | and not receive warnings on System.out you will need to depend on a slf4j logger 50 | binding, for example slf4j-log4j with the following Maven configuration: 51 | 52 | [source,xml] 53 | 54 | org.slf4j 55 | slf4j-log4j 56 | 1.6.1 57 | 58 | 59 | === Read more 60 | For more complete descriptions of methods and failure states, please see 61 | the https://developers.yubico.com/yubico-java-client[JavaDoc]. 62 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /demo-server/README: -------------------------------------------------------------------------------- 1 | == yubico-demo-server 2 | A simple self-contained demo server supporting multiple YubiKeys per user. The central part is the 3 | https://github.com/Yubico/yubico-java-client/blob/master/demo-server/src/main/java/demo/Resource.java[Resource] 4 | class. 5 | 6 | === Usage 7 | Compile using `mvn clean install` and then run using 8 | `java -jar target/yubico-demo-server.jar server config.yml`. 9 | 10 | Then point a web browser to 11 | link:http://localhost:8080/registerIndex[localhost:8080/registerIndex]. 12 | -------------------------------------------------------------------------------- /demo-server/README.adoc: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /demo-server/config.yml: -------------------------------------------------------------------------------- 1 | server: 2 | applicationConnectors: 3 | - type: http 4 | port: 8080 -------------------------------------------------------------------------------- /demo-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | yubico-validation-client 7 | com.yubico 8 | 3.0.5 9 | 10 | 4.0.0 11 | 12 | yubico-demo-server 13 | 14 | 15 | 16 | com.yubico 17 | yubico-validation-client2 18 | 3.0.5 19 | 20 | 21 | io.dropwizard 22 | dropwizard-core 23 | 0.7.1 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-shade-plugin 32 | 1.6 33 | 34 | true 35 | 36 | 37 | *:* 38 | 39 | META-INF/*.SF 40 | META-INF/*.DSA 41 | META-INF/*.RSA 42 | 43 | 44 | 45 | 46 | 47 | 48 | package 49 | 50 | shade 51 | 52 | 53 | ${project.artifactId} 54 | 55 | 56 | 57 | demo.App 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /demo-server/src/main/java/demo/App.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import io.dropwizard.Application; 4 | import io.dropwizard.setup.Bootstrap; 5 | import io.dropwizard.setup.Environment; 6 | 7 | public class App extends Application { 8 | @Override 9 | public void initialize(Bootstrap bootstrap) { 10 | } 11 | 12 | @Override 13 | public void run(Config config, Environment environment) throws Exception { 14 | environment.jersey().register(new Resource()); 15 | } 16 | 17 | public static void main(String... args) throws Exception { 18 | new App().run(args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo-server/src/main/java/demo/Config.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import io.dropwizard.Configuration; 4 | 5 | public class Config extends Configuration { 6 | } 7 | -------------------------------------------------------------------------------- /demo-server/src/main/java/demo/Resource.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.yubico.client.v2.VerificationResponse; 5 | import com.yubico.client.v2.YubicoClient; 6 | 7 | import javax.ws.rs.*; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | 11 | @Path("/") 12 | @Produces(MediaType.TEXT_HTML) 13 | public class Resource { 14 | 15 | public static final String NAVIGATION = "

Navigation: Login | Register

"; 16 | 17 | // Don't use this client ID in production. Instead, get your own from https://upgrade.yubico.com/getapikey 18 | public static final int CLIENT_ID = 21188; 19 | public static final String API_KEY = "p38Z7DuEB/JC/LbDkkjmvMRB5GI="; 20 | 21 | private final YubicoClient client = YubicoClient.getClient(CLIENT_ID, API_KEY); 22 | private final HashMultimap yubikeyIds = HashMultimap.create(); 23 | 24 | @Path("registerIndex") 25 | @GET 26 | public Response registerIndex() { 27 | return Response.ok() 28 | .entity(Resource.class.getResourceAsStream("registerIndex.html")) 29 | .build(); 30 | } 31 | 32 | @Path("register") 33 | @POST 34 | public String register(@FormParam("username") String username, @FormParam("otp") String otp) throws Exception { 35 | VerificationResponse response = client.verify(otp); 36 | if (response.isOk()) { 37 | String yubikeyId = YubicoClient.getPublicId(otp); 38 | yubikeyIds.put(username, yubikeyId); 39 | return "Successfully registered YubiKey!" + NAVIGATION; 40 | } 41 | return "Invalid OTP: " + response; 42 | } 43 | 44 | @Path("loginIndex") 45 | @GET 46 | public Response loginIndex() { 47 | return Response.ok() 48 | .entity(Resource.class.getResourceAsStream("loginIndex.html")) 49 | .build(); 50 | } 51 | 52 | @Path("login") 53 | @POST 54 | public String login(@FormParam("username") String username, @FormParam("otp") String otp) throws Exception { 55 | VerificationResponse response = client.verify(otp); 56 | if (response.isOk()) { 57 | String yubikeyId = YubicoClient.getPublicId(otp); 58 | if(yubikeyIds.get(username).contains(yubikeyId)) { 59 | return "Success fully logged in " + username + "!" + NAVIGATION; 60 | } 61 | return "No such username and YubiKey combination."; 62 | } 63 | return "Invalid OTP: " + response; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /demo-server/src/main/resources/demo/loginIndex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |

7 | 8 | 9 |

10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /demo-server/src/main/resources/demo/registerIndex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |

7 | 8 | 9 |

10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /doc/development.adoc: -------------------------------------------------------------------------------- 1 | == Running test coverage 2 | 3 | ``` 4 | $ COVERALLS=true mvn jacoco:report 5 | ``` 6 | 7 | 8 | == Releasing a new version 9 | 10 | 1. Run the tests: `mvn clean test` 11 | 2. Update NEWS 12 | 3. Bump version number: `mvn versions:set` and commit 13 | 4. Tag release: `git tag -as yubico-validation-client-` 14 | 5. Release to Maven Central via Sonatype Nexus (see below) 15 | 16 | 17 | === Releasing to Maven Central 18 | 19 | First, create a staging repository: 20 | 21 | ``` 22 | $ mvn clean test && mvn deploy -DperformRelease=true 23 | ``` 24 | 25 | Then go to https://oss.sonatype.org/#stagingRepositories, _close_ the staging 26 | repository and then _release_ it once successfully closed. 27 | -------------------------------------------------------------------------------- /jaas/README: -------------------------------------------------------------------------------- 1 | These are JAAS-plugins for authentication using one time password tokens 2 | (YubiKeys primarily). 3 | For information about JAAS configuration, see 4 | http://download.oracle.com/javase/1.5.0/docs/api/javax/security/auth/login/Configuration.html 5 | 6 | YubikeyLoginModule : 7 | 8 | This JAAS plugin authenticates OTPs against the online Yubico validation 9 | servers. Client id and API key can be fetched from 10 | https://upgrade.yubico.com/getapikey/ 11 | 12 | Parameters : 13 | 14 | clientId Your Client API id for the validation service. 15 | clientKey Your Client API key for the validaiton service. 16 | id2name_textfile Filename with "public_idusername" info about which 17 | user owns what key. 18 | verify_yubikey_owner default: "true". Only set to "false" in pre-production 19 | environments, otherwise ANY Yubikey will be accepted 20 | for ANY user! 21 | auto_provision_owner default: "false". If set to "true", we will 22 | automatically record any new Yubikeys used as belonging 23 | to the user that first logged in with them. 24 | id_realm Something to append to the Yubikey public id when we 25 | construct principals (e.g. 26 | "@my-validation-service.example.org"). 27 | soft_fail_on_no_otps default: false. Should the JAAS login module return 28 | failure or asked to be ignored in case no OTPs are 29 | provided for validation? 30 | wsapi_urls default: the YubiCloud validation URL. A "|" delimeted 31 | list of ykval wsapi 2.0 URLs to use for OTP validation. 32 | sync_policy default: none, let the server decide. a value between 0 33 | and 100 indicating the percentage of synchronization 34 | required by the client. 35 | jacc default: false, if true module picks up the otp from j_otp 36 | FORM authentication too 37 | 38 | Example configuration : 39 | 40 | YourApplicationAuth { 41 | com.yubico.jaas.YubikeyLoginModule required 42 | clientId="4711"; 43 | }; 44 | 45 | 46 | HttpOathOtpLoginModule : 47 | 48 | This JAAS plugin validates OATH OTPs using HTTP. The username and password 49 | entered in your application will be used to attempt a HTTP Basic Auth login 50 | to an URL you specify, and if that succeeds and the resulting response contains 51 | an expected string, authentication is granted. 52 | 53 | One tested backend solution for validation of the HOTPs is the Apache mod_authn_otp : 54 | 55 | http://code.google.com/p/mod-authn-otp/ 56 | 57 | Parameters : 58 | 59 | protectedUrl (required) The URL you have protected with OATH-HOTP HTTP 60 | Basic Auth. 61 | expectedOutput Default is "Authenticated OK". 62 | minLength Default is 6. 63 | maxLength Default is 12 (6-8 bytes HOTP and 4 bytes PIN). 64 | requireAllDigits Default is "true". 65 | id_realm Something to append to the username when we construct 66 | principals (e.g. "@my-validation-service.example.org"). 67 | 68 | 69 | Example configuration : 70 | 71 | YourApplicationAuth { 72 | com.yubico.jaas.HTTPOathHotpLoginModule sufficient 73 | protectedUrl = "http://auth.example.com/oath-protected/" 74 | expectedOutput = "User authenticated OK"; 75 | }; 76 | -------------------------------------------------------------------------------- /jaas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | yubico-jaas-module 4 | Yubico JAAS module 5 | JAAS module for verifying YubiKey OTPs 6 | https://github.com/Yubico/yubico-java-client 7 | jar 8 | 9 | com.yubico 10 | yubico-validation-client 11 | 3.0.5 12 | ../ 13 | 14 | 15 | 16 | Etienne Dysli 17 | etienne.dysli@unil.ch 18 | 19 | 20 | 21 | 22 | 23 | ${project.groupId} 24 | yubico-validation-client2 25 | ${project.version} 26 | 27 | 28 | org.slf4j 29 | slf4j-api 30 | 1.6.1 31 | 32 | 33 | edu.vt.middleware 34 | vt-ldap 35 | 3.3.3 36 | 37 | 38 | commons-codec 39 | commons-codec 40 | 1.4 41 | 42 | 43 | org.apache.geronimo.specs 44 | geronimo-jacc_1.4_spec 45 | 1.0 46 | 47 | 48 | org.apache.geronimo.specs 49 | geronimo-servlet_3.0_spec 50 | 1.0 51 | 52 | 53 | junit 54 | junit 55 | 4.13.1 56 | test 57 | 58 | 59 | 60 | 61 | 62 | maven-assembly-plugin 63 | 64 | 65 | jar-with-dependencies 66 | 67 | 68 | 69 | 70 | make-my-jar-with-dependencies 71 | package 72 | 73 | single 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/HttpOathOtpLoginModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | package com.yubico.jaas; 31 | 32 | import java.io.BufferedReader; 33 | import java.io.IOException; 34 | import java.io.InputStreamReader; 35 | import java.net.URL; 36 | import java.net.URLConnection; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | import java.util.Map; 40 | 41 | import javax.security.auth.Subject; 42 | import javax.security.auth.callback.Callback; 43 | import javax.security.auth.callback.CallbackHandler; 44 | import javax.security.auth.callback.NameCallback; 45 | import javax.security.auth.callback.UnsupportedCallbackException; 46 | import javax.security.auth.login.LoginException; 47 | import javax.security.auth.spi.LoginModule; 48 | 49 | import org.apache.commons.codec.binary.Base64; 50 | import org.slf4j.Logger; 51 | import org.slf4j.LoggerFactory; 52 | 53 | /** 54 | * A JAAS module for verifying OATH OTPs (One Time Passwords) using 55 | * HTTP Basic Auth to a backend server doing the real authentication. 56 | * 57 | * @author Fredrik Thulin (fredrik@yubico.com) 58 | * 59 | */ 60 | public class HttpOathOtpLoginModule implements LoginModule { 61 | 62 | /* Options */ 63 | public static final String OPTION_HTTPOATHOTP_PROTECTED_URL = "protectedUrl"; 64 | public static final String OPTION_HTTPOATHOTP_EXPECTED_OUTPUT = "expectedOutput"; 65 | public static final String OPTION_HTTPOATHOTP_MIN_LENGTH = "minLength"; 66 | public static final String OPTION_HTTPOATHOTP_MAX_LENGTH = "maxLength"; 67 | public static final String OPTION_HTTPOATHOTP_REQUIRE_ALL_DIGITS = "requireAllDigits"; 68 | public static final String OPTION_HTTPOATHOTP_ID_REALM = "id_realm"; 69 | 70 | public String protectedUrl; 71 | public String expectedOutput = "Authenticated OK"; 72 | public int minLength = 6; 73 | public int maxLength = 12; 74 | public boolean requireAllDigits = true; 75 | public String idRealm; 76 | 77 | /* JAAS stuff */ 78 | private Subject subject; 79 | private CallbackHandler callbackHandler; 80 | 81 | private final Logger log = LoggerFactory.getLogger(HttpOathOtpLoginModule.class); 82 | 83 | private YubikeyPrincipal principal; 84 | 85 | /* (non-Javadoc) 86 | * @see javax.security.auth.spi.LoginModule#abort() 87 | */ 88 | public boolean abort() throws LoginException { 89 | subject.getPrincipals().remove(this.principal); 90 | return true; 91 | } 92 | 93 | /* (non-Javadoc) 94 | * @see javax.security.auth.spi.LoginModule#commit() 95 | */ 96 | public boolean commit() throws LoginException { 97 | log.trace("In commit()"); 98 | subject.getPrincipals().add(this.principal); 99 | return true; 100 | } 101 | 102 | /* (non-Javadoc) 103 | * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) 104 | */ 105 | public void initialize(Subject newSubject, CallbackHandler newCallbackHandler, 106 | Map sharedState, Map options) { 107 | 108 | log.trace("Initializing HTTP OATH OTP LoginModule"); 109 | 110 | this.subject = newSubject; 111 | this.callbackHandler = newCallbackHandler; 112 | 113 | this.protectedUrl = options.get(OPTION_HTTPOATHOTP_PROTECTED_URL).toString(); 114 | 115 | if (options.get(OPTION_HTTPOATHOTP_EXPECTED_OUTPUT) != null) { 116 | this.expectedOutput = options.get(OPTION_HTTPOATHOTP_EXPECTED_OUTPUT).toString(); 117 | } 118 | 119 | if (options.get(OPTION_HTTPOATHOTP_MIN_LENGTH) != null) { 120 | this.minLength = Integer.parseInt(options.get(OPTION_HTTPOATHOTP_MIN_LENGTH).toString()); 121 | } 122 | if (options.get(OPTION_HTTPOATHOTP_MAX_LENGTH) != null) { 123 | this.maxLength = Integer.parseInt(options.get(OPTION_HTTPOATHOTP_MAX_LENGTH).toString()); 124 | } 125 | if (options.get(OPTION_HTTPOATHOTP_REQUIRE_ALL_DIGITS) != null) { 126 | String s = options.get(OPTION_HTTPOATHOTP_REQUIRE_ALL_DIGITS).toString(); 127 | if (s.equals("true")) { 128 | this.requireAllDigits = true; 129 | } else if (s.equals("false")) { 130 | this.requireAllDigits = false; 131 | } else { 132 | log.error("Bad value for option {}", OPTION_HTTPOATHOTP_REQUIRE_ALL_DIGITS); 133 | } 134 | } 135 | /* Realm of principals added after authentication */ 136 | if (options.get(OPTION_HTTPOATHOTP_ID_REALM) != null) { 137 | this.idRealm = options.get(OPTION_HTTPOATHOTP_ID_REALM).toString(); 138 | } 139 | } 140 | 141 | /* (non-Javadoc) 142 | * @see javax.security.auth.spi.LoginModule#login() 143 | */ 144 | public boolean login() throws LoginException { 145 | log.trace("Begin OATH OTP login"); 146 | 147 | if (callbackHandler == null) { 148 | throw new LoginException("No callback handler available in login()"); 149 | } 150 | 151 | NameCallback nameCb = new NameCallback("Enter username: "); 152 | 153 | List otps = get_tokens(nameCb); 154 | 155 | for (String otp : otps) { 156 | String userName = nameCb.getName(); 157 | 158 | log.trace("Checking OATH OTP for user {}", userName); 159 | 160 | if (verify_otp(userName, otp)) { 161 | log.info("OATH OTP verified successfully"); 162 | principal = new YubikeyPrincipal(userName, this.idRealm); 163 | return true; 164 | } 165 | log.info("OATH OTP did NOT verify"); 166 | } 167 | throw new LoginException("OATH OTP verification failed"); 168 | } 169 | 170 | /** 171 | * Access protectedUrl using userName and otp for basic auth. 172 | * Check if what we get back contains expectedOutput. 173 | * @param userName 174 | * @param otp 175 | * @return boolean 176 | */ 177 | boolean verify_otp(String userName, String otp) { 178 | try { 179 | String authString = userName + ":" + otp; 180 | String authStringEnc = Base64.encodeBase64URLSafeString(authString.getBytes()); 181 | 182 | BufferedReader in = attemptAuthentication(authStringEnc); 183 | 184 | String inputLine; 185 | while ((inputLine = in.readLine()) != null) { 186 | if (inputLine.contains(expectedOutput)) { 187 | return true; 188 | } 189 | } 190 | } catch (Exception ex) { 191 | log.error("Failed verifying OATH OTP :", ex); 192 | } 193 | return false; 194 | } 195 | 196 | BufferedReader attemptAuthentication(String authStringEnc) throws IOException { 197 | URL url = new URL(this.protectedUrl); 198 | URLConnection conn = url.openConnection(); 199 | conn.setRequestProperty("Authorization", "Basic " + authStringEnc); 200 | conn.connect(); 201 | 202 | return new BufferedReader(new InputStreamReader( 203 | conn.getInputStream() 204 | )); 205 | } 206 | 207 | private List get_tokens(NameCallback nameCb) throws LoginException { 208 | MultiValuePasswordCallback mv_passCb = new MultiValuePasswordCallback("Enter authentication tokens: ", false); 209 | List result = new ArrayList(); 210 | 211 | try { 212 | /* Fetch a password using the callbackHandler */ 213 | callbackHandler.handle(new Callback[] { nameCb, mv_passCb }); 214 | 215 | for (char[] c : mv_passCb.getSecrets()) { 216 | String s = new String(c); 217 | /* Check that OTP looks like OATH before we verify it. User might have entered 218 | * some other password instead of an OTP, and we don't want to send that, possibly 219 | * in clear text, over the network. 220 | */ 221 | if (s.length() < this.minLength) { 222 | log.info("Skipping token, not a valid OATH OTP token (too short, {} < {})", s.length(), this.minLength); 223 | } else if (s.length() > this.maxLength) { 224 | log.info("Skipping token, not a valid OATH OTP token (too long, {} > {})", s.length(), this.maxLength); 225 | } else { 226 | if (this.requireAllDigits) { 227 | if (s.matches("^[0-9]+$")) { 228 | result.add(s); 229 | } else { 230 | log.info("Skipping token, not a valid OATH OTP token (non-digits not allowed)"); 231 | } 232 | } else { 233 | result.add(s); 234 | } 235 | } 236 | } 237 | } catch (UnsupportedCallbackException ex) { 238 | log.error("Callback type not supported", ex); 239 | } catch (IOException ex) { 240 | log.error("CallbackHandler failed", ex); 241 | } 242 | 243 | return result; 244 | } 245 | 246 | /* (non-Javadoc) 247 | * @see javax.security.auth.spi.LoginModule#logout() 248 | */ 249 | public boolean logout() throws LoginException { 250 | log.trace("In logout()"); 251 | subject.getPrincipals().remove(this.principal); 252 | return false; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/HttpOathOtpPrincipal.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | package com.yubico.jaas; 32 | 33 | import java.security.Principal; 34 | 35 | /** 36 | * @author Fredrik Thulin (fredrik@yubico.com) 37 | * 38 | */ 39 | public class HttpOathOtpPrincipal implements Principal { 40 | /** The name of the principal. */ 41 | private String name; 42 | /** The realm of this principal. Something like a domain name. */ 43 | private String realm = null; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param name principal's name. 49 | */ 50 | public HttpOathOtpPrincipal(String name) { 51 | this.name = name; 52 | } 53 | 54 | /** 55 | * Constructor. 56 | * 57 | * @param id principal's name. 58 | * @param realm Realm of id. 59 | */ 60 | public HttpOathOtpPrincipal(String id, String realm) { 61 | this.name = id; 62 | this.realm = realm; 63 | } 64 | 65 | /** {@inheritDoc} */ 66 | public String getName() { 67 | if (realm != null) { 68 | return this.name + this.realm; 69 | } 70 | return name; 71 | } 72 | 73 | /** {@inheritDoc} */ 74 | public String toString() { 75 | return "" + getName(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/MultiValuePasswordCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | package com.yubico.jaas; 31 | 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | 35 | import javax.security.auth.callback.PasswordCallback; 36 | 37 | /** 38 | * A class that extends PasswordCallback to keep a list of all values 39 | * set using setPassword(). If the application using this JAAS plugin 40 | * wants to pass us multiple authentication factors, it just calls 41 | * setPassword() more than once in the CallbackHandler. 42 | * 43 | * @author Fredrik Thulin (fredrik@yubico.com) 44 | * 45 | */ 46 | public class MultiValuePasswordCallback extends PasswordCallback { 47 | private static final long serialVersionUID = 5362005708680822656L; 48 | private ArrayList secrets = new ArrayList(); 49 | 50 | public MultiValuePasswordCallback(String prompt, boolean echoOn) { 51 | super(prompt, echoOn); 52 | } 53 | 54 | /** 55 | * @return Returns all the secrets. 56 | */ 57 | public List getSecrets() { 58 | return secrets; 59 | } 60 | 61 | /** 62 | * @param password A secret to add to our list. 63 | */ 64 | public void setPassword(char[] password) { 65 | this.secrets.add(password); 66 | } 67 | 68 | /** 69 | * Tries to clear all the passwords from memory. 70 | */ 71 | public void clearPassword() { 72 | for (char pw[] : this.secrets) { 73 | for (int i = 0; i < pw.length; i++) { 74 | pw[i] = 0; 75 | } 76 | } 77 | 78 | /* Now discard the list. */ 79 | this.secrets = new ArrayList(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/YubikeyLoginModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | package com.yubico.jaas; 31 | 32 | 33 | import java.io.IOException; 34 | import java.lang.reflect.InvocationTargetException; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | import java.util.Map; 38 | 39 | import javax.security.auth.Subject; 40 | import javax.security.auth.callback.Callback; 41 | import javax.security.auth.callback.CallbackHandler; 42 | import javax.security.auth.callback.NameCallback; 43 | import javax.security.auth.callback.UnsupportedCallbackException; 44 | import javax.security.auth.login.LoginException; 45 | import javax.security.auth.spi.LoginModule; 46 | import javax.security.jacc.PolicyContext; 47 | import javax.security.jacc.PolicyContextException; 48 | import javax.servlet.http.HttpServletRequest; 49 | 50 | import com.yubico.client.v2.ResponseStatus; 51 | import com.yubico.client.v2.VerificationResponse; 52 | 53 | import org.slf4j.Logger; 54 | import org.slf4j.LoggerFactory; 55 | 56 | import com.yubico.client.v2.YubicoClient; 57 | import com.yubico.client.v2.exceptions.YubicoVerificationException; 58 | import com.yubico.client.v2.exceptions.YubicoValidationFailure; 59 | 60 | /** 61 | * A JAAS module for verifying OTPs (One Time Passwords) against a 62 | * Yubikey Validation Service. 63 | * 64 | * @author Fredrik Thulin (fredrik@yubico.com) 65 | * 66 | */ 67 | public class YubikeyLoginModule implements LoginModule { 68 | /* Options for this class. 69 | * Note that the options map is shared with other classes, like YubikeyToUserMap. 70 | */ 71 | public static final String OPTION_YUBICO_CLIENT_ID = "clientId"; 72 | public static final String OPTION_YUBICO_CLIENT_KEY = "clientKey"; 73 | public static final String OPTION_YUBICO_ID_REALM = "id_realm"; 74 | public static final String OPTION_YUBICO_SOFT_FAIL_NO_OTPS = "soft_fail_on_no_otps"; 75 | public static final String OPTION_YUBICO_WSAPI_URLS = "wsapi_urls"; 76 | public static final String OPTION_YUBICO_USERMAP_CLASS = "usermap_class"; 77 | public static final String OPTION_YUBICO_SYNC_POLICY = "sync_policy"; 78 | public static final String OPTION_YUBICO_JACC = "jacc"; 79 | public static final String JACC_ATTR_WEB_REQUEST_KEY = "javax.servlet.http.HttpServletRequest"; 80 | public static final String HTTP_REQUEST_ATTR_TOTP = "j_otp"; 81 | 82 | /* JAAS stuff */ 83 | private Subject subject; 84 | private CallbackHandler callbackHandler; 85 | 86 | /* YubicoClient settings */ 87 | private YubicoClient yc; 88 | 89 | private boolean soft_fail_on_no_otps; 90 | private boolean jacc; 91 | 92 | private YubikeyToUserMap ykmap; 93 | private String idRealm; 94 | 95 | private final Logger log = LoggerFactory.getLogger(YubikeyLoginModule.class); 96 | 97 | private ArrayList principals = new ArrayList(); 98 | 99 | 100 | /* (non-Javadoc) 101 | * @see javax.security.auth.spi.LoginModule#abort() 102 | */ 103 | public boolean abort() throws LoginException { 104 | log.trace("In abort()"); 105 | for (YubikeyPrincipal p : this.principals) { 106 | this.subject.getPrincipals().remove(p); 107 | } 108 | return true; 109 | } 110 | 111 | /* (non-Javadoc) 112 | * @see javax.security.auth.spi.LoginModule#commit() 113 | */ 114 | public boolean commit() throws LoginException { 115 | log.trace("In commit()"); 116 | for (YubikeyPrincipal p : this.principals) { 117 | log.debug("Committing principal {}", p); 118 | this.subject.getPrincipals().add(p); 119 | } 120 | return true; 121 | } 122 | 123 | /* (non-Javadoc) 124 | * @see javax.security.auth.spi.LoginModule#logout() 125 | */ 126 | public boolean logout() throws LoginException { 127 | log.trace("In logout()"); 128 | for (YubikeyPrincipal p : this.principals) { 129 | this.subject.getPrincipals().remove(p); 130 | } 131 | return false; 132 | } 133 | 134 | /* (non-Javadoc) 135 | * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) 136 | */ 137 | public void initialize(Subject newSubject, CallbackHandler newCallbackHandler, 138 | Map sharedState, Map options) { 139 | 140 | log.debug("Initializing YubikeyLoginModule"); 141 | this.subject = newSubject; 142 | this.callbackHandler = newCallbackHandler; 143 | 144 | /* Yubico verification client */ 145 | Integer clientId = Integer.parseInt(options.get(OPTION_YUBICO_CLIENT_ID).toString()); 146 | String clientKey = options.get(OPTION_YUBICO_CLIENT_KEY).toString(); 147 | this.yc = YubicoClient.getClient(clientId, clientKey); 148 | 149 | /* Realm of principals added after authentication */ 150 | if (options.containsKey(OPTION_YUBICO_ID_REALM)) { 151 | this.idRealm = options.get(OPTION_YUBICO_ID_REALM).toString(); 152 | } 153 | 154 | /* Should this JAAS module be ignored when no OTPs are supplied? */ 155 | if (options.containsKey(OPTION_YUBICO_SOFT_FAIL_NO_OTPS)) { 156 | if ("true".equals(options.get(OPTION_YUBICO_SOFT_FAIL_NO_OTPS).toString())) { 157 | this.soft_fail_on_no_otps = true; 158 | } 159 | } 160 | 161 | /* Should this JAAS module uses j_otp form to pick up otp */ 162 | if (options.containsKey(OPTION_YUBICO_JACC)) { 163 | if ("true".equals(options.get(OPTION_YUBICO_JACC).toString())) { 164 | this.jacc = true; 165 | } 166 | } 167 | 168 | /* User-provided URLs to the Yubico validation service, separated by "|". */ 169 | if (options.containsKey(OPTION_YUBICO_WSAPI_URLS)) { 170 | String in = options.get(OPTION_YUBICO_WSAPI_URLS).toString(); 171 | String l[] = in.split("\\|"); 172 | this.yc.setWsapiUrls(l); 173 | } 174 | 175 | if (options.containsKey(OPTION_YUBICO_SYNC_POLICY)) { 176 | this.yc.setSync(Integer.parseInt(options.get(OPTION_YUBICO_SYNC_POLICY).toString())); 177 | } 178 | 179 | /* Instantiate the specified usermap implementation. */ 180 | String usermap_class_name = null; 181 | if (options.containsKey(OPTION_YUBICO_USERMAP_CLASS)) { 182 | usermap_class_name = options.get(OPTION_YUBICO_USERMAP_CLASS).toString(); 183 | } else { 184 | usermap_class_name = "com.yubico.jaas.impl.YubikeyToUserMapImpl"; // Default implementation 185 | } 186 | try { 187 | log.debug("Trying to instantiate {}",usermap_class_name); 188 | this.ykmap = (YubikeyToUserMap) Class.forName(usermap_class_name).getDeclaredConstructor().newInstance(); 189 | this.ykmap.setOptions(options); 190 | } catch (ClassNotFoundException ex) { 191 | log.error("Could not create usermap from class " + usermap_class_name, ex); 192 | } catch (InstantiationException ex) { 193 | log.error("Could not create usermap from class " + usermap_class_name, ex); 194 | } catch (IllegalAccessException ex) { 195 | log.error("Could not create usermap from class " + usermap_class_name, ex); 196 | } catch (NoSuchMethodException ex) { 197 | log.error("Could not create usermap from class " + usermap_class_name, ex); 198 | } catch (InvocationTargetException ex) { 199 | log.error("Could not create usermap from class " + usermap_class_name, ex); 200 | } 201 | } 202 | 203 | /* (non-Javadoc) 204 | * @see javax.security.auth.spi.LoginModule#login() 205 | */ 206 | public boolean login() throws LoginException { 207 | NameCallback nameCb = new NameCallback("Enter username: "); 208 | 209 | log.debug("Begin OTP login"); 210 | 211 | if (callbackHandler == null) { 212 | throw new LoginException("No callback handler available in login()"); 213 | } 214 | 215 | List otps = get_tokens(nameCb); 216 | if (otps.size() == 0) { 217 | if (this.soft_fail_on_no_otps) { 218 | 219 | log.debug("No OTPs found, and soft-fail is on. Making JAAS ignore this module."); 220 | return false; 221 | } 222 | throw new LoginException("YubiKey OTP authentication failed - no OTPs supplied"); 223 | } 224 | 225 | if (validate_otps(otps, nameCb)) { 226 | return true; 227 | } 228 | 229 | log.info("None out of {} possible YubiKey OTPs for user {} validated successful", 230 | otps.size(), nameCb.getName()); 231 | 232 | throw new LoginException("YubiKey OTP authentication failed"); 233 | } 234 | 235 | /** 236 | * Try to validate all the OTPs provided. 237 | * @param otps Possible YubiKey OTPs 238 | * @param nameCb JAAS callback to get authenticating username 239 | * @return true if one or more of the OTPs validated OK, otherwise false 240 | * @throws LoginException for exceptions during validation, logging real exception at warn level 241 | */ 242 | private boolean validate_otps(List otps, NameCallback nameCb) throws LoginException { 243 | boolean validated = false; 244 | 245 | for (String otp : otps) { 246 | log.trace("Checking OTP {}", otp); 247 | 248 | VerificationResponse ykr; 249 | try { 250 | ykr = this.yc.verify(otp); 251 | } catch (YubicoVerificationException e) { 252 | log.warn("Errors during validation: ", e); 253 | throw new LoginException("Errors during validation: " + e.getMessage()); 254 | } catch (YubicoValidationFailure e) { 255 | log.warn("Something went very wrong during authentication: ", e); 256 | throw new LoginException("Something went very wrong during authentication: " + e.getMessage()); 257 | } 258 | if (ykr != null) { 259 | log.trace("OTP {} verify result : {}", otp, ykr.getStatus().toString()); 260 | if (ykr.getStatus() == ResponseStatus.OK) { 261 | String publicId = YubicoClient.getPublicId(otp); 262 | log.info("OTP verified successfully (YubiKey id {})", publicId); 263 | if (is_right_user(nameCb.getName(), publicId)) { 264 | this.principals.add(new YubikeyPrincipal(publicId, this.idRealm)); 265 | /* Don't just return here, we want to "consume" all OTPs if 266 | * more than one is provided. 267 | */ 268 | validated = true; 269 | } 270 | } else { 271 | log.debug("OTP validation returned {}", ykr.getStatus().toString()); 272 | } 273 | } 274 | } 275 | 276 | return validated; 277 | } 278 | 279 | /** 280 | * After validation of an OTP, check that it came from a YubiKey that actually 281 | * belongs to the user trying to authenticate. 282 | * 283 | * @param username Username to match against YubiKey publicId. 284 | * @param publicId The public ID of the authenticated YubiKey. 285 | * @return true if the username matched the YubiKey, false otherwise 286 | */ 287 | private boolean is_right_user(String username, String publicId) { 288 | log.debug("Check if YubiKey {} belongs to user {}", publicId, username); 289 | return this.ykmap.is_right_user(username, publicId); 290 | } 291 | 292 | /** 293 | * Get username and token(s) from the application, using the 294 | * javax.security.auth.callback.CallbackHandler passed to our initialize() 295 | * function. 296 | * 297 | * The tokens returned have been identified as plausible YubiKey OTPs. 298 | * 299 | * @param nameCb 300 | * @return list of possible YubiKey OTPs 301 | */ 302 | private List get_tokens(NameCallback nameCb) { 303 | MultiValuePasswordCallback mv_passCb = new MultiValuePasswordCallback("Enter authentication tokens: ", false); 304 | List result = new ArrayList(); 305 | 306 | try { 307 | /* Fetch a password using the callbackHandler */ 308 | callbackHandler.handle(new Callback[] { nameCb, mv_passCb }); 309 | 310 | for (char[] c : mv_passCb.getSecrets()) { 311 | String s = new String(c); 312 | /* Check that OTP is at least 32 chars before we verify it. User might have entered 313 | * some other password instead of an OTP, and we don't want to send that, possibly 314 | * in clear text, over the network. 315 | */ 316 | if (s.length() < 32) { 317 | log.debug("Skipping token, not a valid YubiKey OTP (too short, {} < 32)", s.length()); 318 | } else { 319 | result.add(s); 320 | } 321 | } 322 | } catch (UnsupportedCallbackException ex) { 323 | log.error("Callback type not supported", ex); 324 | } catch (IOException ex) { 325 | log.error("CallbackHandler failed", ex); 326 | } 327 | 328 | if (jacc) { 329 | // This is JACC specific mechanism 330 | try { 331 | HttpServletRequest request = (HttpServletRequest) PolicyContext 332 | .getContext(JACC_ATTR_WEB_REQUEST_KEY); 333 | String j_otp = request.getParameter(HTTP_REQUEST_ATTR_TOTP); 334 | 335 | if (j_otp == null || j_otp.length() < 32) { 336 | log.debug("Skipping token from j_otp, not a valid YubiKey OTP (too short, {} < 32)", j_otp == null ? 0 : j_otp.length()); 337 | } else { 338 | result.add(j_otp); 339 | } 340 | log.debug("OTP from j_otp token : {}", j_otp); 341 | } catch (PolicyContextException e) { 342 | log.debug("No OTP from j_otp token)"); 343 | } 344 | } 345 | 346 | return result; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/YubikeyPrincipal.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | package com.yubico.jaas; 32 | 33 | import java.security.Principal; 34 | 35 | /** 36 | * @author Fredrik Thulin (fredrik@yubico.com) 37 | * 38 | */ 39 | public class YubikeyPrincipal implements Principal { 40 | /** The public ID of a Yubikey */ 41 | private String publicId; 42 | /** The realm of this id. Something like a domain name. */ 43 | private String realm = null; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param id principal's name - YubiKey public id. 49 | */ 50 | public YubikeyPrincipal(String id) { 51 | this.publicId = id; 52 | } 53 | 54 | /** 55 | * Constructor. 56 | * 57 | * @param id principal's name - YubiKey public id. 58 | * @param realm Realm of id. 59 | */ 60 | public YubikeyPrincipal(String id, String realm) { 61 | this.publicId = id; 62 | this.realm = realm; 63 | } 64 | 65 | /** {@inheritDoc} */ 66 | public String getName() { 67 | if (realm != null) { 68 | return this.publicId + this.realm; 69 | } 70 | return publicId; 71 | } 72 | 73 | /** {@inheritDoc} */ 74 | public String toString() { 75 | return "" + getName(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/YubikeyToUserMap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | package com.yubico.jaas; 31 | 32 | import java.util.Map; 33 | 34 | /** 35 | * Interface to verify relationship between a username and a YubiKey. 36 | * Classes implementing this interface MUST have a nullary constructor 37 | * for {@link java.lang.Class#newInstance()} to work. 38 | * 39 | * @author Fredrik Thulin (fredrik@yubico.com) 40 | * 41 | */ 42 | public interface YubikeyToUserMap { 43 | 44 | /* 45 | * Verify that there is a known connection between username and publicId. 46 | * If auto-provisioning is enabled and no connection is found, one is registered. 47 | * 48 | * @param username username to match to YubiKey id 49 | * @publicId modhex encoded public id of a YubiKey (e.g. "vvcccccfhc") 50 | * 51 | */ 52 | public boolean is_right_user(String username, String publicId); 53 | 54 | /** 55 | * Sets configuration options received from JAAS. 56 | * 57 | * @param options Configuration options 58 | */ 59 | public void setOptions(Map options); 60 | } 61 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/impl/YubikeyToUserLDAPMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011, Yubico AB. All rights reserved. 3 | * This file is derivative work from YubikeyToUserMapImpl.java in the 4 | * Yubico Java client. The following copyright applies to the 5 | * derivative work: 6 | * Copyright 2011, Université de Lausanne. All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 15 | * * Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in 17 | * the documentation and/or other materials provided with the 18 | * distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 31 | * OF THE POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | package com.yubico.jaas.impl; 35 | 36 | import com.yubico.jaas.YubikeyToUserMap; 37 | import edu.vt.middleware.ldap.Ldap; 38 | import edu.vt.middleware.ldap.LdapConfig; 39 | import edu.vt.middleware.ldap.SearchFilter; 40 | import java.util.Iterator; 41 | import java.util.Map; 42 | import javax.naming.NamingException; 43 | import javax.naming.directory.Attributes; 44 | import javax.naming.directory.SearchResult; 45 | import org.slf4j.Logger; 46 | import org.slf4j.LoggerFactory; 47 | 48 | /** 49 | * Class to verify that a user is the rightful owner of a YubiKey. 50 | * 51 | * This implementation uses a LDAP directory to look up the Yubikey's 52 | * publicId and fetch the associated username. 53 | * 54 | * @author Etienne Dysli (etienne.dysli@unil.ch) 55 | */ 56 | public class YubikeyToUserLDAPMap implements YubikeyToUserMap { 57 | /* Supported JAAS configuration options */ 58 | /** Enable verification of Yubikey owner (default: true) */ 59 | public static final String OPTION_YUBICO_VERIFY_YK_OWNER = "verify_yubikey_owner"; 60 | /** Name of the LDAP attribute containing the Yubikey publicId (default: empty) */ 61 | public static final String OPTION_YUBICO_LDAP_PUBLICID_ATTRIBUTE = "ldap_publicid_attribute"; 62 | /** Name of the LDAP attribute containing the username (default: "uid") */ 63 | public static final String OPTION_YUBICO_LDAP_USERNAME_ATTRIBUTE = "ldap_username_attribute"; 64 | /** URL of the LDAP directory (default: empty) */ 65 | public static final String OPTION_LDAP_URL = "ldap_url"; 66 | /** Base DN for LDAP searches (default: empty) */ 67 | public static final String OPTION_LDAP_BASE_DN = "ldap_base_dn"; 68 | /** DN used to log into the LDAP directory (default: empty) */ 69 | public static final String OPTION_LDAP_BIND_DN = "ldap_bind_dn"; 70 | /** Password for the bind DN (default: empty) */ 71 | public static final String OPTION_LDAP_BIND_CREDENTIAL = "ldap_bind_credential"; 72 | private boolean verify_yubikey_owner = true; 73 | private String publicid_attribute = ""; 74 | private String username_attribute = "uid"; 75 | private String ldap_url = ""; 76 | private String ldap_base_dn = ""; 77 | private String ldap_bind_dn = ""; 78 | private String ldap_bind_credential = ""; 79 | private Ldap ldap; 80 | private final Logger log = LoggerFactory.getLogger(YubikeyToUserLDAPMap.class); 81 | 82 | /** {@inheritDoc} */ 83 | public boolean is_right_user(String username, String publicId) { 84 | log.trace("In is_right_user()"); 85 | if (!this.verify_yubikey_owner) { 86 | log.debug("YubiKey owner verification disabled, returning 'true'"); 87 | return true; 88 | } 89 | String ykuser = null; 90 | try { 91 | SearchFilter filter = new SearchFilter("({0}={1})", new String[]{this.publicid_attribute, publicId}); 92 | log.debug("Searching for YubiKey publicId with filter: {}", filter.toString()); 93 | Iterator results = ldap.search(filter, new String[]{this.username_attribute}); 94 | if (results.hasNext()) { 95 | Attributes results_attributes = results.next().getAttributes(); 96 | log.debug("Found attributes: {}", results_attributes.toString()); 97 | ykuser = results_attributes.get(this.username_attribute).get().toString(); 98 | } else { 99 | log.debug("No search results"); 100 | } 101 | } catch (NamingException ex) { 102 | log.error(ex.getMessage(), ex); 103 | return false; 104 | } 105 | if (ykuser != null) { 106 | if (!ykuser.equals(username)) { 107 | log.info("YubiKey " + publicId + " registered to user {}, NOT {}", ykuser, username); 108 | return false; 109 | } else { 110 | log.info("YubiKey " + publicId + " registered to user {}", ykuser); 111 | return true; 112 | } 113 | } else { 114 | log.info("No record of YubiKey {} found. Returning 'false'.", publicId); 115 | return false; 116 | } 117 | } 118 | 119 | /** {@inheritDoc} */ 120 | public final void setOptions(Map options) { 121 | /* Is verification of YubiKey owners enabled? */ 122 | this.verify_yubikey_owner = true; 123 | if (options.get(OPTION_YUBICO_VERIFY_YK_OWNER) != null) { 124 | if ("false".equals(options.get(OPTION_YUBICO_VERIFY_YK_OWNER).toString())) { 125 | this.verify_yubikey_owner = false; 126 | } 127 | } 128 | if (options.get(OPTION_YUBICO_LDAP_PUBLICID_ATTRIBUTE) != null) { 129 | this.publicid_attribute = options.get(OPTION_YUBICO_LDAP_PUBLICID_ATTRIBUTE).toString(); 130 | } 131 | if (options.get(OPTION_YUBICO_LDAP_USERNAME_ATTRIBUTE) != null) { 132 | this.username_attribute = options.get(OPTION_YUBICO_LDAP_USERNAME_ATTRIBUTE).toString(); 133 | } 134 | if (options.get(OPTION_LDAP_URL) != null) { 135 | this.ldap_url = options.get(OPTION_LDAP_URL).toString(); 136 | } 137 | if (options.get(OPTION_LDAP_BASE_DN) != null) { 138 | this.ldap_base_dn = options.get(OPTION_LDAP_BASE_DN).toString(); 139 | } 140 | if (options.get(OPTION_LDAP_BIND_DN) != null) { 141 | this.ldap_bind_dn = options.get(OPTION_LDAP_BIND_DN).toString(); 142 | } 143 | if (options.get(OPTION_LDAP_BIND_CREDENTIAL) != null) { 144 | this.ldap_bind_credential = options.get(OPTION_LDAP_BIND_CREDENTIAL).toString(); 145 | } 146 | LdapConfig config = new LdapConfig(this.ldap_url, this.ldap_base_dn); 147 | config.setBindDn(this.ldap_bind_dn); 148 | config.setBindCredential(this.ldap_bind_credential); 149 | this.ldap = new Ldap(config); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /jaas/src/main/java/com/yubico/jaas/impl/YubikeyToUserMapImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011, Yubico AB. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | package com.yubico.jaas.impl; 31 | 32 | import java.io.File; 33 | import java.io.FileNotFoundException; 34 | import java.io.FileWriter; 35 | import java.io.IOException; 36 | import java.util.Map; 37 | import java.util.Scanner; 38 | 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | import com.yubico.jaas.YubikeyToUserMap; 43 | 44 | /** 45 | * Class to verify that a user is the rightful user of a YubiKey. 46 | * 47 | * The current implementation is very rudimentary and uses a plain text file 48 | * as a backend, expecting lines like "yk.vvcccfhcb.user = alice". If "bob" 49 | * tries to authenticate with YubiKey "vvcccfhcb" he should be denied. 50 | * 51 | * There is also support for auto provisioning (default: false). If the option 52 | * auto_provision_owner is set to "true", a user logging in without a prior 53 | * YubiKey association will have the YubiKey they are using associated with them. 54 | * This should probably only be used during an initial time window when YubiKey 55 | * tokens are distributed to users, after which the auto provisioning should be 56 | * turned off again. 57 | * 58 | * @author Fredrik Thulin (fredrik@yubico.com) 59 | * 60 | */ 61 | public class YubikeyToUserMapImpl implements YubikeyToUserMap { 62 | /* Options for this class */ 63 | public static final String OPTION_YUBICO_AUTO_PROVISION = "auto_provision_owner"; 64 | public static final String OPTION_YUBICO_ID2NAME_TEXTFILE = "id2name_textfile"; 65 | public static final String OPTION_YUBICO_VERIFY_YK_OWNER = "verify_yubikey_owner"; 66 | private String id2name_textfile; 67 | private boolean auto_provision_owners = false; 68 | private boolean verify_yubikey_owner = true; 69 | private final Logger log = LoggerFactory.getLogger(YubikeyToUserMapImpl.class); 70 | 71 | /** {@inheritDoc} */ 72 | public final void setOptions(Map options) { 73 | /* Is verification of YubiKey owners enabled? */ 74 | this.verify_yubikey_owner = true; 75 | if (options.get(OPTION_YUBICO_VERIFY_YK_OWNER) != null) { 76 | if ("false".equals(options.get(OPTION_YUBICO_VERIFY_YK_OWNER).toString())) { 77 | this.verify_yubikey_owner = false; 78 | } 79 | } 80 | 81 | /* id2name text file */ 82 | if (options.get(OPTION_YUBICO_ID2NAME_TEXTFILE) != null) { 83 | this.id2name_textfile = options.get(OPTION_YUBICO_ID2NAME_TEXTFILE).toString(); 84 | } 85 | 86 | /* should we automatically assign new yubikeys to users? */ 87 | if (options.get(OPTION_YUBICO_AUTO_PROVISION) != null) { 88 | if ("true".equals(options.get(OPTION_YUBICO_AUTO_PROVISION).toString())) { 89 | this.auto_provision_owners = true; 90 | } 91 | } 92 | } 93 | 94 | /** {@inheritDoc} */ 95 | public boolean is_right_user(String username, String publicId) { 96 | if (! this.verify_yubikey_owner) { 97 | log.debug("YubiKey owner verification disabled, returning 'true'"); 98 | return true; 99 | } 100 | if (this.id2name_textfile == null) { 101 | log.debug("No id2name configuration. Defaulting to {}.", this.verify_yubikey_owner); 102 | return this.verify_yubikey_owner; 103 | } 104 | 105 | String ykuser; 106 | try { 107 | ykuser = get_username_for_id(publicId, this.id2name_textfile); 108 | } catch (FileNotFoundException ex) { 109 | log.error("Yubikey to username textfile {} not found", this.id2name_textfile); 110 | return false; 111 | } 112 | 113 | if (ykuser != null) { 114 | if (! ykuser.equals(username)) { 115 | log.info("YubiKey " + publicId + " registered to user {}, NOT {}", ykuser, username); 116 | return false; 117 | } 118 | return true; 119 | } else { 120 | if (this.auto_provision_owners) { 121 | log.info("Registering new YubiKey " + publicId + " as belonging to {}", username); 122 | 123 | add_yubikey_to_user(publicId, username, this.id2name_textfile); 124 | return true; 125 | } 126 | log.debug("No record of YubiKey {} found. Returning 'false'.", publicId); 127 | return false; 128 | } 129 | } 130 | 131 | /** 132 | * Given publicId "vvcccccfhc", scans filename for a line like "yk.vvcccccfhc.user = alice" 133 | * and returns "alice" if found. Null is returned in case there is no matching line in file. 134 | * 135 | * @param publicId YubiKey public ID to scan for 136 | * @param filename name of the file to scan 137 | * @return String username 138 | * @throws FileNotFoundException if the filename does not match an existing file 139 | */ 140 | private String get_username_for_id(String publicId, String filename) throws FileNotFoundException { 141 | Scanner sc = null; 142 | File file = new File(filename); 143 | try { 144 | sc = new Scanner(file); 145 | while (sc.hasNextLine()) { 146 | String line = sc.nextLine(); 147 | if (line.startsWith("yk." + publicId + ".user")) { 148 | String ykuser = line.split("=")[1].trim(); 149 | 150 | return ykuser; 151 | } 152 | } 153 | } finally { 154 | if (sc != null) { 155 | sc.close(); 156 | } 157 | } 158 | return null; 159 | } 160 | 161 | /** 162 | * Stores an association between username and YubiKey publicId in filename. 163 | * 164 | * @param publicId YubiKey public ID to associate with user 165 | * @param username username to associate with a YubiKey public ID 166 | * @param filename name of the file to store to 167 | */ 168 | private void add_yubikey_to_user(String publicId, String username, String filename) { 169 | try { 170 | File file = new File(filename); 171 | FileWriter writer = new FileWriter(file, true); 172 | writer.write("yk." + publicId + ".user = " + username 173 | + System.getProperty("line.separator")); 174 | writer.close(); 175 | } catch (IOException ex) { 176 | log.error("Failed appending entry to file {}", filename, ex); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /jaas/src/test/java/com/yubico/jaas/HttpOathOtpLoginModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.yubico.jaas; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.StringReader; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class HttpOathOtpLoginModuleTest { 12 | 13 | private static class LocalOathOtpLoginModule extends HttpOathOtpLoginModule { 14 | @Override 15 | BufferedReader attemptAuthentication(String authStringEnc) throws IOException { 16 | if ("Pz8_Pz8_Pz8_Pz8_OmNjY2NjY2dldGJraGJramlkZnZuZ3J0a2tpdWV2YmJnbGt0dWpkdWtjbnZs".equals(authStringEnc)) { 17 | return new BufferedReader(new StringReader("Authenticated OK")); 18 | } else { 19 | return new BufferedReader(new StringReader("Authentication not OK")); 20 | } 21 | } 22 | } 23 | 24 | @Test 25 | public void testVerifyBadOtp() { 26 | assertFalse( 27 | new LocalOathOtpLoginModule() 28 | .verify_otp("????????????", "ccccccgetbkhtdelccclkdtugcljfjbjikbvhlbkhllb") 29 | ); 30 | } 31 | 32 | @Test 33 | public void testVerifyGoodOtp() { 34 | assertTrue( 35 | new LocalOathOtpLoginModule() 36 | .verify_otp("????????????", "ccccccgetbkhbkjidfvngrtkkiuevbbglktujdukcnvl") 37 | ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.yubico 4 | yubico-validation-client 5 | 3.0.5 6 | Yubico OTP validation client 7 | Client library written in Java for verifying Yubikey one-time passwords (OTPs). 8 | 9 | Yubico AB 10 | http://www.yubico.com/ 11 | 12 | https://developers.yubico.com/yubico-java-client/ 13 | pom 14 | 15 | UTF-8 16 | 17 | 18 | 19 | 20 | emil 21 | Emil Lundberg 22 | emil@yubico.com 23 | 24 | 25 | nigel 26 | Nigel Williams 27 | nigel.williams@yubico.com 28 | 29 | 30 | 31 | 32 | 33 | BSD-license 34 | Revised 2-clause BSD license 35 | 36 | 37 | 38 | 39 | scm:git:git://github.com/Yubico/yubico-java-client.git 40 | scm:git:git://github.com/Yubico/yubico-java-client.git 41 | scm:git:ssh://git@github.com/Yubico/yubico-java-client.git 42 | 43 | 44 | 45 | v2client 46 | jaas 47 | demo-server 48 | 49 | 50 | 51 | 52 | sonatype-nexus-staging 53 | Nexus Staging Repo 54 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 55 | 56 | 57 | sonatype-nexus-snapshots 58 | Nexus Snapshot Repo 59 | https://oss.sonatype.org/content/repositories/snapshots/ 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-compiler-plugin 68 | 2.0.2 69 | 70 | 1.7 71 | 1.7 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-release-plugin 77 | 2.2.2 78 | 79 | true 80 | false 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-source-plugin 86 | 2.1.2 87 | 88 | 89 | attach-sources 90 | verify 91 | 92 | jar-no-fork 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-javadoc-plugin 100 | 2.8.1 101 | 102 | private 103 | true 104 | 105 | 106 | 107 | attach-javadoc 108 | verify 109 | 110 | jar 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | release-sign-artifacts 120 | 121 | 122 | performRelease 123 | true 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-gpg-plugin 131 | 1.1 132 | 133 | 134 | sign-artifacts 135 | verify 136 | 137 | sign 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | test-coveralls 147 | 148 | 149 | env.COVERALLS 150 | true 151 | 152 | 153 | 154 | 155 | 156 | org.jacoco 157 | jacoco-maven-plugin 158 | 0.8.3 159 | 160 | 161 | prepare-agent 162 | 163 | prepare-agent 164 | 165 | 166 | 167 | 168 | 169 | org.eluder.coveralls 170 | coveralls-maven-plugin 171 | 3.0.1 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /v2client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | yubico-validation-client2 4 | Yubico OTP validation client protocol 2 5 | Client library written in Java for verifying Yubikey one-time passwords (OTPs) with validation protocol 2. 6 | jar 7 | 8 | 9 | com.yubico 10 | yubico-validation-client 11 | 3.0.5 12 | ../ 13 | 14 | 15 | 16 | 17 | Linus Widströmer 18 | linus.widstromer@it.su.se 19 | 20 | 21 | Simon Buckle 22 | simon@webteq.eu 23 | 24 | 25 | 26 | 27 | 28 | junit 29 | junit 30 | 4.13.1 31 | test 32 | 33 | 34 | org.slf4j 35 | slf4j-api 36 | 1.6.1 37 | 38 | 39 | org.slf4j 40 | slf4j-simple 41 | 1.6.1 42 | test 43 | 44 | 45 | commons-codec 46 | commons-codec 47 | 1.4 48 | 49 | 50 | 51 | 52 | 53 | src/main/resources 54 | true 55 | 56 | 57 | 58 | 59 | maven-assembly-plugin 60 | 61 | 62 | 63 | com.yubico.client.v2.Cmd 64 | 65 | 66 | 67 | jar-with-dependencies 68 | 69 | 70 | 71 | 72 | make-my-jar-with-dependencies 73 | package 74 | 75 | single 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/Cmd.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | 29 | Written by Linus Widströmer , January 2011. 30 | */ 31 | 32 | package com.yubico.client.v2; 33 | 34 | public class Cmd { 35 | 36 | public static void main (String args []) throws Exception 37 | { 38 | if (args.length != 3) { 39 | System.err.println("\n*** Test your Yubikey against Yubico OTP validation server ***"); 40 | System.err.println("\nUsage: java -jar client.jar Client_ID Client_key OTP"); 41 | System.err.println("\nEg. java -jar client.jar 28 vvfucnlcrrnejlbuthlktguhclhvegbungldcrefbnku"); 42 | System.err.println("\nTouch Yubikey to generate the OTP. Visit Yubico.com for more details."); 43 | return; 44 | } 45 | 46 | String otp = args[2]; 47 | 48 | YubicoClient yc = YubicoClient.getClient(Integer.parseInt(args[0]), args[1]); 49 | VerificationResponse response = yc.verify(otp); 50 | 51 | if(response!=null && response.getStatus() == ResponseStatus.OK) { 52 | System.out.println("\n* OTP verified OK"); 53 | } else { 54 | System.out.println("\n* Failed to verify OTP"); 55 | } 56 | 57 | System.out.println("\n* Last response: " + response); 58 | System.exit(0); 59 | 60 | } // End of main 61 | 62 | } 63 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLEncoder; 5 | import java.util.Map; 6 | 7 | public class HttpUtils { 8 | public static String toQueryString(Map requestMap) throws UnsupportedEncodingException { 9 | String paramStr = ""; 10 | for(Map.Entry entry : requestMap.entrySet()) { 11 | if(!paramStr.isEmpty()) { 12 | paramStr += "&"; 13 | } 14 | paramStr += entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"); 15 | } 16 | return paramStr; 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/ResponseStatus.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | 29 | Written by Linus Widströmer , January 2011. 30 | */ 31 | 32 | package com.yubico.client.v2; 33 | 34 | public enum ResponseStatus { 35 | /* The OTP is valid. */ OK, 36 | /* The OTP is invalid format. */ BAD_OTP, 37 | /* The OTP has already been seen by the service. */ REPLAYED_OTP, 38 | /* The HMAC signature verification failed. */ BAD_SIGNATURE, 39 | /* The request lacks a parameter. */ MISSING_PARAMETER, 40 | /* The request id does not exist. */ NO_SUCH_CLIENT, 41 | /* The request id is not allowed to verify OTPs. */ OPERATION_NOT_ALLOWED, 42 | /* Unexpected error in our server. Please contact us if you see this error. */ BACKEND_ERROR, 43 | /* Server could not get requested number of syncs during before timeout */ NOT_ENOUGH_ANSWERS, 44 | /* Server has seen the OTP/Nonce combination before, see https://forum.yubico.com/viewtopic21be.html */ 45 | REPLAYED_REQUEST; 46 | 47 | public boolean isError() 48 | { 49 | return this == BACKEND_ERROR || 50 | this == BAD_OTP || 51 | this == BAD_SIGNATURE || 52 | this == NO_SUCH_CLIENT || 53 | this == MISSING_PARAMETER; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/Signature.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2; 2 | 3 | /* Copyright (c) 2011, Simon Buckle. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 18 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 22 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 27 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | SUCH DAMAGE. 30 | 31 | Written by Simon Buckle , September 2011. 32 | */ 33 | 34 | import javax.crypto.Mac; 35 | import javax.crypto.spec.SecretKeySpec; 36 | 37 | import com.yubico.client.v2.exceptions.YubicoSignatureException; 38 | import java.io.UnsupportedEncodingException; 39 | import java.security.InvalidKeyException; 40 | import java.security.NoSuchAlgorithmException; 41 | import org.apache.commons.codec.binary.Base64; 42 | 43 | public class Signature { 44 | 45 | private final static String HMAC_SHA1 = "HmacSHA1"; 46 | 47 | public static String calculate(String data, byte[] key) 48 | throws YubicoSignatureException { 49 | try { 50 | SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1); 51 | Mac mac = Mac.getInstance(HMAC_SHA1); 52 | mac.init(signingKey); 53 | byte[] raw = mac.doFinal(data.getBytes("UTF-8")); 54 | // Base64 encode the result, use old API call to work on android 55 | return new String(Base64.encodeBase64(raw)); 56 | } catch (NoSuchAlgorithmException e) { 57 | throw new YubicoSignatureException("No such algorithm (HMAC_SHA1?)", e); 58 | } catch (InvalidKeyException e) { 59 | throw new YubicoSignatureException("Invalid key in signature.", e); 60 | } catch (IllegalStateException e) { 61 | throw new YubicoSignatureException("Illegal state in signature", e); 62 | } catch (UnsupportedEncodingException e) { 63 | throw new YubicoSignatureException("Unsupported encoding (utf8?)", e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/VerificationRequester.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2; 2 | 3 | /* Copyright (c) 2011, Simon Buckle. All rights reserved. 4 | Copyright (c) 2012, Yubico AB. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 19 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 20 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 23 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 28 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 29 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | SUCH DAMAGE. 31 | 32 | Written by Simon Buckle (simon@webteq.eu), September 2011. 33 | */ 34 | 35 | import com.yubico.client.v2.exceptions.YubicoVerificationException; 36 | import com.yubico.client.v2.impl.VerificationResponseImpl; 37 | import java.io.InputStream; 38 | import org.slf4j.Logger; 39 | import org.slf4j.LoggerFactory; 40 | 41 | import java.io.IOException; 42 | import java.net.HttpURLConnection; 43 | import java.net.URL; 44 | import java.util.ArrayList; 45 | import java.util.List; 46 | import java.util.concurrent.*; 47 | 48 | import static com.yubico.client.v2.ResponseStatus.BACKEND_ERROR; 49 | import static com.yubico.client.v2.ResponseStatus.REPLAYED_REQUEST; 50 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 51 | import static java.util.concurrent.TimeUnit.MINUTES; 52 | 53 | /** 54 | * Fires off a number of validation requests to each specified URL 55 | * in parallel. 56 | * 57 | * @author Simon Buckle (simon@webteq.eu) 58 | */ 59 | public class VerificationRequester { 60 | private final ExecutorCompletionService completionService; 61 | 62 | /** 63 | * Sets up thread pool for validation requests. 64 | */ 65 | public VerificationRequester() { 66 | ThreadPoolExecutor pool = new ThreadPoolExecutor(0, 100, 250L, 67 | MILLISECONDS, new SynchronousQueue()); 68 | completionService = new ExecutorCompletionService(pool); 69 | } 70 | 71 | /** 72 | * Alias of fetch(urls, userAgent, 5). 73 | * @deprecated Use {@link #fetch(List, String, int)} with an explicit 74 | * maxRetries argument instead. 75 | */ 76 | @Deprecated 77 | public VerificationResponse fetch(List urls, String userAgent) throws YubicoVerificationException { 78 | return fetch(urls, userAgent, 5); 79 | } 80 | 81 | /** 82 | * Fires off a validation request to each url in the list, returning the first one 83 | * that is not {@link ResponseStatus#REPLAYED_REQUEST} 84 | * 85 | * @param urls a list of validation urls to be contacted 86 | * @param userAgent userAgent to send in request, if null one will be generated 87 | * @param maxRetries maximum number of retries in the case of network errors. Must not be negative. 88 | * @return {@link VerificationResponse} object from the first server response that is not 89 | * {@link ResponseStatus#REPLAYED_REQUEST} 90 | * @throws com.yubico.client.v2.exceptions.YubicoVerificationException if validation fails on all urls 91 | */ 92 | public VerificationResponse fetch(List urls, String userAgent, int maxRetries) throws YubicoVerificationException { 93 | if (maxRetries < 0) { 94 | throw new IllegalArgumentException("negative maxRetries is not valid."); 95 | } 96 | 97 | List> tasks = new ArrayList>(); 98 | for(String url : urls) { 99 | tasks.add(completionService.submit(createTask(userAgent, url, maxRetries))); 100 | } 101 | VerificationResponse response = null; 102 | try { 103 | int tasksDone = 0; 104 | Throwable savedException = null; 105 | Future futureResponse = completionService.poll(1L, MINUTES); 106 | while(futureResponse != null) { 107 | try { 108 | tasksDone++; 109 | tasks.remove(futureResponse); 110 | response = futureResponse.get(); 111 | /** 112 | * If the response returned is REPLAYED_REQUEST keep looking at responses 113 | * and hope we get something else. REPLAYED_REQUEST will be returned if a 114 | * validation server got sync before it parsed our query (otp and nonce is 115 | * the same). 116 | * @see https://forum.yubico.com/viewtopic21be.html 117 | * 118 | * Also if the response is BACKEND_ERROR, keep looking for a server that 119 | * sends a valid response 120 | * @see https://github.com/Yubico/yubico-java-client/issues/12 121 | */ 122 | if(!response.getStatus().equals(REPLAYED_REQUEST) && !response.getStatus().equals(BACKEND_ERROR)) { 123 | break; 124 | } 125 | } catch (CancellationException ignored) { 126 | // this would be thrown by old cancelled calls. 127 | tasksDone--; 128 | } catch (ExecutionException e) { 129 | // tuck the real exception away and use it if we don't get any valid answers. 130 | savedException = e.getCause(); 131 | } 132 | if(tasksDone >= urls.size()) { 133 | break; 134 | } 135 | futureResponse = completionService.poll(1L, MINUTES); 136 | } 137 | if(futureResponse == null || response == null) { 138 | if(savedException != null) { 139 | throw new YubicoVerificationException( 140 | "Exception while executing validation.", savedException); 141 | } else { 142 | throw new YubicoVerificationException("Validation timeout."); 143 | } 144 | } 145 | } catch (InterruptedException e) { 146 | throw new YubicoVerificationException("Validation interrupted.", e); 147 | } 148 | 149 | for(Future task : tasks) { 150 | task.cancel(true); 151 | } 152 | 153 | return response; 154 | } 155 | 156 | protected VerifyTask createTask(String userAgent, String url, int maxRetries) { 157 | return new VerifyTask(url, userAgent, maxRetries); 158 | } 159 | 160 | /** 161 | * Inner class for doing requests to validation server. 162 | */ 163 | static class VerifyTask implements Callable { 164 | 165 | private final Logger log = LoggerFactory.getLogger(VerifyTask.class); 166 | 167 | private final String url; 168 | private final String userAgent; 169 | private final int maxRetries; 170 | 171 | /** 172 | * Set up a VerifyTask for the Yubico Validation protocol v2 173 | * @param url the url to be used 174 | * @param userAgent the userAgent to be sent to the server, or NULL and one is calculated 175 | * @param maxRetries the maximum number of times to retry on network or server error 176 | */ 177 | public VerifyTask(String url, String userAgent, int maxRetries) { 178 | if (maxRetries < 0) { 179 | throw new IllegalArgumentException("negative maxRetries is not valid."); 180 | } 181 | this.url = url; 182 | this.userAgent = userAgent; 183 | this.maxRetries = maxRetries; 184 | } 185 | 186 | /** 187 | * Do the validation query for previous URL. 188 | * @throws Exception should not be anything but {@link IOException} 189 | */ 190 | public VerificationResponse call() throws Exception { 191 | URL url = new URL(this.url); 192 | try { 193 | return new VerificationResponseImpl(getResponseStream(url)); 194 | } catch (IOException e) { 195 | log.warn("Exception when requesting {}.", url.getHost(), e); 196 | throw e; 197 | } 198 | } 199 | 200 | protected InputStream getResponseStream(URL url) throws IOException { 201 | int attempt = 0; 202 | IOException lastException; 203 | 204 | do { 205 | try { 206 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 207 | conn.setRequestProperty("User-Agent", userAgent); 208 | conn.setConnectTimeout(15000); 209 | conn.setReadTimeout(15000); 210 | return conn.getInputStream(); 211 | } catch (IOException e) { 212 | log.warn("Exception when requesting {}, retrying.", url.getHost(), e); 213 | lastException = e; 214 | } 215 | 216 | try { 217 | // Delay a little bit and hope whatever is happening network-wise clears up. 218 | Thread.sleep(500); 219 | } catch (InterruptedException e) { 220 | // Oh well. Try again anyway. 221 | } 222 | attempt++; 223 | } while (attempt <= maxRetries); 224 | 225 | throw lastException; 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/VerificationResponse.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | 29 | Written by Linus Widströmer , January 2011. 30 | */ 31 | 32 | package com.yubico.client.v2; 33 | 34 | import java.util.Map; 35 | 36 | /** 37 | * Object built from server response, detailing the status of validation. 38 | * 39 | */ 40 | public interface VerificationResponse { 41 | 42 | /** 43 | * Whether the response status from the server was OK, representing a valid OTP. 44 | * 45 | * @return true if the response status was OK, false otherwise 46 | */ 47 | boolean isOk(); 48 | 49 | /** 50 | * Signature of the response, with the same API key as the request. 51 | * 52 | * @return response signature 53 | */ 54 | String getH(); 55 | 56 | /** 57 | * UTC timestamp from the server when response was processed. 58 | * 59 | * @return server UTC timestamp 60 | */ 61 | String getT(); 62 | 63 | /** 64 | * Server response to the request. 65 | * 66 | * @see ResponseStatus 67 | * @return response status 68 | */ 69 | ResponseStatus getStatus(); 70 | 71 | /** 72 | * Returns the internal timestamp from the YubiKey 8hz timer. 73 | * 74 | * @return yubikey internal timestamp 75 | */ 76 | String getTimestamp(); 77 | 78 | /** 79 | * Returns the non-volatile counter that is incremented on power-up. 80 | * 81 | * @return session counter 82 | */ 83 | String getSessioncounter(); 84 | 85 | /** 86 | * Returns the volatile counter that is incremented on each button-press, 87 | * starts at 0 after power-up. 88 | * 89 | * @return session use counter 90 | */ 91 | String getSessionuse(); 92 | 93 | /** 94 | * Returns the amount of sync the server achieved before sending the 95 | * response. 96 | * 97 | * @return sync, in procent 98 | */ 99 | String getSl(); 100 | 101 | /** 102 | * Echos back the OTP from the request, should match. 103 | * 104 | * @return otp 105 | */ 106 | String getOtp(); 107 | 108 | /** 109 | * Echos back the nonce from the request. Should match. 110 | * @return nonce 111 | */ 112 | public String getNonce(); 113 | 114 | /** 115 | * Returns all parameters from the response as a Map 116 | * 117 | * @return map of all values 118 | */ 119 | public Map getKeyValueMap(); 120 | 121 | /** 122 | * Returns the public id of the returned OTP 123 | * 124 | * @return public id 125 | */ 126 | public String getPublicId(); 127 | } 128 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/Version.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Yubico AB. All rights reseved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | */ 29 | 30 | package com.yubico.client.v2; 31 | 32 | import java.io.IOException; 33 | import java.io.InputStream; 34 | import java.util.Properties; 35 | 36 | public class Version { 37 | public static final String version; 38 | 39 | static { 40 | Properties properties = new Properties(); 41 | try { 42 | InputStream stream = Version.class.getResourceAsStream("/version.properties"); 43 | properties.load(stream); 44 | stream.close(); 45 | version = properties.getProperty("version"); 46 | } catch (IOException e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/YubicoClient.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | Copyright (c) 2011-2012, Yubico AB. All rights reserved. 3 | Copyright (c) 2011, Simon Buckle. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 18 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 22 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 27 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | SUCH DAMAGE. 30 | 31 | Written by Linus Widströmer , January 2011. 32 | */ 33 | 34 | package com.yubico.client.v2; 35 | 36 | import com.yubico.client.v2.exceptions.YubicoValidationFailure; 37 | import com.yubico.client.v2.exceptions.YubicoVerificationException; 38 | import com.yubico.client.v2.impl.YubicoClientImpl; 39 | import org.apache.commons.codec.binary.Base64; 40 | import org.slf4j.Logger; 41 | import org.slf4j.LoggerFactory; 42 | 43 | /** 44 | * Base class for doing YubiKey validations using version 2 of the validation protocol. 45 | */ 46 | 47 | public abstract class YubicoClient { 48 | private static final Logger log = LoggerFactory.getLogger(YubicoClient.class); 49 | 50 | protected Integer clientId; 51 | protected byte[] key; 52 | protected Integer sync; 53 | protected int maxRetries = 5; 54 | protected String wsapi_urls[] = { 55 | "https://api.yubico.com/wsapi/2.0/verify" 56 | }; 57 | 58 | protected String userAgent = "yubico-java-client/" + Version.version + 59 | " (" + System.getProperty("java.vendor") + " " + System.getProperty("java.version") + ")"; 60 | 61 | /** 62 | * Validate an OTP using a webservice call to one or more ykval validation servers. 63 | * 64 | * @param otp YubiKey OTP 65 | * @return result of the webservice validation operation 66 | * @throws com.yubico.client.v2.exceptions.YubicoVerificationException for validation errors, like unreachable servers 67 | * @throws YubicoValidationFailure for validation failures, like non matching OTPs in request and response 68 | * @throws IllegalArgumentException for arguments that are not correctly formatted OTP strings. 69 | */ 70 | public abstract VerificationResponse verify(String otp) throws YubicoVerificationException, YubicoValidationFailure; 71 | 72 | /** 73 | * Get the ykval client identifier used to identify the application. 74 | * @return ykval client identifier 75 | * @see YubicoClient#setClientId(Integer) 76 | */ 77 | public Integer getClientId() { 78 | return clientId; 79 | } 80 | 81 | /** 82 | * Set the ykval client identifier, used to identify the client application to 83 | * the validation servers. Such validation is only required for non-https-v2.0 84 | * validation queries, where the clientId tells the server what API key (shared 85 | * secret) to use to validate requests and sign responses. 86 | * 87 | * You can get a clientId and API key for the YubiCloud validation service at 88 | * https://upgrade.yubico.com/getapikey/ 89 | * 90 | * @param clientId ykval client identifier 91 | */ 92 | public void setClientId(Integer clientId) { 93 | this.clientId = clientId; 94 | } 95 | 96 | /** 97 | * Set api key to be used for signing requests 98 | * @param key ykval client key 99 | * @see YubicoClient#setClientId(Integer) 100 | */ 101 | public void setKey(String key) { 102 | this.key = Base64.decodeBase64(key.getBytes()); 103 | } 104 | 105 | /** 106 | * Get the api key that is used for signing requests 107 | * @return ykval client key 108 | * @see YubicoClient#setClientId(Integer) 109 | */ 110 | public String getKey() { 111 | return new String(Base64.encodeBase64(this.key)); 112 | } 113 | 114 | /** 115 | * Set the sync percentage required for a successful auth. 116 | * Default is to let the server decide. 117 | * @param sync percentage or strings 'secure' or 'fast' 118 | */ 119 | public void setSync(Integer sync) { 120 | this.sync = sync; 121 | } 122 | 123 | /** 124 | * Set the maximum number of retries to attempt in the event of a network-related failure. 125 | * Default is 5. 126 | * @param maxRetries maximum number of retries. Must not be negative. 127 | */ 128 | public void setMaxRetries(int maxRetries) { 129 | if (maxRetries < 0) { 130 | throw new IllegalArgumentException("negative maxRetries is not valid."); 131 | } 132 | 133 | this.maxRetries = maxRetries; 134 | } 135 | 136 | /** 137 | * Get the list of URLs that will be used for validating OTPs. 138 | * @return list of base URLs 139 | */ 140 | public String[] getWsapiUrls() { 141 | return wsapi_urls; 142 | } 143 | 144 | /** 145 | * Configure what URLs to use for validating OTPs. These URLs will have 146 | * all the necessary parameters appended to them. Example : 147 | * {"https://api.yubico.com/wsapi/2.0/verify"} 148 | * @param wsapi list of base URLs 149 | */ 150 | public void setWsapiUrls(String[] wsapi) { 151 | for (String url : wsapi) { 152 | warnIfDeprecatedUrl(url); 153 | } 154 | this.wsapi_urls = wsapi; 155 | } 156 | 157 | protected void warnIfDeprecatedUrl(String url) { 158 | if (url != null && url.startsWith("http:")) { 159 | log.warn("Deprecated YubiCloud URL: {} - naked HTTP requests are deprecated and will not be supported from 2019-02-04. See: https://status.yubico.com/2018/11/26/deprecating-yubicloud-v1-protocol-plain-text-requests-and-old-tls-versions/", url); 160 | } 161 | if (url != null && 162 | (url.startsWith("https://api2.yubico.com/") || 163 | url.startsWith("https://api3.yubico.com/") || 164 | url.startsWith("https://api4.yubico.com/") || 165 | url.startsWith("https://api5.yubico.com/"))) { 166 | log.warn("Deprecated YubiCloud URL: {} - api2, api3, api4 and api5 are deprecated will not be supported from 2020-07-01. See: https://status.yubico.com/", url); 167 | } 168 | } 169 | 170 | /** 171 | * Set user agent to be used in request to validation server 172 | * @param userAgent the user agent used in requests 173 | */ 174 | public void setUserAgent(String userAgent) { 175 | this.userAgent = userAgent; 176 | } 177 | 178 | /** 179 | * Instantiate a YubicoClient object. 180 | * 181 | * @param clientId Retrieved from https://upgrade.yubico.com/getapikey 182 | * @return client that can be used to validate YubiKey OTPs 183 | */ 184 | public static YubicoClient getClient(Integer clientId, String key) { 185 | return new YubicoClientImpl(clientId, key); 186 | } 187 | 188 | /** 189 | * Extract the public ID of a YubiKey from an OTP it generated. 190 | * 191 | * @param otp The OTP to extract ID from, in modhex format. 192 | * 193 | * @return string Public ID of YubiKey that generated otp. Between 0 and 12 lower-case characters. 194 | * 195 | * @throws IllegalArgumentException for arguments that are null or too short to be valid OTP strings. 196 | */ 197 | public static String getPublicId(String otp) { 198 | if ((otp == null) || (otp.length() < OTP_MIN_LEN)){ 199 | //not a valid OTP format, throw an exception 200 | throw new IllegalArgumentException("The OTP is too short to be valid"); 201 | } 202 | 203 | Integer len = otp.length(); 204 | 205 | /* The OTP part is always the last 32 bytes of otp. Whatever is before that 206 | * (if anything) is the public ID of the YubiKey. The ID can be set to '' 207 | * through personalization. 208 | */ 209 | return otp.substring(0, len - 32).toLowerCase(); 210 | } 211 | 212 | private static final Integer OTP_MIN_LEN = 32; 213 | private static final Integer OTP_MAX_LEN = 48; 214 | /** 215 | * Determines whether a given OTP is of the correct length 216 | * and only contains printable characters, as per the recommendation. 217 | * 218 | * @param otp The OTP to validate 219 | * @return boolean Returns true if it's valid; false otherwise 220 | * 221 | */ 222 | public static boolean isValidOTPFormat(String otp) { 223 | if (otp == null){ 224 | return false; 225 | } 226 | int len = otp.length(); 227 | for (char c : otp.toCharArray()) { 228 | if (c < 0x20 || c > 0x7E) { 229 | return false; 230 | } 231 | } 232 | return OTP_MIN_LEN <= len && len <= OTP_MAX_LEN; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/exceptions/YubicoInvalidResponse.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Yubico AB. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | */ 29 | 30 | package com.yubico.client.v2.exceptions; 31 | 32 | public class YubicoInvalidResponse extends Exception { 33 | private static final long serialVersionUID = 1L; 34 | 35 | public YubicoInvalidResponse(String message) { 36 | super(message); 37 | } 38 | 39 | public YubicoInvalidResponse(Throwable cause) { 40 | super(cause); 41 | } 42 | 43 | public YubicoInvalidResponse(String message, Throwable cause) { 44 | super(message, cause); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/exceptions/YubicoSignatureException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Yubico AB. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | */ 29 | 30 | package com.yubico.client.v2.exceptions; 31 | 32 | /** 33 | * This is thrown on signature algorithm or key errors. 34 | */ 35 | 36 | public class YubicoSignatureException extends Exception { 37 | private static final long serialVersionUID = 1L; 38 | 39 | public YubicoSignatureException(String message, Throwable cause) { 40 | super(message, cause); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/exceptions/YubicoValidationFailure.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Yubico AB. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | */ 29 | 30 | package com.yubico.client.v2.exceptions; 31 | 32 | /** 33 | * This is thrown for validation failures
34 | * * OTP in request and response isn't matching, could mean a man-in-the-middle
35 | * * nonce in request and response isn't matching, could mean a man-in-the-middle
36 | * * response signature verification failed 37 | */ 38 | 39 | public class YubicoValidationFailure extends Exception { 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | public YubicoValidationFailure(String message) { 44 | super(message); 45 | } 46 | 47 | public YubicoValidationFailure(Throwable cause) { 48 | super(cause); 49 | } 50 | 51 | public YubicoValidationFailure(String message, Throwable cause) { 52 | super(message, cause); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/exceptions/YubicoVerificationException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Yubico AB. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | */ 29 | 30 | package com.yubico.client.v2.exceptions; 31 | 32 | /** 33 | * This is thrown for errors during validation process, 34 | * like all servers unreachable. Or Connection timeouts. 35 | * 36 | */ 37 | 38 | public class YubicoVerificationException extends Exception { 39 | 40 | private static final long serialVersionUID = 1L; 41 | 42 | public YubicoVerificationException(String message, Throwable cause) { 43 | super(message, cause); 44 | } 45 | 46 | public YubicoVerificationException(String message) { 47 | super(message); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/impl/VerificationResponseImpl.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 16 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 26 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | 29 | Written by Linus Widströmer , January 2011. 30 | */ 31 | 32 | package com.yubico.client.v2.impl; 33 | 34 | import com.yubico.client.v2.YubicoClient; 35 | import com.yubico.client.v2.VerificationResponse; 36 | import com.yubico.client.v2.ResponseStatus; 37 | import com.yubico.client.v2.exceptions.YubicoInvalidResponse; 38 | 39 | import java.io.BufferedReader; 40 | import java.io.IOException; 41 | import java.io.InputStream; 42 | import java.io.InputStreamReader; 43 | import java.util.Map; 44 | import java.util.TreeMap; 45 | 46 | public class VerificationResponseImpl implements VerificationResponse { 47 | 48 | private String h; 49 | private String t; 50 | private ResponseStatus status; 51 | private String timestamp; 52 | private String sessioncounter; 53 | private String sessionuse; 54 | private String sl; 55 | private String otp; 56 | private String nonce; 57 | 58 | private final Map keyValueMap = new TreeMap(); 59 | 60 | public VerificationResponseImpl(InputStream inStream) throws IOException, YubicoInvalidResponse { 61 | if(inStream == null) { 62 | throw new IOException("InputStream argument was null"); 63 | } 64 | 65 | BufferedReader in = new BufferedReader(new InputStreamReader(inStream)); 66 | 67 | String inputLine; 68 | while ((inputLine = in.readLine()) != null) { 69 | int ix=inputLine.indexOf("="); 70 | if(ix==-1) continue; // Invalid line 71 | String key=inputLine.substring(0,ix); 72 | String val=inputLine.substring(ix+1); 73 | 74 | if ("h".equals(key)) { 75 | this.h = val; 76 | } else if ("t".equals(key)) { 77 | this.t = val; 78 | } else if ("otp".equals(key)) { 79 | this.otp = val; 80 | } else if ("status".equals(key)) { 81 | this.status = ResponseStatus.valueOf(val); 82 | } else if ("timestamp".equals(key)) { 83 | this.timestamp = val; 84 | } else if ("sessioncounter".equals(key)) { 85 | this.sessioncounter = val; 86 | } else if ("sessionuse".equals(key)) { 87 | this.sessionuse = val; 88 | } else if ("sl".equals(key)) { 89 | this.sl = val; 90 | } else if ("nonce".equals(key)) { 91 | this.nonce = val; 92 | } 93 | 94 | keyValueMap.put(key, val); 95 | } 96 | in.close(); 97 | 98 | if(status == null) { 99 | throw new YubicoInvalidResponse("Invalid response, contains no status."); 100 | } 101 | } 102 | 103 | public Map getKeyValueMap() { 104 | return keyValueMap; 105 | } 106 | 107 | public String toString() { 108 | return otp + ":" + status; 109 | } 110 | 111 | public boolean isOk() { 112 | return getStatus() == ResponseStatus.OK; 113 | } 114 | 115 | public String getH() { 116 | return h; 117 | } 118 | 119 | public String getT() { 120 | return t; 121 | } 122 | 123 | public ResponseStatus getStatus() { 124 | return status; 125 | } 126 | 127 | public String getTimestamp() { 128 | return timestamp; 129 | } 130 | 131 | public String getSessioncounter() { 132 | return sessioncounter; 133 | } 134 | 135 | public String getSessionuse() { 136 | return sessionuse; 137 | } 138 | 139 | public String getSl() { 140 | return sl; 141 | } 142 | 143 | public String getOtp() { 144 | return otp; 145 | } 146 | 147 | public String getNonce() { 148 | return nonce; 149 | } 150 | 151 | public String getPublicId() { 152 | return YubicoClient.getPublicId(otp); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /v2client/src/main/java/com/yubico/client/v2/impl/YubicoClientImpl.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 2 | 3 | Copyright (c) 2011, Simon Buckle. All rights reserved. 4 | 5 | Copyright (c) 2014, Yubico AB. All rights reseved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following 16 | disclaimer in the documentation and/or other materials provided 17 | with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 20 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 21 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 22 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 26 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 29 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | SUCH DAMAGE. 32 | 33 | Written by Linus Widströmer , January 2011. 34 | 35 | Modified by Simon Buckle , September 2011. 36 | - Added support for generating and validating signatures 37 | */ 38 | 39 | package com.yubico.client.v2.impl; 40 | 41 | import com.yubico.client.v2.Signature; 42 | import com.yubico.client.v2.YubicoClient; 43 | import com.yubico.client.v2.VerificationResponse; 44 | import com.yubico.client.v2.VerificationRequester; 45 | import com.yubico.client.v2.exceptions.YubicoSignatureException; 46 | import com.yubico.client.v2.exceptions.YubicoVerificationException; 47 | import com.yubico.client.v2.exceptions.YubicoValidationFailure; 48 | 49 | import java.io.UnsupportedEncodingException; 50 | import java.net.URLEncoder; 51 | import java.util.*; 52 | import java.util.Map.Entry; 53 | 54 | import static com.yubico.client.v2.HttpUtils.toQueryString; 55 | import static com.yubico.client.v2.ResponseStatus.BAD_SIGNATURE; 56 | 57 | public class YubicoClientImpl extends YubicoClient { 58 | private final VerificationRequester validationService; 59 | 60 | YubicoClientImpl(VerificationRequester validationService) { 61 | this.validationService = validationService; 62 | } 63 | 64 | /** 65 | * Creates a YubicoClient that will be using the given Client ID. 66 | * 67 | * @param clientId Retrieved from https://upgrade.yubico.com/getapikey 68 | */ 69 | public YubicoClientImpl(Integer clientId) { 70 | this(new VerificationRequester()); 71 | this.clientId = clientId; 72 | } 73 | 74 | /** 75 | * Creates a YubicoClient that will be using the given Client ID and API key. 76 | * 77 | * @param clientId Retrieved from https://upgrade.yubico.com/getapikey 78 | * @param apiKey Retrieved from https://upgrade.yubico.com/getapikey 79 | */ 80 | public YubicoClientImpl(Integer clientId, String apiKey) { 81 | this(clientId); 82 | setKey(apiKey); 83 | } 84 | 85 | /** 86 | * Creates a YubicoClient that will be using the given Client ID and API key. 87 | * 88 | * @param clientId Retrieved from https://upgrade.yubico.com/getapikey 89 | * @param apiKey Retrieved from https://upgrade.yubico.com/getapikey 90 | * @param sync A value 0 to 100 indicating percentage of syncing required by client, or strings "fast" or "secure" 91 | * to use server-configured values; if absent, let the server decide 92 | */ 93 | public YubicoClientImpl(Integer clientId, String apiKey, Integer sync) { 94 | this(clientId, apiKey); 95 | setSync(sync); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | public VerificationResponse verify(String otp) throws YubicoVerificationException, YubicoValidationFailure { 102 | if (!isValidOTPFormat(otp)) { 103 | throw new IllegalArgumentException("The OTP is not a valid format"); 104 | } 105 | Map requestMap = new TreeMap(); 106 | String nonce = UUID.randomUUID().toString().replaceAll("-", ""); 107 | requestMap.put("nonce", nonce); 108 | requestMap.put("id", clientId.toString()); 109 | requestMap.put("otp", otp); 110 | requestMap.put("timestamp", "1"); 111 | if (sync != null) { 112 | requestMap.put("sl", sync.toString()); 113 | } 114 | 115 | String queryString; 116 | try { 117 | queryString = toQueryString(requestMap); 118 | } catch (UnsupportedEncodingException e) { 119 | throw new YubicoVerificationException("Failed to encode parameter.", e); 120 | } 121 | 122 | if (key != null) { 123 | queryString = sign(queryString); 124 | } 125 | 126 | 127 | String[] wsapiUrls = this.getWsapiUrls(); 128 | List validationUrls = new ArrayList(); 129 | for (String wsapiUrl : wsapiUrls) { 130 | warnIfDeprecatedUrl(wsapiUrl); 131 | validationUrls.add(wsapiUrl + "?" + queryString); 132 | } 133 | 134 | VerificationResponse response = validationService.fetch(validationUrls, userAgent, maxRetries); 135 | 136 | if (key != null) { 137 | verifySignature(response); 138 | } 139 | 140 | // NONCE/OTP fields are not returned to the client when sending error codes. 141 | // If there is an error response, don't need to check them. 142 | if (!response.getStatus().isError()) { 143 | if (response.getOtp() == null || !otp.equals(response.getOtp())) { 144 | throw new YubicoValidationFailure("OTP mismatch in response, is there a man-in-the-middle?"); 145 | } 146 | if (response.getNonce() == null || !nonce.equals(response.getNonce())) { 147 | throw new YubicoValidationFailure("Nonce mismatch in response, is there a man-in-the-middle?"); 148 | } 149 | } 150 | return response; 151 | } 152 | 153 | private void verifySignature(VerificationResponse response) throws YubicoValidationFailure, YubicoVerificationException { 154 | StringBuilder keyValueStr = new StringBuilder(); 155 | for (Entry entry : response.getKeyValueMap().entrySet()) { 156 | if ("h".equals(entry.getKey())) { 157 | continue; 158 | } 159 | if (keyValueStr.length() > 0) { 160 | keyValueStr.append("&"); 161 | } 162 | keyValueStr 163 | .append(entry.getKey()) 164 | .append("=") 165 | .append(entry.getValue()); 166 | } 167 | try { 168 | String signature = Signature.calculate(keyValueStr.toString(), key).trim(); 169 | if (!response.getH().equals(signature) && 170 | !response.getStatus().equals(BAD_SIGNATURE)) { 171 | // don't throw a ValidationFailure if the server said bad signature, in that 172 | // case we probably have the wrong key/id and want to check it. 173 | throw new YubicoValidationFailure("Signatures do not match"); 174 | } 175 | } catch (YubicoSignatureException e) { 176 | throw new YubicoVerificationException("Failed to calculate the response signature.", e); 177 | } 178 | } 179 | 180 | private String sign(String queryString) throws YubicoVerificationException { 181 | try { 182 | queryString += "&h=" + URLEncoder.encode(Signature.calculate(queryString, key), "UTF-8"); 183 | } catch (YubicoSignatureException e) { 184 | throw new YubicoVerificationException("Failed signing of request", e); 185 | } catch (UnsupportedEncodingException e) { 186 | throw new YubicoVerificationException("Failed to encode signature", e); 187 | } 188 | return queryString; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /v2client/src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubico/yubico-java-client/e21fb672e290a118f7420daaf81ccb96d319d52c/v2client/src/main/resources/META-INF/beans.xml -------------------------------------------------------------------------------- /v2client/src/main/resources/version.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | -------------------------------------------------------------------------------- /v2client/src/test/java/com/yubico/client/v2/YubicoClientTest.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2; 2 | 3 | import com.yubico.client.v2.exceptions.YubicoValidationFailure; 4 | import com.yubico.client.v2.exceptions.YubicoVerificationException; 5 | import com.yubico.client.v2.impl.TestYubicoClientImpl; 6 | import com.yubico.client.v2.impl.YubicoClientImpl; 7 | import java.io.ByteArrayInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.net.URL; 11 | import java.util.List; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertFalse; 18 | import static org.junit.Assert.assertNotNull; 19 | import static org.junit.Assert.assertTrue; 20 | import static org.junit.Assert.fail; 21 | 22 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 23 | Copyright (c) 2012, Yubico AB. All rights reserved. 24 | 25 | Redistribution and use in source and binary forms, with or without 26 | modification, are permitted provided that the following conditions 27 | are met: 28 | 29 | * Redistributions of source code must retain the above copyright 30 | notice, this list of conditions and the following disclaimer. 31 | 32 | * Redistributions in binary form must reproduce the above copyright 33 | notice, this list of conditions and the following 34 | disclaimer in the documentation and/or other materials provided 35 | with the distribution. 36 | 37 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 38 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 39 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 40 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 41 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 42 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 43 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 44 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 45 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 46 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 47 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 48 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 49 | SUCH DAMAGE. 50 | 51 | Written by Linus Widströmer , January 2011. 52 | */ 53 | 54 | public class YubicoClientTest { 55 | 56 | private YubicoClient client = null; 57 | 58 | /* 59 | * API key for signing/verifying request/response. Don't reuse this one (or 60 | * you will have zero security), get your own at 61 | * https://upgrade.yubico.com/getapikey/ 62 | */ 63 | private final int clientId = 21188; 64 | private final String apiKey = "p38Z7DuEB/JC/LbDkkjmvMRB5GI="; 65 | 66 | @Before 67 | public void setup() { 68 | client = YubicoClient.getClient(this.clientId, apiKey); 69 | } 70 | 71 | @Test 72 | public void verifyConstruct() { 73 | assertTrue(client instanceof YubicoClientImpl); 74 | } 75 | 76 | @Test 77 | public void testBadOTP() throws YubicoVerificationException, YubicoValidationFailure { 78 | String otp="11111111111111111111111111111111111"; 79 | VerificationResponse response = client.verify(otp); 80 | assertNotNull(response); 81 | assertEquals(ResponseStatus.BAD_OTP, response.getStatus()); 82 | } 83 | 84 | @Test 85 | public void testReplayedOTP() throws YubicoVerificationException, YubicoValidationFailure { 86 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 87 | VerificationResponse response = client.verify(otp); 88 | assertNotNull(response); 89 | assertEquals(otp, response.getOtp()); 90 | assertEquals(ResponseStatus.REPLAYED_OTP, response.getStatus()); 91 | } 92 | 93 | @Test 94 | public void testSignature() throws YubicoVerificationException, YubicoValidationFailure { 95 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 96 | client.setKey(this.apiKey); 97 | VerificationResponse response = client.verify(otp); 98 | assertNotNull(response); 99 | assertEquals(otp, response.getOtp()); 100 | assertEquals(ResponseStatus.REPLAYED_OTP, response.getStatus()); 101 | } 102 | 103 | @Test 104 | public void testBadSignature() throws YubicoVerificationException, YubicoValidationFailure { 105 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 106 | client.setKey("bAX9u78e8BRHXPGDVV3lQUm4yVw="); 107 | VerificationResponse response = client.verify(otp); 108 | assertEquals(ResponseStatus.BAD_SIGNATURE, response.getStatus()); 109 | } 110 | 111 | @Test 112 | public void testUnPrintableOTP() { 113 | String otp = new String(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}); 114 | assertFalse(YubicoClient.isValidOTPFormat(otp)); 115 | } 116 | 117 | @Test 118 | public void testShortOTP() { 119 | String otp = "cccccc"; 120 | assertFalse(YubicoClient.isValidOTPFormat(otp)); 121 | } 122 | 123 | @Test 124 | public void testLongOTP() { 125 | String otp = "cccccccccccccccccccccccccccccccccccccccccccccccccc"; 126 | assertFalse(YubicoClient.isValidOTPFormat(otp)); 127 | } 128 | 129 | @Test 130 | public void testTwoQueries() throws YubicoVerificationException, YubicoValidationFailure { 131 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 132 | VerificationResponse response = client.verify(otp); 133 | assertEquals(ResponseStatus.REPLAYED_OTP, response.getStatus()); 134 | VerificationResponse response2 = client.verify(otp); 135 | assertEquals(ResponseStatus.REPLAYED_OTP, response2.getStatus()); 136 | } 137 | 138 | @Test(expected=YubicoVerificationException.class) 139 | public void testBadUrls() throws YubicoVerificationException, YubicoValidationFailure { 140 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 141 | client.setWsapiUrls(new String[] { 142 | "https://www.example.com/wsapi/2.0/verify", 143 | "https://api2.example.com/wsapi/2.0/verify" 144 | }); 145 | client.verify(otp); 146 | fail("Expected exception to be thrown."); 147 | } 148 | 149 | @Test 150 | public void testGoodAndBadUrls() throws YubicoVerificationException, YubicoValidationFailure { 151 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 152 | client.setWsapiUrls(new String[] { 153 | "https://api.example.com/wsapi/2.0/verify", 154 | "https://www.example.com/wsapi/2.0/verify", 155 | "https://api3.yubico.com/wsapi/2.0/verify" 156 | }); 157 | VerificationResponse response = client.verify(otp); 158 | assertEquals(ResponseStatus.REPLAYED_OTP, response.getStatus()); 159 | } 160 | 161 | @Test 162 | public void testBackendErrorResponseIsIgnoredIfOtherResponseIsAvailable() throws YubicoVerificationException, YubicoValidationFailure { 163 | String otp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 164 | 165 | YubicoClient client = new TestYubicoClientImpl(new VerificationRequester() { 166 | private boolean firstCall = true; 167 | 168 | @Override 169 | @SuppressWarnings("deprecation") 170 | public VerificationResponse fetch(List urls, String userAgent) throws YubicoVerificationException { 171 | // Plain pass-through just to test that the signature is stable 172 | return super.fetch(urls, userAgent); 173 | } 174 | 175 | @Override 176 | protected VerifyTask createTask(String userAgent, String url, int maxRetries) { 177 | if (firstCall) { 178 | firstCall = false; 179 | return new VerifyTask(url, userAgent, maxRetries) { 180 | @Override 181 | protected InputStream getResponseStream(URL url) throws IOException { 182 | return new ByteArrayInputStream("status=BACKEND_ERROR".getBytes("UTF-8")); 183 | } 184 | }; 185 | } else { 186 | return super.createTask(userAgent, url, maxRetries); 187 | } 188 | } 189 | }); 190 | client.setClientId(clientId); 191 | client.setKey(apiKey); 192 | 193 | client.setWsapiUrls(new String[] { 194 | "https://whatever.this.will.be.ignored.anyway.yubico.com/wsapi/2.0/verify", 195 | "https://api3.yubico.com/wsapi/2.0/verify", 196 | }); 197 | VerificationResponse response = client.verify(otp); 198 | assertEquals(ResponseStatus.REPLAYED_OTP, response.getStatus()); 199 | } 200 | 201 | @Test(expected=IllegalArgumentException.class) 202 | public void testNullOTPPublicId() { 203 | YubicoClient.getPublicId(null); 204 | } 205 | 206 | @Test(expected=IllegalArgumentException.class) 207 | public void testEmptyOTPPublicId() { 208 | YubicoClient.getPublicId(""); 209 | } 210 | 211 | @Test(expected=IllegalArgumentException.class) 212 | public void testNegativeMaxRetries() { 213 | client.setMaxRetries(-1); 214 | } 215 | 216 | @Test 217 | public void testValidOTPPublicId() { 218 | String testOtp = "cccccccfhcbelrhifnjrrddcgrburluurftrgfdrdifj"; 219 | String testPublicId = "cccccccfhcbe"; 220 | String resultPublicId = YubicoClient.getPublicId(testOtp); 221 | assertEquals(testPublicId, resultPublicId); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /v2client/src/test/java/com/yubico/client/v2/impl/TestYubicoClientImpl.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2.impl; 2 | 3 | import com.yubico.client.v2.VerificationRequester; 4 | 5 | public class TestYubicoClientImpl extends YubicoClientImpl { 6 | 7 | public TestYubicoClientImpl(VerificationRequester verificationRequester) { 8 | super(verificationRequester); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /v2client/src/test/java/com/yubico/client/v2/impl/VerificationResponseImplTest.java: -------------------------------------------------------------------------------- 1 | package com.yubico.client.v2.impl; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.IOException; 9 | 10 | import org.junit.Test; 11 | 12 | import com.yubico.client.v2.VerificationResponse; 13 | import com.yubico.client.v2.exceptions.YubicoInvalidResponse; 14 | 15 | /* Copyright (c) 2011, Linus Widströmer. All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions 19 | are met: 20 | 21 | * Redistributions of source code must retain the above copyright 22 | notice, this list of conditions and the following disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above copyright 25 | notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials provided 27 | with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 32 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 33 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 34 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 35 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 36 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 37 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 38 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 39 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 40 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 41 | SUCH DAMAGE. 42 | 43 | Written by Linus Widströmer , January 2011. 44 | */ 45 | 46 | public class VerificationResponseImplTest { 47 | 48 | @Test 49 | public void testParserForNullArg() throws YubicoInvalidResponse { 50 | try { 51 | new VerificationResponseImpl(null); 52 | fail("Expected an IOException to be thrown."); 53 | } catch (IOException ioe) { 54 | } 55 | } 56 | 57 | @Test 58 | public void testToString() throws YubicoInvalidResponse { 59 | String testData= "h=lPuwrWh8/5ZuRBN1q+v7/pCOfYo=\n" + 60 | "t=2011-01-26T11:48:21Z0323\n" + 61 | "otp=cccccccfhcbeceeiinhjfjhfjutfvrjetfkjlhbduvdd\n" + 62 | "nonce=askjdnkagfdgdgdgggggggddddddddd\n" + 63 | "timestamp=4711\n" + 64 | "sessioncounter=42\n" + 65 | "sessionuse=666\n" + 66 | "sl=foo\n" + 67 | "status=REPLAYED_OTP\n"; 68 | 69 | try { 70 | VerificationResponse response = new VerificationResponseImpl(new ByteArrayInputStream(testData.getBytes("UTF-8"))); 71 | assertTrue(response.toString().contains("REPLAYED_OTP")); 72 | assertTrue(response.toString().contains("cccccccfhcbeceeiinhjfjhfjutfvrjetfkjlhbduvdd")); 73 | } catch (IOException ioe) { 74 | fail("Encountered an exception"); 75 | } 76 | } 77 | 78 | @Test 79 | public void testParser() throws YubicoInvalidResponse { 80 | String testData= "h=lPuwrWh8/5ZuRBN1q+v7/pCOfYo=\n" + 81 | "t=2011-01-26T11:48:21Z0323\n" + 82 | "otp=cccccccfhcbeceeiinhjfjhfjutfvrjetfkjlhbduvdd\n" + 83 | "nonce=askjdnkagfdgdgdgggggggddddddddd\n" + 84 | "timestamp=4711\n" + 85 | "sessioncounter=42\n" + 86 | "sessionuse=666\n" + 87 | "sl=foo\n" + 88 | "status=REPLAYED_OTP\n"; 89 | 90 | try { 91 | VerificationResponse response = new VerificationResponseImpl(new ByteArrayInputStream(testData.getBytes("UTF-8"))); 92 | assertEquals("2011-01-26T11:48:21Z0323",response.getT()); 93 | assertEquals("lPuwrWh8/5ZuRBN1q+v7/pCOfYo=", response.getH()); 94 | assertEquals("REPLAYED_OTP", response.getStatus().toString()); 95 | assertEquals("cccccccfhcbeceeiinhjfjhfjutfvrjetfkjlhbduvdd", response.getOtp()); 96 | assertEquals("askjdnkagfdgdgdgggggggddddddddd", response.getNonce()); 97 | assertEquals("4711", response.getTimestamp()); 98 | assertEquals("42", response.getSessioncounter()); 99 | assertEquals("foo", response.getSl()); 100 | assertEquals("666", response.getSessionuse()); 101 | assertEquals("cccccccfhcbe", response.getPublicId()); 102 | 103 | } catch (IOException ioe) { 104 | fail("Encountered an exception"); 105 | } 106 | } 107 | 108 | @Test(expected=YubicoInvalidResponse.class) 109 | public void testBrokenResponse() throws YubicoInvalidResponse { 110 | String testData = "foo=bar\n" + 111 | "kaka=blahonga\n"; 112 | try { 113 | new VerificationResponseImpl(new ByteArrayInputStream(testData.getBytes("UTF-8"))); 114 | } catch (IOException ioe) { 115 | fail("Encountered an exception"); 116 | } 117 | } 118 | } 119 | 120 | --------------------------------------------------------------------------------