";
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 |
12 |
13 |
--------------------------------------------------------------------------------
/demo-server/src/main/resources/demo/registerIndex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
--------------------------------------------------------------------------------