From 264fd24b6586b66392bf9e2c022fa31918719a8a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 19 Nov 2017 21:14:18 -0800 Subject: [PATCH 01/20] GUACAMOLE-96: Add skeleton TOTP authentication extension (hard-coded, fake TOTP). --- extensions/guacamole-auth-totp/.gitignore | 3 + extensions/guacamole-auth-totp/pom.xml | 159 ++++++++++++ .../guacamole-auth-totp/src/licenses/LICENSE | 239 ++++++++++++++++++ .../guacamole-auth-totp/src/licenses/NOTICE | 5 + .../src/licenses/bundled/README | 4 + .../licenses/bundled/aopalliance-1.0/LICENSE | 4 + .../src/licenses/bundled/guice-3.0/COPYING | 202 +++++++++++++++ .../bundled/javax.inject-1/LICENSE-2.0.txt | 202 +++++++++++++++ .../src/main/assembly/dist.xml | 53 ++++ .../auth/totp/TOTPAuthenticationProvider.java | 123 +++++++++ .../TOTPAuthenticationProviderModule.java | 78 ++++++ .../auth/totp/UserVerificationService.java | 102 ++++++++ .../src/main/resources/guac-manifest.json | 16 ++ .../src/main/resources/license.txt | 18 ++ .../src/main/resources/translations/en.json | 13 + pom.xml | 1 + 16 files changed, 1222 insertions(+) create mode 100644 extensions/guacamole-auth-totp/.gitignore create mode 100644 extensions/guacamole-auth-totp/pom.xml create mode 100644 extensions/guacamole-auth-totp/src/licenses/LICENSE create mode 100644 extensions/guacamole-auth-totp/src/licenses/NOTICE create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/README create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt create mode 100644 extensions/guacamole-auth-totp/src/main/assembly/dist.xml create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java create mode 100644 extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json create mode 100644 extensions/guacamole-auth-totp/src/main/resources/license.txt create mode 100644 extensions/guacamole-auth-totp/src/main/resources/translations/en.json diff --git a/extensions/guacamole-auth-totp/.gitignore b/extensions/guacamole-auth-totp/.gitignore new file mode 100644 index 000000000..1de9633ae --- /dev/null +++ b/extensions/guacamole-auth-totp/.gitignore @@ -0,0 +1,3 @@ +src/main/resources/generated/ +target/ +*~ diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml new file mode 100644 index 000000000..b7fa965f8 --- /dev/null +++ b/extensions/guacamole-auth-totp/pom.xml @@ -0,0 +1,159 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-auth-totp + jar + 0.9.14 + guacamole-auth-totp + http://guacamole.incubator.apache.org/ + + + UTF-8 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.6 + 1.6 + + -Xlint:all + -Werror + + true + + + + + + maven-assembly-plugin + 2.5.3 + + ${project.artifactId}-${project.version} + false + + src/main/assembly/dist.xml + + + + + make-dist-archive + package + + single + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack-dependencies + + + runtime + ${project.build.directory}/classes + + + + + + + + org.apache.rat + apache-rat-plugin + 0.12 + + + + **/*.json + src/licenses/**/* + + + + + + + validate + validate + + check + + + + + + + + + + + + + + org.apache.guacamole + guacamole-ext + 0.9.14 + provided + + + + + com.google.inject + guice + 3.0 + + + com.google.inject.extensions + guice-multibindings + 3.0 + + + + + javax.servlet + servlet-api + 2.5 + provided + + + + + diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE new file mode 100644 index 000000000..c6cbf77e8 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE @@ -0,0 +1,239 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +============================================================================== + +APACHE GUACAMOLE SUBCOMPONENTS + +Apache Guacamole includes a number of subcomponents with separate copyright +notices and license terms. Your use of these subcomponents is subject to the +terms and conditions of the following licenses. + + +AOP Alliance (http://aopalliance.sourceforge.net/) +-------------------------------------------------- + + Version: 1.0 + From: 'AOP Alliance' (http://aopalliance.sourceforge.net/members.html) + License(s): + Public Domain (bundled/aopalliance-1.0/LICENSE) + + +Google Guice (https://github.com/google/guice) +---------------------------------------------- + + Version: 3.0 + From: 'Google Inc.' (http://www.google.com/) + License(s): + Apache v2.0 (bundled/guice-3.0/COPYING) + + +JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/) +---------------------------------------------------------------------------- + + Version: 1 + From: 'JSR-330 Expert Group' (https://jcp.org/en/jsr/detail?id=330) + License(s): + Apache v2.0 (bundled/javax.inject-1/LICENSE-2.0.txt) + diff --git a/extensions/guacamole-auth-totp/src/licenses/NOTICE b/extensions/guacamole-auth-totp/src/licenses/NOTICE new file mode 100644 index 000000000..47f2b4c24 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/NOTICE @@ -0,0 +1,5 @@ +Apache Guacamole +Copyright 2017 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/README b/extensions/guacamole-auth-totp/src/licenses/bundled/README new file mode 100644 index 000000000..47ba19db0 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/README @@ -0,0 +1,4 @@ +Apache Guacamole includes a number of subcomponents with separate copyright +notices and license terms. Your use of these subcomponents is subject to the +terms and conditions of their respective licenses, included within this +directory for reference. diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE b/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE new file mode 100644 index 000000000..8e0e3786b --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/aopalliance-1.0/LICENSE @@ -0,0 +1,4 @@ +From http://aopalliance.sourceforge.net/: + + LICENCE: all the source code provided by AOP Alliance is Public Domain. + diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING b/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/guice-3.0/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt b/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/javax.inject-1/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/guacamole-auth-totp/src/main/assembly/dist.xml b/extensions/guacamole-auth-totp/src/main/assembly/dist.xml new file mode 100644 index 000000000..b89fd534c --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/assembly/dist.xml @@ -0,0 +1,53 @@ + + + + + dist + ${project.artifactId}-${project.version} + + + + tar.gz + + + + + + + + + src/licenses + + + + + target + + + *.jar + + + + + + diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java new file mode 100644 index 000000000..835ba87d3 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.UserContext; + +/** + * AuthenticationProvider implementation which uses TOTP as an additional + * authentication factor for users which have already been authenticated by + * some other AuthenticationProvider. + */ +public class TOTPAuthenticationProvider implements AuthenticationProvider { + + /** + * Injector which will manage the object graph of this authentication + * provider. + */ + private final Injector injector; + + /** + * Creates a new TOTPAuthenticationProvider that verifies users using TOTP. + * + * @throws GuacamoleException + * If a required property is missing, or an error occurs while parsing + * a property. + */ + public TOTPAuthenticationProvider() throws GuacamoleException { + + // Set up Guice injector. + injector = Guice.createInjector( + new TOTPAuthenticationProviderModule(this) + ); + + } + + @Override + public String getIdentifier() { + return "totp"; + } + + @Override + public Object getResource() { + return null; + } + + @Override + public AuthenticatedUser authenticateUser(Credentials credentials) + throws GuacamoleException { + return null; + } + + @Override + public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser, + Credentials credentials) throws GuacamoleException { + return authenticatedUser; + } + + @Override + public UserContext getUserContext(AuthenticatedUser authenticatedUser) + throws GuacamoleException { + return null; + } + + @Override + public UserContext updateUserContext(UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + return context; + } + + @Override + public UserContext decorate(UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + + UserVerificationService verificationService = + injector.getInstance(UserVerificationService.class); + + // Verify identity of user + verificationService.verifyIdentity(context, authenticatedUser); + + // User has been verified, and authentication should be allowed to + // continue + return context; + + } + + @Override + public UserContext redecorate(UserContext decorated, UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + return context; + } + + @Override + public void shutdown() { + // Do nothing + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java new file mode 100644 index 000000000..757a4121e --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp; + +import com.google.inject.AbstractModule; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.environment.LocalEnvironment; +import org.apache.guacamole.net.auth.AuthenticationProvider; + +/** + * Guice module which configures TOTP-specific injections. + */ +public class TOTPAuthenticationProviderModule extends AbstractModule { + + /** + * Guacamole server environment. + */ + private final Environment environment; + + /** + * A reference to the TOTPAuthenticationProvider on behalf of which this + * module has configured injection. + */ + private final AuthenticationProvider authProvider; + + /** + * Creates a new TOTP authentication provider module which configures + * injection for the TOTPAuthenticationProvider. + * + * @param authProvider + * The AuthenticationProvider for which injection is being configured. + * + * @throws GuacamoleException + * If an error occurs while retrieving the Guacamole server + * environment. + */ + public TOTPAuthenticationProviderModule(AuthenticationProvider authProvider) + throws GuacamoleException { + + // Get local environment + this.environment = new LocalEnvironment(); + + // Store associated auth provider + this.authProvider = authProvider; + + } + + @Override + protected void configure() { + + // Bind core implementations of guacamole-ext classes + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(Environment.class).toInstance(environment); + + // Bind TOTP-specific services + bind(UserVerificationService.class); + + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java new file mode 100644 index 000000000..f28149a15 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp; + +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import org.apache.guacamole.GuacamoleClientException; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.form.Field; +import org.apache.guacamole.form.TextField; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.auth.credentials.CredentialsInfo; +import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; + +/** + * Service for verifying the identity of a user using TOTP. + */ +public class UserVerificationService { + + /** + * The name of the HTTP parameter which will contain the TOTP code provided + * by the user to verify their identity. + */ + private static final String TOTP_PARAMETER_NAME = "guac-totp"; + + /** + * The field which should be exposed to the user to request that they + * provide their TOTP code. + */ + private static final Field TOTP_FIELD = new TextField(TOTP_PARAMETER_NAME); + + /** + * CredentialsInfo object describing the credentials expected for a user + * who has verified their identity with TOTP. + */ + private static final CredentialsInfo TOTP_CREDENTIALS = new CredentialsInfo( + Collections.singletonList(TOTP_FIELD) + ); + + /** + * Verifies the identity of the given user using TOTP. If a authentication + * code from the user's TOTP device has not already been provided, a code is + * requested in the form of additional expected credentials. Any provided + * code is cryptographically verified. If no code is present, or the + * received code is invalid, an exception is thrown. + * + * @param context + * The UserContext provided for the user by another authentication + * extension. + * + * @param authenticatedUser + * The user whose identity should be verified using TOTP. + * + * @throws GuacamoleException + * If required TOTP-specific configuration options are missing or + * malformed, or if the user's identity cannot be verified. + */ + public void verifyIdentity(UserContext context, + AuthenticatedUser authenticatedUser) throws GuacamoleException { + + // Pull the original HTTP request used to authenticate + Credentials credentials = authenticatedUser.getCredentials(); + HttpServletRequest request = credentials.getRequest(); + + // Ignore anonymous users + if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER)) + return; + + // Retrieve TOTP from request + String totp = request.getParameter(TOTP_PARAMETER_NAME); + + // If no TOTP provided, request one + if (totp == null) + throw new GuacamoleInsufficientCredentialsException( + "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS); + + // FIXME: Hard-coded code + if (!totp.equals("123456")) + throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED"); + + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..539562ccf --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json @@ -0,0 +1,16 @@ +{ + + "guacamoleVersion" : "0.9.14", + + "name" : "TOTP TFA Authentication Backend", + "namespace" : "totp", + + "authProviders" : [ + "org.apache.guacamole.auth.totp.TOTPAuthenticationProvider" + ], + + "translations" : [ + "translations/en.json" + ] + +} diff --git a/extensions/guacamole-auth-totp/src/main/resources/license.txt b/extensions/guacamole-auth-totp/src/main/resources/license.txt new file mode 100644 index 000000000..042f3ce1f --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/license.txt @@ -0,0 +1,18 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json new file mode 100644 index 000000000..540b94e36 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json @@ -0,0 +1,13 @@ +{ + + "DATA_SOURCE_TOTP" : { + "NAME" : "TOTP TFA Backend" + }, + + "LOGIN" : { + "FIELD_HEADER_GUAC_TOTP" : "Authentication Code", + "INFO_TOTP_REQUIRED" : "Please enter your authentication code to verify your identity.", + "INFO_TOTP_VERIFICATION_FAILED" : "Verification failed. Please try again." + } + +} diff --git a/pom.xml b/pom.xml index 59cff02e2..4f7c8ed6a 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,7 @@ extensions/guacamole-auth-jdbc extensions/guacamole-auth-ldap extensions/guacamole-auth-openid + extensions/guacamole-auth-totp doc/guacamole-example From b55e56179c656191d3363b9089e3e3f235351d83 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 00:22:26 -0800 Subject: [PATCH 02/20] GUACAMOLE-96: Add TOTP generator implementation based on reference implementation from IETF. --- LICENSE | 37 ++ extensions/guacamole-auth-totp/pom.xml | 15 + .../guacamole-auth-totp/src/licenses/LICENSE | 37 ++ .../totp-reference-impl-07/license.txt | 28 ++ .../apache/guacamole/totp/TOTPGenerator.java | 402 ++++++++++++++++++ .../guacamole/totp/TOTPGeneratorTest.java | 168 ++++++++ 6 files changed, 687 insertions(+) create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java create mode 100644 extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java diff --git a/LICENSE b/LICENSE index 2c1ab31b3..3fa304c53 100644 --- a/LICENSE +++ b/LICENSE @@ -254,3 +254,40 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +TOTP Reference Implementation (https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl) +------------------------------------------------------------------------------- + + Verson: 07 + From: 'IETF Trust' (http://trustee.ietf.org/license-info) + License(s): + BSD 3-clause (extensions/guacamole-auth-duo/src/licenses/bundled/totp-reference-impl-07/license.txt) + +Copyright (c) 2011 IETF Trust and the persons identified as authors +of the code. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of Internet Society, IETF or IETF Trust, nor the names + of specific contributors, may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml index b7fa965f8..5b421d72b 100644 --- a/extensions/guacamole-auth-totp/pom.xml +++ b/extensions/guacamole-auth-totp/pom.xml @@ -154,6 +154,21 @@ provided + + + com.google.guava + guava + 18.0 + + + + + junit + junit + 4.12 + test + + diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE index c6cbf77e8..e1fa0fcab 100644 --- a/extensions/guacamole-auth-totp/src/licenses/LICENSE +++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE @@ -237,3 +237,40 @@ JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/) License(s): Apache v2.0 (bundled/javax.inject-1/LICENSE-2.0.txt) + +TOTP Reference Implementation (https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl) +------------------------------------------------------------------------------- + + Verson: 07 + From: 'IETF Trust' (http://trustee.ietf.org/license-info) + License(s): + BSD 3-clause (bundled/totp-reference-impl-07/license.txt) + +Copyright (c) 2011 IETF Trust and the persons identified as authors +of the code. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of Internet Society, IETF or IETF Trust, nor the names + of specific contributors, may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt b/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt new file mode 100644 index 000000000..bb1afcd2c --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/totp-reference-impl-07/license.txt @@ -0,0 +1,28 @@ +Copyright (c) 2011 IETF Trust and the persons identified as authors +of the code. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of Internet Society, IETF or IETF Trust, nor the names + of specific contributors, may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java new file mode 100644 index 000000000..004c23ba2 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java @@ -0,0 +1,402 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.totp; + +import com.google.common.primitives.Longs; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/* + * NOTE: This TOTP implementation is based on the TOTP reference implementation + * provided by the IETF Trust at: + * + * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl + */ + +/* + * Copyright (c) 2011 IETF Trust and the persons identified as authors + * of the code. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of Internet Society, IETF or IETF Trust, nor the names + * of specific contributors, may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Generator which uses the TOTP algorithm to generate authentication codes. + */ +public class TOTPGenerator { + + /** + * The default time to use as the basis for comparison when transforming + * provided TOTP timestamps into counter values required for HOTP, in + * seconds since midnight, 1970-01-01, UTC (UNIX epoch). + */ + public static final long DEFAULT_START_TIME = 0; + + /** + * The default frequency at which new TOTP codes should be generated (and + * old codes invalidated), in seconds. + */ + public static final long DEFAULT_TIME_STEP = 30; + + /** + * The TOTP generation mode. The mode dictates the hash function which + * should be used to generate authentication codes, as well as the required + * key size. + */ + private final Mode mode; + + /** + * The shared key to use to generate authentication codes. The size + * required for this key depends on the generation mode. + */ + private final Key key; + + /** + * The length of codes to generate, in digits. + */ + private final int length; + + /** + * The base time against which the timestamp specified for each TOTP + * should be compared to produce the corresponding HOTP counter value, in + * seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the value + * value referred to as "T0" in the TOTP specification. + */ + private final long startTime; + + /** + * The frequency that new TOTP codes should be generated and invalidated, + * in seconds. This is the value referred to as "X" in the TOTP + * specification. + */ + private final long timeStep; + + /** + * The operating mode for TOTP, defining the hash algorithm to be used. + */ + public enum Mode { + + /** + * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode + * requires 160-bit keys. + */ + SHA1("HmacSHA1"), + + /** + * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode + * requires 256-bit keys. + */ + SHA256("HmacSHA256"), + + /** + * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode + * requires 512-bit keys. + */ + SHA512("HmacSHA512"); + + /** + * The name of the HMAC algorithm which the TOTP implementation should + * use when operating in this mode, in the format required by + * Mac.getInstance(). + */ + private final String algorithmName; + + /** + * Creates a new TOTP operating mode which is associated with the + * given HMAC algorithm. + * + * @param algorithmName + * The name of the HMAC algorithm which the TOTP implementation + * should use when operating in this mode, in the format required + * by Mac.getInstance(). + */ + private Mode(String algorithmName) { + this.algorithmName = algorithmName; + } + + /** + * Returns the name of the HMAC algorithm which the TOTP implementation + * should use when operating in this mode. The name returned will be + * in the format required by Mac.getInstance(). + * + * @return + * The name of the HMAC algorithm which the TOTP implementation + * should use. + */ + public String getAlgorithmName() { + return algorithmName; + } + + } + + /** + * Creates a new TOTP generator which uses the given shared key to generate + * authentication codes. The provided generation mode dictates the size of + * the key required, while the given start time and time step dictate how + * timestamps provided for code generation are converted to the counter + * value used by HOTP (the algorithm which forms the basis of TOTP). + * + * @param key + * The shared key to use to generate authentication codes. + * + * @param mode + * The mode in which the TOTP algorithm should operate. + * + * @param length + * The length of the codes to generate, in digits. As required + * by the specification, this value MUST be at least 6 but no greater + * than 8. + * + * @param startTime + * The base time against which the timestamp specified for each TOTP + * should be compared to produce the corresponding HOTP counter value, + * in seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the + * value referred to as "T0" in the TOTP specification. + * + * @param timeStep + * The frequency that new TOTP codes should be generated and + * invalidated, in seconds. This is the value referred to as "X" in the + * TOTP specification. + * + * @throws InvalidKeyException + * If the provided key is invalid for the requested TOTP mode. + */ + public TOTPGenerator(byte[] key, Mode mode, int length, long startTime, + long timeStep) throws InvalidKeyException { + + // Validate length is within spec + if (length < 6 || length > 8) + throw new IllegalArgumentException("TOTP codes must be at least 6 " + + "digits and no more than 8 digits."); + + this.key = new SecretKeySpec(key, "RAW"); + this.mode = mode; + this.length = length; + this.startTime = startTime; + this.timeStep = timeStep; + + // Verify key validity + getMacInstance(this.mode, this.key); + + } + + /** + * Creates a new TOTP generator which uses the given shared key to generate + * authentication codes. The provided generation mode dictates the size of + * the key required. The start time and time step used to produce the + * counter value used by HOTP (the algorithm which forms the basis of TOTP) + * are set to the default values recommended by the TOTP specification (0 + * and 30 respectively). + * + * @param key + * The shared key to use to generate authentication codes. + * + * @param mode + * The mode in which the TOTP algorithm should operate. + * + * @param length + * The length of the codes to generate, in digits. As required + * by the specification, this value MUST be at least 6 but no greater + * than 8. + * + * @throws InvalidKeyException + * If the provided key is invalid for the requested TOTP mode. + */ + public TOTPGenerator(byte[] key, Mode mode, int length) + throws InvalidKeyException { + this(key, mode, length, DEFAULT_START_TIME, DEFAULT_TIME_STEP); + } + + /** + * Returns a new Mac instance which produces message authentication codes + * using the given secret key and the algorithm required by the given TOTP + * mode. + * + * @param mode + * The TOTP mode which dictates the HMAC algorithm to be used. + * + * @param key + * The secret key to use to produce message authentication codes. + * + * @return + * A new Mac instance which produces message authentication codes + * using the given secret key and the algorithm required by the given + * TOTP mode. + * + * @throws InvalidKeyException + * If the provided key is invalid for the requested TOTP mode. + */ + private static Mac getMacInstance(Mode mode, Key key) + throws InvalidKeyException { + + try { + Mac hmac = Mac.getInstance(mode.getAlgorithmName()); + hmac.init(key); + return hmac; + } + catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Support for the HMAC " + + "algorithm required for TOTP in " + mode + " mode is " + + "missing.", e); + } + + } + + /** + * Calculates the HMAC for the given message using the key and algorithm + * provided when this TOTPGenerator was created. + * + * @param message + * The message to calculate the HMAC of. + * + * @return + * The HMAC of the given message. + */ + private byte[] getHMAC(byte[] message) { + + try { + return getMacInstance(mode, key).doFinal(message); + } + catch (InvalidKeyException e) { + + // As the key is verified during construction of the TOTPGenerator, + // this should never happen + throw new IllegalStateException("Provided key became invalid after " + + "passing validation.", e); + + } + + } + + /** + * Given an arbitrary integer value, returns a code containing the decimal + * representation of that value and exactly the given number of digits. If + * the given value has more than the desired number of digits, leading + * digits will be truncated to reduce the length. If the given value has + * fewer than the desired number of digits, leading zeroes will be added to + * increase the length. + * + * @param value + * The value to convert into a decimal code of the given length. + * + * @param length + * The number of digits to include in the code. + * + * @return + * A code containing the decimal value of the given integer, truncated + * or padded such that exactly the given number of digits are present. + */ + private String toCode(int value, int length) { + + // Convert value to simple integer string + String valueString = Integer.toString(value); + + // If the resulting string is too long, truncate to the last N digits + if (valueString.length() > length) + return valueString.substring(valueString.length() - length); + + // Otherwise, add zeroes until the desired length is reached + StringBuilder builder = new StringBuilder(length); + for (int i = valueString.length(); i < length; i++) + builder.append('0'); + + // Return the padded integer string + builder.append(valueString); + return builder.toString(); + + } + + /** + * Generates a TOTP code of the given length using the given absolute + * timestamp rather than the current system time. + * + * @param time + * The absolute timestamp to use to generate the TOTP code, in seconds + * since midnight, 1970-01-01, UTC (UNIX epoch). + * + * @return + * The TOTP code which corresponds to the given timestamp, having + * exactly the given length. + * + * @throws IllegalArgumentException + * If the given length is invalid as defined by the TOTP specification. + */ + public String generate(long time) { + + // Calculate HOTP counter value based on provided time + long counter = (time - startTime) / timeStep; + byte[] hash = getHMAC(Longs.toByteArray(counter)); + + // Calculate HOTP value as defined by section 5.2 of RFC 4226: + // https://tools.ietf.org/html/rfc4226#section-5.2 + int offset = hash[hash.length - 1] & 0xF; + int binary + = ((hash[offset] & 0x7F) << 24) + | ((hash[offset + 1] & 0xFF) << 16) + | ((hash[offset + 2] & 0xFF) << 8) + | (hash[offset + 3] & 0xFF); + + // Truncate or pad the value accordingly + return toCode(binary, length); + + } + + /** + * Generates a TOTP code of the given length using the current system time. + * + * @return + * The TOTP code which corresponds to the current system time, having + * exactly the given length. + * + * @throws IllegalArgumentException + * If the given length is invalid as defined by the TOTP specification. + */ + public String generate() { + return generate(System.currentTimeMillis() / 1000); + } + +} diff --git a/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java b/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java new file mode 100644 index 000000000..9266ad7e3 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/test/java/org/apache/guacamole/totp/TOTPGeneratorTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.totp; + +import java.security.InvalidKeyException; +import org.junit.Test; +import static org.junit.Assert.*; + +/* + * NOTE: The tests for this TOTP implementation is based on the TOTP reference + * implementation provided by the IETF Trust at: + * + * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl + */ + +/* + * Copyright (c) 2011 IETF Trust and the persons identified as authors + * of the code. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of Internet Society, IETF or IETF Trust, nor the names + * of specific contributors, may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Test which verifies the correctness of the TOTPGenerator class against the + * test inputs and results provided in the IETF reference implementation and + * spec for TOTP: + * + * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Test-Vectors + */ +public class TOTPGeneratorTest { + + /** + * Verifies the results of generating authentication codes using the TOTP + * algorithm in SHA1 mode. + */ + @Test + public void testGenerateSHA1() { + + // 160-bit key consisting of the bytes "12345678901234567890" repeated + // as necessary + final byte[] key = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' + }; + + try { + final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 8); + assertEquals("94287082", totp.generate(59)); + assertEquals("07081804", totp.generate(1111111109)); + assertEquals("14050471", totp.generate(1111111111)); + assertEquals("89005924", totp.generate(1234567890)); + assertEquals("69279037", totp.generate(2000000000)); + assertEquals("65353130", totp.generate(20000000000L)); + } + catch (InvalidKeyException e) { + fail("SHA1 test key is invalid."); + } + + + } + + /** + * Verifies the results of generating authentication codes using the TOTP + * algorithm in SHA256 mode. + */ + @Test + public void testGenerateSHA256() { + + // 256-bit key consisting of the bytes "12345678901234567890" repeated + // as necessary + final byte[] key = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2' + }; + + try { + final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA256, 8); + assertEquals("46119246", totp.generate(59)); + assertEquals("68084774", totp.generate(1111111109)); + assertEquals("67062674", totp.generate(1111111111)); + assertEquals("91819424", totp.generate(1234567890)); + assertEquals("90698825", totp.generate(2000000000)); + assertEquals("77737706", totp.generate(20000000000L)); + } + catch (InvalidKeyException e) { + fail("SHA256 test key is invalid."); + } + + } + + /** + * Verifies the results of generating authentication codes using the TOTP + * algorithm in SHA512 mode. + */ + @Test + public void testGenerateSHA512() { + + // 512-bit key consisting of the bytes "12345678901234567890" repeated + // as necessary + final byte[] key = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4' + }; + + try { + final TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA512, 8); + assertEquals("90693936", totp.generate(59)); + assertEquals("25091201", totp.generate(1111111109)); + assertEquals("99943326", totp.generate(1111111111)); + assertEquals("93441116", totp.generate(1234567890)); + assertEquals("38618901", totp.generate(2000000000)); + assertEquals("47863826", totp.generate(20000000000L)); + } + catch (InvalidKeyException e) { + fail("SHA512 test key is invalid."); + } + + } + +} From 19e03a1632eee39508378a434f3362b9e9f9a3f8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 00:57:37 -0800 Subject: [PATCH 03/20] GUACAMOLE-96: Verify TOTP of all users against hard-coded key. --- .../auth/totp/UserVerificationService.java | 75 ++++++++++++++++--- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index f28149a15..7cffffe09 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -19,6 +19,8 @@ package org.apache.guacamole.auth.totp; +import com.google.common.io.BaseEncoding; +import java.security.InvalidKeyException; import java.util.Collections; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; @@ -30,12 +32,20 @@ import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.apache.guacamole.totp.TOTPGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service for verifying the identity of a user using TOTP. */ public class UserVerificationService { + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class); + /** * The name of the HTTP parameter which will contain the TOTP code provided * by the user to verify their identity. @@ -56,6 +66,30 @@ public class UserVerificationService { Collections.singletonList(TOTP_FIELD) ); + /** + * BaseEncoding instance which decoded/encodes base32. + */ + private static final BaseEncoding BASE32 = BaseEncoding.base32(); + + /** + * Retrieves the base32-encoded TOTP key associated with user having the + * given UserContext. If no TOTP key is associated with the user, null is + * returned. + * + * @param context + * The UserContext of the user whose TOTP key should be retrieved. + * + * @return + * The base32-encoded TOTP key associated with user having the given + * UserContext, or null if no TOTP key is associated with the user. + */ + public String getKey(UserContext context){ + + // FIXME: Hard-coded key + return "JBSWY3DPEHPK3PXP"; + + } + /** * Verifies the identity of the given user using TOTP. If a authentication * code from the user's TOTP device has not already been provided, a code is @@ -77,25 +111,48 @@ public class UserVerificationService { public void verifyIdentity(UserContext context, AuthenticatedUser authenticatedUser) throws GuacamoleException { + // Ignore anonymous users + String username = authenticatedUser.getIdentifier(); + if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER)) + return; + + // Ignore users which do not have an associated key + String encodedKey = getKey(context); + if (encodedKey == null) + return; + // Pull the original HTTP request used to authenticate Credentials credentials = authenticatedUser.getCredentials(); HttpServletRequest request = credentials.getRequest(); - // Ignore anonymous users - if (authenticatedUser.getIdentifier().equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER)) - return; - // Retrieve TOTP from request - String totp = request.getParameter(TOTP_PARAMETER_NAME); + String code = request.getParameter(TOTP_PARAMETER_NAME); // If no TOTP provided, request one - if (totp == null) + if (code == null) throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS); - // FIXME: Hard-coded code - if (!totp.equals("123456")) - throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED"); + try { + + // Verify provided TOTP against value produced by generator + byte[] key = BASE32.decode(encodedKey); + TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6); + if (code.equals(totp.generate())) + return; + + } + catch (InvalidKeyException e) { + logger.warn("User \"{}\" is associated with an invalid TOTP key.", username); + logger.debug("TOTP key is not valid.", e); + } + catch (IllegalArgumentException e) { + logger.warn("TOTP key of user \"{}\" is not valid base32.", username); + logger.debug("TOTP key is not valid base32.", e); + } + + // Provided code is not valid + throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED"); } From 8dd5537cf30f9b9a57e637dae53c47c237416064 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 01:05:42 -0800 Subject: [PATCH 04/20] GUACAMOLE-96: Pull TOTP key from user attribute. --- .../guacamole/auth/totp/UserVerificationService.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index 7cffffe09..cb737307f 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.totp; import com.google.common.io.BaseEncoding; import java.security.InvalidKeyException; import java.util.Collections; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; @@ -46,6 +47,11 @@ public class UserVerificationService { */ private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class); + /** + * The name of the user attribute which stores the TOTP key. + */ + private static final String TOTP_KEY_ATTRIBUTE_NAME = "guac-totp-key"; + /** * The name of the HTTP parameter which will contain the TOTP code provided * by the user to verify their identity. @@ -84,10 +90,8 @@ public class UserVerificationService { * UserContext, or null if no TOTP key is associated with the user. */ public String getKey(UserContext context){ - - // FIXME: Hard-coded key - return "JBSWY3DPEHPK3PXP"; - + Map attributes = context.self().getAttributes(); + return attributes.get(TOTP_KEY_ATTRIBUTE_NAME); } /** From 78c398f45d484ba4935870f6cd5a146a6f9d2f16 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 01:19:39 -0800 Subject: [PATCH 05/20] GUACAMOLE-96: Allow users to enter either the current or previous TOTP codes. --- .../auth/totp/UserVerificationService.java | 2 +- .../apache/guacamole/totp/TOTPGenerator.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index cb737307f..823c5ef29 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -142,7 +142,7 @@ public class UserVerificationService { // Verify provided TOTP against value produced by generator byte[] key = BASE32.decode(encodedKey); TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6); - if (code.equals(totp.generate())) + if (code.equals(totp.generate()) || code.equals(totp.previous())) return; } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java index 004c23ba2..b8c0d9561 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java @@ -399,4 +399,33 @@ public class TOTPGenerator { return generate(System.currentTimeMillis() / 1000); } + /** + * Returns the TOTP code which would have been generated immediately prior + * to the code returned by invoking generate() with the given timestamp. + * + * @param time + * The absolute timestamp to use to generate the TOTP code, in seconds + * since midnight, 1970-01-01, UTC (UNIX epoch). + * + * @return + * The TOTP code which would have been generated immediately prior to + * the the code returned by invoking generate() with the given + * timestamp. + */ + public String previous(long time) { + return generate(Math.max(startTime, time - timeStep)); + } + + /** + * Returns the TOTP code which would have been generated immediately prior + * to the code currently being returned by generate(). + * + * @return + * The TOTP code which would have been generated immediately prior to + * the code currently being returned by generate(). + */ + public String previous() { + return previous(System.currentTimeMillis() / 1000); + } + } From 4178a4b8b3f4898db4af9159ea2d3df747a01638 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 10:37:23 -0800 Subject: [PATCH 06/20] GUACAMOLE-96: Include recommended key length for each TOTP mode. --- .../apache/guacamole/totp/TOTPGenerator.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java index b8c0d9561..d075c8afc 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java @@ -124,19 +124,19 @@ public class TOTPGenerator { * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode * requires 160-bit keys. */ - SHA1("HmacSHA1"), + SHA1("HmacSHA1", 20), /** * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode * requires 256-bit keys. */ - SHA256("HmacSHA256"), + SHA256("HmacSHA256", 32), /** * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode * requires 512-bit keys. */ - SHA512("HmacSHA512"); + SHA512("HmacSHA512", 64); /** * The name of the HMAC algorithm which the TOTP implementation should @@ -145,6 +145,13 @@ public class TOTPGenerator { */ private final String algorithmName; + /** + * The recommended length of keys generated for TOTP in this mode, in + * bytes. Keys are recommended to be the same length as the hash + * involved. + */ + private final int recommendedKeyLength; + /** * Creates a new TOTP operating mode which is associated with the * given HMAC algorithm. @@ -153,9 +160,14 @@ public class TOTPGenerator { * The name of the HMAC algorithm which the TOTP implementation * should use when operating in this mode, in the format required * by Mac.getInstance(). + * + * @param recommendedKeyLength + * The recommended length of keys generated for TOTP in this mode, + * in bytes. */ - private Mode(String algorithmName) { + private Mode(String algorithmName, int recommendedKeyLength) { this.algorithmName = algorithmName; + this.recommendedKeyLength = recommendedKeyLength; } /** @@ -171,6 +183,19 @@ public class TOTPGenerator { return algorithmName; } + /** + * Returns the recommended length of keys generated for TOTP in this + * mode, in bytes. Keys are recommended to be the same length as the + * hash involved. + * + * @return + * The recommended length of keys generated for TOTP in this mode, + * in bytes. + */ + public int getRecommendedKeyLength() { + return recommendedKeyLength; + } + } /** From 8e3cbf06274c385afb99340e3b1c153a7946fb08 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 10:56:35 -0800 Subject: [PATCH 07/20] GUACAMOLE-96: Abstract TOTP key into separate class with confirmation semantics. --- .../guacamole/auth/totp/UserTOTPKey.java | 126 +++++++++++++++ .../auth/totp/UserVerificationService.java | 147 ++++++++++++++++-- 2 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java new file mode 100644 index 000000000..d93d2533e --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * The key used to generate TOTP codes for a particular user. + */ +public class UserTOTPKey { + + /** + * Secure source of random bytes. + */ + private static final Random RANDOM = new SecureRandom(); + + /** + * Whether the associated secret key has been confirmed by the user. A key + * is confirmed once the user has successfully entered a valid TOTP + * derived from that key. + */ + private boolean confirmed; + + /** + * The base32-encoded TOTP key associated with the user. + */ + private byte[] secret; + + /** + * Generates the given number of random bytes. + * + * @param length + * The number of random bytes to generate. + * + * @return + * A new array of exactly the given number of random bytes. + */ + private static byte[] generateBytes(int length) { + byte[] bytes = new byte[length]; + RANDOM.nextBytes(bytes); + return bytes; + } + + /** + * Creates a new, unconfirmed, randomly-generated TOTP key having the given + * length. + * + * @param length + * The length of the key to generate, in bytes. + */ + public UserTOTPKey(int length) { + this(generateBytes(length), false); + } + + /** + * Creates a new UserTOTPKey containing the given key and having the given + * confirmed state. + * + * @param secret + * The raw binary secret key to be used to generate TOTP codes. + * + * @param confirmed + * true if the user associated with the key has confirmed that they can + * successfully generate the corresponding TOTP codes (the user has + * been "enrolled"), false otherwise. + */ + public UserTOTPKey(byte[] secret, boolean confirmed) { + this.confirmed = confirmed; + this.secret = secret; + } + + /** + * Returns the raw binary secret key to be used to generate TOTP codes. + * + * @return + * The raw binary secret key to be used to generate TOTP codes. + */ + public byte[] getSecret() { + return secret; + } + + /** + * Returns whether the user associated with the key has confirmed that they + * can successfully generate the corresponding TOTP codes (the user has + * been "enrolled"). + * + * @return + * true if the user has confirmed that they can successfully generate + * the TOTP codes generated by this key, false otherwise. + */ + public boolean isConfirmed() { + return confirmed; + } + + /** + * Sets whether the user associated with the key has confirmed that they + * can successfully generate the corresponding TOTP codes (the user has + * been "enrolled"). + * + * @param confirmed + * true if the user has confirmed that they can successfully generate + * the TOTP codes generated by this key, false otherwise. + */ + public void setConfirmed(boolean confirmed) { + this.confirmed = confirmed; + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index 823c5ef29..d694c5e90 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -22,14 +22,17 @@ package org.apache.guacamole.auth.totp; import com.google.common.io.BaseEncoding; import java.security.InvalidKeyException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleUnsupportedException; import org.apache.guacamole.form.Field; import org.apache.guacamole.form.TextField; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; @@ -50,7 +53,13 @@ public class UserVerificationService { /** * The name of the user attribute which stores the TOTP key. */ - private static final String TOTP_KEY_ATTRIBUTE_NAME = "guac-totp-key"; + private static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret"; + + /** + * The name of the user attribute defines whether the TOTP key has been + * confirmed by the user, and the user is thus fully enrolled. + */ + private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed"; /** * The name of the HTTP parameter which will contain the TOTP code provided @@ -78,20 +87,115 @@ public class UserVerificationService { private static final BaseEncoding BASE32 = BaseEncoding.base32(); /** - * Retrieves the base32-encoded TOTP key associated with user having the - * given UserContext. If no TOTP key is associated with the user, null is + * Retrieves and decodes the base32-encoded TOTP key associated with user + * having the given UserContext. If no TOTP key is associated with the user, + * a random key is generated and associated with the user. If the extension + * storing the user does not support storage of the TOTP key, null is * returned. * * @param context * The UserContext of the user whose TOTP key should be retrieved. * * @return - * The base32-encoded TOTP key associated with user having the given - * UserContext, or null if no TOTP key is associated with the user. + * The TOTP key associated with the user having the given UserContext, + * or null if the extension storing the user does not support storage + * of the TOTP key. + * + * @throws GuacamoleException + * If a new key is generated, but the extension storing the associated + * user fails while updating the user account. */ - public String getKey(UserContext context){ + private UserTOTPKey getKey(UserContext context) throws GuacamoleException { + + // Retrieve attributes from current user + User self = context.self(); Map attributes = context.self().getAttributes(); - return attributes.get(TOTP_KEY_ATTRIBUTE_NAME); + + // If no key is defined, attempt to generate a new key + String secret = attributes.get(TOTP_KEY_SECRET_ATTRIBUTE_NAME); + if (secret == null) { + + // Generate random key for user + UserTOTPKey generated = new UserTOTPKey(TOTPGenerator.Mode.SHA1.getRecommendedKeyLength()); + if (setKey(context, generated)) + return generated; + + // Fail if key cannot be set + return null; + + } + + // Parse retrieved base32 key value + byte[] key; + try { + key = BASE32.decode(secret); + } + + // If key is not valid base32, warn but otherwise pretend the key does + // not exist + catch (IllegalArgumentException e) { + logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier()); + logger.debug("TOTP key is not valid base32.", e); + return null; + } + + // Otherwise, parse value from attributes + boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)); + return new UserTOTPKey(key, confirmed); + + } + + /** + * Attempts to store the given TOTP key within the user account of the user + * having the given UserContext. As not all extensions will support storage + * of arbitrary attributes, this operation may fail. + * + * @param context + * The UserContext associated with the user whose TOTP key is to be + * stored. + * + * @param key + * The TOTP key to store. + * + * @return + * true if the TOTP key was successfully stored, false if the extension + * handling storage does not support storage of the key. + * + * @throws GuacamoleException + * If the extension handling storage fails internally while attempting + * to update the user. + */ + private boolean setKey(UserContext context, UserTOTPKey key) + throws GuacamoleException { + + // Get mutable set of attributes + User self = context.self(); + Map attributes = new HashMap(); + + // Set/overwrite current TOTP key state + attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret())); + attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false"); + self.setAttributes(attributes); + + // Confirm that attributes have actually been set + Map setAttributes = self.getAttributes(); + if (!setAttributes.containsKey(TOTP_KEY_SECRET_ATTRIBUTE_NAME) + || !setAttributes.containsKey(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)) + return false; + + // Update user object + try { + context.getUserDirectory().update(self); + } + catch (GuacamoleUnsupportedException e) { + logger.debug("Extension storage for user is explicitly read-only. " + + "Cannot update attributes to store TOTP key.", e); + return false; + } + + // TOTP key successfully stored/updated + return true; + } /** @@ -121,8 +225,8 @@ public class UserVerificationService { return; // Ignore users which do not have an associated key - String encodedKey = getKey(context); - if (encodedKey == null) + UserTOTPKey key = getKey(context); + if (key == null) return; // Pull the original HTTP request used to authenticate @@ -133,27 +237,36 @@ public class UserVerificationService { String code = request.getParameter(TOTP_PARAMETER_NAME); // If no TOTP provided, request one - if (code == null) + if (code == null) { + + // FIXME: Handle key.isConfirmed() for initial prompt throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS); + } + try { // Verify provided TOTP against value produced by generator - byte[] key = BASE32.decode(encodedKey); - TOTPGenerator totp = new TOTPGenerator(key, TOTPGenerator.Mode.SHA1, 6); - if (code.equals(totp.generate()) || code.equals(totp.previous())) + TOTPGenerator totp = new TOTPGenerator(key.getSecret(), TOTPGenerator.Mode.SHA1, 6); + if (code.equals(totp.generate()) || code.equals(totp.previous())) { + + // Record key as confirmed, if it hasn't already been so recorded + if (!key.isConfirmed()) { + key.setConfirmed(true); + setKey(context, key); + } + + // User has been verified return; + } + } catch (InvalidKeyException e) { logger.warn("User \"{}\" is associated with an invalid TOTP key.", username); logger.debug("TOTP key is not valid.", e); } - catch (IllegalArgumentException e) { - logger.warn("TOTP key of user \"{}\" is not valid base32.", username); - logger.debug("TOTP key is not valid base32.", e); - } // Provided code is not valid throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED"); From 0844e9d42297a7f87e6bf2a8fb2f75a198aa0d3f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 11:00:15 -0800 Subject: [PATCH 08/20] GUACAMOLE-96: Add license for Guava. --- .../guacamole-auth-totp/src/licenses/LICENSE | 9 + .../src/licenses/bundled/guava-18.0/COPYING | 202 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE index e1fa0fcab..37d934cea 100644 --- a/extensions/guacamole-auth-totp/src/licenses/LICENSE +++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE @@ -229,6 +229,15 @@ Google Guice (https://github.com/google/guice) Apache v2.0 (bundled/guice-3.0/COPYING) +Guava: Google Core Libraries for Java (https://github.com/google/guava) +----------------------------------------------------------------------- + + Version: 18.0 + From: 'Google Inc.' (http://www.google.com/) + License(s): + Apache v2.0 (bundled/guava-18.0/COPYING) + + JSR-330 / Dependency Injection for Java (http://code.google.com/p/atinject/) ---------------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING b/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/guava-18.0/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 8ac8fec47834a13317591a676faf11ed29b34929 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 12:03:18 -0800 Subject: [PATCH 09/20] GUACAMOLE-96: Migrate to TOTP-specific field type for authentication code. --- extensions/guacamole-auth-totp/pom.xml | 80 +++++++++++++++++++ .../auth/totp/UserVerificationService.java | 28 ++----- .../totp/form/AuthenticationCodeField.java | 48 +++++++++++ .../src/main/resources/config/totpConfig.js | 33 ++++++++ .../authenticationCodeFieldController.js | 29 +++++++ .../src/main/resources/guac-manifest.json | 14 +++- .../src/main/resources/styles/totp.css | 20 +++++ .../templates/authenticationCodeField.html | 3 + .../src/main/resources/totpModule.js | 28 +++++++ 9 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java create mode 100644 extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js create mode 100644 extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js create mode 100644 extensions/guacamole-auth-totp/src/main/resources/styles/totp.css create mode 100644 extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html create mode 100644 extensions/guacamole-auth-totp/src/main/resources/totpModule.js diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml index 5b421d72b..17aff05dd 100644 --- a/extensions/guacamole-auth-totp/pom.xml +++ b/extensions/guacamole-auth-totp/pom.xml @@ -53,6 +53,85 @@ + + + com.keithbranton.mojo + angular-maven-plugin + 0.3.2 + + + generate-resources + + html2js + + + + + ${basedir}/src/main/resources + **/*.html + ${basedir}/src/main/resources/generated/templates-main/templates.js + app/ext/totp + + + + + + com.samaxes.maven + minify-maven-plugin + 1.7.5 + + + default-cli + + UTF-8 + + ${basedir}/src/main/resources + ${project.build.directory}/classes + + / + / + totp.css + + + license.txt + + + + **/*.css + + + / + / + totp.js + + + license.txt + + + + **/*.js + + + + + **/*.test.js + + CLOSURE + + + + OFF + OFF + + + + + minify + + + + + maven-assembly-plugin @@ -105,6 +184,7 @@ **/*.json src/licenses/**/* + src/main/resources/templates/*.html diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index d694c5e90..da24995a6 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -28,8 +28,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.auth.totp.form.AuthenticationCodeField; import org.apache.guacamole.form.Field; -import org.apache.guacamole.form.TextField; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.User; @@ -61,26 +61,6 @@ public class UserVerificationService { */ private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed"; - /** - * The name of the HTTP parameter which will contain the TOTP code provided - * by the user to verify their identity. - */ - private static final String TOTP_PARAMETER_NAME = "guac-totp"; - - /** - * The field which should be exposed to the user to request that they - * provide their TOTP code. - */ - private static final Field TOTP_FIELD = new TextField(TOTP_PARAMETER_NAME); - - /** - * CredentialsInfo object describing the credentials expected for a user - * who has verified their identity with TOTP. - */ - private static final CredentialsInfo TOTP_CREDENTIALS = new CredentialsInfo( - Collections.singletonList(TOTP_FIELD) - ); - /** * BaseEncoding instance which decoded/encodes base32. */ @@ -234,14 +214,16 @@ public class UserVerificationService { HttpServletRequest request = credentials.getRequest(); // Retrieve TOTP from request - String code = request.getParameter(TOTP_PARAMETER_NAME); + String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME); // If no TOTP provided, request one if (code == null) { // FIXME: Handle key.isConfirmed() for initial prompt throw new GuacamoleInsufficientCredentialsException( - "LOGIN.INFO_TOTP_REQUIRED", TOTP_CREDENTIALS); + "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( + Collections.singletonList(new AuthenticationCodeField()) + )); } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java new file mode 100644 index 000000000..8119657a6 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.form; + +import org.apache.guacamole.form.Field; + +/** + * Field which prompts the user for an authentication code generated via TOTP. + */ +public class AuthenticationCodeField extends Field { + + /** + * The name of the HTTP parameter which will contain the TOTP code provided + * by the user to verify their identity. + */ + public static final String PARAMETER_NAME = "guac-totp"; + + /** + * The unique name associated with this field type. + */ + private static final String FIELD_TYPE_NAME = "GUAC_TOTP_CODE"; + + /** + * Creates a new field which prompts the user for an authentication code + * generated via TOTP. + */ + public AuthenticationCodeField() { + super(PARAMETER_NAME, FIELD_TYPE_NAME); + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js new file mode 100644 index 000000000..54bb56c08 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Config block which registers TOTP-specific field types. + */ +angular.module('guacTOTP').config(['formServiceProvider', + function guacTOTPConfig(formServiceProvider) { + + // Define field for the TOTP code provided by the user + formServiceProvider.registerFieldType('GUAC_TOTP_CODE', { + module : 'guacTOTP', + controller : 'authenticationCodeFieldController', + templateUrl : 'app/ext/totp/templates/authenticationCodeField.html' + }); + +}]); diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js new file mode 100644 index 000000000..c9cecc68d --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter + * the code generated by their authentication device. + */ +angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$element', + function authenticationCodeFieldController($scope, $element) { + + // STUB + +}]); diff --git a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json index 539562ccf..dee829170 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-totp/src/main/resources/guac-manifest.json @@ -11,6 +11,18 @@ "translations" : [ "translations/en.json" - ] + ], + + "js" : [ + "totp.min.js" + ], + + "css" : [ + "totp.min.css" + ], + + "resources" : { + "templates/authenticationCodeField.html" : "text/html" + } } diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css new file mode 100644 index 000000000..8181e2ccd --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* STUB */ diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html new file mode 100644 index 000000000..4e7fb0f8c --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/extensions/guacamole-auth-totp/src/main/resources/totpModule.js b/extensions/guacamole-auth-totp/src/main/resources/totpModule.js new file mode 100644 index 000000000..c6a0c7ea4 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/totpModule.js @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Module which provides handling for TOTP multi-factor authentication. + */ +angular.module('guacTOTP', [ + 'form' +]); + +// Ensure the guacTOTP module is loaded along with the rest of the app +angular.module('index').requires.push('guacTOTP'); From 170a11bf2a17a98b7ce292ecc53c947b755bad14 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 14:01:39 -0800 Subject: [PATCH 10/20] GUACAMOLE-96: Handle enrollment via QR code for unconfirmed users. --- extensions/guacamole-auth-totp/pom.xml | 20 ++ .../guacamole-auth-totp/src/licenses/LICENSE | 9 + .../src/licenses/bundled/zxing-3.3.1/LICENSE | 245 ++++++++++++++++++ .../guacamole/auth/totp/UserTOTPKey.java | 28 +- .../auth/totp/UserVerificationService.java | 21 +- .../totp/form/AuthenticationCodeField.java | 141 +++++++++- .../templates/authenticationCodeField.html | 12 +- 7 files changed, 465 insertions(+), 11 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE diff --git a/extensions/guacamole-auth-totp/pom.xml b/extensions/guacamole-auth-totp/pom.xml index 17aff05dd..af4445581 100644 --- a/extensions/guacamole-auth-totp/pom.xml +++ b/extensions/guacamole-auth-totp/pom.xml @@ -249,6 +249,26 @@ test + + + com.google.zxing + javase + 3.3.1 + + + com.google.zxing + core + 3.3.1 + + + + + javax.ws.rs + javax.ws.rs-api + 2.0 + provided + + diff --git a/extensions/guacamole-auth-totp/src/licenses/LICENSE b/extensions/guacamole-auth-totp/src/licenses/LICENSE index 37d934cea..8a66d231a 100644 --- a/extensions/guacamole-auth-totp/src/licenses/LICENSE +++ b/extensions/guacamole-auth-totp/src/licenses/LICENSE @@ -283,3 +283,12 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ZXing Barcode Scanning Library (https://github.com/zxing/zxing/) +---------------------------------------------------------------- + + Version: 3.3.1 + From: 'ZXing authors' (https://github.com/zxing/zxing/blob/zxing-3.3.1/AUTHORS) + License(s): + Apache v2.0 (bundled/zxing-3.3.1/LICENSE) + diff --git a/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE b/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE new file mode 100644 index 000000000..510991eda --- /dev/null +++ b/extensions/guacamole-auth-totp/src/licenses/bundled/zxing-3.3.1/LICENSE @@ -0,0 +1,245 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +======================================================================== +jai-imageio +======================================================================== + +Copyright (c) 2005 Sun Microsystems, Inc. +Copyright © 2010-2014 University of Manchester +Copyright © 2010-2015 Stian Soiland-Reyes +Copyright © 2015 Peter Hull +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +Neither the name of Sun Microsystems, Inc. or the names of +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +This software is provided "AS IS," without a warranty of any +kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND +WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY +EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL +NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF +USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS +DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR +ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, +CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND +REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR +INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +You acknowledge that this software is not designed or intended for +use in the design, construction, operation or maintenance of any +nuclear facility. \ No newline at end of file diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java index d93d2533e..3de378581 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java @@ -32,6 +32,11 @@ public class UserTOTPKey { */ private static final Random RANDOM = new SecureRandom(); + /** + * The username of the user associated with this key. + */ + private final String username; + /** * Whether the associated secret key has been confirmed by the user. A key * is confirmed once the user has successfully entered a valid TOTP @@ -63,17 +68,23 @@ public class UserTOTPKey { * Creates a new, unconfirmed, randomly-generated TOTP key having the given * length. * + * @param username + * The username of the user associated with this key. + * * @param length * The length of the key to generate, in bytes. */ - public UserTOTPKey(int length) { - this(generateBytes(length), false); + public UserTOTPKey(String username, int length) { + this(username, generateBytes(length), false); } /** * Creates a new UserTOTPKey containing the given key and having the given * confirmed state. * + * @param username + * The username of the user associated with this key. + * * @param secret * The raw binary secret key to be used to generate TOTP codes. * @@ -82,11 +93,22 @@ public class UserTOTPKey { * successfully generate the corresponding TOTP codes (the user has * been "enrolled"), false otherwise. */ - public UserTOTPKey(byte[] secret, boolean confirmed) { + public UserTOTPKey(String username, byte[] secret, boolean confirmed) { + this.username = username; this.confirmed = confirmed; this.secret = secret; } + /** + * Returns the username of the user associated with this key. + * + * @return + * The username of the user associated with this key. + */ + public String getUsername() { + return username; + } + /** * Returns the raw binary secret key to be used to generate TOTP codes. * diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index da24995a6..a44b6ee7a 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -76,6 +76,9 @@ public class UserVerificationService { * @param context * The UserContext of the user whose TOTP key should be retrieved. * + * @param username + * The username of the user associated with the given UserContext. + * * @return * The TOTP key associated with the user having the given UserContext, * or null if the extension storing the user does not support storage @@ -85,7 +88,8 @@ public class UserVerificationService { * If a new key is generated, but the extension storing the associated * user fails while updating the user account. */ - private UserTOTPKey getKey(UserContext context) throws GuacamoleException { + private UserTOTPKey getKey(UserContext context, + String username) throws GuacamoleException { // Retrieve attributes from current user User self = context.self(); @@ -96,7 +100,7 @@ public class UserVerificationService { if (secret == null) { // Generate random key for user - UserTOTPKey generated = new UserTOTPKey(TOTPGenerator.Mode.SHA1.getRecommendedKeyLength()); + UserTOTPKey generated = new UserTOTPKey(username, TOTPGenerator.Mode.SHA1.getRecommendedKeyLength()); if (setKey(context, generated)) return generated; @@ -121,7 +125,7 @@ public class UserVerificationService { // Otherwise, parse value from attributes boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)); - return new UserTOTPKey(key, confirmed); + return new UserTOTPKey(username, key, confirmed); } @@ -205,7 +209,7 @@ public class UserVerificationService { return; // Ignore users which do not have an associated key - UserTOTPKey key = getKey(context); + UserTOTPKey key = getKey(context, username); if (key == null) return; @@ -219,7 +223,14 @@ public class UserVerificationService { // If no TOTP provided, request one if (code == null) { - // FIXME: Handle key.isConfirmed() for initial prompt + // If the user hasn't completed enrollment, request that they do + if (!key.isConfirmed()) + throw new GuacamoleInsufficientCredentialsException( + "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( + Collections.singletonList(new AuthenticationCodeField(key)) + )); + + // Otherwise simply request the user's authentication code throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( Collections.singletonList(new AuthenticationCodeField()) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index 8119657a6..1be5f276d 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -19,7 +19,21 @@ package org.apache.guacamole.auth.totp.form; +import com.google.common.io.BaseEncoding; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import javax.ws.rs.core.UriBuilder; +import javax.xml.bind.DatatypeConverter; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.UserTOTPKey; import org.apache.guacamole.form.Field; +import org.codehaus.jackson.annotate.JsonProperty; /** * Field which prompts the user for an authentication code generated via TOTP. @@ -37,12 +51,135 @@ public class AuthenticationCodeField extends Field { */ private static final String FIELD_TYPE_NAME = "GUAC_TOTP_CODE"; + /** + * The width of QR codes to generate, in pixels. + */ + private static final int QR_CODE_WIDTH = 256; + + /** + * The height of QR codes to generate, in pixels. + */ + private static final int QR_CODE_HEIGHT = 256; + + /** + * BaseEncoding which encodes/decodes base32. + */ + private static final BaseEncoding BASE32 = BaseEncoding.base32(); + + /** + * The TOTP key to expose to the user for the sake of enrollment, if any. + * If no such key should be exposed to the user, this will be null. + */ + private final UserTOTPKey key; + /** * Creates a new field which prompts the user for an authentication code - * generated via TOTP. + * generated via TOTP, and provide the user with their TOTP key to + * facilitate enrollment. + * + * @param key + * The TOTP key to expose to the user for the sake of enrollment. + */ + public AuthenticationCodeField(UserTOTPKey key) { + super(PARAMETER_NAME, FIELD_TYPE_NAME); + this.key = key; + } + + /** + * Creates a new field which prompts the user for an authentication code + * generated via TOTP. The user's TOTP key is not exposed for enrollment. */ public AuthenticationCodeField() { - super(PARAMETER_NAME, FIELD_TYPE_NAME); + this(null); + } + + /** + * Returns the "otpauth" URI for the secret key used to generate TOTP codes + * for the current user. If the secret key is not being exposed to + * facilitate enrollment, null is returned. + * + * @return + * The "otpauth" URI for the secret key used to generate TOTP codes + * for the current user, or null is the secret ket is not being exposed + * to facilitate enrollment. + * + * @throws GuacamoleException + * If the configuration information required for generating the key URI + * cannot be read from guacamole.properties. + */ + @JsonProperty("keyUri") + public URI getKeyURI() throws GuacamoleException { + + // Do not generate a key URI if no key is being exposed + if (key == null) + return null; + + // FIXME: Pull from configuration + String issuer = "Some Issuer"; + String algorithm = "SHA1"; + String digits = "6"; + String period = "30"; + + // Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format) + return UriBuilder.fromUri("otpauth://totp/") + .path(issuer + ":" + key.getUsername()) + .queryParam("secret", BASE32.encode(key.getSecret())) + .queryParam("issuer", issuer) + .queryParam("algorithm", algorithm) + .queryParam("digits", digits) + .queryParam("period", period) + .build(); + + } + + /** + * Returns the URL of a QR code describing the user's TOTP key and + * configuration. If the key is not being exposed for enrollment, null is + * returned. + * + * @return + * The URL of a QR code describing the user's TOTP key and + * configuration, or null if the key is not being exposed for + * enrollment. + * + * @throws GuacamoleException + * If the configuration information required for generating the QR code + * cannot be read from guacamole.properties. + */ + @JsonProperty("qrCode") + public String getQRCode() throws GuacamoleException { + + // Do not generate a QR code if no key is being exposed + URI keyURI = getKeyURI(); + if (keyURI == null) + return null; + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + try { + + // Create QR code writer + QRCodeWriter writer = new QRCodeWriter(); + BitMatrix matrix = writer.encode(keyURI.toString(), + BarcodeFormat.QR_CODE, QR_CODE_WIDTH, QR_CODE_HEIGHT); + + // Produce PNG image of TOTP key text + MatrixToImageWriter.writeToStream(matrix, "PNG", stream); + + } + catch (WriterException e) { + throw new IllegalArgumentException("QR code could not be " + + "generated for TOTP key.", e); + } + catch (IOException e) { + throw new IllegalStateException("Image stream of QR code could " + + "not be written.", e); + } + + // Return data URI for generated image + return "data:image/png;base64," + + DatatypeConverter.printBase64Binary(stream.toByteArray()); + } } diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html index 4e7fb0f8c..ae155d817 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html +++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html @@ -1,3 +1,13 @@
- + + +
+ +
+ + +
+ +
+
From a422fdf9c235e898d5c05499cef638501beb6508 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 14:29:03 -0800 Subject: [PATCH 11/20] GUACAMOLE-96: Add configuration parameters for details of TOTP generation. --- .../TOTPAuthenticationProviderModule.java | 2 + .../auth/totp/UserVerificationService.java | 33 +++- .../auth/totp/conf/ConfigurationService.java | 161 ++++++++++++++++++ .../auth/totp/conf/TOTPModeProperty.java | 62 +++++++ .../totp/form/AuthenticationCodeField.java | 48 +++--- 5 files changed, 277 insertions(+), 29 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java index 757a4121e..e72beeca6 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.totp; import com.google.inject.AbstractModule; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -71,6 +72,7 @@ public class TOTPAuthenticationProviderModule extends AbstractModule { bind(Environment.class).toInstance(environment); // Bind TOTP-specific services + bind(ConfigurationService.class); bind(UserVerificationService.class); } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index a44b6ee7a..987d4ca37 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -20,6 +20,8 @@ package org.apache.guacamole.auth.totp; import com.google.common.io.BaseEncoding; +import com.google.inject.Inject; +import com.google.inject.Provider; import java.security.InvalidKeyException; import java.util.Collections; import java.util.HashMap; @@ -28,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.auth.totp.form.AuthenticationCodeField; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.AuthenticatedUser; @@ -66,6 +69,18 @@ public class UserVerificationService { */ private static final BaseEncoding BASE32 = BaseEncoding.base32(); + /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Provider for AuthenticationCodeField instances. + */ + @Inject + private Provider codeFieldProvider; + /** * Retrieves and decodes the base32-encoded TOTP key associated with user * having the given UserContext. If no TOTP key is associated with the user, @@ -100,7 +115,8 @@ public class UserVerificationService { if (secret == null) { // Generate random key for user - UserTOTPKey generated = new UserTOTPKey(username, TOTPGenerator.Mode.SHA1.getRecommendedKeyLength()); + TOTPGenerator.Mode mode = confService.getMode(); + UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength()); if (setKey(context, generated)) return generated; @@ -223,25 +239,32 @@ public class UserVerificationService { // If no TOTP provided, request one if (code == null) { + AuthenticationCodeField field = codeFieldProvider.get(); + // If the user hasn't completed enrollment, request that they do - if (!key.isConfirmed()) + if (!key.isConfirmed()) { + field.exposeKey(key); throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( - Collections.singletonList(new AuthenticationCodeField(key)) + Collections.singletonList(field) )); + } // Otherwise simply request the user's authentication code throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( - Collections.singletonList(new AuthenticationCodeField()) + Collections.singletonList(field) )); } try { + // Get generator based on user's key and provided configuration + TOTPGenerator totp = new TOTPGenerator(key.getSecret(), + confService.getMode(), confService.getDigits()); + // Verify provided TOTP against value produced by generator - TOTPGenerator totp = new TOTPGenerator(key.getSecret(), TOTPGenerator.Mode.SHA1, 6); if (code.equals(totp.generate()) || code.equals(totp.previous())) { // Record key as confirmed, if it hasn't already been so recorded diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java new file mode 100644 index 000000000..8658849be --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.conf; + +import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; +import org.apache.guacamole.totp.TOTPGenerator; + +/** + * Service for retrieving configuration information regarding the TOTP + * authentication extension. + */ +public class ConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * The human-readable name of the entity issuing user accounts. By default, + * this will be "Apache Guacamole". + */ + private static final StringGuacamoleProperty TOTP_ISSUER = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "totp-issuer"; } + + }; + + /** + * The number of digits which should be included in each generated TOTP + * code. By default, this will be 6. + */ + private static final IntegerGuacamoleProperty TOTP_DIGITS= + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "totp-digits"; } + + }; + + /** + * The duration that each generated code should remain valid, in seconds. + * By default, this will be 30. + */ + private static final IntegerGuacamoleProperty TOTP_PERIOD = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "totp-period"; } + + }; + + /** + * The hash algorithm that should be used to generate TOTP codes. By + * default, this will be "sha1". Legal values are "sha1", "sha256", and + * "sha512". + */ + private static final TOTPModeProperty TOTP_MODE = + new TOTPModeProperty() { + + @Override + public String getName() { return "totp-mode"; } + + }; + + /** + * Returns the human-readable name of the entity issuing user accounts. If + * not specified, "Apache Guacamole" will be used by default. + * + * @return + * The human-readable name of the entity issuing user accounts. + * + * @throws GuacamoleException + * If the "totp-issuer" property cannot be read from + * guacamole.properties. + */ + public String getIssuer() throws GuacamoleException { + return environment.getProperty(TOTP_ISSUER, "Apache Guacamole"); + } + + /** + * Returns the number of digits which should be included in each generated + * TOTP code. If not specified, 6 will be used by default. + * + * @return + * The number of digits which should be included in each generated + * TOTP code. + * + * @throws GuacamoleException + * If the "totp-digits" property cannot be read from + * guacamole.properties. + */ + public int getDigits() throws GuacamoleException { + + // Validate legal number of digits + int digits = environment.getProperty(TOTP_DIGITS, 6); + if (digits < 6 || digits > 8) + throw new GuacamoleServerException("TOTP codes may have no fewer " + + "than 6 digits and no more than 8 digits."); + + return digits; + + } + + /** + * Returns the duration that each generated code should remain valid, in + * seconds. If not specified, 30 will be used by default. + * + * @return + * The duration that each generated code should remain valid, in + * seconds. + * + * @throws GuacamoleException + * If the "totp-period" property cannot be read from + * guacamole.properties. + */ + public int getPeriod() throws GuacamoleException { + return environment.getProperty(TOTP_PERIOD, 30); + } + + /** + * Returns the hash algorithm that should be used to generate TOTP codes. If + * not specified, SHA1 will be used by default. + * + * @return + * The hash algorithm that should be used to generate TOTP codes. + * + * @throws GuacamoleException + * If the "totp-mode" property cannot be read from + * guacamole.properties. + */ + public TOTPGenerator.Mode getMode() throws GuacamoleException { + return environment.getProperty(TOTP_MODE, TOTPGenerator.Mode.SHA1); + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java new file mode 100644 index 000000000..bfe3ef307 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.properties.GuacamoleProperty; +import org.apache.guacamole.totp.TOTPGenerator; + +/** + * A GuacamoleProperty whose value is a TOTP generation method. The string + * values "sha1", "sha256", and "sha512" are each parsed to their corresponding + * values within the TOTPGenerator.Mode enum. All other string values result in + * parse errors. + */ +public abstract class TOTPModeProperty + implements GuacamoleProperty { + + @Override + public TOTPGenerator.Mode parseValue(String value) + throws GuacamoleException { + + // If no value provided, return null. + if (value == null) + return null; + + // SHA1 + if (value.equals("sha1")) + return TOTPGenerator.Mode.SHA1; + + // SHA256 + if (value.equals("sha256")) + return TOTPGenerator.Mode.SHA256; + + // SHA512 + if (value.equals("sha512")) + return TOTPGenerator.Mode.SHA512; + + // The provided value is not legal + throw new GuacamoleServerException("TOTP mode must be one of " + + "\"sha1\", \"sha256\", or \"sha512\"."); + + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index 1be5f276d..e0333ddab 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.totp.form; import com.google.common.io.BaseEncoding; +import com.google.inject.Inject; import com.google.zxing.BarcodeFormat; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; @@ -32,6 +33,7 @@ import javax.ws.rs.core.UriBuilder; import javax.xml.bind.DatatypeConverter; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.totp.UserTOTPKey; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.form.Field; import org.codehaus.jackson.annotate.JsonProperty; @@ -66,31 +68,34 @@ public class AuthenticationCodeField extends Field { */ private static final BaseEncoding BASE32 = BaseEncoding.base32(); + /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + /** * The TOTP key to expose to the user for the sake of enrollment, if any. * If no such key should be exposed to the user, this will be null. */ - private final UserTOTPKey key; - - /** - * Creates a new field which prompts the user for an authentication code - * generated via TOTP, and provide the user with their TOTP key to - * facilitate enrollment. - * - * @param key - * The TOTP key to expose to the user for the sake of enrollment. - */ - public AuthenticationCodeField(UserTOTPKey key) { - super(PARAMETER_NAME, FIELD_TYPE_NAME); - this.key = key; - } + private UserTOTPKey key; /** * Creates a new field which prompts the user for an authentication code * generated via TOTP. The user's TOTP key is not exposed for enrollment. */ public AuthenticationCodeField() { - this(null); + super(PARAMETER_NAME, FIELD_TYPE_NAME); + } + + /** + * Exposes the given key to facilitate enrollment. + * + * @param key + * The TOTP key to expose to the user for the sake of enrollment. + */ + public void exposeKey(UserTOTPKey key) { + this.key = key; } /** @@ -114,20 +119,15 @@ public class AuthenticationCodeField extends Field { if (key == null) return null; - // FIXME: Pull from configuration - String issuer = "Some Issuer"; - String algorithm = "SHA1"; - String digits = "6"; - String period = "30"; - // Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format) + String issuer = confService.getIssuer(); return UriBuilder.fromUri("otpauth://totp/") .path(issuer + ":" + key.getUsername()) .queryParam("secret", BASE32.encode(key.getSecret())) .queryParam("issuer", issuer) - .queryParam("algorithm", algorithm) - .queryParam("digits", digits) - .queryParam("period", period) + .queryParam("algorithm", confService.getMode()) + .queryParam("digits", confService.getDigits()) + .queryParam("period", confService.getPeriod()) .build(); } From 2a894c487cf25e1ffa35548de1dc791aefed6471 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 15:51:06 -0800 Subject: [PATCH 12/20] GUACAMOLE-96: Clean up enrollment interface. Provide help text for user. --- .../auth/totp/UserVerificationService.java | 6 ++--- .../totp/form/AuthenticationCodeField.java | 22 +++++++++++++++++++ .../src/main/resources/styles/totp.css | 14 +++++++++++- .../templates/authenticationCodeField.html | 13 +++++++---- .../src/main/resources/translations/en.json | 17 +++++++++++--- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index 987d4ca37..851bb9484 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -245,14 +245,14 @@ public class UserVerificationService { if (!key.isConfirmed()) { field.exposeKey(key); throw new GuacamoleInsufficientCredentialsException( - "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( + "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo( Collections.singletonList(field) )); } // Otherwise simply request the user's authentication code throw new GuacamoleInsufficientCredentialsException( - "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( + "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo( Collections.singletonList(field) )); @@ -285,7 +285,7 @@ public class UserVerificationService { } // Provided code is not valid - throw new GuacamoleClientException("LOGIN.INFO_TOTP_VERIFICATION_FAILED"); + throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED"); } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index e0333ddab..c3ca20710 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -98,6 +98,28 @@ public class AuthenticationCodeField extends Field { this.key = key; } + /** + * Returns the number of digits used for each TOTP code. If the user's key + * is not being exposed to facilitate enrollment, this value will not be + * exposed either. + * + * @return + * The number of digits used for each TOTP code, or null if the user's + * key is not being exposed to facilitate enrollment. + * + * @throws GuacamoleException + * If the number of digits cannot be read from guacamole.properties. + */ + public Integer getDigits() throws GuacamoleException { + + // Do not reveal code size unless enrollment is in progress + if (key == null) + return null; + + return confService.getDigits(); + + } + /** * Returns the "otpauth" URI for the secret key used to generate TOTP codes * for the current user. If the secret key is not being exposed to diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css index 8181e2ccd..6db7729da 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css +++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css @@ -17,4 +17,16 @@ * under the License. */ -/* STUB */ +.totp-enroll p { + font-size: 0.8em; +} + +.totp-qr-code { + text-align: center; +} + +.totp-qr-code img { + margin: 1em; + border: 1px solid rgba(0,0,0,0.25); + box-shadow: 1px 1px 2px rgba(0,0,0,0.25); +} diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html index ae155d817..5a39be7a4 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html +++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html @@ -1,13 +1,18 @@
- -
- + +
+

+
+

- +
diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json index 540b94e36..bd0e9e204 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json @@ -5,9 +5,20 @@ }, "LOGIN" : { - "FIELD_HEADER_GUAC_TOTP" : "Authentication Code", - "INFO_TOTP_REQUIRED" : "Please enter your authentication code to verify your identity.", - "INFO_TOTP_VERIFICATION_FAILED" : "Verification failed. Please try again." + "FIELD_HEADER_GUAC_TOTP" : "" + }, + + "TOTP" : { + + "FIELD_PLACEHOLDER_CODE" : "Authentication Code", + + "INFO_CODE_REQUIRED" : "Please enter your authentication code to verify your identity.", + "INFO_ENROLL_REQUIRED" : "Multi-factor authentication has been enabled on your account.", + "INFO_VERIFICATION_FAILED" : "Verification failed. Please try again.", + + "HELP_ENROLL_BARCODE" : "To complete the enrollment process, scan the barcode below with the two-factor authentication app on your phone or device.", + "HELP_ENROLL_VERIFY" : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful." + } } From 96e3d029992ac09d27aac808c489779000fb6fe1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 16:15:01 -0800 Subject: [PATCH 13/20] GUACAMOLE-96: Block external access to TOTP-internal attributes. --- .../auth/totp/TOTPAuthenticationProvider.java | 6 +- .../TOTPAuthenticationProviderModule.java | 1 + .../totp/form/AuthenticationCodeField.java | 2 +- .../guacamole/auth/totp/user/TOTPUser.java | 102 ++++++++++++++++++ .../auth/totp/user/TOTPUserContext.java | 64 +++++++++++ .../auth/totp/{ => user}/UserTOTPKey.java | 2 +- .../{ => user}/UserVerificationService.java | 25 ++--- 7 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java rename extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/{ => user}/UserTOTPKey.java (98%) rename extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/{ => user}/UserVerificationService.java (91%) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java index 835ba87d3..28e2380b2 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java @@ -19,9 +19,11 @@ package org.apache.guacamole.auth.totp; +import org.apache.guacamole.auth.totp.user.UserVerificationService; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.user.TOTPUserContext; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -104,7 +106,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider { // User has been verified, and authentication should be allowed to // continue - return context; + return new TOTPUserContext(context); } @@ -112,7 +114,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider { public UserContext redecorate(UserContext decorated, UserContext context, AuthenticatedUser authenticatedUser, Credentials credentials) throws GuacamoleException { - return context; + return new TOTPUserContext(context); } @Override diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java index e72beeca6..94b723223 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java @@ -19,6 +19,7 @@ package org.apache.guacamole.auth.totp; +import org.apache.guacamole.auth.totp.user.UserVerificationService; import com.google.inject.AbstractModule; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.totp.conf.ConfigurationService; diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index c3ca20710..764fe9568 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -32,7 +32,7 @@ import java.net.URI; import javax.ws.rs.core.UriBuilder; import javax.xml.bind.DatatypeConverter; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.totp.UserTOTPKey; +import org.apache.guacamole.auth.totp.user.UserTOTPKey; import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.form.Field; import org.codehaus.jackson.annotate.JsonProperty; diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java new file mode 100644 index 000000000..4199d4380 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.user; + +import java.util.HashMap; +import java.util.Map; +import org.apache.guacamole.net.auth.DelegatingUser; +import org.apache.guacamole.net.auth.User; + +/** + * TOTP-specific User implementation which wraps a User from another extension, + * hiding and blocking access to the core attributes used by TOTP. + */ +public class TOTPUser extends DelegatingUser { + + /** + * The name of the user attribute which stores the TOTP key. + */ + public static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret"; + + /** + * The name of the user attribute defines whether the TOTP key has been + * confirmed by the user, and the user is thus fully enrolled. + */ + public static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed"; + + /** + * The User object wrapped by this TOTPUser. + */ + private final User undecorated; + + /** + * Wraps the given User object, hiding and blocking access to the core + * attributes used by TOTP. + * + * @param user + * The User object to wrap. + */ + public TOTPUser(User user) { + super(user); + this.undecorated = user; + } + + /** + * Returns the User object wrapped by this TOTPUser. + * + * @return + * The wrapped User object. + */ + public User getUndecorated() { + return undecorated; + } + + @Override + public Map getAttributes() { + + // Create independent, mutable copy of attributes + Map attributes = + new HashMap(super.getAttributes()); + + // Do not expose any TOTP-related attributes outside this extension + attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME); + attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME); + + // Expose only non-TOTP attributes + return attributes; + + } + + @Override + public void setAttributes(Map attributes) { + + // Create independent, mutable copy of attributes + attributes = new HashMap(attributes); + + // Do not expose any TOTP-related attributes outside this extension + attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME); + attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME); + + // Set only non-TOTP attributes + super.setAttributes(attributes); + + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java new file mode 100644 index 000000000..980bbf782 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.user; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.DecoratingDirectory; +import org.apache.guacamole.net.auth.DelegatingUserContext; +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.UserContext; + +/** + * TOTP-specific UserContext implementation which wraps the UserContext of + * some other extension, providing (or hiding) additional data. + */ +public class TOTPUserContext extends DelegatingUserContext { + + /** + * Creates a new TOTPUserContext which wraps the given UserContext, + * providing (or hiding) additional TOTP-specific data. + * + * @param userContext + * The UserContext to wrap. + */ + public TOTPUserContext(UserContext userContext) { + super(userContext); + } + + @Override + public Directory getUserDirectory() throws GuacamoleException { + return new DecoratingDirectory(super.getUserDirectory()) { + + @Override + protected User decorate(User object) { + return new TOTPUser(object); + } + + @Override + protected User undecorate(User object) { + assert(object instanceof TOTPUser); + return ((TOTPUser) object).getUndecorated(); + } + + }; + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java similarity index 98% rename from extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java rename to extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java index 3de378581..d7bc903e7 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.totp; +package org.apache.guacamole.auth.totp.user; import java.security.SecureRandom; import java.util.Random; diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java similarity index 91% rename from extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java rename to extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java index 851bb9484..8264efd1a 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.guacamole.auth.totp; +package org.apache.guacamole.auth.totp.user; import com.google.common.io.BaseEncoding; import com.google.inject.Inject; @@ -53,17 +53,6 @@ public class UserVerificationService { */ private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class); - /** - * The name of the user attribute which stores the TOTP key. - */ - private static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret"; - - /** - * The name of the user attribute defines whether the TOTP key has been - * confirmed by the user, and the user is thus fully enrolled. - */ - private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed"; - /** * BaseEncoding instance which decoded/encodes base32. */ @@ -111,7 +100,7 @@ public class UserVerificationService { Map attributes = context.self().getAttributes(); // If no key is defined, attempt to generate a new key - String secret = attributes.get(TOTP_KEY_SECRET_ATTRIBUTE_NAME); + String secret = attributes.get(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME); if (secret == null) { // Generate random key for user @@ -140,7 +129,7 @@ public class UserVerificationService { } // Otherwise, parse value from attributes - boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)); + boolean confirmed = "true".equals(attributes.get(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)); return new UserTOTPKey(username, key, confirmed); } @@ -173,14 +162,14 @@ public class UserVerificationService { Map attributes = new HashMap(); // Set/overwrite current TOTP key state - attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret())); - attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false"); + attributes.put(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret())); + attributes.put(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false"); self.setAttributes(attributes); // Confirm that attributes have actually been set Map setAttributes = self.getAttributes(); - if (!setAttributes.containsKey(TOTP_KEY_SECRET_ATTRIBUTE_NAME) - || !setAttributes.containsKey(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)) + if (!setAttributes.containsKey(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME) + || !setAttributes.containsKey(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME)) return false; // Update user object From 456b8a0394c7cc5dc6ed8dd02f9a83383b05d63b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 20 Nov 2017 16:20:13 -0800 Subject: [PATCH 14/20] GUACAMOLE-96: Remove unused field controller. --- .../src/main/resources/config/totpConfig.js | 1 - .../authenticationCodeFieldController.js | 29 ------------------- 2 files changed, 30 deletions(-) delete mode 100644 extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js index 54bb56c08..81cc07d63 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js +++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js @@ -26,7 +26,6 @@ angular.module('guacTOTP').config(['formServiceProvider', // Define field for the TOTP code provided by the user formServiceProvider.registerFieldType('GUAC_TOTP_CODE', { module : 'guacTOTP', - controller : 'authenticationCodeFieldController', templateUrl : 'app/ext/totp/templates/authenticationCodeField.html' }); diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js deleted file mode 100644 index c9cecc68d..000000000 --- a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter - * the code generated by their authentication device. - */ -angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$element', - function authenticationCodeFieldController($scope, $element) { - - // STUB - -}]); From b1c23f20d00b030cb8a8691f8aad1d53a341f8ff Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 22 Nov 2017 18:53:29 -0800 Subject: [PATCH 15/20] GUACAMOLE-96: Ensure valid codes cannot be reused. --- .../auth/totp/TOTPAuthenticationProvider.java | 3 +- .../TOTPAuthenticationProviderModule.java | 2 + .../totp/user/CodeUsageTrackingService.java | 264 ++++++++++++++++++ .../totp/user/UserVerificationService.java | 9 +- 4 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java index 28e2380b2..4f18304a7 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java @@ -23,6 +23,7 @@ import org.apache.guacamole.auth.totp.user.UserVerificationService; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService; import org.apache.guacamole.auth.totp.user.TOTPUserContext; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -119,7 +120,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider { @Override public void shutdown() { - // Do nothing + injector.getInstance(CodeUsageTrackingService.class).shutdown(); } } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java index 94b723223..d1f7f9616 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java @@ -23,6 +23,7 @@ import org.apache.guacamole.auth.totp.user.UserVerificationService; import com.google.inject.AbstractModule; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.totp.conf.ConfigurationService; +import org.apache.guacamole.auth.totp.user.CodeUsageTrackingService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -73,6 +74,7 @@ public class TOTPAuthenticationProviderModule extends AbstractModule { bind(Environment.class).toInstance(environment); // Bind TOTP-specific services + bind(CodeUsageTrackingService.class); bind(ConfigurationService.class); bind(UserVerificationService.class); diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java new file mode 100644 index 000000000..c9a94b4b5 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/CodeUsageTrackingService.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.totp.user; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for tracking past valid uses of TOTP codes. An internal thread + * periodically walks through records of past codes, removing records which + * should be invalid by their own nature (no longer matching codes generated by + * the secret key). + */ +@Singleton +public class CodeUsageTrackingService { + + /** + * The number of periods during which a previously-used code should remain + * unusable. Once this period has elapsed, the code can be reused again if + * it is otherwise valid. + */ + private static final int INVALID_INTERVAL = 2; + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(CodeUsageTrackingService.class); + + /** + * Executor service which runs the cleanup task. + */ + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + + /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Map of previously-used codes to the timestamp after which the code can + * be used again, providing the TOTP key legitimately generates that code. + */ + private final ConcurrentMap invalidCodes = + new ConcurrentHashMap(); + + /** + * Creates a new CodeUsageTrackingService which tracks past valid uses of + * TOTP codes on a per-user basis. + */ + public CodeUsageTrackingService() { + executor.scheduleAtFixedRate(new CodeEvictionTask(), 1, 1, TimeUnit.MINUTES); + } + + /** + * Task which iterates through all explicitly-invalidated codes, evicting + * those codes which are old enough that they would fail validation against + * the secret key anyway. + */ + private class CodeEvictionTask implements Runnable { + + @Override + public void run() { + + // Get start time of cleanup check + long checkStart = System.currentTimeMillis(); + + // For each code still being tracked, remove those which are old + // enough that they would fail validation against the secret key + Iterator> entries = invalidCodes.entrySet().iterator(); + while (entries.hasNext()) { + + Map.Entry entry = entries.next(); + long invalidUntil = entry.getValue(); + + // If code is sufficiently old, evict it and check the next one + if (checkStart >= invalidUntil) + entries.remove(); + + } + + // Log completion and duration + logger.debug("TOTP tracking cleanup check completed in {} ms.", + System.currentTimeMillis() - checkStart); + + } + + } + + /** + * A valid TOTP code which was previously used by a particular user. + */ + private class UsedCode { + + /** + * The username of the user which previously used this code. + */ + private final String username; + + /** + * The valid code given by the user. + */ + private final String code; + + /** + * Creates a new UsedCode which records the given code as having been + * used by the given user. + * + * @param username + * The username of the user which previously used the given code. + * + * @param code + * The valid code given by the user. + */ + public UsedCode(String username, String code) { + this.username = username; + this.code = code; + } + + /** + * Returns the username of the user which previously used the code + * associated with this UsedCode. + * + * @return + * The username of the user which previously used this code. + */ + public String getUsername() { + return username; + } + + /** + * Returns the valid code given by the user when this UsedCode was + * created. + * + * @return + * The valid code given by the user. + */ + public String getCode() { + return code; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + this.username.hashCode(); + hash = 79 * hash + this.code.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) + return true; + + if (obj == null) + return false; + + if (getClass() != obj.getClass()) + return false; + + final UsedCode other = (UsedCode) obj; + return username.equals(other.username) && code.equals(other.code); + + } + + } + + /** + * Attempts to mark the given code as used. The code MUST have already been + * validated against the user's secret key, as this function only verifies + * whether the code has been previously used, not whether it is actually + * valid. If the code has not previously been used, the code is stored as + * having been used by the given user at the current time. + * + * @param username + * The username of the user who has attempted to use the given valid + * code. + * + * @param code + * The otherwise-valid code given by the user. + * + * @return + * true if the code has not previously been used by the given user and + * has now been marked as previously used, false otherwise. + * + * @throws GuacamoleException + * If configuration information necessary to determine the length of + * time a code should be marked as invalid cannot be read from + * guacamole.properties. + */ + public boolean useCode(String username, String code) + throws GuacamoleException { + + // Repeatedly attempt to use the given code until an explicit success + // or failure has occurred + UsedCode usedCode = new UsedCode(username, code); + for (;;) { + + // Explicitly invalidate each used code for two periods after its + // first successful use + long current = System.currentTimeMillis(); + long invalidUntil = current + confService.getPeriod() * 1000 * INVALID_INTERVAL; + + // Try to use the given code, marking it as used within the map of + // now-invalidated codes + Long expires = invalidCodes.putIfAbsent(usedCode, invalidUntil); + if (expires == null) + return true; + + // If the code was already used, fail to use the code if + // insufficient time has elapsed since it was last used + // successfully + if (expires > current) + return false; + + + // Otherwise, the code is actually valid - remove the invalidated + // code only if it still has the expected expiration time, and + // retry using the code + invalidCodes.remove(usedCode, expires); + + } + + } + + /** + * Cleans up resources which may be in use by this service in the + * background, such as other threads. This function MUST be invoked during + * webapp shutdown to avoid leaking these resources. + */ + public void shutdown() { + executor.shutdownNow(); + } + +} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java index 8264efd1a..30108e1c6 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java @@ -64,6 +64,12 @@ public class UserVerificationService { @Inject private ConfigurationService confService; + /** + * Service for tracking whether TOTP codes have been used. + */ + @Inject + private CodeUsageTrackingService codeService; + /** * Provider for AuthenticationCodeField instances. */ @@ -254,7 +260,8 @@ public class UserVerificationService { confService.getMode(), confService.getDigits()); // Verify provided TOTP against value produced by generator - if (code.equals(totp.generate()) || code.equals(totp.previous())) { + if ((code.equals(totp.generate()) || code.equals(totp.previous())) + && codeService.useCode(username, code)) { // Record key as confirmed, if it hasn't already been so recorded if (!key.isConfirmed()) { From 5b2b633707b997212de553130e1e9f7b6627c30e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 24 Nov 2017 12:34:09 -0800 Subject: [PATCH 16/20] GUACAMOLE-96: Include all TOTP key details in field when enrolling. --- .../totp/form/AuthenticationCodeField.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index 764fe9568..1a61e8982 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -35,6 +35,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.totp.user.UserTOTPKey; import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.form.Field; +import org.apache.guacamole.totp.TOTPGenerator; import org.codehaus.jackson.annotate.JsonProperty; /** @@ -98,6 +99,46 @@ public class AuthenticationCodeField extends Field { this.key = key; } + /** + * Returns the username of the user associated with the key being used to + * generate TOTP codes. If the user's key is not being exposed to facilitate + * enrollment, this value will not be exposed either. + * + * @return + * The username of the user associated with the key being used to + * generate TOTP codes, or null if the user's key is not being exposed + * to facilitate enrollment. + */ + public String getUsername() { + + // Do not reveal TOTP mode unless enrollment is in progress + if (key == null) + return null; + + return key.getUsername(); + + } + + /** + * Returns the base32-encoded secret key that is being used to generate TOTP + * codes for the authenticating user. If the user's key is not being exposed + * to facilitate enrollment, this value will not be exposed either. + * + * @return + * The base32-encoded secret key that is being used to generate TOTP + * codes for the authenticating user, or null if the user's key is not + * being exposed to facilitate enrollment. + */ + public String getSecret() { + + // Do not reveal TOTP mode unless enrollment is in progress + if (key == null) + return null; + + return BASE32.encode(key.getSecret()); + + } + /** * Returns the number of digits used for each TOTP code. If the user's key * is not being exposed to facilitate enrollment, this value will not be @@ -120,6 +161,74 @@ public class AuthenticationCodeField extends Field { } + /** + * Returns the human-readable name of the entity issuing user accounts. If + * the user's key is not being exposed to facilitate enrollment, this value + * will not be exposed either. + * + * @return + * The human-readable name of the entity issuing user accounts, or null + * if the user's key is not being exposed to facilitate enrollment. + * + * @throws GuacamoleException + * If the issuer cannot be read from guacamole.properties. + */ + public String getIssuer() throws GuacamoleException { + + // Do not reveal code issuer unless enrollment is in progress + if (key == null) + return null; + + return confService.getIssuer(); + + } + + /** + * Returns the mode that TOTP code generation is operating in. This value + * will be one of "SHA1", "SHA256", or "SHA512". If the user's key is not + * being exposed to facilitate enrollment, this value will not be exposed + * either. + * + * @return + * The mode that TOTP code generation is operating in, such as "SHA1", + * "SHA256", or "SHA512", or null if the user's key is not being + * exposed to facilitate enrollment. + * + * @throws GuacamoleException + * If the TOTP mode cannot be read from guacamole.properties. + */ + public TOTPGenerator.Mode getMode() throws GuacamoleException { + + // Do not reveal TOTP mode unless enrollment is in progress + if (key == null) + return null; + + return confService.getMode(); + + } + + /** + * Returns the number of seconds that each TOTP code remains valid. If the + * user's key is not being exposed to facilitate enrollment, this value will + * not be exposed either. + * + * @return + * The number of seconds that each TOTP code remains valid, or null if + * the user's key is not being exposed to facilitate enrollment. + * + * @throws GuacamoleException + * If the period cannot be read from guacamole.properties. + */ + public Integer getPeriod() throws GuacamoleException { + + // Do not reveal code period unless enrollment is in progress + if (key == null) + return null; + + return confService.getPeriod(); + + } + /** * Returns the "otpauth" URI for the secret key used to generate TOTP codes * for the current user. If the secret key is not being exposed to From 78cde50df96fcda535622fd9039ac0c6dc59c5ef Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 24 Nov 2017 13:33:39 -0800 Subject: [PATCH 17/20] GUACAMOLE-96: Allow user's raw TOTP key details to be exposed within UI during enrollment. --- .../src/main/resources/config/totpConfig.js | 1 + .../authenticationCodeFieldController.js | 59 +++++++++++++++++++ .../src/main/resources/styles/totp.css | 56 +++++++++++++++++- .../templates/authenticationCodeField.html | 31 +++++++++- .../src/main/resources/translations/en.json | 12 +++- 5 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js diff --git a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js index 81cc07d63..54bb56c08 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js +++ b/extensions/guacamole-auth-totp/src/main/resources/config/totpConfig.js @@ -26,6 +26,7 @@ angular.module('guacTOTP').config(['formServiceProvider', // Define field for the TOTP code provided by the user formServiceProvider.registerFieldType('GUAC_TOTP_CODE', { module : 'guacTOTP', + controller : 'authenticationCodeFieldController', templateUrl : 'app/ext/totp/templates/authenticationCodeField.html' }); diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js new file mode 100644 index 000000000..27881f8e5 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter + * the code generated by their authentication device. + */ +angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', + function authenticationCodeFieldController($scope) { + + /** + * The secret key split into groups of at most four characters each, or + * null if the secret key is not exposed. + * + * @type String[] + */ + $scope.groupedSecret = $scope.field.secret && $scope.field.secret.match(/.{1,4}/g); + + /** + * Whether the raw details of the secret key and TOTP configuration should + * be shown. By default, such details are hidden. If the secret key is not + * exposed, this property has no effect. + */ + $scope.detailsShown = false; + + /** + * Shows the raw details of the secret key and TOTP configuration. If the + * secret key is not exposed, or the details are already shown, this + * function has no effect. + */ + $scope.showDetails = function showDetails() { + $scope.detailsShown = true; + }; + + /** + * Hides the raw details of the secret key and TOTP configuration. If the + * details are already hidden, this function has no effect. + */ + $scope.hideDetails = function hideDetails() { + $scope.detailsShown = false; + }; + +}]); diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css index 6db7729da..6d2d89f22 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css +++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css @@ -17,7 +17,7 @@ * under the License. */ -.totp-enroll p { +.totp-enroll p, .totp-details { font-size: 0.8em; } @@ -30,3 +30,57 @@ border: 1px solid rgba(0,0,0,0.25); box-shadow: 1px 1px 2px rgba(0,0,0,0.25); } + +h3.totp-details-header { + font-size: 0.8em; +} + +h3.totp-details-header::before { + content: '▸ '; +} + +.totp-details-visible h3.totp-details-header::before { + content: '▾ '; +} + +.totp-details, +.totp-hide-details { + display: none; +} + +.totp-details-visible .totp-details { + display: table; +} + +.totp-details-visible .totp-hide-details { + display: inline; +} + +.totp-details-visible .totp-show-details { + display: none; +} + +.totp-hide-details, .totp-show-details { + color: blue; + text-decoration: underline; + cursor: pointer; + margin: 0 0.25em; + font-weight: normal; +} + +.totp-details { + margin: 0 auto; +} + +.totp-details th { + padding-right: 0.25em; +} + +.totp-details td { + font-family: monospace; +} + +.totp-detail { + display: inline-block; + margin: 0 0.25em; +} diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html index 5a39be7a4..0837b74c9 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html +++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html @@ -1,11 +1,40 @@ -
+
+

+ +
+

+ {{'TOTP.SECTION_HEADER_DETAILS' | translate}} + {{'TOTP.ACTION_SHOW_DETAILS' | translate}} + {{'TOTP.ACTION_HIDE_DETAILS' | translate}} +

+ + + + + + + + + + + + + + + + + +
{{'TOTP.FIELD_HEADER_SECRET_KEY' | translate}}{{ group }}
{{'TOTP.FIELD_HEADER_DIGITS' | translate}}{{ field.digits }}
{{'TOTP.FIELD_HEADER_ALGORITHM' | translate}}{{ field.mode }}
{{'TOTP.FIELD_HEADER_INTERVAL' | translate}}{{ field.period }}
+

+
diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json index bd0e9e204..6f73aa02d 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json @@ -10,6 +10,14 @@ "TOTP" : { + "ACTION_HIDE_DETAILS" : "Hide", + "ACTION_SHOW_DETAILS" : "Show", + + "FIELD_HEADER_ALGORITHM" : "Algorithm:", + "FIELD_HEADER_DIGITS" : "Digits:", + "FIELD_HEADER_INTERVAL" : "Interval:", + "FIELD_HEADER_SECRET_KEY" : "Secret Key:", + "FIELD_PLACEHOLDER_CODE" : "Authentication Code", "INFO_CODE_REQUIRED" : "Please enter your authentication code to verify your identity.", @@ -17,7 +25,9 @@ "INFO_VERIFICATION_FAILED" : "Verification failed. Please try again.", "HELP_ENROLL_BARCODE" : "To complete the enrollment process, scan the barcode below with the two-factor authentication app on your phone or device.", - "HELP_ENROLL_VERIFY" : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful." + "HELP_ENROLL_VERIFY" : "After scanning the barcode, enter the {DIGITS}-digit authentication code displayed to verify that enrollment was successful.", + + "SECTION_HEADER_DETAILS" : "Details:" } From b9dba7ddf4eaa9c291035c706bfc6b32408544e8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 24 Nov 2017 13:54:26 -0800 Subject: [PATCH 18/20] GUACAMOLE-96: Open "otpauth" link when user clicks on barcode. --- .../authenticationCodeFieldController.js | 13 +++++++++++-- .../src/main/resources/styles/totp.css | 1 + .../templates/authenticationCodeField.html | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js index 27881f8e5..8f19c9f6c 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js +++ b/extensions/guacamole-auth-totp/src/main/resources/controllers/authenticationCodeFieldController.js @@ -21,8 +21,8 @@ * Controller for the "GUAC_TOTP_CODE" field which prompts the user to enter * the code generated by their authentication device. */ -angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', - function authenticationCodeFieldController($scope) { +angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$scope', '$window', + function authenticationCodeFieldController($scope, $window) { /** * The secret key split into groups of at most four characters each, or @@ -56,4 +56,13 @@ angular.module('guacTOTP').controller('authenticationCodeFieldController', ['$sc $scope.detailsShown = false; }; + /** + * Attempts to open the "otpauth" URI containing the user's TOTP key, + * invoking whichever application may be installed locally for handling + * multi-factor authentication. + */ + $scope.openKeyURI = function openKeyURI() { + $window.open($scope.field.keyUri); + }; + }]); diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css index 6d2d89f22..e578e6a04 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css +++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css @@ -29,6 +29,7 @@ margin: 1em; border: 1px solid rgba(0,0,0,0.25); box-shadow: 1px 1px 2px rgba(0,0,0,0.25); + cursor: pointer; } h3.totp-details-header { diff --git a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html index 0837b74c9..c493a2050 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html +++ b/extensions/guacamole-auth-totp/src/main/resources/templates/authenticationCodeField.html @@ -6,7 +6,7 @@

-
+

{{'TOTP.SECTION_HEADER_DETAILS' | translate}} {{'TOTP.ACTION_SHOW_DETAILS' | translate}} From a426f59765471adb72dc011a56b50d9c403d4ab4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 24 Nov 2017 14:02:18 -0800 Subject: [PATCH 19/20] GUACAMOLE-96: Scroll login interface if too large for screen. --- guacamole/src/main/webapp/app/login/styles/login.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/login/styles/login.css b/guacamole/src/main/webapp/app/login/styles/login.css index 17078274c..4cb07f18a 100644 --- a/guacamole/src/main/webapp/app/login/styles/login.css +++ b/guacamole/src/main/webapp/app/login/styles/login.css @@ -20,7 +20,7 @@ div.login-ui { height: 100%; width: 100%; - position: fixed; + position: absolute; left: 0; top: 0; display: table; From 608a11170baba9b983e2bf8601b8a7936c42d986 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 24 Nov 2017 14:05:32 -0800 Subject: [PATCH 20/20] GUACAMOLE-96: TOTP detail headers should always be left-aligned. --- .../guacamole-auth-totp/src/main/resources/styles/totp.css | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css index e578e6a04..c8a250534 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css +++ b/extensions/guacamole-auth-totp/src/main/resources/styles/totp.css @@ -75,6 +75,7 @@ h3.totp-details-header::before { .totp-details th { padding-right: 0.25em; + text-align: left; } .totp-details td {