Repository: Yubico/java-u2flib-server Branch: master Commit: df59a357d079 Files: 129 Total size: 314.4 KB Directory structure: gitextract_r69ouwzl/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── scan.yml ├── .gitignore ├── .travis.yml ├── COPYING ├── NEWS ├── README ├── buildSrc/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── groovy/ │ └── com/ │ └── yubico/ │ └── gradle/ │ └── pitest/ │ └── tasks/ │ └── PitestMergeTask.groovy ├── dev-util/ │ ├── example-authentication.py │ └── example-registration.py ├── lombok.config ├── pom.xml ├── u2flib-server-attestation/ │ ├── README.adoc │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── yubico/ │ │ │ └── u2f/ │ │ │ └── attestation/ │ │ │ ├── Attestation.java │ │ │ ├── DeviceMatcher.java │ │ │ ├── MetadataObject.java │ │ │ ├── MetadataResolver.java │ │ │ ├── MetadataService.java │ │ │ ├── Transport.java │ │ │ ├── matchers/ │ │ │ │ ├── ExtensionMatcher.java │ │ │ │ └── FingerprintMatcher.java │ │ │ └── resolvers/ │ │ │ └── SimpleResolver.java │ │ └── resources/ │ │ └── metadata.json │ └── test/ │ └── java/ │ └── com/ │ └── yubico/ │ └── u2f/ │ └── attestation/ │ ├── MetadataObjectTest.java │ ├── MetadataServiceTest.java │ ├── TransportTest.java │ ├── matchers/ │ │ └── FingerprintMatcherTest.java │ └── resolvers/ │ └── SimpleResolverTest.java ├── u2flib-server-core/ │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── yubico/ │ │ └── u2f/ │ │ ├── AppId.java │ │ ├── U2F.java │ │ ├── U2fPrimitives.java │ │ ├── crypto/ │ │ │ ├── BouncyCastleCrypto.java │ │ │ ├── ChallengeGenerator.java │ │ │ ├── Crypto.java │ │ │ └── RandomChallengeGenerator.java │ │ ├── data/ │ │ │ ├── DeviceRegistration.java │ │ │ └── messages/ │ │ │ ├── ClientData.java │ │ │ ├── RegisterRequest.java │ │ │ ├── RegisterRequestData.java │ │ │ ├── RegisterResponse.java │ │ │ ├── RegisteredKey.java │ │ │ ├── SignRequest.java │ │ │ ├── SignRequestData.java │ │ │ ├── SignResponse.java │ │ │ ├── json/ │ │ │ │ ├── JsonSerializable.java │ │ │ │ └── Persistable.java │ │ │ └── key/ │ │ │ ├── RawRegisterResponse.java │ │ │ ├── RawSignResponse.java │ │ │ └── util/ │ │ │ ├── ByteInputStream.java │ │ │ ├── CertificateParser.java │ │ │ └── U2fB64Encoding.java │ │ └── exceptions/ │ │ ├── DeviceCompromisedException.java │ │ ├── InvalidDeviceCounterException.java │ │ ├── NoEligableDevicesException.java │ │ ├── NoEligibleDevicesException.java │ │ ├── U2fAuthenticationException.java │ │ ├── U2fBadConfigurationException.java │ │ ├── U2fBadInputException.java │ │ ├── U2fCeremonyException.java │ │ └── U2fRegistrationException.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── yubico/ │ │ └── u2f/ │ │ ├── AppIdTest.java │ │ ├── SystemTest.java │ │ ├── TestUtils.java │ │ ├── U2FTest.java │ │ ├── U2fPrimitivesTest.java │ │ ├── codec/ │ │ │ ├── RawCodecTest.java │ │ │ └── SerialCodecTest.java │ │ ├── data/ │ │ │ ├── DeviceRegistrationTest.java │ │ │ └── messages/ │ │ │ ├── ClientDataTest.java │ │ │ ├── RegisterRequestDataTest.java │ │ │ ├── RegisterRequestTest.java │ │ │ ├── RegisterResponseTest.java │ │ │ ├── SignRequestDataTest.java │ │ │ ├── SignRequestTest.java │ │ │ ├── SignResponseTest.java │ │ │ ├── json/ │ │ │ │ └── JsonSerializableTest.java │ │ │ └── key/ │ │ │ ├── Client.java │ │ │ ├── CodecTestUtils.java │ │ │ └── util/ │ │ │ ├── CertificateParserTest.java │ │ │ └── U2fB64EncodingTest.java │ │ ├── json/ │ │ │ └── SerializationTest.java │ │ ├── softkey/ │ │ │ ├── SoftKey.java │ │ │ ├── SoftKeyTest.java │ │ │ └── messages/ │ │ │ ├── RegisterRequest.java │ │ │ └── SignRequest.java │ │ └── testdata/ │ │ ├── AcmeKey.java │ │ ├── GnubbyKey.java │ │ └── TestVectors.java │ └── resources/ │ └── com/ │ └── yubico/ │ └── u2f/ │ └── testdata/ │ ├── acme/ │ │ └── attestation-certificate.der │ └── gnubby/ │ ├── attestation-certificate-private-key.hex │ └── attestation-certificate.der └── u2flib-server-demo/ ├── README ├── build.gradle ├── config.yml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore.jks ├── pom.xml └── src/ └── main/ ├── java/ │ └── demo/ │ ├── App.java │ ├── Config.java │ ├── Resource.java │ ├── U2fDemoException.java │ └── view/ │ ├── AuthenticationView.java │ ├── FinishAuthenticationView.java │ ├── FinishRegistrationView.java │ └── RegistrationView.java └── resources/ ├── assets/ │ ├── loginIndex.html │ ├── registerIndex.html │ └── u2f-api-1.1.js └── demo/ └── view/ ├── authenticate.ftl ├── finishAuthentication.ftl ├── finishRegistration.ftl ├── navigation.ftl └── register.ftl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto-detect text files * text=auto # Always treat these as LF gradlew text eol=lf *.sh text eol=lf # Always treat these as CRLF *.bat text eol=crlf ================================================ FILE: .github/workflows/scan.yml ================================================ name: static code analysis # Documentation: https://github.com/Yubico/yes-static-code-analysis on: push: schedule: - cron: '0 0 * * 1' env: SCAN_IMG: yubico-yes-docker-local.jfrog.io/static-code-analysis/java:v1 SECRET: ${{ secrets.ARTIFACTORY_READER_TOKEN }} jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Scan and fail on warnings run: | if [ "${SECRET}" != "" ]; then docker login yubico-yes-docker-local.jfrog.io/ \ -u svc-static-code-analysis-reader -p ${SECRET} docker pull ${SCAN_IMG} docker run -v${PWD}:/k -e PROJECT_NAME=${GITHUB_REPOSITORY#Yubico/} \ -t ${SCAN_IMG} else echo "No docker registry credentials, not scanning" fi - uses: actions/upload-artifact@master if: failure() with: name: suppression_files path: suppression_files ================================================ FILE: .gitignore ================================================ # Eclipse .classpath .project .settings/ # Intellij .idea/ out/ *.iml *.iws # Mac .DS_Store # Maven log/ target/ *.versionsBackup # Gradle .gradle/ /build/ /*/build/ ================================================ FILE: .travis.yml ================================================ language: java jdk: - openjdk8 after_success: - ./gradlew coveralls addons: hosts: - travis-issue-5227-workaround hostname: travis-issue-5227-workaround ================================================ FILE: COPYING ================================================ Copyright (c) 2014, Yubico AB 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. 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. ------------------------------- Copyright (c) 2014, Google Inc. 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 Google Inc. nor the names of its 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. ================================================ FILE: NEWS ================================================ == Version 0.19.12 == * Deprecated all classes with note that the library is obsolete. This is planned to be the final release of java-u2flib-server. == Version 0.19.11 == * Bumped Guava dependency to version [24.1.1,30) in response to CVE-2018-10237 == Version 0.19.10 == * Bumped Jackson dependency to version 2.11.0 in response to CVEs: ** CVE-2020-9546 ** CVE-2020-10672 ** CVE-2020-10969 ** CVE-2020-11620 == Version 0.19.9 == * Bumped Jackson dependency to version 2.9.10.3 in response to CVE-2019-20330 and CVE-2020-8840 == Version 0.19.8 == * Bumped Jackson dependency to version 2.9.10.1 which has patched CVE-2019-16942 == Version 0.19.7 == * Bumped Jackson dependency to version 2.9.10 which has patched CVE-2019-16335, CVE-2019-14540 == Version 0.19.6 == * Bumped Jackson dependency to version 2.9.9.3 which fixes a regression in 2.9.9.2 == Version 0.19.5 == * Bumped Jackson dependency to version 2.9.9.2 which has patched CVE-2019-12814, CVE-2019-14439, CVE-2019-14379 == Version 0.19.4 == * Bumped Jackson dependency to version 2.9.9 which has patched CVE-2019-12086 == Version 0.19.3 == Bug fixes: * Use BouncyCastle security provider explicitly == Version 0.19.2 == * Bumped Jackson dependency version to 2.9.8 which has patched CVE-2018-19360, CVE-2018-19362 and CVE-2018-19361 == Version 0.19.1 == * Bumped Jackson dependency version to 2.9.7 which has patched CVE-2018-7489 and CVE-2017-7525 == Version 0.19.0 == Breaking changes: * Overhauled exception hierarchy ** New exception class: `U2fCeremonyException` ** New exception class: `U2fRegistrationException extends U2fCeremonyException` ** New exception class: `U2fAuthenticationException extends U2fCeremonyException` ** The following exception classes now extend `U2fAuthenticationException`: *** `DeviceCompromisedException` *** `InvalidDeviceCounterException` *** `NoEligableDevicesException` *** `NoEligibleDevicesException` ** `U2fBadConfigurationException` is now a checked exception ** `U2fBadInputException` is now a checked exception, and is no longer thrown directly by the methods of `U2F`. *** Methods of `U2F` now catch this exception and wrap it in a `U2fRegistrationException` or ``U2fAuthenticationException`. * `DeviceRegistration.getAttestationCertificate()` now returns `null` instead of throwing `NoSuchFieldException` * `static ClientData.getString(JsonNode, String)` now throws `U2fBadInputException` instead of `NullPointerException`, or if the returned field is not a `String` value * Some `AssertionError`s and `IllegalArgumentException`s are now `U2fBadInputException`s instead Improvements: * `BouncyCastleCrypto` now throws more descriptive exceptions Bug fixes: * Improved error handling in client data input validation ** Thanks to Nicholas Wilson for the contribution, see https://github.com/Yubico/java-u2flib-server/pull/25 == Version 0.18.1 == * Lombok now longer leaks into runtime dependencies == Version 0.18.0 == === u2flib-server-core === Breaking changes: * "Authenticate" renamed to "sign" everywhere in `u2flib-server-core` ** Classes `AuthenticateRequest` renamed to `SignRequest` ** Class `AuthenticateRequestData` renamed to `SignRequestData` ** Class `AuthenticateResponse` renamed to `SignResponse` ** Method `Client.authenticate` renamed to `sign` ** Class `RawAuthenticateResponse` renamed to `RawSignResponse` ** Method `SoftKey.authenticate` renamed to `sign` ** Method `U2F.finishAuthentication` renamed to `finishSignature` ** Method `U2F.startAuthentication` renamed to `startSignature` ** Method `U2fPrimitives.finishAuthentication` renamed to `finishSignature` ** Method `U2fPrimitives.startAuthenticateion` renamed to `startSignature` * Constants `AUTHENTICATE_TYP` and `REGISTER_TYPE` in `U2fPrimitives` are now private == Version 0.17.1 == * u2flib-server-attestation module now uses SLF4J logging instead of `Throwable.printStackTrace` == Version 0.17.0 == === u2flib-server-core === Breaking changes: * Field `RegisterRequestData.authenticateRequests: List` replaced by field `registeredKeys: List` Additions: * Fields added to class `AuthenticateRequestData`: * `challenge: String` * `appId: String` * New class `RegisteredKey` * Field `appId: String` added to `RegisterRequestData` === u2flib-server-demo === * `u2f-api.js` upgraded from version 1.0 to 1.1 * JS calls in views updated to work with version 1.1 of the JS API * All views except `loginIndex` and `registerIndex` are now rendered via templates * Navigation links added to all views * Error feedback improved == Version 0.13.1 (unreleased) == * Changed demo server URL to `localhost:8080`. * Added the method `ClientData.getString` to get arbitrary clientData fields. * Added u2flib-server-attestation for device attestation and metadata. == Version 0.13.0 == * Added built-in support for multiple devices per user. * Fixed demo server bug when running from jar. Thanks to axianx. ================================================ FILE: README ================================================ == java-u2flib-server NOTE: _OBSOLETE: This project is no longer maintained. U2F has been superseded by https://www.w3.org/TR/webauthn/[Web Authentication], and this project is superseded by https://github.com/Yubico/java-webauthn-server/[java-webauthn-server]. We recommend using WebAuthn instead._ image:https://travis-ci.org/Yubico/java-u2flib-server.svg?branch=master["Build Status", link="https://travis-ci.org/Yubico/java-u2flib-server"] image:https://coveralls.io/repos/github/Yubico/java-u2flib-server/badge.svg["Coverage Status", link="https://coveralls.io/github/Yubico/java-u2flib-server"] Server-side https://developers.yubico.com/U2F[U2F] library for Java. Provides functionality for registering U2F devices and authenticating with said devices. == Migrating to WebAuthn See the https://github.com/Yubico/java-webauthn-server#migrating-from-u2f[Migrating from U2F] section in the https://github.com/Yubico/java-webauthn-server/[java-webauthn-server] documentation. === Dependency Maven: [source, xml] com.yubico u2flib-server-core 0.19.12 Gradle: [source, groovy] repositories{ mavenCentral() } dependencies { compile 'com.yubico:u2flib-server-core:0.19.12' } === Example Usage NOTE: Make sure that you have read https://developers.yubico.com/U2F/Libraries/Using_a_library.html[Using a U2F library] before continuing. [source, java] ---- private abstract Iterable getRegistrations(String username); @GET public View startAuthentication(String username) throws NoEligibleDevicesException { // Generate a challenge for each U2F device that this user has registered SignRequestData requestData = u2f.startSignature(SERVER_ADDRESS, getRegistrations(username)); // Store the challenges for future reference requestStorage.put(requestData.getRequestId(), requestData.toJson()); // Return an HTML page containing the challenges return new AuthenticationView(requestData.toJson(), username); } @POST public String finishAuthentication(SignResponse response, String username) throws DeviceCompromisedException { // Get the challenges that we stored when starting the authentication SignRequestData signRequest = requestStorage.remove(response.getRequestId()); // Verify the that the given response is valid for one of the registered devices u2f.finishSignature(signRequest, response, getRegistrations(username)); return "Successfully authenticated!"; } ---- In the above example `getRegistrations()` will return the U2F devices currently associated with a given user. This is most likely stored in a database. See link:u2flib-server-demo[`u2flib-server-demo`] for a complete demo server (including registration and storage of U2F devices). === JavaDoc JavaDoc can be found at https://developers.yubico.com/java-u2flib-server[developers.yubico.com/java-u2flib-server]. === Attestation The attestation module (`u2flib-server-attestation`) enables you to restrict registrations to certain U2F devices (e.g. devices made by a specific vendor). It can also provide metadata for devices. === Serialization All relevant classes implement `Serializable`, so instead of using `toJson()`, you can use Java's built in serialization mechanism. Internally the classes use Jackson to serialize to and from JSON, and the ObjectMapper from Jackson can be used. ================================================ FILE: buildSrc/build.gradle ================================================ apply plugin: 'groovy' repositories { mavenCentral() } dependencies { compile( 'commons-io:commons-io:2.5', 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.1.11', ) } ================================================ FILE: buildSrc/src/main/groovy/com/yubico/gradle/pitest/tasks/PitestMergeTask.groovy ================================================ package com.yubico.gradle.pitest.tasks import groovy.xml.XmlUtil import info.solidsoft.gradle.pitest.PitestTask import org.gradle.api.DefaultTask import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction /** * Merges PIT mutations.xml reports from all subprojects into one * report in the parent project. */ class PitestMergeTask extends DefaultTask { @OutputFile def File destinationFile = project.file("${project.buildDir}/reports/pitest/mutations.xml") PitestMergeTask() { project.subprojects.each { subproject -> subproject.tasks.withType(PitestTask).each { pitestTask -> inputs.files pitestTask.outputs.files } } } def Set findMutationsXmlFiles(File f, Set found) { if (f.isDirectory()) { Set result = found for (File child : f.listFiles()) { result = findMutationsXmlFiles(child, result) } return result } else if (f.getName().endsWith(".xml")) { return found.plus(f) } else { return found } } def getMutations(File mutationsXmlFile) { return new XmlParser().parseText(mutationsXmlFile.text).children() } @TaskAction void merge() { Set mutationsXmlFiles = new HashSet() inputs.files.each { File f -> mutationsXmlFiles = findMutationsXmlFiles(f, mutationsXmlFiles) } def rootNode = new XmlParser().createNode(null, 'mutations', [:]) mutationsXmlFiles.each { getMutations(it).each { mutation -> rootNode.append(mutation) } } if (!destinationFile.exists()) { destinationFile.createNewFile() } def os = destinationFile.newOutputStream() XmlUtil.serialize(rootNode, os) os.close() } } ================================================ FILE: dev-util/example-authentication.py ================================================ #!/usr/bin/python3 # # Scratchpad for working with raw U2F messages, useful for creating raw messages as test data. # Example keys from secion 8.2 of # https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success from binascii import hexlify, unhexlify from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec sig_alg = ec.ECDSA(hashes.SHA256()) private_key_hex = 'ffa1e110dde5a2f8d93c4df71e2d4337b7bf5ddb60c75dc2b6b81433b54dd3c0' public_key_hex = '04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d' example_payload_hex = '4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca0100000001ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57' example_signature_hex = '304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f' s = int(private_key_hex, 16) x = int(public_key_hex[2:66], 16) y = int(public_key_hex[66:], 16) keynums = ec.EllipticCurvePrivateNumbers(s, ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1())) private_key = keynums.private_key(default_backend()) public_key = private_key.public_key() # Just ensure that we can successfully verify the example signature against the example key public_key.verify(unhexlify(example_signature_hex), unhexlify(example_payload_hex), sig_alg) # Successful authentication message, but with invalid user presence byte payload_hex = '4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca0000000001ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57' payload_signature = private_key.sign(unhexlify(payload_hex), sig_alg) print("Private key:", private_key_hex) print("Public key:", public_key_hex) print("Signing payload:", payload_hex) print("Signature:", hexlify(payload_signature)) ================================================ FILE: dev-util/example-registration.py ================================================ #!/usr/bin/python3 # # Scratchpad for working with raw U2F messages, useful for creating raw messages as test data. # Example keys from secion 8.1 of # https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#registration-example from base64 import urlsafe_b64encode as b64encode from binascii import hexlify, unhexlify from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec def hextokeys(private_key_hex, public_key_hex): s = int(private_key_hex, 16) x = int(public_key_hex[2:66], 16) y = int(public_key_hex[66:], 16) keynums = ec.EllipticCurvePrivateNumbers(s, ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1())) private_key = keynums.private_key(default_backend()) return (private_key, private_key.public_key()) def make_signature_base(application_parameter_hex, client_data_json, key_handle_hex, user_public_key_hex): challenge_parameter_hex = make_challenge_parameter_hex(client_data_json) return '00' + application_parameter_hex + challenge_parameter_hex + key_handle_hex + user_public_key_hex def sign(private_key, message_hex): return private_key.sign(message_hex, sig_alg) def make_challenge_parameter_hex(client_data_json): return sha256(bytes(client_data_json, 'UTF-8')).hex() def make_registration_request_message(user_public_key_hex, key_handle_hex, attestation_certificate_hex, signature_hex): return '05' + user_public_key_hex + hex(len(key_handle_hex) // 2)[2:] + key_handle_hex + attestation_certificate_hex + signature_hex def sha256(data): hasher = hashes.Hash(hashes.SHA256(), default_backend()) hasher.update(data) return hasher.finalize() def byteStringToDecimalBytes(data): return [int(b) for b in data] def byteStringToDecimalSignedBytes(data): return [i if i < 128 else i - 256 for i in byteStringToDecimalBytes(data)] sig_alg = ec.ECDSA(hashes.SHA256()) private_attestation_key_hex = 'f3fccc0d00d8031954f90864d43c247f4bf5f0665c6b50cc17749a27d1cf7664' public_attestation_key_hex = '048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101' attestation_certificate_hex = '3082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df' private_user_key_hex = '9a9684b127c5e3a706d618c86401c7cf6fd827fd0bc18d24b0eb842e36d16df1' public_user_key_hex = '04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9' key_handle_hex = '2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25' client_data = '{"typ":"navigator.id.finishEnrollment","challenge":"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}' application_parameter_hex = 'f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4' # 1 byte RFU = 0x00 # 32 bytes application parameter # 32 bytes challenge parameter # L bytes key handle # 65 bytes user public key example_signature_base_hex = make_signature_base(application_parameter_hex, client_data, key_handle_hex, public_user_key_hex) example_signature_hex = '304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871' # 1 reserved byte = 0x05 # 65 bytes user public key # 1 byte key handle length = L # L bytes key handle # X.509 attestation certificate # X bytes = above signature example_response_message_hex = '05' + public_user_key_hex + '40' + key_handle_hex + attestation_certificate_hex + example_signature_hex (private_attestation_key, public_attestation_key) = hextokeys(private_attestation_key_hex, public_attestation_key_hex) (private_user_key, public_user_key) = hextokeys(private_user_key_hex, public_user_key_hex) # Just ensure that we can successfully verify the example signature against the example key public_attestation_key.verify(unhexlify(example_signature_hex), unhexlify(example_signature_base_hex), sig_alg) different_client_data = '{"typ":"navigator.id.launchNukes","challenge":"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo","origin":"http://example.com"}' different_challenge_parameter_hex = sha256(bytes(different_client_data, 'UTF-8')).hex() public_attestation_key.verify(private_attestation_key.sign(b'Hello, World!', sig_alg), b'Hello, World!', sig_alg) def print_results(private_user_key_hex, public_user_key_hex, private_attestation_key, application_parameter_hex, client_data_json, key_handle_hex, user_public_key_hex): signature_base_hex = make_signature_base(application_parameter_hex, client_data_json, key_handle_hex, user_public_key_hex) signature = sign(private_attestation_key, unhexlify(signature_base_hex)) message = make_registration_request_message(public_user_key_hex, key_handle_hex, attestation_certificate_hex, signature.hex()) public_attestation_key.verify(signature, unhexlify(signature_base_hex), sig_alg) print("Private key:\n ", private_user_key_hex) print("Public key:\n ", public_user_key_hex) print("App ID:\n ", application_parameter_hex) print("Client data:\n ", client_data_json) print("Data to sign:\n ", signature_base_hex) print("Signature:\n ", signature.hex()) print("Registration request message:\n ", message) print("Registration request message Base64:\n ", b64encode(unhexlify(message))) print_results( private_user_key_hex, public_user_key_hex, private_attestation_key, application_parameter_hex, different_client_data, key_handle_hex, public_user_key_hex ) ================================================ FILE: lombok.config ================================================ config.stopBubbling = true lombok.addLombokGeneratedAnnotation = true ================================================ FILE: pom.xml ================================================ 4.0.0 com.yubico u2flib-server-parent pom 0.19.12 U2F parent Java server-side library for U2F UTF-8 Yubico https://www.yubico.com/ https://developers.yubico.com/ dain Dain Nilsson dain@yubico.com emil Emil Lundberg emil@yubico.com BSD-license Revised 2-clause BSD license scm:git:git://github.com/Yubico/java-u2flib-server.git scm:git:git://github.com/Yubico/java-u2flib-server.git scm:git:ssh://git@github.com/Yubico/java-u2flib-server.git HEAD u2flib-server-core u2flib-server-demo u2flib-server-attestation sonatype-nexus-staging Nexus Staging Repo https://oss.sonatype.org/service/local/staging/deploy/maven2/ sonatype-nexus-snapshots Nexus Snapshot Repo https://oss.sonatype.org/content/repositories/snapshots/ org.slf4j slf4j-api 1.7.25 org.slf4j slf4j-simple 1.7.25 test junit junit 4.13.1 test org.mockito mockito-core 2.8.47 test org.projectlombok lombok 1.16.18 org.apache.maven.plugins maven-compiler-plugin 2.0.2 1.6 1.6 org.apache.maven.plugins maven-release-plugin 2.5.1 true false org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources verify jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.8.1 *.crypto;*.json;*.key attach-javadoc verify jar org.sonatype.plugins nexus-staging-maven-plugin 1.6.3 true ossrh https://oss.sonatype.org/ true org.pitest pitest-maven 1.2.2 false release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign test-coveralls env.COVERALLS true org.jacoco jacoco-maven-plugin 0.7.5.201505241946 prepare-agent prepare-agent org.eluder.coveralls coveralls-maven-plugin 3.0.1 ================================================ FILE: u2flib-server-attestation/README.adoc ================================================ == Device attestation module Module for verifying attestation certificates and providing additional device metadata. This is useful if you want to: - Restrict which types of devices that can be registered with your server - Give users rich data about their registered devices, such as: * The vendor and model name * An image of the device model === Dependency Maven: [source, xml] com.yubico u2flib-server-attestation 0.19.1 Gradle: [source, groovy] repositories{ mavenCentral() } dependencies { compile 'com.yubico:u2flib-server-attestation:0.19.1' } === Usage The Attestation class lets you know if the attestation certificate from the registration is trusted, and provides you with metadata about the vendor as well as the device itself. [source,java] ---- DeviceRegistration registration = U2F.finishRegistration(request, response); Attestation attestation = metadataService.getAttestation( registration.getAttestationCertificate() ); // Check if the device is trusted assert attestation.isTrusted(); // Check that the vendor is Yubico assert attestation.getVendorProperties().get("name").equals("Yubico"); // Get device name and image String deviceName = attestation.getDeviceProperties().get("displayName"); String imageUrl = attestation.getDeviceProperties().get("imageUrl"); ---- ================================================ FILE: u2flib-server-attestation/build.gradle ================================================ description = 'U2F attestation' dependencies { compile( project(':u2flib-server-core'), ) } ================================================ FILE: u2flib-server-attestation/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: u2flib-server-attestation/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: u2flib-server-attestation/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: u2flib-server-attestation/pom.xml ================================================ u2flib-server-parent com.yubico 0.19.12 4.0.0 u2flib-server-attestation U2F attestation com.yubico u2flib-server-core 0.19.12 ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/Attestation.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.io.Serializable; import java.util.EnumSet; import java.util.Map; import java.util.Set; import lombok.Getter; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @Getter public class Attestation implements Serializable { private final String metadataIdentifier; private final Map vendorProperties; private final Map deviceProperties; private final Set transports; private Attestation() { metadataIdentifier = null; vendorProperties = null; deviceProperties = null; transports = Sets.immutableEnumSet(null); } public Attestation(String metadataIdentifier, Map vendorProperties, Map deviceProperties, Set transports) { this.metadataIdentifier = metadataIdentifier; this.vendorProperties = vendorProperties == null ? ImmutableMap.of() : ImmutableMap.copyOf(vendorProperties); this.deviceProperties = deviceProperties == null ? ImmutableMap.of() : ImmutableMap.copyOf(deviceProperties); this.transports = Sets.immutableEnumSet(transports == null ? ImmutableSet.of() : transports); } public boolean isTrusted() { return metadataIdentifier != null; } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/DeviceMatcher.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation; import com.fasterxml.jackson.databind.JsonNode; import java.security.cert.X509Certificate; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public interface DeviceMatcher { public boolean matches(X509Certificate attestationCertificate, JsonNode parameters); } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/MetadataObject.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.util.List; import java.util.Map; import lombok.EqualsAndHashCode; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @JsonIgnoreProperties(ignoreUnknown = true) @EqualsAndHashCode(of = { "data" }) public class MetadataObject extends JsonSerializable { private static final TypeReference> MAP_STRING_STRING_TYPE = new TypeReference>() { }; private static final TypeReference> LIST_STRING_TYPE = new TypeReference>() { }; private static final TypeReference> LIST_JSONNODE_TYPE = new TypeReference>() { }; private final transient JsonNode data; private final String identifier; private final long version; private final Map vendorInfo; private final List trustedCertificates; private final List devices; @JsonCreator public MetadataObject(JsonNode data) throws U2fBadInputException { this.data = data; try { vendorInfo = OBJECT_MAPPER.readValue(data.get("vendorInfo").traverse(), MAP_STRING_STRING_TYPE); trustedCertificates = OBJECT_MAPPER.readValue(data.get("trustedCertificates").traverse(), LIST_STRING_TYPE); devices = OBJECT_MAPPER.readValue(data.get("devices").traverse(), LIST_JSONNODE_TYPE); } catch (JsonMappingException e) { throw new U2fBadInputException("Invalid JSON data", e); } catch (JsonParseException e) { throw new U2fBadInputException("Invalid JSON data", e); } catch (IOException e) { throw new U2fBadInputException("Invalid JSON data", e); } identifier = data.get("identifier").asText(); version = data.get("version").asLong(); } @Override public String toJson() { return data.toString(); } public String getIdentifier() { return identifier; } public long getVersion() { return version; } public Map getVendorInfo() { return vendorInfo; } public List getTrustedCertificates() { return trustedCertificates; } public List getDevices() { return MoreObjects.firstNonNull(devices, ImmutableList.of()); } public static List parseFromJson(String jsonData) throws U2fBadInputException { JsonNode items; try { items = OBJECT_MAPPER.readValue(jsonData, JsonNode.class); if (!items.isArray()) { items = OBJECT_MAPPER.createArrayNode().add(items); } } catch (IOException e) { throw new U2fBadInputException("Malformed data", e); } ImmutableList.Builder objects = ImmutableList.builder(); for (JsonNode item : items) { objects.add(MetadataObject.fromJson(item.toString())); } return objects.build(); } public static MetadataObject fromJson(String json) throws U2fBadInputException { try { return new MetadataObject(OBJECT_MAPPER.readTree(json)); } catch (IOException e) { throw new U2fBadInputException("Malformed data", e); } } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/MetadataResolver.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation; import java.security.cert.X509Certificate; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public interface MetadataResolver { MetadataObject resolve(X509Certificate attestationCertificate); } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/MetadataService.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Charsets; import com.google.common.base.Predicates; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.google.common.io.CharStreams; import com.google.common.io.Closeables; import com.yubico.u2f.attestation.matchers.ExtensionMatcher; import com.yubico.u2f.attestation.matchers.FingerprintMatcher; import com.yubico.u2f.attestation.resolvers.SimpleResolver; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class MetadataService { private static final Logger logger = LoggerFactory.getLogger(MetadataService.class); public static final String SELECTORS = "selectors"; private static final String SELECTOR_TYPE = "type"; private static final String SELECTOR_PARAMETERS = "parameters"; private static final String TRANSPORTS = "transports"; private static final String TRANSPORTS_EXT_OID = "1.3.6.1.4.1.45724.2.1.1"; public static final Map DEFAULT_DEVICE_MATCHERS = ImmutableMap.of( ExtensionMatcher.SELECTOR_TYPE, new ExtensionMatcher(), FingerprintMatcher.SELECTOR_TYPE, new FingerprintMatcher() ); private static MetadataResolver createDefaultMetadataResolver() { SimpleResolver resolver = new SimpleResolver(); InputStream is = null; try { is = MetadataService.class.getResourceAsStream("/metadata.json"); resolver.addMetadata(CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8))); } catch (IOException e) { logger.error("createDefaultMetadataResolver failed", e); } catch (CertificateException e) { logger.error("createDefaultMetadataResolver failed", e); } catch (U2fBadInputException e) { logger.error("createDefaultMetadataResolver failed", e); } finally { Closeables.closeQuietly(is); } return resolver; } private final Attestation unknownAttestation = new Attestation(null, null, null, null); private final MetadataResolver resolver; private final Map matchers = new HashMap(); private final Cache cache; public MetadataService(MetadataResolver resolver, Cache cache, Map matchers) { this.resolver = resolver != null ? resolver : createDefaultMetadataResolver(); this.cache = cache != null ? cache : CacheBuilder.newBuilder().build(); if (matchers == null) { matchers = DEFAULT_DEVICE_MATCHERS; } this.matchers.putAll(matchers); } public MetadataService() { this(null, null, null); } public MetadataService(MetadataResolver resolver) { this(resolver, null, null); } public MetadataService(MetadataResolver resolver, Map matchers) { this(resolver, null, matchers); } public MetadataService(MetadataResolver resolver, Cache cache) { this(resolver, cache, null); } public void registerDeviceMatcher(String matcherType, DeviceMatcher matcher) { matchers.put(matcherType, matcher); } private boolean deviceMatches(JsonNode selectors, X509Certificate attestationCertificate) { if (selectors != null && !selectors.isNull()) { for (JsonNode selector : selectors) { DeviceMatcher matcher = matchers.get(selector.get(SELECTOR_TYPE).asText()); if (matcher != null && matcher.matches(attestationCertificate, selector.get(SELECTOR_PARAMETERS))) { return true; } } return false; } return true; //Match if selectors is null or missing. } public Attestation getCachedAttestation(String attestationCertificateFingerprint) { return cache.getIfPresent(attestationCertificateFingerprint); } public Attestation getAttestation(final X509Certificate attestationCertificate) { if (attestationCertificate == null) { return unknownAttestation; } try { String fingerprint = Hashing.sha1().hashBytes(attestationCertificate.getEncoded()).toString(); return cache.get(fingerprint, new Callable() { @Override public Attestation call() throws Exception { return lookupAttestation(attestationCertificate); } }); } catch (Exception e) { return unknownAttestation; } } private Attestation lookupAttestation(X509Certificate attestationCertificate) { MetadataObject metadata = resolver.resolve(attestationCertificate); Map vendorProperties = null; Map deviceProperties = null; String identifier = null; int transports = 0; if (metadata != null) { identifier = metadata.getIdentifier(); vendorProperties = Maps.filterValues(metadata.getVendorInfo(), Predicates.notNull()); for (JsonNode device : metadata.getDevices()) { if (deviceMatches(device.get(SELECTORS), attestationCertificate)) { JsonNode transportNode = device.get(TRANSPORTS); if(transportNode != null) { transports = transportNode.asInt(0); } ImmutableMap.Builder devicePropertiesBuilder = ImmutableMap.builder(); for (Map.Entry deviceEntry : Lists.newArrayList(device.fields())) { JsonNode value = deviceEntry.getValue(); if (value.isTextual()) { devicePropertiesBuilder.put(deviceEntry.getKey(), value.asText()); } } deviceProperties = devicePropertiesBuilder.build(); break; } } } transports |= get_transports(attestationCertificate.getExtensionValue(TRANSPORTS_EXT_OID)); return new Attestation(identifier, vendorProperties, deviceProperties, Transport.fromInt(transports)); } private int get_transports(byte[] extensionValue) { if(extensionValue == null) { return 0; } // Mask out unused bits (shouldn't be needed as they should already be 0). int unusedBitMask = 0xff; for(int i=0; i < extensionValue[3]; i++) { unusedBitMask <<= 1; } extensionValue[extensionValue.length-1] &= unusedBitMask; int transports = 0; for(int i=extensionValue.length - 1; i >= 5; i--) { byte b = extensionValue[i]; for(int bi=0; bi < 8; bi++) { transports = (transports << 1) | (b & 1); b >>= 1; } } return transports; } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/Transport.java ================================================ package com.yubico.u2f.attestation; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; /** * Created by Dain on 2016-02-18. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public enum Transport { BT_CLASSIC(1), BLE(2), USB(4), NFC(8); private final int bitpos; Transport(int bitpos) { this.bitpos = bitpos; } public static Set fromInt(int bits) { EnumSet transports = EnumSet.noneOf(Transport.class); for(Transport transport : Transport.values()) { if((transport.bitpos & bits) != 0) { transports.add(transport); } } return transports; } public static int toInt(Iterable transports) { int transportsInt = 0; for(Transport transport : transports) { transportsInt |= transport.bitpos; } return transportsInt; } public static int toInt(Transport...transports) { return toInt(Arrays.asList(transports)); } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/matchers/ExtensionMatcher.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation.matchers; import com.fasterxml.jackson.databind.JsonNode; import com.yubico.u2f.attestation.DeviceMatcher; import org.bouncycastle.util.Strings; import java.security.cert.X509Certificate; import java.util.Arrays; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class ExtensionMatcher implements DeviceMatcher { public static final String SELECTOR_TYPE = "x509Extension"; private static final String EXTENSION_KEY = "key"; private static final String EXTENSION_VALUE = "value"; @Override public boolean matches(X509Certificate attestationCertificate, JsonNode parameters) { String matchKey = parameters.get(EXTENSION_KEY).asText(); JsonNode matchValue = parameters.get(EXTENSION_VALUE); byte[] extensionValue = attestationCertificate.getExtensionValue(matchKey); if (extensionValue != null) { if (matchValue == null) { return true; } else { //TODO: Handle long lengths? Verify length? String readValue = Strings.fromByteArray(Arrays.copyOfRange(extensionValue, 2, extensionValue.length)); if (matchValue.asText().equals(readValue)) { return true; } } } return false; } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/matchers/FingerprintMatcher.java ================================================ package com.yubico.u2f.attestation.matchers; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.hash.Hashing; import com.yubico.u2f.attestation.DeviceMatcher; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class FingerprintMatcher implements DeviceMatcher { public static final String SELECTOR_TYPE = "fingerprint"; private static final String FINGERPRINTS_KEY = "fingerprints"; @Override public boolean matches(X509Certificate attestationCertificate, JsonNode parameters) { JsonNode fingerprints = parameters.get(FINGERPRINTS_KEY); if(fingerprints.isArray()) { try { String fingerprint = Hashing.sha1().hashBytes(attestationCertificate.getEncoded()).toString().toLowerCase(); for(JsonNode candidate : fingerprints) { if(fingerprint.equals(candidate.asText().toLowerCase())) { return true; } } } catch (CertificateEncodingException e) { //Fall through to return false. } } return false; } } ================================================ FILE: u2flib-server-attestation/src/main/java/com/yubico/u2f/attestation/resolvers/SimpleResolver.java ================================================ /* Copyright 2015 Yubico */ package com.yubico.u2f.attestation.resolvers; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.yubico.u2f.attestation.MetadataObject; import com.yubico.u2f.attestation.MetadataResolver; import com.yubico.u2f.data.messages.key.util.CertificateParser; import com.yubico.u2f.exceptions.U2fBadInputException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class SimpleResolver implements MetadataResolver { private static final Logger logger = LoggerFactory.getLogger(SimpleResolver.class); private final Multimap certs = ArrayListMultimap.create(); private final Map metadata = new HashMap(); public void addMetadata(String jsonData) throws CertificateException, U2fBadInputException { for (MetadataObject object : MetadataObject.parseFromJson(jsonData)) { addMetadata(object); } } public void addMetadata(MetadataObject object) throws CertificateException { for (String caPem : object.getTrustedCertificates()) { X509Certificate caCert = CertificateParser.parsePem(caPem); certs.put(caCert.getSubjectDN().getName(), caCert); metadata.put(caCert, object); } } @Override public MetadataObject resolve(X509Certificate attestationCertificate) { String issuer = attestationCertificate.getIssuerDN().getName(); for (X509Certificate cert : certs.get(issuer)) { try { attestationCertificate.verify(cert.getPublicKey()); return metadata.get(cert); } catch (CertificateException e) { logger.error("resolve failed", e); } catch (NoSuchAlgorithmException e) { logger.error("resolve failed", e); } catch (InvalidKeyException e) { logger.error("resolve failed", e); } catch (NoSuchProviderException e) { logger.error("resolve failed", e); } catch (SignatureException e) { logger.error("resolve failed", e); } } return null; } } ================================================ FILE: u2flib-server-attestation/src/main/resources/metadata.json ================================================ { "identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe", "version": 4, "vendorInfo": { "url": "https://yubico.com", "imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png", "name": "Yubico" }, "trustedCertificates": [ "-----BEGIN CERTIFICATE-----\nMIIDHjCCAgagAwIBAgIEG1BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ\ndWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290\nIENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk\n5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep\n8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw\nnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT\n9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw\nLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ\nhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN\nBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4\nMYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt\nhX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k\nLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U\nsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc\nU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==\n-----END CERTIFICATE-----" ], "devices": [ { "deviceId": "1.3.6.1.4.1.41482.1.1", "displayName": "Security Key by Yubico", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/fido-u2f-security-key/", "imageUrl": "https://developers.yubico.com/U2F/Images/SKY.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.1" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.1", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.2", "displayName": "YubiKey NEO/NEO-n", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/yubikey-neo/", "imageUrl": "https://developers.yubico.com/U2F/Images/NEO.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.2" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.2", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.3", "displayName": "YubiKey Plus", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/", "imageUrl": "https://developers.yubico.com/U2F/Images/PLS.png", "selectors": [ { "type": "x509Extension", "parameters": { "key": "1.3.6.1.4.1.41482.1.3" } }, { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.3", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.4", "displayName": "YubiKey Edge", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/", "imageUrl": "https://developers.yubico.com/U2F/Images/YKE.png", "selectors": [ { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.4", "key": "1.3.6.1.4.1.41482.2" } } ] }, { "deviceId": "1.3.6.1.4.1.41482.1.5", "displayName": "YubiKey 4/YubiKey 4 Nano", "transports": 4, "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/yubikey4/", "imageUrl": "https://developers.yubico.com/U2F/Images/YK4.png", "selectors": [ { "type": "x509Extension", "parameters": { "value": "1.3.6.1.4.1.41482.1.5", "key": "1.3.6.1.4.1.41482.2" } } ] } ] } ================================================ FILE: u2flib-server-attestation/src/test/java/com/yubico/u2f/attestation/MetadataObjectTest.java ================================================ package com.yubico.u2f.attestation; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Iterables; import com.yubico.u2f.exceptions.U2fBadInputException; import org.junit.Test; import static org.junit.Assert.assertEquals; public class MetadataObjectTest { public static final String JSON = "{\"identifier\":\"foobar\",\"version\":1,\"trustedCertificates\":[\"-----BEGIN CERTIFICATE-----\\nMIIDHjCCAgagAwIBAgIEG1BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ\\ndWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw\\nMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290\\nIENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\\nAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk\\n5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep\\n8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw\\nnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT\\n9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw\\nLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ\\nhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN\\nBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4\\nMYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt\\nhX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k\\nLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U\\nsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc\\nU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==\\n-----END CERTIFICATE-----\"],\"vendorInfo\":{\"name\":\"Yubico\",\"url\":\"https://yubico.com\",\"imageUrl\":\"https://developers.yubico.com/U2F/Images/yubico.png\"},\"devices\":[{\"displayName\":\"YubiKey NEO/NEO-n\",\"deviceId\":\"1.3.6.1.4.1.41482.1.2\",\"deviceUrl\":\"https://www.yubico.com/products/yubikey-hardware/yubikey-neo/\",\"imageUrl\":\"https://developers.yubico.com/U2F/Images/NEO.png\",\"selectors\":[{\"type\":\"x509Extension\",\"parameters\":{\"key\":\"1.3.6.1.4.1.41482.1.2\"}}]}]}"; @Test public void testToAndFromJson() throws Exception { MetadataObject metadata = MetadataObject.fromJson(JSON); ObjectMapper objectMapper = new ObjectMapper(); MetadataObject metadata2 = objectMapper.readValue(objectMapper.writeValueAsString(metadata), MetadataObject.class); assertEquals("foobar", metadata.getIdentifier()); assertEquals(1, metadata.getVersion()); assertEquals(1, metadata.getTrustedCertificates().size()); assertEquals("Yubico", metadata.getVendorInfo().get("name")); assertEquals("1.3.6.1.4.1.41482.1.2", metadata.getDevices().get(0).get("deviceId").asText()); assertEquals(metadata, metadata2); assertEquals(JSON, metadata.toJson()); } @Test public void testParseFromJson() throws U2fBadInputException { MetadataObject parsed1 = Iterables.getOnlyElement(MetadataObject.parseFromJson(JSON)); MetadataObject parsed2 = Iterables.getOnlyElement(MetadataObject.parseFromJson("[" + JSON + "]")); MetadataObject expected = MetadataObject.fromJson(JSON); assertEquals(expected.toJson(), parsed1.toJson()); assertEquals(expected.toJson(), parsed2.toJson()); } } ================================================ FILE: u2flib-server-attestation/src/test/java/com/yubico/u2f/attestation/MetadataServiceTest.java ================================================ package com.yubico.u2f.attestation; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; import com.yubico.u2f.data.messages.key.util.CertificateParser; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.EnumSet; import org.junit.Test; import org.mockito.Matchers; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class MetadataServiceTest { private static final String ATTESTATION_CERT = "MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLonckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IBAQG9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nDpxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh+uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAWDUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO"; private static final String ATTESTATION_CERT2 = "MIICLzCCARmgAwIBAgIEQvUaTTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDExMjMzNTkzMDkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQphQ+PJYiZjZEVHtrx5QGE3/LE1+OytZPTwzrpWBKywji/3qmg22mwmVFl32PO269TxY+yVN4jbfVf5uX0EWJWoyYwJDAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuNDALBgkqhkiG9w0BAQsDggEBALSc3YwTRbLwXhePj/imdBOhWiqh6ssS2ONgp5tphJCHR5Agjg2VstLBRsJzyJnLgy7bGZ0QbPOyh/J0hsvgBfvjByXOu1AwCW+tcoJ+pfxESojDLDn8hrFph6eWZoCtBsWMDh6vMqPENeP6grEAECWx4fTpBL9Bm7F+0Rp/d1/l66g4IhF/ZvuRFhY+BUK94BfivuBHpEkMwxKENTas7VkxvlVstUvPqhPHGYOq7RdF1D/THsbNY8+tgCTgvTziEG+bfDeY6zIz5h7bxb1rpajNVTpUDWtVYL7/w44e1KCoErqdS+kEbmmkmm7KvDE8kuyg42Fmb5DTMsbY2jxMlMU="; private static final String ATTESTATION_CERT_WITH_TRANSPORTS = "MIICIjCCAQygAwIBAgIEIHHwozALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEdGVzdDAeFw0xNTA4MTEwOTAwMzNaFw0xNjA4MTAwOTAwMzNaMCkxJzAlBgNVBAMTHll1YmljbyBVMkYgRUUgU2VyaWFsIDU0NDMzODA4MzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPdFG1pBjBBQVhLrD39Qg1vKjuR2kRdBZnwLI/zgzztQpf4ffpkrkB/3E0TXj5zg8gN9sgMkX48geBe+tBEpvMmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEBCwOCAQEAb3YpnmHHduNuWEXlLqlnww9034ZeZaojhPAYSLR8d5NPk9gc0hkjQKmIaaBM7DsaHbcHMKpXoMGTQSC++NCZTcKvZ0Lt12mp5HRnM1NNBPol8Hte5fLmvW4tQ9EzLl4gkz7LSlORxTuwTbae1eQqNdxdeB+0ilMFCEUc+3NGCNM0RWd+sP5+gzMXBDQAI1Sc9XaPIg8t3du5JChAl1ifpu/uERZ2WQgtxeBDO6z1Xoa5qz4svf5oURjPZjxS0WUKht48Z2rIjk5lZzERSaY3RrX3UtrnZEIzCmInXOrcRPeAD4ZutpiwuHe62ABsjuMRnKbATbOUiLdknNyPYYQz2g=="; @Test public void testGetAttestation_x509extension_key() throws Exception { MetadataService service = new MetadataService(); X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT); Attestation attestation = service.getAttestation(attestationCert); assertTrue(attestation.isTrusted()); assertEquals("Yubico", attestation.getVendorProperties().get("name")); assertEquals("1.3.6.1.4.1.41482.1.2", attestation.getDeviceProperties().get("deviceId")); } @Test public void testGetAttestation_x509extension_key_value() throws Exception { MetadataService service = new MetadataService(); X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT2); Attestation attestation = service.getAttestation(attestationCert); assertTrue(attestation.isTrusted()); assertEquals("Yubico", attestation.getVendorProperties().get("name")); assertEquals("1.3.6.1.4.1.41482.1.4", attestation.getDeviceProperties().get("deviceId")); } @Test public void testGetTransportsFromCertificate() throws CertificateException { MetadataService service = new MetadataService(mock(MetadataResolver.class)); X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT_WITH_TRANSPORTS); Attestation attestation = service.getAttestation(attestationCert); assertEquals(EnumSet.of(Transport.USB, Transport.NFC), attestation.getTransports()); } @Test public void testGetTransportsFromMetadata() throws CertificateException { MetadataService service = new MetadataService(); X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT2); Attestation attestation = service.getAttestation(attestationCert); assertEquals(EnumSet.of(Transport.USB), attestation.getTransports()); } @Test public void getCachedAttestationReturnsCertIfPresent() throws Exception { MetadataService service = new MetadataService(); final X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT); final String certFingerprint = Hashing.sha1().hashBytes(attestationCert.getEncoded()).toString(); assertNull(service.getCachedAttestation(certFingerprint)); service.getAttestation(attestationCert); Attestation attestation = service.getCachedAttestation(certFingerprint); assertTrue(attestation.isTrusted()); assertEquals("Yubico", attestation.getVendorProperties().get("name")); assertEquals("1.3.6.1.4.1.41482.1.2", attestation.getDeviceProperties().get("deviceId")); } @Test public void getAttestationReturnsUnknownIfFingerprintEncodingFails() throws Exception { MetadataService service = new MetadataService(); final X509Certificate attestationCert = mock(X509Certificate.class); when(attestationCert.getEncoded()).thenThrow(new CertificateEncodingException("Forced failure")); Attestation attestation = service.getAttestation(attestationCert); assertFalse(attestation.isTrusted()); } @Test public void deviceMatchesReturnsTrueIfNoSelectorsAreGiven() throws Exception { MetadataResolver resolver = mock(MetadataResolver.class); JsonNode device = mock(JsonNode.class); MetadataObject metadata = mock(MetadataObject.class); when(metadata.getDevices()).thenReturn(ImmutableList.of(device)); when(resolver.resolve(Matchers.any())).thenReturn(metadata); MetadataService service = new MetadataService(resolver); final X509Certificate attestationCert = CertificateParser.parsePem(ATTESTATION_CERT); Attestation attestation = service.getAttestation(attestationCert); verify(device, times(1)).get("transports"); } } ================================================ FILE: u2flib-server-attestation/src/test/java/com/yubico/u2f/attestation/TransportTest.java ================================================ package com.yubico.u2f.attestation; import org.junit.Test; import java.util.EnumSet; import static org.junit.Assert.assertEquals; /** * Created by Dain on 2016-02-18. */ public class TransportTest { @Test public void testParsingSingleValuesFromInt() { assertEquals(EnumSet.of(Transport.BT_CLASSIC), Transport.fromInt(1)); assertEquals(EnumSet.of(Transport.BLE), Transport.fromInt(2)); assertEquals(EnumSet.of(Transport.USB), Transport.fromInt(4)); assertEquals(EnumSet.of(Transport.NFC), Transport.fromInt(8)); } @Test public void testParsingSetsFromInt() { assertEquals(EnumSet.noneOf(Transport.class), Transport.fromInt(0)); assertEquals(EnumSet.of(Transport.BLE, Transport.NFC), Transport.fromInt(10)); assertEquals(EnumSet.of(Transport.USB, Transport.BT_CLASSIC), Transport.fromInt(5)); assertEquals(EnumSet.of(Transport.BT_CLASSIC, Transport.BLE, Transport.USB, Transport.NFC), Transport.fromInt(15)); } @Test public void testEncodingSingleValuesToInt() { assertEquals(1, Transport.toInt(Transport.BT_CLASSIC)); assertEquals(2, Transport.toInt(Transport.BLE)); assertEquals(4, Transport.toInt(Transport.USB)); assertEquals(8, Transport.toInt(Transport.NFC)); } @Test public void testEncodingSetsToInt() { assertEquals(0, Transport.toInt()); assertEquals(10, Transport.toInt(Transport.BLE, Transport.NFC)); assertEquals(5, Transport.toInt(Transport.USB, Transport.BT_CLASSIC)); assertEquals(15, Transport.toInt(Transport.BT_CLASSIC, Transport.BLE, Transport.USB, Transport.NFC)); } } ================================================ FILE: u2flib-server-attestation/src/test/java/com/yubico/u2f/attestation/matchers/FingerprintMatcherTest.java ================================================ package com.yubico.u2f.attestation.matchers; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.hash.Hashing; import com.yubico.u2f.data.messages.key.util.CertificateParser; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class FingerprintMatcherTest { private static final String ATTESTATION_CERT = "MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLonckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IBAQG9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nDpxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh+uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAWDUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO"; @Test public void matchesIsFalseForNonArrayFingerprints() { JsonNode parameters = mock(JsonNode.class); when(parameters.get("fingerprints")).thenReturn(BooleanNode.TRUE); assertFalse(new FingerprintMatcher().matches(mock(X509Certificate.class), parameters)); } @Test public void matchesIsFalseIfNoFingerprintMatches() throws CertificateException { final X509Certificate cert = CertificateParser.parsePem(ATTESTATION_CERT); final String fingerprint = Hashing.sha1().hashBytes(cert.getEncoded()).toString().toLowerCase(); ArrayNode fingerprints = new ArrayNode(JsonNodeFactory.instance); fingerprints.add(new TextNode("foo")); fingerprints.add(new TextNode("bar")); JsonNode parameters = mock(JsonNode.class); when(parameters.get("fingerprints")).thenReturn(fingerprints); assertFalse(new FingerprintMatcher().matches(cert, parameters)); } @Test public void matchesIsTrueIfSomeFingerprintMatches() throws CertificateException { final X509Certificate cert = CertificateParser.parsePem(ATTESTATION_CERT); final String fingerprint = Hashing.sha1().hashBytes(cert.getEncoded()).toString().toLowerCase(); ArrayNode fingerprints = new ArrayNode(JsonNodeFactory.instance); fingerprints.add(new TextNode("foo")); fingerprints.add(new TextNode(fingerprint)); JsonNode parameters = mock(JsonNode.class); when(parameters.get("fingerprints")).thenReturn(fingerprints); assertTrue(new FingerprintMatcher().matches(cert, parameters)); } } ================================================ FILE: u2flib-server-attestation/src/test/java/com/yubico/u2f/attestation/resolvers/SimpleResolverTest.java ================================================ package com.yubico.u2f.attestation.resolvers; import com.yubico.u2f.attestation.MetadataObject; import com.yubico.u2f.data.messages.key.util.CertificateParser; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Principal; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import org.junit.Test; import org.mockito.Matchers; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SimpleResolverTest { private static final String METADATA_JSON = "{\"identifier\":\"foobar\",\"version\":1,\"trustedCertificates\":[\"-----BEGIN CERTIFICATE-----\\nMIIDHjCCAgagAwIBAgIEG1BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ\\ndWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw\\nMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290\\nIENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\\nAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk\\n5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep\\n8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw\\nnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT\\n9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw\\nLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ\\nhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN\\nBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4\\nMYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt\\nhX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k\\nLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U\\nsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc\\nU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==\\n-----END CERTIFICATE-----\"],\"vendorInfo\":{\"name\":\"Yubico\",\"url\":\"https://yubico.com\",\"imageUrl\":\"https://developers.yubico.com/U2F/Images/yubico.png\"},\"devices\":[{\"displayName\":\"YubiKey NEO/NEO-n\",\"deviceId\":\"1.3.6.1.4.1.41482.1.2\",\"deviceUrl\":\"https://www.yubico.com/products/yubikey-hardware/yubikey-neo/\",\"imageUrl\":\"https://developers.yubico.com/U2F/Images/NEO.png\",\"selectors\":[{\"type\":\"x509Extension\",\"parameters\":{\"key\":\"1.3.6.1.4.1.41482.1.2\"}}]}] }"; private static final String ATTESTATION_CERT = "MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLonckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IBAQG9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nDpxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh+uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAWDUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO"; @Test public void testResolve() throws Exception { SimpleResolver resolver = new SimpleResolver(); resolver.addMetadata(METADATA_JSON); X509Certificate certificate = CertificateParser.parseDer(ATTESTATION_CERT); MetadataObject metadata = resolver.resolve(certificate); assertNotNull(metadata); assertEquals("foobar", metadata.getIdentifier()); } @Test public void resolveReturnsNullOnUntrustedSignature() throws Exception { SimpleResolver resolver = new SimpleResolver(); resolver.addMetadata(METADATA_JSON); X509Certificate cert = mock(X509Certificate.class); doThrow( new CertificateException("Forced failure"), new NoSuchAlgorithmException("Forced failure"), new InvalidKeyException("Forced failure"), new NoSuchProviderException("Forced failure"), new SignatureException("Forced failure") ).when(cert).verify(Matchers.any()); Principal issuerDN = mock(Principal.class); when(issuerDN.getName()).thenReturn("CN=Yubico U2F Root CA Serial 457200631"); when(cert.getIssuerDN()).thenReturn(issuerDN); assertNull(resolver.resolve(cert)); assertNull(resolver.resolve(cert)); assertNull(resolver.resolve(cert)); assertNull(resolver.resolve(cert)); assertNull(resolver.resolve(cert)); } } ================================================ FILE: u2flib-server-core/build.gradle ================================================ description = 'U2F core' dependencies { compile( [group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version:'1.54'], [group: 'com.google.guava', name: 'guava', version:'19.0'], [group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:'2.11.0'], ) } ================================================ FILE: u2flib-server-core/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: u2flib-server-core/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: u2flib-server-core/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: u2flib-server-core/pom.xml ================================================ u2flib-server-parent com.yubico 0.19.12 4.0.0 u2flib-server-core U2F core org.bouncycastle bcpkix-jdk15on 1.54 com.google.guava guava [24.1.1,30) com.fasterxml.jackson.core jackson-databind 2.11.0 ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/AppId.java ================================================ package com.yubico.u2f; import com.google.common.net.InetAddresses; import com.yubico.u2f.exceptions.U2fBadConfigurationException; import com.yubico.u2f.exceptions.U2fBadInputException; import java.net.URI; import java.net.URISyntaxException; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class AppId { public static final String DISABLE_INSTRUCTIONS = "To disable this check, instantiate the U2F object using U2F.withoutAppIdValidation()"; /** * Throws {@link U2fBadConfigurationException} if the given App ID is found to be incompatible with the U2F specification or any major * U2F Client implementation. * * @param appId the App ID to be validated */ public static void checkIsValid(String appId) throws U2fBadConfigurationException { if(!appId.contains(":")) { throw new U2fBadConfigurationException("App ID does not look like a valid facet or URL. Web facets must start with 'https://'. " + DISABLE_INSTRUCTIONS); } if(appId.startsWith("http:")) { throw new U2fBadConfigurationException("HTTP is not supported for App IDs (by Chrome). Use HTTPS instead. " + DISABLE_INSTRUCTIONS); } if(appId.startsWith("https://")) { URI url = checkValidUrl(appId); checkPathIsNotSlash(url); checkNotIpAddress(url); } } private static void checkPathIsNotSlash(URI url) throws U2fBadConfigurationException { if("/".equals(url.getPath())) { throw new U2fBadConfigurationException("The path of the URL set as App ID is '/'. This is probably not what you want -- remove the trailing slash of the App ID URL. " + DISABLE_INSTRUCTIONS); } } private static URI checkValidUrl(String appId) throws U2fBadConfigurationException { URI url = null; try { url = new URI(appId); } catch (URISyntaxException e) { throw new U2fBadConfigurationException("App ID looks like a HTTPS URL, but has syntax errors.", e); } return url; } private static void checkNotIpAddress(URI url) throws U2fBadConfigurationException { if (InetAddresses.isInetAddress(url.getAuthority()) || (url.getHost() != null && InetAddresses.isInetAddress(url.getHost()))) { throw new U2fBadConfigurationException("App ID must not be an IP-address, since it is not supported (by Chrome). Use a host name instead. " + DISABLE_INSTRUCTIONS); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/U2F.java ================================================ /* Copyright 2014 Yubico */ package com.yubico.u2f; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.crypto.RandomChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.*; import com.yubico.u2f.exceptions.DeviceCompromisedException; import com.yubico.u2f.exceptions.InvalidDeviceCounterException; import com.yubico.u2f.exceptions.NoEligibleDevicesException; import com.yubico.u2f.exceptions.U2fAuthenticationException; import com.yubico.u2f.exceptions.U2fBadConfigurationException; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.exceptions.U2fRegistrationException; import java.util.Set; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2F { private final ChallengeGenerator challengeGenerator; private final U2fPrimitives primitives; private final boolean validateAppId; public U2F() { this(true); } public static U2F withoutAppIdValidation() { return new U2F(false); } private U2F(boolean validateAppId) { this.challengeGenerator = new RandomChallengeGenerator(); primitives = new U2fPrimitives(new BouncyCastleCrypto(), challengeGenerator); this.validateAppId = validateAppId; } /** * Initiates a high-level registration of a device, given a set of already registered devices. * * @param appId the U2F AppID. Set this to the Web Origin of the login page, unless you need to * support logging in from multiple Web Origins. * @param devices the devices currently registered to the user. * @return a RegisterRequestData, which should be sent to the client and temporarily saved by the server. */ public RegisterRequestData startRegistration(String appId, Iterable devices) throws U2fBadConfigurationException { if(validateAppId) { AppId.checkIsValid(appId); } return new RegisterRequestData(appId, devices, primitives, challengeGenerator); } public SignRequestData startSignature(String appId, Iterable devices) throws U2fBadConfigurationException, NoEligibleDevicesException { if(validateAppId) { AppId.checkIsValid(appId); } return new SignRequestData(appId, devices, primitives, challengeGenerator); } /*** * */ public DeviceRegistration finishRegistration(RegisterRequestData registerRequestData, RegisterResponse response) throws U2fRegistrationException { return finishRegistration(registerRequestData, response, null); } /** * Finishes a previously started high-level registration. * * @param registerRequestData the RegisterResponseData created by calling startRegistration * @param response The response from the device/client. * @param facets A list of valid facets to verify against. * @return a DeviceRegistration object, holding information about the registered device. Servers should * persist this. * @throws U2fRegistrationException if parsing or verification of the response fails */ public DeviceRegistration finishRegistration(RegisterRequestData registerRequestData, RegisterResponse response, Set facets) throws U2fRegistrationException { return primitives.finishRegistration(registerRequestData.getRegisterRequest(response), response, facets); } /** * @see U2F#finishSignature(SignRequestData, SignResponse, Iterable, java.util.Set) */ public DeviceRegistration finishSignature(SignRequestData signRequestData, SignResponse response, Iterable devices) throws U2fAuthenticationException { return finishSignature(signRequestData, response, devices, null); } /** * Finishes a previously started high-level signing action. * * @param signRequestData the SignRequestData created by calling startSignature * @param response the response from the device/client. * @param devices the devices currently registered to the user. * @param facets A list of valid facets to verify against. * @return The (updated) DeviceRegistration that signed the challenge. * * @throws InvalidDeviceCounterException if the response is valid but the signature counter has not increased. * @throws DeviceCompromisedException if the device with the response's key handle is marked as compromised. * @throws U2fAuthenticationException if parsing or verification of the response fails. */ public DeviceRegistration finishSignature(SignRequestData signRequestData, SignResponse response, Iterable devices, Set facets) throws U2fAuthenticationException { try { final SignRequest request = signRequestData.getSignRequest(response); DeviceRegistration device = Iterables.find(devices, new Predicate() { @Override public boolean apply(DeviceRegistration input) { return Objects.equal(request.getKeyHandle(), input.getKeyHandle()); } @Override public boolean test(DeviceRegistration input) { return apply(input); } }); if (device.isCompromised()) { throw new DeviceCompromisedException(device, "The device is marked as possibly compromised, and cannot make trusted signatures."); } primitives.finishSignature(request, response, device, facets); return device; } catch (U2fBadInputException e) { throw new U2fAuthenticationException("finishSignature failed", e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/U2fPrimitives.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f; import com.google.common.base.Optional; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.crypto.RandomChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.*; import com.yubico.u2f.data.messages.key.RawSignResponse; import com.yubico.u2f.data.messages.key.RawRegisterResponse; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.DeviceCompromisedException; import com.yubico.u2f.exceptions.InvalidDeviceCounterException; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.exceptions.U2fAuthenticationException; import com.yubico.u2f.exceptions.U2fRegistrationException; import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fPrimitives { private static final String SIGN_TYPE = "navigator.id.getAssertion"; private static final String REGISTER_TYPE = "navigator.id.finishEnrollment"; public static final String U2F_VERSION = "U2F_V2"; private final Crypto crypto; private final ChallengeGenerator challengeGenerator; public U2fPrimitives(Crypto crypto, ChallengeGenerator challengeGenerator) { this.crypto = crypto; this.challengeGenerator = challengeGenerator; } public U2fPrimitives() { this(new BouncyCastleCrypto(), new RandomChallengeGenerator()); } /** * @see U2fPrimitives#startRegistration(String, byte[]) */ public RegisterRequest startRegistration(String appId) { return startRegistration(appId, challengeGenerator.generateChallenge()); } /** * Initiates the registration of a device. * * @param appId the U2F AppID. Set this to the Web Origin of the login page, unless you need to * support logging in from multiple Web Origins. * @param challenge the challenge to use * @return a RegisterRequest, which should be sent to the client and temporary saved by the * server. */ public RegisterRequest startRegistration(String appId, byte[] challenge) { return new RegisterRequest(U2fB64Encoding.encode(challenge), appId); } /** * @see U2fPrimitives#finishRegistration(com.yubico.u2f.data.messages.RegisterRequest, com.yubico.u2f.data.messages.RegisterResponse, java.util.Set) */ public DeviceRegistration finishRegistration(RegisterRequest registerRequest, RegisterResponse response) throws U2fRegistrationException { return finishRegistration(registerRequest, response, null); } /** * Finishes a previously started registration. * * @param registerRequest * @param response the response from the device/client. * @return a DeviceRegistration object, holding information about the registered device. Servers should * persist this. */ public DeviceRegistration finishRegistration(RegisterRequest registerRequest, RegisterResponse response, Set facets) throws U2fRegistrationException { try { ClientData clientData = response.getClientData(); clientData.checkContent(REGISTER_TYPE, registerRequest.getChallenge(), Optional.fromNullable(facets)); RawRegisterResponse rawRegisterResponse = RawRegisterResponse.fromBase64(response.getRegistrationData(), crypto); rawRegisterResponse.checkSignature(registerRequest.getAppId(), clientData.asJson()); return rawRegisterResponse.createDevice(); } catch (U2fBadInputException e) { throw new U2fRegistrationException("finishRegistration failed", e); } } /** * Initiates the signing process. * * @see U2fPrimitives#startSignature(String, com.yubico.u2f.data.DeviceRegistration, byte[]) */ public SignRequest startSignature(String appId, DeviceRegistration deviceRegistration) { return startSignature(appId, deviceRegistration, challengeGenerator.generateChallenge()); } /** * Initiates the signing process. * * @param appId the U2F AppID. Set this to the Web Origin of the login page, unless you need to * support logging in from multiple Web Origins. * @param deviceRegistration the DeviceRegistration for which to initiate signing. * @param challenge the challenge to use * @return an SignRequest which should be sent to the client and temporary saved by * the server. */ public SignRequest startSignature(String appId, DeviceRegistration deviceRegistration, byte[] challenge) { checkArgument(!deviceRegistration.isCompromised(), "Device has been marked as compromised, cannot sign."); return SignRequest.builder() .appId(appId) .challenge(U2fB64Encoding.encode(challenge)) .keyHandle(deviceRegistration.getKeyHandle()) .build(); } /** * @see U2fPrimitives#finishSignature(SignRequest, SignResponse, com.yubico.u2f.data.DeviceRegistration, java.util.Set) */ public void finishSignature(SignRequest signRequest, SignResponse response, DeviceRegistration deviceRegistration) throws U2fAuthenticationException { finishSignature(signRequest, response, deviceRegistration, null); } /** * Finishes a previously started signature. * * @param signRequest * @param response the response from the device/client. */ public void finishSignature(SignRequest signRequest, SignResponse response, DeviceRegistration deviceRegistration, Set facets) throws U2fAuthenticationException { checkArgument(!deviceRegistration.isCompromised(), "Device has been marked as compromised, cannot sign."); checkArgument(signRequest.getKeyHandle().equals(deviceRegistration.getKeyHandle()), "Wrong DeviceRegistration for the given SignRequest"); if (!deviceRegistration.getKeyHandle().equals(response.getKeyHandle())) { throw new U2fAuthenticationException("KeyHandle of SignResponse does not match"); } try { ClientData clientData = response.getClientData(); clientData.checkContent(SIGN_TYPE, signRequest.getChallenge(), Optional.fromNullable(facets)); RawSignResponse rawSignResponse = RawSignResponse.fromBase64( response.getSignatureData(), crypto ); rawSignResponse.checkSignature( signRequest.getAppId(), clientData.asJson(), U2fB64Encoding.decode(deviceRegistration.getPublicKey()) ); rawSignResponse.checkUserPresence(); deviceRegistration.checkAndUpdateCounter(rawSignResponse.getCounter()); } catch (U2fBadInputException e) { throw new U2fAuthenticationException("finishSignature failed", e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/crypto/BouncyCastleCrypto.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.crypto; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fBadInputException; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import java.security.*; import java.security.cert.X509Certificate; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class BouncyCastleCrypto implements Crypto { private static final Provider provider = new BouncyCastleProvider(); public Provider getProvider() { return provider; } @Override public void checkSignature(X509Certificate attestationCertificate, byte[] signedBytes, byte[] signature) throws U2fBadInputException { checkSignature(attestationCertificate.getPublicKey(), signedBytes, signature); } @Override public void checkSignature(PublicKey publicKey, byte[] signedBytes, byte[] signature) throws U2fBadInputException { try { Signature ecdsaSignature = Signature.getInstance("SHA256withECDSA", provider); ecdsaSignature.initVerify(publicKey); ecdsaSignature.update(signedBytes); if (!ecdsaSignature.verify(signature)) { throw new U2fBadInputException(String.format( "Signature is invalid. Public key: %s, signed data: %s , signature: %s", publicKey, U2fB64Encoding.encode(signedBytes), U2fB64Encoding.encode(signature) )); } } catch (GeneralSecurityException e) { throw new RuntimeException( String.format( "Failed to verify signature. This could be a problem with your JVM environment, or a bug in u2flib-server-core. Public key: %s, signed data: %s , signature: %s", publicKey, U2fB64Encoding.encode(signedBytes), U2fB64Encoding.encode(signature) ), e ); } } @Override public PublicKey decodePublicKey(byte[] encodedPublicKey) throws U2fBadInputException { try { X9ECParameters curve = SECNamedCurves.getByName("secp256r1"); ECPoint point; try { point = curve.getCurve().decodePoint(encodedPublicKey); } catch (RuntimeException e) { throw new U2fBadInputException("Could not parse user public key", e); } return KeyFactory.getInstance("ECDSA", provider).generatePublic( new ECPublicKeySpec(point, new ECParameterSpec( curve.getCurve(), curve.getG(), curve.getN(), curve.getH() ) ) ); } catch (GeneralSecurityException e) { //This should not happen throw new RuntimeException( "Failed to decode public key: " + U2fB64Encoding.encode(encodedPublicKey), e ); } } @Override public byte[] hash(byte[] bytes) { try { return MessageDigest.getInstance("SHA-256", provider).digest(bytes); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @Override public byte[] hash(String str) { return hash(str.getBytes()); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/crypto/ChallengeGenerator.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.crypto; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public interface ChallengeGenerator { byte[] generateChallenge(); } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/crypto/Crypto.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.crypto; import com.yubico.u2f.exceptions.U2fBadInputException; import java.security.PublicKey; import java.security.cert.X509Certificate; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public interface Crypto { void checkSignature(X509Certificate attestationCertificate, byte[] signedBytes, byte[] signature) throws U2fBadInputException; void checkSignature(PublicKey publicKey, byte[] signedBytes, byte[] signature) throws U2fBadInputException; PublicKey decodePublicKey(byte[] encodedPublicKey) throws U2fBadInputException; byte[] hash(byte[] bytes); byte[] hash(String str); } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/crypto/RandomChallengeGenerator.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.crypto; import java.security.SecureRandom; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class RandomChallengeGenerator implements ChallengeGenerator { private final SecureRandom random = new SecureRandom(); @Override public byte[] generateChallenge() { byte[] randomBytes = new byte[32]; random.nextBytes(randomBytes); return randomBytes; } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/DeviceRegistration.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.base.MoreObjects; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.key.util.CertificateParser; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.InvalidDeviceCounterException; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.Serializable; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import lombok.EqualsAndHashCode; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode(of = { "keyHandle", "publicKey", "attestationCert" }) public class DeviceRegistration extends JsonSerializable implements Serializable { private static final long serialVersionUID = -142942195464329902L; public static final long INITIAL_COUNTER_VALUE = -1; @JsonProperty private final String keyHandle; @JsonProperty private final String publicKey; @JsonProperty private final String attestationCert; @JsonProperty private long counter; @JsonProperty private boolean compromised; @JsonCreator public DeviceRegistration(@JsonProperty("keyHandle") String keyHandle, @JsonProperty("publicKey") String publicKey, @JsonProperty("attestationCert") String attestationCert, @JsonProperty("counter") long counter, @JsonProperty("compromised") boolean compromised) { this.keyHandle = keyHandle; this.publicKey = publicKey; this.attestationCert = attestationCert; this.counter = counter; this.compromised = compromised; } public DeviceRegistration(String keyHandle, String publicKey, X509Certificate attestationCert, long counter) throws U2fBadInputException { this.keyHandle = keyHandle; this.publicKey = publicKey; try { this.attestationCert = U2fB64Encoding.encode(attestationCert.getEncoded()); } catch (CertificateEncodingException e) { throw new U2fBadInputException("Malformed attestation certificate", e); } this.counter = counter; } public String getKeyHandle() { return keyHandle; } public String getPublicKey() { return publicKey; } @JsonIgnore public X509Certificate getAttestationCertificate() throws U2fBadInputException, CertificateException { if (attestationCert == null) { return null; } else { return CertificateParser.parseDer(U2fB64Encoding.decode(attestationCert)); } } public long getCounter() { return counter; } public boolean isCompromised() { return compromised; } public void markCompromised() { compromised = true; } @Override public String toString() { X509Certificate certificate = null; try { certificate = getAttestationCertificate(); } catch (CertificateException e) { // do nothing } catch (U2fBadInputException e) { // do nothing } return MoreObjects.toStringHelper(this) .omitNullValues() .add("Key handle", keyHandle) .add("Public key", publicKey) .add("Counter", counter) .add("Attestation certificate", certificate) .toString(); } public static DeviceRegistration fromJson(String json) throws U2fBadInputException { return fromJson(json, DeviceRegistration.class); } @Override public String toJson() { try { return OBJECT_MAPPER.writeValueAsString(new DeviceWithoutCertificate(keyHandle, publicKey, counter, compromised)); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } } public String toJsonWithAttestationCert() { return super.toJson(); } public void checkAndUpdateCounter(long clientCounter) throws InvalidDeviceCounterException { if (clientCounter <= getCounter()) { markCompromised(); throw new InvalidDeviceCounterException(this); } counter = clientCounter; } private static class DeviceWithoutCertificate { @JsonProperty private final String keyHandle; @JsonProperty private final String publicKey; @JsonProperty private final long counter; @JsonProperty private final boolean compromised; private DeviceWithoutCertificate(String keyHandle, String publicKey, long counter, boolean compromised) { this.keyHandle = keyHandle; this.publicKey = publicKey; this.counter = counter; this.compromised = compromised; } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/ClientData.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Set; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class ClientData { private static final String TYPE_PARAM = "typ"; private static final String CHALLENGE_PARAM = "challenge"; private static final String ORIGIN_PARAM = "origin"; private final String type; private final String challenge; private final String origin; private final String rawClientData; public String asJson() { return rawClientData; } public ClientData(String clientData) throws U2fBadInputException { rawClientData = new String(U2fB64Encoding.decode(clientData)); try { JsonNode data = new ObjectMapper().readTree(rawClientData); type = getString(data, TYPE_PARAM); challenge = getString(data, CHALLENGE_PARAM); origin = getString(data, ORIGIN_PARAM); } catch (IOException e) { throw new U2fBadInputException("Malformed ClientData", e); } } @Override public String toString() { return rawClientData; } public String getChallenge() { return challenge; } private static String getString(JsonNode data, String key) throws U2fBadInputException { JsonNode node = data.get(key); if (node == null) { throw new U2fBadInputException("Bad clientData: missing field " + key); } if (!node.isTextual()) { throw new U2fBadInputException("Bad clientData: field " + key + " not a string"); } return node.asText(); } public void checkContent(String type, String challenge, Optional> facets) throws U2fBadInputException { if (!type.equals(this.type)) { throw new U2fBadInputException("Bad clientData: wrong type " + this.type); } if (!challenge.equals(this.challenge)) { throw new U2fBadInputException("Bad clientData: wrong challenge"); } if (facets.isPresent()) { Set allowedFacets = canonicalizeOrigins(facets.get()); String canonicalOrigin; try { canonicalOrigin = canonicalizeOrigin(origin); } catch (RuntimeException e) { throw new U2fBadInputException("Bad clientData: Malformed origin", e); } verifyOrigin(canonicalOrigin, allowedFacets); } } private static void verifyOrigin(String origin, Set allowedOrigins) throws U2fBadInputException { if (!allowedOrigins.contains(origin)) { throw new U2fBadInputException(origin + " is not a recognized facet for this application"); } } public static Set canonicalizeOrigins(Set origins) { ImmutableSet.Builder result = ImmutableSet.builder(); for (String origin : origins) { result.add(canonicalizeOrigin(origin)); } return result.build(); } public static String canonicalizeOrigin(String url) { try { URI uri = new URI(url); if (uri.getAuthority() == null) { return url; } return uri.getScheme() + "://" + uri.getAuthority(); } catch (URISyntaxException e) { throw new IllegalArgumentException("specified bad origin", e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/RegisterRequest.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.exceptions.U2fBadInputException; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.NonNull; import lombok.Value; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @Value @Builder @AllArgsConstructor @JsonDeserialize(builder = RegisterRequest.RegisterRequestBuilder.class) public class RegisterRequest extends JsonSerializable implements Persistable { private static final long serialVersionUID = 24349091760814188L; /** * Version of the protocol that the to-be-registered U2F token must speak. For * the version of the protocol described herein, must be "U2F_V2" */ @NonNull String version; /** * The websafe-base64-encoded challenge. */ @NonNull String challenge; /** * The application id that the RP would like to assert. The U2F token will * enforce that the key handle provided above is associated with this * application id. The browser enforces that the calling origin belongs to the * application identified by the application id. */ @NonNull String appId; public RegisterRequest(String challenge, String appId) { this(U2fPrimitives.U2F_VERSION, challenge, appId); } @Override public String getRequestId() { return getChallenge(); } public static RegisterRequest fromJson(String json) throws U2fBadInputException { return fromJson(json, RegisterRequest.class); } @JsonPOJOBuilder(withPrefix = "") static class RegisterRequestBuilder {} } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/RegisterRequestData.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.exceptions.U2fBadInputException; import java.util.List; import lombok.EqualsAndHashCode; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode public class RegisterRequestData extends JsonSerializable implements Persistable { private static final long serialVersionUID = 60855174227617680L; @JsonProperty private final String appId; @JsonProperty private final List registeredKeys; @JsonProperty private final List registerRequests; public RegisterRequestData(@JsonProperty("appId") String appId, @JsonProperty("registeredKeys") List registeredKeys, @JsonProperty("registerRequests") List registerRequests) { this.appId = appId; this.registeredKeys = registeredKeys; this.registerRequests = registerRequests; } public RegisterRequestData(String appId, Iterable devices, U2fPrimitives u2f, ChallengeGenerator challengeGenerator) { this.appId = appId; ImmutableList.Builder registeredKeys = ImmutableList.builder(); for (DeviceRegistration device : devices) { if(!device.isCompromised()) { registeredKeys.add(new RegisteredKey(device.getKeyHandle())); } } this.registeredKeys = registeredKeys.build(); this.registerRequests = ImmutableList.of(u2f.startRegistration(appId, challengeGenerator.generateChallenge())); } public List getRegisteredKeys() { return ImmutableList.copyOf(registeredKeys); } public List getRegisterRequests() { return ImmutableList.copyOf(registerRequests); } public RegisterRequest getRegisterRequest(RegisterResponse response) { return Iterables.getOnlyElement(registerRequests); } public String getRequestId() { return Iterables.getOnlyElement(registerRequests).getChallenge(); } public static RegisterRequestData fromJson(String json) throws U2fBadInputException { return fromJson(json, RegisterRequestData.class); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/RegisterResponse.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import lombok.EqualsAndHashCode; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode @JsonIgnoreProperties(ignoreUnknown = true) public class RegisterResponse extends JsonSerializable implements Persistable { private static final int MAX_SIZE = 20000; /** * base64(raw registration response message) */ @JsonProperty private final String registrationData; /** * base64(UTF8(client data)) */ @JsonProperty("clientData") private final String clientDataRaw; private transient ClientData clientDataRef; @JsonCreator public RegisterResponse(@JsonProperty("registrationData") String registrationData, @JsonProperty("clientData") String clientData) throws U2fBadInputException { this.registrationData = checkNotNull(registrationData); this.clientDataRaw = checkNotNull(clientData); this.clientDataRef = new ClientData(clientData); } public String getRegistrationData() { return registrationData; } @JsonIgnore public ClientData getClientData() { return clientDataRef; } public String getRequestId() { return getClientData().getChallenge(); } public static RegisterResponse fromJson(String json) throws U2fBadInputException { checkArgument(json.length() < MAX_SIZE, "Client response bigger than allowed"); return JsonSerializable.fromJson(json, RegisterResponse.class); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); try { clientDataRef = new ClientData(clientDataRaw); } catch (U2fBadInputException e) { throw new IOException(e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/RegisteredKey.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.data.messages.json.JsonSerializable; import java.io.Serializable; import java.util.Set; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.NonNull; import lombok.Value; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @Value @Builder @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonDeserialize(builder = RegisteredKey.RegisteredKeyBuilder.class) public class RegisteredKey extends JsonSerializable implements Serializable { private static final long serialVersionUID = -5509788965855488374L; /** * Version of the protocol that the to-be-registered U2F token must speak. For * the version of the protocol described herein, must be "U2F_V2" */ @NonNull String version; /** * websafe-base64 encoding of the key handle obtained from the U2F token * during registration. */ @NonNull String keyHandle; String appId; Set transports; public RegisteredKey(String keyHandle) { this(U2fPrimitives.U2F_VERSION, keyHandle, null, null); } @JsonPOJOBuilder(withPrefix = "") public static class RegisteredKeyBuilder {} } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/SignRequest.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.exceptions.U2fBadInputException; import lombok.Builder; import lombok.NonNull; import lombok.Value; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @Value @Builder @JsonDeserialize(builder = SignRequest.SignRequestBuilder.class) public class SignRequest extends JsonSerializable implements Persistable { private static final long serialVersionUID = -27808961388655010L; /** * Version of the protocol that the to-be-registered U2F token must speak. For * the version of the protocol described herein, must be "U2F_V2" */ @JsonProperty @NonNull @Builder.Default String version = U2fPrimitives.U2F_VERSION; /** * The websafe-base64-encoded challenge. */ @JsonProperty @NonNull String challenge; /** * The application id that the RP would like to assert. The U2F token will * enforce that the key handle provided above is associated with this * application id. The browser enforces that the calling origin belongs to the * application identified by the application id. */ @JsonProperty @NonNull String appId; /** * websafe-base64 encoding of the key handle obtained from the U2F token * during registration. */ @JsonProperty @NonNull String keyHandle; public String getRequestId() { return challenge; } public static SignRequest fromJson(String json) throws U2fBadInputException { return fromJson(json, SignRequest.class); } @JsonPOJOBuilder(withPrefix = "") public static class SignRequestBuilder {} } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/SignRequestData.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.NoEligibleDevicesException; import com.yubico.u2f.exceptions.U2fBadInputException; import java.util.List; import lombok.EqualsAndHashCode; import static com.google.common.base.Preconditions.checkArgument; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode public class SignRequestData extends JsonSerializable implements Persistable { private static final long serialVersionUID = 35378338769078256L; @JsonProperty private final String appId; /** * The websafe-base64-encoded challenge. */ @JsonProperty private final String challenge; @JsonProperty private final List signRequests; @JsonCreator public SignRequestData(@JsonProperty("appId") String appId, @JsonProperty("challenge") String challenge, @JsonProperty("signRequests") List signRequests) { this.appId = appId; this.challenge = challenge; this.signRequests = signRequests; } public SignRequestData(String appId, Iterable devices, U2fPrimitives u2f, ChallengeGenerator challengeGenerator) throws NoEligibleDevicesException { this.appId = appId; byte[] challenge = challengeGenerator.generateChallenge(); this.challenge = U2fB64Encoding.encode(challenge); ImmutableList.Builder requestBuilder = ImmutableList.builder(); for (DeviceRegistration device : devices) { if(!device.isCompromised()) { requestBuilder.add(u2f.startSignature(appId, device, challenge)); } } signRequests = requestBuilder.build(); if (signRequests.isEmpty()) { if(Iterables.isEmpty(devices)) { throw new NoEligibleDevicesException(ImmutableList.of(), "No devices registrered"); } else { throw new NoEligibleDevicesException(devices, "All devices compromised"); } } } public List getSignRequests() { return ImmutableList.copyOf(signRequests); } public SignRequest getSignRequest(SignResponse response) throws U2fBadInputException { checkArgument(Objects.equal(getRequestId(), response.getRequestId()), "Wrong request for response data"); for (SignRequest request : signRequests) { if (Objects.equal(request.getKeyHandle(), response.getKeyHandle())) { return request; } } throw new U2fBadInputException("Responses keyHandle does not match any contained request"); } public String getRequestId() { return signRequests.get(0).getChallenge(); } public static SignRequestData fromJson(String json) throws U2fBadInputException { return fromJson(json, SignRequestData.class); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/SignResponse.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.yubico.u2f.data.messages.json.JsonSerializable; import com.yubico.u2f.data.messages.json.Persistable; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import lombok.EqualsAndHashCode; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @JsonIgnoreProperties(ignoreUnknown = true) @EqualsAndHashCode public class SignResponse extends JsonSerializable implements Persistable { private static final int MAX_SIZE = 20000; /* base64(client data) */ @JsonProperty("clientData") private final String clientDataRaw; @JsonIgnore private transient ClientData clientDataRef; /* base64(raw response from U2F device) */ @JsonProperty private final String signatureData; /* keyHandle originally passed */ @JsonProperty private final String keyHandle; @JsonCreator public SignResponse(@JsonProperty("clientData") String clientData, @JsonProperty("signatureData") String signatureData, @JsonProperty("keyHandle") String keyHandle) throws U2fBadInputException { this.clientDataRaw = checkNotNull(clientData); this.signatureData = checkNotNull(signatureData); this.keyHandle = checkNotNull(keyHandle); clientDataRef = new ClientData(clientData); } @JsonIgnore public ClientData getClientData() { return clientDataRef; } public String getSignatureData() { return signatureData; } public String getKeyHandle() { return keyHandle; } public String getRequestId() { return getClientData().getChallenge(); } public static SignResponse fromJson(String json) throws U2fBadInputException { checkArgument(json.length() < MAX_SIZE, "Client response bigger than allowed"); return fromJson(json, SignResponse.class); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); try { clientDataRef = new ClientData(clientDataRaw); } catch (U2fBadInputException e) { throw new IOException(e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/json/JsonSerializable.java ================================================ package com.yubico.u2f.data.messages.json; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public abstract class JsonSerializable { protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @JsonIgnore public String toJson() { try { return OBJECT_MAPPER.writeValueAsString(this); } catch (IOException e) { throw new IllegalArgumentException(e); } } @Override public String toString() { return toJson(); } public static T fromJson(String json, Class cls) throws U2fBadInputException { try { return OBJECT_MAPPER.readValue(json, cls); } catch (JsonMappingException e) { throw new U2fBadInputException("Invalid JSON data", e); } catch (JsonParseException e) { throw new U2fBadInputException("Invalid JSON data", e); } catch (IOException e) { throw new U2fBadInputException("Invalid JSON data", e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/json/Persistable.java ================================================ package com.yubico.u2f.data.messages.json; import com.fasterxml.jackson.annotation.JsonIgnore; import java.io.Serializable; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public interface Persistable extends Serializable { @JsonIgnore public String getRequestId(); public String toJson(); } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/key/RawRegisterResponse.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages.key; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.key.util.ByteInputStream; import com.yubico.u2f.data.messages.key.util.CertificateParser; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import lombok.EqualsAndHashCode; /** * The register response produced by the token/key, which is transformed by the client into an RegisterResponse * and sent to the server. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode public class RawRegisterResponse { public static final byte REGISTRATION_RESERVED_BYTE_VALUE = (byte) 0x05; public static final byte REGISTRATION_SIGNED_RESERVED_BYTE_VALUE = (byte) 0x00; private transient final Crypto crypto; /** * The (uncompressed) x,y-representation of a curve point on the P-256 * NIST elliptic curve. */ final byte[] userPublicKey; /** * A handle that allows the U2F token to identify the generated key pair. */ final byte[] keyHandle; final X509Certificate attestationCertificate; /** * A ECDSA signature (on P-256) */ final byte[] signature; public RawRegisterResponse(byte[] userPublicKey, byte[] keyHandle, X509Certificate attestationCertificate, byte[] signature) { this(userPublicKey, keyHandle, attestationCertificate, signature, new BouncyCastleCrypto()); } public RawRegisterResponse(byte[] userPublicKey, byte[] keyHandle, X509Certificate attestationCertificate, byte[] signature, Crypto crypto) { this.userPublicKey = userPublicKey; this.keyHandle = keyHandle; this.attestationCertificate = attestationCertificate; this.signature = signature; this.crypto = crypto; } public static RawRegisterResponse fromBase64(String rawDataBase64, Crypto crypto) throws U2fBadInputException { ByteInputStream bytes = new ByteInputStream(U2fB64Encoding.decode(rawDataBase64)); try { byte reservedByte = bytes.readSigned(); if (reservedByte != REGISTRATION_RESERVED_BYTE_VALUE) { throw new U2fBadInputException( "Incorrect value of reserved byte. Expected: " + REGISTRATION_RESERVED_BYTE_VALUE + ". Was: " + reservedByte ); } return new RawRegisterResponse( bytes.read(65), bytes.read(bytes.readUnsigned()), CertificateParser.parseDer(bytes), bytes.readAll(), crypto ); } catch (CertificateException e) { throw new U2fBadInputException("Malformed attestation certificate", e); } catch (IOException e) { throw new U2fBadInputException("Truncated registration data", e); } } public void checkSignature(String appId, String clientData) throws U2fBadInputException { byte[] signedBytes = packBytesToSign(crypto.hash(appId), crypto.hash(clientData), keyHandle, userPublicKey); crypto.checkSignature(attestationCertificate, signedBytes, signature); } public static byte[] packBytesToSign(byte[] appIdHash, byte[] clientDataHash, byte[] keyHandle, byte[] userPublicKey) { ByteArrayDataOutput encoded = ByteStreams.newDataOutput(); encoded.write(REGISTRATION_SIGNED_RESERVED_BYTE_VALUE); encoded.write(appIdHash); encoded.write(clientDataHash); encoded.write(keyHandle); encoded.write(userPublicKey); return encoded.toByteArray(); } public DeviceRegistration createDevice() throws U2fBadInputException { return new DeviceRegistration( U2fB64Encoding.encode(keyHandle), U2fB64Encoding.encode(userPublicKey), attestationCertificate, DeviceRegistration.INITIAL_COUNTER_VALUE ); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/key/RawSignResponse.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages.key; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.key.util.ByteInputStream; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fBadInputException; import java.io.IOException; import lombok.EqualsAndHashCode; /** * The sign response produced by the token/key, which is transformed by the client into an * {@link SignResponse} and sent to the server. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @EqualsAndHashCode(of = { "userPresence", "counter", "signature" }) public class RawSignResponse { public static final byte USER_PRESENT_FLAG = 0x01; private final byte userPresence; private final long counter; private final byte[] signature; private final Crypto crypto; public RawSignResponse(byte userPresence, long counter, byte[] signature) { this(userPresence, counter, signature, new BouncyCastleCrypto()); } public RawSignResponse(byte userPresence, long counter, byte[] signature, Crypto crypto) { this.userPresence = userPresence; this.counter = counter; this.signature = signature; this.crypto = crypto; } public static RawSignResponse fromBase64(String rawDataBase64, Crypto crypto) throws U2fBadInputException { ByteInputStream bytes = new ByteInputStream(U2fB64Encoding.decode(rawDataBase64)); try { return new RawSignResponse( bytes.readSigned(), bytes.readInteger(), bytes.readAll(), crypto ); } catch (IOException e) { throw new U2fBadInputException("Truncated authentication data", e); } } public void checkSignature(String appId, String clientData, byte[] publicKey) throws U2fBadInputException { byte[] signedBytes = packBytesToSign( crypto.hash(appId), userPresence, counter, crypto.hash(clientData) ); crypto.checkSignature( crypto.decodePublicKey(publicKey), signedBytes, signature ); } public static byte[] packBytesToSign(byte[] appIdHash, byte userPresence, long counter, byte[] challengeHash) { ByteArrayDataOutput encoded = ByteStreams.newDataOutput(); encoded.write(appIdHash); encoded.write(userPresence); encoded.writeInt((int) counter); encoded.write(challengeHash); return encoded.toByteArray(); } /** * Bit 0 is set to 1, which means that user presence was verified. (This version of the protocol doesn't specify a * way to request sign responses without requiring user presence.) A different value of bit 0, as well as bits 1 * through 7, are reserved for future use. The values of bit 1 through 7 SHOULD be 0 */ public byte getUserPresence() { return userPresence; } /** * This is the big-endian representation of a counter value that the U2F device * increments every time it performs a sign operation. */ public long getCounter() { return counter; } /** * This is a ECDSA signature (on P-256) */ public byte[] getSignature() { return signature; } public void checkUserPresence() throws U2fBadInputException { if (userPresence != USER_PRESENT_FLAG) { throw new U2fBadInputException("User presence invalid during signing"); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/key/util/ByteInputStream.java ================================================ /* * Copyright 2014 Yubico. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. */ package com.yubico.u2f.data.messages.key.util; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; /** * Provides an easy way to read a byte array in chunks. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class ByteInputStream extends DataInputStream { public ByteInputStream(byte[] data) { super(new ByteArrayInputStream(data)); } public byte[] read(int numberOfBytes) throws IOException { byte[] readBytes = new byte[numberOfBytes]; readFully(readBytes); return readBytes; } public byte[] readAll() throws IOException { byte[] readBytes = new byte[available()]; readFully(readBytes); return readBytes; } public int readInteger() throws IOException { return readInt(); } public byte readSigned() throws IOException { return readByte(); } public int readUnsigned() throws IOException { return readUnsignedByte(); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/key/util/CertificateParser.java ================================================ package com.yubico.u2f.data.messages.key.util; import com.google.common.io.BaseEncoding; import com.yubico.u2f.crypto.BouncyCastleCrypto; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringReader; import java.security.Provider; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class CertificateParser { private static final Provider BC_PROVIDER = new BouncyCastleCrypto().getProvider(); private final static List FIXSIG = Arrays.asList( "CN=Yubico U2F EE Serial 776137165", "CN=Yubico U2F EE Serial 1086591525", "CN=Yubico U2F EE Serial 1973679733", "CN=Yubico U2F EE Serial 13503277888", "CN=Yubico U2F EE Serial 13831167861", "CN=Yubico U2F EE Serial 14803321578" ); public static X509Certificate parsePem(String pemEncodedCert) throws CertificateException { return parseDer(pemEncodedCert.replaceAll("-----BEGIN CERTIFICATE-----", "").replaceAll("-----END CERTIFICATE-----", "").replaceAll("\n", "")); } public static X509Certificate parseDer(String base64DerEncodedCert) throws CertificateException { return parseDer(BaseEncoding.base64().decodingStream(new StringReader(base64DerEncodedCert))); } public static X509Certificate parseDer(byte[] derEncodedCert) throws CertificateException { return parseDer(new ByteArrayInputStream(derEncodedCert)); } public static X509Certificate parseDer(InputStream is) throws CertificateException { X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509", BC_PROVIDER).generateCertificate(is); //Some known certs have an incorrect "unused bits" value, which causes problems on newer versions of BouncyCastle. if(FIXSIG.contains(cert.getSubjectDN().getName())) { byte[] encoded = cert.getEncoded(); encoded[encoded.length-257] = 0; // Fix the "unused bits" field (should always be 0). cert = (X509Certificate) CertificateFactory.getInstance("X.509", BC_PROVIDER).generateCertificate(new ByteArrayInputStream(encoded)); } return cert; } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/data/messages/key/util/U2fB64Encoding.java ================================================ package com.yubico.u2f.data.messages.key.util; import com.google.common.io.BaseEncoding; import com.yubico.u2f.exceptions.U2fBadInputException; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fB64Encoding { private final static BaseEncoding BASE64_ENCODER = BaseEncoding.base64Url().omitPadding(); private final static BaseEncoding BASE64_DECODER = BaseEncoding.base64Url(); public static String encode(byte[] decoded) { return BASE64_ENCODER.encode(decoded); } public static byte[] decode(String encoded) throws U2fBadInputException { try { return BASE64_DECODER.decode(encoded); } catch (IllegalArgumentException e) { throw new U2fBadInputException("Bad base64 encoding", e); } } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/DeviceCompromisedException.java ================================================ package com.yubico.u2f.exceptions; import com.yubico.u2f.data.DeviceRegistration; import lombok.Getter; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @Getter public class DeviceCompromisedException extends U2fAuthenticationException { private final DeviceRegistration deviceRegistration; public DeviceCompromisedException(DeviceRegistration deviceRegistration, String message, Throwable cause) { super(message, cause); this.deviceRegistration = deviceRegistration; } public DeviceCompromisedException(DeviceRegistration deviceRegistration, String message) { super(message); this.deviceRegistration = deviceRegistration; } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/InvalidDeviceCounterException.java ================================================ package com.yubico.u2f.exceptions; import com.yubico.u2f.data.DeviceRegistration; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class InvalidDeviceCounterException extends DeviceCompromisedException { public InvalidDeviceCounterException(DeviceRegistration registration) { super(registration, "The device's internal counter was was smaller than expected." + "It's possible that the device has been cloned!"); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/NoEligableDevicesException.java ================================================ package com.yubico.u2f.exceptions; import com.google.common.collect.ImmutableList; import com.yubico.u2f.data.DeviceRegistration; import java.util.List; import lombok.Getter; /** * @deprecated use {@link NoEligibleDevicesException} */ @Deprecated @Getter public class NoEligableDevicesException extends U2fAuthenticationException { private final List devices; public NoEligableDevicesException(Iterable devices, String message, Throwable cause) { super(message, cause); this.devices = ImmutableList.copyOf(devices); } public NoEligableDevicesException(Iterable devices, String message) { super(message); this.devices = ImmutableList.copyOf(devices); } public boolean hasDevices() { return !devices.isEmpty(); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/NoEligibleDevicesException.java ================================================ package com.yubico.u2f.exceptions; import com.yubico.u2f.data.DeviceRegistration; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @SuppressWarnings("deprecation") public class NoEligibleDevicesException extends NoEligableDevicesException { public NoEligibleDevicesException(Iterable devices, String message, Throwable cause) { super(devices, message, cause); } public NoEligibleDevicesException(Iterable devices, String message) { super(devices, message); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/U2fAuthenticationException.java ================================================ package com.yubico.u2f.exceptions; /** * Base class for exceptions thrown when a U2F authentication ceremony fails. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fAuthenticationException extends U2fCeremonyException { public U2fAuthenticationException(String message, Throwable cause) { super(message, cause); } public U2fAuthenticationException(String message) { super(message); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/U2fBadConfigurationException.java ================================================ package com.yubico.u2f.exceptions; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fBadConfigurationException extends Exception { public U2fBadConfigurationException(String message) { super(message); } public U2fBadConfigurationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/U2fBadInputException.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.exceptions; /** * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated @SuppressWarnings("serial") public class U2fBadInputException extends Exception { public U2fBadInputException(String message) { super(message); } public U2fBadInputException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/U2fCeremonyException.java ================================================ package com.yubico.u2f.exceptions; /** * Base class for exceptions thrown when a U2F registration or authentication ceremony fails. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fCeremonyException extends Exception { public U2fCeremonyException(String message, Throwable cause) { super(message, cause); } public U2fCeremonyException(String message) { super(message); } } ================================================ FILE: u2flib-server-core/src/main/java/com/yubico/u2f/exceptions/U2fRegistrationException.java ================================================ package com.yubico.u2f.exceptions; /** * Base class for exceptions thrown when a U2F registration ceremony fails. * * @deprecated The java-u2flib-server library is obsolete. Use java-webauthn-server instead. */ @Deprecated public class U2fRegistrationException extends U2fCeremonyException { public U2fRegistrationException(String message, Throwable cause) { super(message, cause); } public U2fRegistrationException(String message) { super(message); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/AppIdTest.java ================================================ package com.yubico.u2f; import com.yubico.u2f.exceptions.U2fBadConfigurationException; import org.junit.Test; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; public class AppIdTest { @Test public void validUrls() { assertTrue(isValid("https://www.example.com")); assertTrue(isValid("https://internal-server")); assertTrue(isValid("https://åäö.se:8443")); assertTrue(isValid("https://localhost:8443/myAppId.json")); } @Test public void validUris() { assertTrue(isValid("android:apk-key-hash:585215fd5153209a7e246f53286035838a0be227")); assertTrue(isValid("ios:bundle-id:com.example.Example")); } @Test public void disallowHttp() { assertFalse(isValid("http://www.example.com")); } @Test public void disallowSlashAsPath() { assertFalse(isValid("https://www.example.com/")); } @Test public void disallowIP() { assertFalse(isValid("https://127.0.0.1:8443")); assertFalse(isValid("https://127.0.0.1")); assertFalse(isValid("https://127.0.0.1/foo")); assertFalse(isValid("https://2001:0db8:0000:0000:0000:ff00:0042:8329")); assertFalse(isValid("https://2001:0db8:0000:0000:0000:ff00:0042:8329/åäö")); } @Test public void badSyntax() { assertFalse(isValid("https://bad[syntax]")); } private static boolean isValid(String appId) { try { AppId.checkIsValid(appId); return true; } catch (U2fBadConfigurationException e) { return false; } } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/SystemTest.java ================================================ package com.yubico.u2f; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.RegisterRequest; import com.yubico.u2f.data.messages.RegisterResponse; import org.junit.Ignore; import java.util.Scanner; @Ignore("Includes manual steps") public class SystemTest { public static final ImmutableSet TRUSTED_DOMAINS = ImmutableSet.of("http://example.com"); public static final String APP_ID = "my-app"; private static Scanner scan = new Scanner(System.in); private static final U2fPrimitives u2f = new U2fPrimitives(); /* For manual testing with physical keys. Can e.g. be combined with these libu2f-host commands: u2f-host -aregister -o http://example.com u2f-host -aauthenticate -o http://example.com */ public static void main(String... args) throws Exception { String startedRegistration = u2f.startRegistration(APP_ID).toJson(); System.out.println("Registration data:"); System.out.println(startedRegistration); System.out.println(); System.out.println("Enter token response:"); String json = scan.nextLine(); RegisterResponse registerResponse = RegisterResponse.fromJson(json); registerResponse.getClientData().getChallenge(); DeviceRegistration deviceRegistration = u2f.finishRegistration( RegisterRequest.fromJson(startedRegistration), registerResponse, TRUSTED_DOMAINS ); System.out.println(deviceRegistration); String startedSignature = u2f.startSignature(APP_ID, deviceRegistration).toJson(); System.out.println("Signature data:"); System.out.println(startedSignature); System.out.println(); System.out.println("Enter token response:"); u2f.finishSignature( SignRequest.fromJson(startedSignature), SignResponse.fromJson(scan.nextLine()), deviceRegistration, TRUSTED_DOMAINS ); System.out.println("Device counter: " + deviceRegistration.getCounter()); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/TestUtils.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f; import com.google.common.io.BaseEncoding; import com.google.common.io.ByteArrayDataOutput; import com.yubico.u2f.data.messages.key.util.CertificateParser; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import java.io.*; import java.math.BigInteger; import java.security.*; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.util.Scanner; public class TestUtils { static { Security.addProvider(new BouncyCastleProvider()); } public static final BaseEncoding HEX = BaseEncoding.base16().lowerCase(); public static final BaseEncoding BASE64 = BaseEncoding.base64(); public static X509Certificate fetchCertificate(InputStream resourceAsStream) { Scanner in = new Scanner(resourceAsStream); String base64String = in.nextLine(); return parseCertificate(BASE64.decode(base64String)); } public static X509Certificate parseCertificate(byte[] encodedDerCertificate) { try { return CertificateParser.parseDer(encodedDerCertificate); } catch (CertificateException e) { throw new RuntimeException(e); } } public static PrivateKey parsePrivateKey(InputStream is) { String keyBytesHex = new Scanner(is).nextLine(); return parsePrivateKey(keyBytesHex); } public static PrivateKey parsePrivateKey(String keyBytesHex) { try { KeyFactory fac = KeyFactory.getInstance("ECDSA"); X9ECParameters curve = SECNamedCurves.getByName("secp256r1"); ECParameterSpec curveSpec = new ECParameterSpec( curve.getCurve(), curve.getG(), curve.getN(), curve.getH()); ECPrivateKeySpec keySpec = new ECPrivateKeySpec( new BigInteger(keyBytesHex, 16), curveSpec); return fac.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new RuntimeException(e); } } public static PublicKey parsePublicKey(byte[] keyBytes) { try { X9ECParameters curve = SECNamedCurves.getByName("secp256r1"); ECParameterSpec curveSpec = new ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), curve.getH()); ECPoint point = curve.getCurve().decodePoint(keyBytes); return KeyFactory.getInstance("ECDSA").generatePublic( new ECPublicKeySpec(point, curveSpec)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new RuntimeException(e); } } public static byte[] serialize(Object o) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objectOut = new ObjectOutputStream(out); objectOut.writeObject(o); objectOut.close(); return out.toByteArray(); } public static T deserialize(byte[] serialized) throws IOException, ClassNotFoundException { ByteArrayInputStream is = new ByteArrayInputStream(serialized); ObjectInputStream objectIn = new ObjectInputStream(is); T object = (T) objectIn.readObject(); objectIn.close(); return object; } public static T clone(T input) throws IOException, ClassNotFoundException { return deserialize(serialize(input)); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/U2FTest.java ================================================ package com.yubico.u2f; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignRequestData; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.RegisterRequest; import com.yubico.u2f.data.messages.RegisterRequestData; import com.yubico.u2f.data.messages.RegisterResponse; import com.yubico.u2f.data.messages.RegisteredKey; import com.yubico.u2f.exceptions.DeviceCompromisedException; import com.yubico.u2f.exceptions.NoEligibleDevicesException; import com.yubico.u2f.exceptions.U2fBadConfigurationException; import com.yubico.u2f.exceptions.U2fBadInputException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static com.yubico.u2f.testdata.GnubbyKey.ATTESTATION_CERTIFICATE; import static com.yubico.u2f.testdata.TestVectors.*; import static org.hamcrest.core.Is.isA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class U2FTest { U2F u2f = U2F.withoutAppIdValidation(); @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void startRegistration_compromisedDevice() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); deviceRegistration.markCompromised(); u2f.startRegistration(APP_ID_ENROLL, ImmutableList.of(deviceRegistration)); } @Test(expected = NoEligibleDevicesException.class) public void startSignature_compromisedDevices() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); deviceRegistration.markCompromised(); u2f.startSignature(APP_ID_ENROLL, ImmutableList.of(deviceRegistration)); } @Test(expected = U2fBadConfigurationException.class) public void defaultConstructedU2FstartRegistrationShouldRefuseInvalidAppId() throws U2fBadInputException, U2fBadConfigurationException { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); deviceRegistration.markCompromised(); new U2F().startRegistration("example.com", ImmutableList.of(deviceRegistration)); fail("startRegistration did not refuse an invalid app ID."); } @Test public void startRegistrationShouldReturnARandomChallenge() throws U2fBadInputException, U2fBadConfigurationException { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); RegisterRequestData data = u2f.startRegistration("example.com", ImmutableList.of(deviceRegistration)); RegisterRequestData data2 = u2f.startRegistration("example.com", ImmutableList.of(deviceRegistration)); assertEquals(1, data.getRegisterRequests().size()); assertEquals(1, data2.getRegisterRequests().size()); assertNotEquals( "startRegistration must not return the same challenge twice in a row.", data.getRegisterRequests().get(0).getChallenge(), data2.getRegisterRequests().get(0).getChallenge() ); } @Test(expected = U2fBadConfigurationException.class) public void defaultConstructedU2FstartSignatureShouldRefuseInvalidAppId() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); deviceRegistration.markCompromised(); new U2F().startSignature("example.com", ImmutableList.of(deviceRegistration)); fail("startRegistration did not refuse an invalid app ID."); } @Test public void startSignatureShouldReturnARandomChallenge() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); SignRequestData data = u2f.startSignature("example.com", ImmutableList.of(deviceRegistration)); SignRequestData data2 = u2f.startSignature("example.com", ImmutableList.of(deviceRegistration)); assertEquals(1, data.getSignRequests().size()); assertNotNull(data.getSignRequests().get(0).getChallenge()); assertNotEquals( "startSignature must not return the same challenge twice in a row.", data.getSignRequests().get(0).getChallenge(), data2.getSignRequests().get(0).getChallenge() ); } @Test(expected = DeviceCompromisedException.class) public void finishSignature_compromisedDevice() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); SignRequest request = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64); SignRequestData requestData = mock(SignRequestData.class); when(requestData.getSignRequest(tokenResponse)).thenReturn(request); deviceRegistration.markCompromised(); u2f.finishSignature(requestData, tokenResponse, ImmutableList.of(deviceRegistration)); } @Test public void finishSignature_invalidFacet() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); SignRequest request = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64); SignRequestData requestData = mock(SignRequestData.class); when(requestData.getSignRequest(tokenResponse)).thenReturn(request); u2f.finishSignature(requestData, tokenResponse, ImmutableList.of(deviceRegistration), ImmutableSet.of("https://wrongfacet.com")); } @Test public void finishRegistrationShouldReturnAMatchedDevice() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); DeviceRegistration deviceRegistration2 = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); RegisterRequest request = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); RegisterResponse tokenResponse = new RegisterResponse( REGISTRATION_DATA_BASE64, CLIENT_DATA_REGISTRATION_BASE64 ); RegisterRequestData registerRequest = new RegisterRequestData( APP_ID_ENROLL, ImmutableList.of(), ImmutableList.of(request) ); DeviceRegistration device = u2f.finishRegistration(registerRequest, tokenResponse, ImmutableSet.of(APP_ID_ENROLL)); DeviceRegistration overloadDevice = u2f.finishRegistration(registerRequest, tokenResponse); assertEquals(KEY_HANDLE_BASE64, device.getKeyHandle()); assertEquals(device, overloadDevice); } @Test public void finishSignatureShouldReturnAMatchedDevice() throws Exception { DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); DeviceRegistration deviceRegistration2 = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); SignRequest request = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64); SignRequestData requestData = new SignRequestData( APP_ID_SIGN, SERVER_CHALLENGE_SIGN_BASE64, ImmutableList.of(request) ); DeviceRegistration device = u2f.finishSignature(requestData, tokenResponse, ImmutableList.of(deviceRegistration), ImmutableSet.of(APP_ID_ENROLL)); DeviceRegistration overloadDevice = u2f.finishSignature(requestData, tokenResponse, ImmutableList.of(deviceRegistration2)); assertEquals(KEY_HANDLE_BASE64, device.getKeyHandle()); assertEquals(device, overloadDevice); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/U2fPrimitivesTest.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.RegisterRequest; import com.yubico.u2f.data.messages.RegisterResponse; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fAuthenticationException; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.testdata.AcmeKey; import com.yubico.u2f.testdata.TestVectors; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import java.util.HashSet; import java.util.Set; import org.junit.rules.ExpectedException; import static com.yubico.u2f.testdata.GnubbyKey.ATTESTATION_CERTIFICATE; import static com.yubico.u2f.testdata.TestVectors.*; import static org.hamcrest.core.Is.isA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class U2fPrimitivesTest { final HashSet allowedOrigins = new HashSet(); U2fPrimitives u2f = new U2fPrimitives(); @Rule public ExpectedException expectedException = ExpectedException.none(); @Before public void setup() throws Exception { allowedOrigins.add("http://example.com"); } @Test public void finishRegistration() throws Exception { RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); DeviceRegistration response = u2f.finishRegistration(registerRequest, new RegisterResponse(TestVectors.REGISTRATION_DATA_BASE64, CLIENT_DATA_REGISTRATION_BASE64), TRUSTED_DOMAINS); assertEquals(KEY_HANDLE_BASE64, response.getKeyHandle()); } @Test public void finishRegistration2() throws Exception { RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); DeviceRegistration deviceRegistration = u2f.finishRegistration(registerRequest, new RegisterResponse(AcmeKey.REGISTRATION_DATA_BASE64, AcmeKey.CLIENT_DATA_BASE64), TRUSTED_DOMAINS); assertEquals(new DeviceRegistration(AcmeKey.KEY_HANDLE, AcmeKey.USER_PUBLIC_KEY_B64, AcmeKey.ATTESTATION_CERTIFICATE, 0), deviceRegistration); } @Test public void finishRegistrationWithoutAllowedAppIds() throws Exception { RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); DeviceRegistration response = u2f.finishRegistration( registerRequest, new RegisterResponse( TestVectors.REGISTRATION_DATA_BASE64, CLIENT_DATA_REGISTRATION_BASE64 ) ); assertEquals(KEY_HANDLE_BASE64, response.getKeyHandle()); } @Test public void finishRegistrationShouldDetectIncorrectAppId() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); DeviceRegistration response = u2f.finishRegistration( registerRequest, new RegisterResponse( TestVectors.REGISTRATION_DATA_WITH_DIFFERENT_APP_ID_BASE64, CLIENT_DATA_REGISTRATION_BASE64 ) ); fail("finishRegistration did not detect incorrect app ID"); } @Test public void finishRegistrationShouldDetectIncorrectChallenge() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); String clientDataBase64 = U2fB64Encoding.encode("{\"typ\":\"navigator.id.finishEnrollment\",\"challenge\":\"ARGHABLARGHLER\",\"origin\":\"http://example.com\"}".getBytes("UTF-8")); u2f.finishRegistration( registerRequest, new RegisterResponse( TestVectors.REGISTRATION_DATA_BASE64, clientDataBase64 ) ); fail("finishRegistration did not detect incorrect challenge"); } @Test public void finishRegistrationShouldDetectIncorrectClientDataType() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); String clientDataBase64 = U2fB64Encoding.encode("{\"typ\":\"navigator.id.launchNukes\",\"challenge\":\"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo\",\"origin\":\"http://example.com\"}".getBytes("UTF-8")); u2f.finishRegistration( registerRequest, new RegisterResponse( TestVectors.REGISTRATION_DATA_WITH_DIFFERENT_CLIENT_DATA_TYPE_BASE64, clientDataBase64 ) ); fail("finishRegistration did not detect incorrect type in client data"); } @Test public void finishRegistrationShouldDetectIncorrectClientDataOrigin() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); String clientDataBase64 = U2fB64Encoding.encode("{\"typ\":\"navigator.id.finishEnrollment\",\"challenge\":\"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo\",\"origin\":\"http://evil.com\"}".getBytes("UTF-8")); u2f.finishRegistration( registerRequest, new RegisterResponse( TestVectors.REGISTRATION_DATA_BASE64, clientDataBase64 ) ); fail("finishRegistration did not detect incorrect origin in client data"); } @Test public void finishSignature() throws Exception { SignRequest signRequest = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64); u2f.finishSignature(signRequest, tokenResponse, new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0), allowedOrigins); } @Test(expected = U2fAuthenticationException.class) public void finishSignature_badOrigin() throws Exception { Set allowedOrigins = ImmutableSet.of("some-other-domain.com"); SignRequest request = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse response = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, SERVER_CHALLENGE_SIGN_BASE64); u2f.finishSignature(request, response, new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0), allowedOrigins); } @Test(expected = U2fBadInputException.class) public void finishAuthentication_badBase64() throws Exception { SignRequest authentication = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse response = new SignResponse("****", "****", "****"); u2f.finishSignature(authentication, response, new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0)); } @Test(expected = U2fBadInputException.class) public void finishAuthentication_clientDataMissingField() throws Exception { SignRequest authentication = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse response = new SignResponse(U2fB64Encoding.encode("{}".getBytes()), "", ""); u2f.finishSignature(authentication, response, new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0)); } @Test public void finishAuthentication_truncatedData() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); SignRequest authentication = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse response = new SignResponse(CLIENT_DATA_SIGN_BASE64, "", KEY_HANDLE_BASE64); u2f.finishSignature(authentication, response, new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0)); } @Test(expected = IllegalArgumentException.class) public void startSignature_compromisedDevice() throws Exception { RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); DeviceRegistration deviceRegistration = u2f.finishRegistration(registerRequest, new RegisterResponse(AcmeKey.REGISTRATION_DATA_BASE64, AcmeKey.CLIENT_DATA_BASE64), TRUSTED_DOMAINS); deviceRegistration.markCompromised(); u2f.startSignature(APP_ID_ENROLL, deviceRegistration); } @Test(expected = IllegalArgumentException.class) public void finishSignature_compromisedDevice() throws Exception { SignRequest signRequest = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64); DeviceRegistration deviceRegistration = new DeviceRegistration(KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0); deviceRegistration.markCompromised(); u2f.finishSignature(signRequest, tokenResponse, deviceRegistration, allowedOrigins); } @Test public void finishSignatureShouldDetectInvalidUserPresence() throws Exception { expectedException.expectCause(isA(U2fBadInputException.class)); SignRequest signRequest = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse( CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_INVALID_USER_PRESENCE_BASE64, KEY_HANDLE_BASE64 ); u2f.finishSignature( signRequest, tokenResponse, new DeviceRegistration( KEY_HANDLE_BASE64, USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0 ), allowedOrigins ); fail("finishSignature did not detect a non-0x01 user presence byte in the sign response."); } @Test(expected = IllegalArgumentException.class) public void finishSignatureShouldDetectIncorrectDeviceRegistration() throws Exception { SignRequest signRequest = SignRequest.builder() .challenge(SERVER_CHALLENGE_SIGN_BASE64) .appId(APP_ID_SIGN) .keyHandle(KEY_HANDLE_BASE64) .build(); SignResponse tokenResponse = new SignResponse( CLIENT_DATA_SIGN_BASE64, SIGN_RESPONSE_DATA_BASE64, KEY_HANDLE_BASE64 ); u2f.finishSignature( signRequest, tokenResponse, new DeviceRegistration( "ARGHABLARGHLER", USER_PUBLIC_KEY_SIGN_HEX, ATTESTATION_CERTIFICATE, 0 ), allowedOrigins ); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/codec/RawCodecTest.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.codec; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.data.messages.key.RawSignResponse; import com.yubico.u2f.data.messages.key.RawRegisterResponse; import com.yubico.u2f.testdata.TestVectors; import org.junit.Test; import static com.yubico.u2f.data.messages.key.CodecTestUtils.encodeSignResponse; import static com.yubico.u2f.data.messages.key.CodecTestUtils.encodeRegisterResponse; import static com.yubico.u2f.testdata.GnubbyKey.ATTESTATION_CERTIFICATE; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class RawCodecTest { Crypto crypto = new BouncyCastleCrypto(); @Test public void testEncodeRegisterResponse() throws Exception { RawRegisterResponse rawRegisterResponse = new RawRegisterResponse(USER_PUBLIC_KEY_REGISTER_HEX, KEY_HANDLE, ATTESTATION_CERTIFICATE, SIGNATURE_REGISTER); byte[] encodedBytes = encodeRegisterResponse(rawRegisterResponse); assertArrayEquals(TestVectors.REGISTRATION_RESPONSE_DATA, encodedBytes); } @Test public void testEncodeRegisterSignedBytes() throws Exception { byte[] encodedBytes = RawRegisterResponse.packBytesToSign(APP_ID_ENROLL_SHA256, CLIENT_DATA_ENROLL_SHA256, KEY_HANDLE, USER_PUBLIC_KEY_REGISTER_HEX); assertArrayEquals(EXPECTED_REGISTER_SIGNED_BYTES, encodedBytes); } @Test public void testDecodeRegisterResponse() throws Exception { RawRegisterResponse rawRegisterResponse = RawRegisterResponse.fromBase64(TestVectors.REGISTRATION_DATA_BASE64, crypto); assertEquals(new RawRegisterResponse(USER_PUBLIC_KEY_REGISTER_HEX, KEY_HANDLE, ATTESTATION_CERTIFICATE, SIGNATURE_REGISTER), rawRegisterResponse); } @Test public void testEncodeSignResponse() throws Exception { RawSignResponse rawSignResponse = new RawSignResponse( RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, SIGNATURE_SIGN); byte[] encodedBytes = encodeSignResponse(rawSignResponse); assertArrayEquals(SIGN_RESPONSE_DATA, encodedBytes); } @Test public void testDecodeSignResponse() throws Exception { RawSignResponse rawSignResponse = RawSignResponse.fromBase64(SIGN_RESPONSE_DATA_BASE64, crypto); assertEquals(new RawSignResponse(RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, SIGNATURE_SIGN), rawSignResponse); } @Test public void testEncodeSignedBytes() throws Exception { byte[] encodedBytes = RawSignResponse.packBytesToSign(APP_ID_SIGN_SHA256, RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, CLIENT_DATA_SIGN_SHA256); assertArrayEquals(EXPECTED_SIGN_SIGNED_BYTES, encodedBytes); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/codec/SerialCodecTest.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.codec; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.data.messages.key.CodecTestUtils; import com.yubico.u2f.data.messages.key.RawSignResponse; import com.yubico.u2f.data.messages.key.RawRegisterResponse; import com.yubico.u2f.testdata.TestVectors; import org.junit.Test; import static com.yubico.u2f.testdata.GnubbyKey.ATTESTATION_CERTIFICATE; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class SerialCodecTest { private static final Crypto crypto = new BouncyCastleCrypto(); @Test public void testEncodeRegisterResponse() throws Exception { RawRegisterResponse rawRegisterResponse = new RawRegisterResponse(USER_PUBLIC_KEY_REGISTER_HEX, KEY_HANDLE, ATTESTATION_CERTIFICATE, SIGNATURE_REGISTER); byte[] encodedBytes = CodecTestUtils.encodeRegisterResponse(rawRegisterResponse); assertArrayEquals(TestVectors.REGISTRATION_RESPONSE_DATA, encodedBytes); } @Test public void testEncodeRegisterSignedBytes() throws Exception { byte[] encodedBytes = RawRegisterResponse.packBytesToSign(APP_ID_ENROLL_SHA256, CLIENT_DATA_ENROLL_SHA256, KEY_HANDLE, USER_PUBLIC_KEY_REGISTER_HEX); assertArrayEquals(EXPECTED_REGISTER_SIGNED_BYTES, encodedBytes); } @Test public void testDecodeRegisterResponse() throws Exception { RawRegisterResponse rawRegisterResponse = RawRegisterResponse.fromBase64(TestVectors.REGISTRATION_DATA_BASE64, crypto); assertEquals(new RawRegisterResponse(USER_PUBLIC_KEY_REGISTER_HEX, KEY_HANDLE, ATTESTATION_CERTIFICATE, SIGNATURE_REGISTER), rawRegisterResponse); } @Test public void testEncodeSignResponse() throws Exception { RawSignResponse rawSignResponse = new RawSignResponse( RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, SIGNATURE_SIGN); byte[] encodedBytes = CodecTestUtils.encodeSignResponse(rawSignResponse); assertArrayEquals(SIGN_RESPONSE_DATA, encodedBytes); } @Test public void testDecodeSignResponse() throws Exception { RawSignResponse rawSignResponse = RawSignResponse.fromBase64(SIGN_RESPONSE_DATA_BASE64, crypto); assertEquals(new RawSignResponse(RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, SIGNATURE_SIGN), rawSignResponse); } @Test public void testEncodeSignedBytes() throws Exception { byte[] encodedBytes = RawSignResponse.packBytesToSign(APP_ID_SIGN_SHA256, RawSignResponse.USER_PRESENT_FLAG, COUNTER_VALUE, CLIENT_DATA_SIGN_SHA256); assertArrayEquals(EXPECTED_SIGN_SIGNED_BYTES, encodedBytes); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/DeviceRegistrationTest.java ================================================ package com.yubico.u2f.data; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.yubico.u2f.data.messages.key.Client; import com.yubico.u2f.exceptions.InvalidDeviceCounterException; import com.yubico.u2f.softkey.SoftKey; import org.junit.Test; import static junit.framework.Assert.fail; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class DeviceRegistrationTest { @Test public void shouldSerialize() throws Exception { DeviceRegistration deviceRegistration = getDeviceRegistration(); String json = deviceRegistration.toJson(); DeviceRegistration deserializedDeviceRegistration = DeviceRegistration.fromJson(json); assertEquals(deviceRegistration.getKeyHandle(), deserializedDeviceRegistration.getKeyHandle()); assertEquals(deviceRegistration.getPublicKey(), deserializedDeviceRegistration.getPublicKey()); assertEquals(deviceRegistration.getCounter(), deserializedDeviceRegistration.getCounter()); assertNotEquals(deviceRegistration, deserializedDeviceRegistration); // Cert should be missing } @Test public void shouldSerializeWithAttestationCertificate() throws Exception { DeviceRegistration deviceRegistration = getDeviceRegistration(); String json = deviceRegistration.toJsonWithAttestationCert(); DeviceRegistration deserializedDeviceRegistration = DeviceRegistration.fromJson(json); assertEquals(deviceRegistration, deserializedDeviceRegistration); } @Test public void serializationRoundTripWithJackson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(DeserializationFeature.UNWRAP_ROOT_VALUE); objectMapper.disable(SerializationFeature.WRAP_ROOT_VALUE); DeviceRegistration input = getDeviceRegistration(); String json = objectMapper.writeValueAsString(input); DeviceRegistration output = objectMapper.readValue(json, DeviceRegistration.class); assertEquals(input, output); } @Test public void shouldAcceptValidCounters() throws Exception { DeviceRegistration deviceRegistration = getDeviceRegistration(); deviceRegistration.checkAndUpdateCounter(3); deviceRegistration.checkAndUpdateCounter(10); deviceRegistration.checkAndUpdateCounter(97); assertFalse(deviceRegistration.isCompromised()); } @Test public void shouldDetectInvalidCounters() throws Exception { DeviceRegistration deviceRegistration = getDeviceRegistration(); deviceRegistration.checkAndUpdateCounter(9); try { deviceRegistration.checkAndUpdateCounter(9); fail(); } catch (InvalidDeviceCounterException e) { assertTrue(deviceRegistration.isCompromised()); } } @Test public void equalsAndHashCodeIgnoreCounter() { DeviceRegistration dr1 = new DeviceRegistration("A", "B", "C", 0, false); DeviceRegistration dr2 = new DeviceRegistration("A", "B", "C", 1, false); assertEquals(dr1, dr2); assertEquals(dr1.hashCode(), dr2.hashCode()); } @Test public void equalsAndHashCodeIgnoreCompromisedFlag() { DeviceRegistration dr1 = new DeviceRegistration("A", "B", "C", 0, false); DeviceRegistration dr2 = new DeviceRegistration("A", "B", "C", 1, true); assertEquals(dr1, dr2); assertEquals(dr1.hashCode(), dr2.hashCode()); } @Test public void equalsAndHashCodeDoNotIgnoreKeyHandle() { DeviceRegistration dr1 = new DeviceRegistration("A", "B", "C", 0, false); DeviceRegistration dr2 = new DeviceRegistration("D", "B", "C", 0, false); assertNotEquals(dr1, dr2); assertNotEquals(dr1.hashCode(), dr2.hashCode()); } @Test public void equalsAndHashCodeDoNotIgnorePublicKey() { DeviceRegistration dr1 = new DeviceRegistration("A", "B", "C", 0, false); DeviceRegistration dr2 = new DeviceRegistration("A", "D", "C", 0, false); assertNotEquals(dr1, dr2); assertNotEquals(dr1.hashCode(), dr2.hashCode()); } @Test public void equalsAndHashCodeDoNotIgnoreAttestationCert() { DeviceRegistration dr1 = new DeviceRegistration("A", "B", "C", 0, false); DeviceRegistration dr2 = new DeviceRegistration("A", "B", "D", 0, false); assertNotEquals(dr1, dr2); assertNotEquals(dr1.hashCode(), dr2.hashCode()); } @Test public void toStringDoesNotReturnNull() { assertNotNull(new DeviceRegistration("A", "B", null, 0, false).toString()); } private DeviceRegistration getDeviceRegistration() throws Exception { Client client = new Client(new SoftKey()); return client.register(); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/ClientDataTest.java ================================================ package com.yubico.u2f.data.messages; import com.google.common.base.Optional; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.exceptions.U2fBadInputException; import org.junit.Test; import java.util.Set; import static com.yubico.u2f.data.messages.ClientData.canonicalizeOrigin; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertEquals; public class ClientDataTest { @Test public void shouldCanonicalizeOrigin() throws U2fBadInputException { assertEquals("http://example.com", canonicalizeOrigin("http://example.com")); assertEquals("http://example.com", canonicalizeOrigin("http://example.com/")); assertEquals("http://example.com", canonicalizeOrigin("http://example.com/foo")); assertEquals("http://example.com", canonicalizeOrigin("http://example.com/foo?bar=b")); assertEquals("http://example.com", canonicalizeOrigin("http://example.com/foo#fragment")); assertEquals("https://example.com", canonicalizeOrigin("https://example.com")); assertEquals("https://example.com", canonicalizeOrigin("https://example.com/foo")); assertEquals("android:apk-key-hash:2jmj7l5rSw0yVb/vlWAYkK/YBwk", canonicalizeOrigin("android:apk-key-hash:2jmj7l5rSw0yVb/vlWAYkK/YBwk")); } @Test public void shouldCheckContent() throws U2fBadInputException { ClientData clientData = new ClientData(CLIENT_DATA_REGISTRATION_BASE64); clientData.checkContent("navigator.id.finishEnrollment", SERVER_CHALLENGE_REGISTER_BASE64, Optional.>of(ImmutableSet.of(APP_ID_ENROLL))); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/RegisterRequestDataTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.yubico.u2f.TestUtils; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.crypto.RandomChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.APP_ID_ENROLL; import static com.yubico.u2f.testdata.TestVectors.SERVER_CHALLENGE_REGISTER_BASE64; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class RegisterRequestDataTest { public static final String KEY_HANDLE = "KlUt_bdHftZf2EEz-GGWAQsiFbV9p10xW3uej-LjklpgGVUbq2HRZZFlnLrwC0lQ96v-ZmDi4Ab3aGi3ctcMJQ"; public static final String JSON = "{\"registeredKeys\":[{\"keyHandle\":\"" + KEY_HANDLE + "\",\"version\":\"U2F_V2\"}],\"registerRequests\":[{\"challenge\":\"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo\",\"appId\":\"http://example.com\",\"version\":\"U2F_V2\"}]}"; @Test public void testGetters() throws Exception { DeviceRegistration device = mock(DeviceRegistration.class); when(device.getKeyHandle()).thenReturn(KEY_HANDLE); U2fPrimitives primitives = mock(U2fPrimitives.class); ChallengeGenerator challengeGenerator = mock(ChallengeGenerator.class); byte[] challenge = U2fB64Encoding.decode(SERVER_CHALLENGE_REGISTER_BASE64); when(challengeGenerator.generateChallenge()).thenReturn(challenge); SignRequest signRequest = SignRequest.fromJson(SignRequestTest.JSON); when(primitives.startSignature(APP_ID_ENROLL, device)).thenReturn(signRequest); RegisterRequest registerRequest = RegisterRequest.fromJson(RegisterRequestTest.JSON); when(primitives.startRegistration(APP_ID_ENROLL, challenge)).thenReturn(registerRequest); RegisterRequestData requestData = new RegisterRequestData(APP_ID_ENROLL, ImmutableList.of(device), primitives, challengeGenerator); assertEquals(SERVER_CHALLENGE_REGISTER_BASE64, requestData.getRequestId()); RegisterRequest registerRequest2 = Iterables.getOnlyElement(requestData.getRegisterRequests()); assertEquals(registerRequest, registerRequest2); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); RegisterRequestData requestData = RegisterRequestData.fromJson(JSON); RegisterRequestData requestData2 = objectMapper.readValue(requestData.toJson(), RegisterRequestData.class); assertEquals(requestData, requestData2); assertEquals(requestData.getRequestId(), requestData2.getRequestId()); assertEquals(requestData.toJson(), objectMapper.writeValueAsString(requestData)); assertEquals(KEY_HANDLE, requestData.getRegisteredKeys().get(0).getKeyHandle()); } @Test public void testJavaSerializer() throws Exception { RegisterRequestData requestData = RegisterRequestData.fromJson(JSON); RegisterRequestData requestData2 = TestUtils.clone(requestData); assertEquals(requestData, requestData2); assertEquals(requestData.getRequestId(), requestData2.getRequestId()); } private DeviceRegistration mockDevice(final String keyHandle, boolean compromised) { DeviceRegistration device = mock(DeviceRegistration.class); when(device.getKeyHandle()).thenReturn(keyHandle); when(device.isCompromised()).thenReturn(compromised); return device; } @Test public void testConstructorAddsOneRegisteredKeyForEachGivenNonCompromisedDeviceRegistration() { DeviceRegistration good1 = mockDevice("A", false); DeviceRegistration good2 = mockDevice("B", false); DeviceRegistration bad1 = mockDevice("C", true); DeviceRegistration bad2 = mockDevice("D", true); RegisterRequestData result = new RegisterRequestData( "AppId", ImmutableList.of(good1, bad1, bad2, good2), new U2fPrimitives(), new RandomChallengeGenerator() ); assertEquals(2, result.getRegisteredKeys().size()); assertTrue(result.getRegisteredKeys().contains(new RegisteredKey(U2fPrimitives.U2F_VERSION, "A", null, null))); assertTrue(result.getRegisteredKeys().contains(new RegisteredKey(U2fPrimitives.U2F_VERSION, "B", null, null))); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/RegisterRequestTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.TestUtils; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.APP_ID_ENROLL; import static com.yubico.u2f.testdata.TestVectors.SERVER_CHALLENGE_REGISTER_BASE64; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class RegisterRequestTest { public static final String JSON = "{\"challenge\":\"vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo\",\"appId\":\"http://example.com\",\"version\":\"U2F_V2\"}"; @Test public void testGetters() throws Exception { RegisterRequest registerRequest = new RegisterRequest(SERVER_CHALLENGE_REGISTER_BASE64, APP_ID_ENROLL); assertEquals(SERVER_CHALLENGE_REGISTER_BASE64, registerRequest.getChallenge()); assertEquals(APP_ID_ENROLL, registerRequest.getAppId()); assertNotNull(SERVER_CHALLENGE_REGISTER_BASE64, registerRequest.getRequestId()); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); RegisterRequest registerRequest = RegisterRequest.fromJson(JSON); RegisterRequest registerRequest2 = objectMapper.readValue(registerRequest.toJson(), RegisterRequest.class); assertEquals(registerRequest.getRequestId(), registerRequest2.getRequestId()); assertEquals(registerRequest.getChallenge(), registerRequest2.getChallenge()); assertEquals(registerRequest.getAppId(), registerRequest2.getAppId()); assertEquals(registerRequest.toJson(), objectMapper.writeValueAsString(registerRequest)); } @Test public void testJavaSerializer() throws Exception { RegisterRequest registerRequest = RegisterRequest.fromJson(JSON); RegisterRequest registerRequest2 = TestUtils.clone(registerRequest); assertEquals(registerRequest, registerRequest2); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/RegisterResponseTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.TestUtils; import com.yubico.u2f.exceptions.U2fBadInputException; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; public class RegisterResponseTest { public static final String JSON = "{\"registrationData\":\"BQSxdLxJx8olS3DS5cIHzunPF0gg69d-o8ZVCMJtpRtlfBzGuVL4YhaXk2SC2gptPTgmpZCV2vbNfAPi5gOF0vbZQCpVLf23R37WX9hBM_hhlgELIhW1faddMVt7no_i45JaYBlVG6th0WWRZZy68AtJUPer_mZg4uAG92hot3LXDCUwggE8MIHkoAMCAQICCkeQEoAAEVWVc1IwCgYIKoZIzj0EAwIwFzEVMBMGA1UEAxMMR251YmJ5IFBpbG90MB4XDTEyMDgxNDE4MjkzMloXDTEzMDgxNDE4MjkzMlowMTEvMC0GA1UEAxMmUGlsb3RHbnViYnktMC40LjEtNDc5MDEyODAwMDExNTU5NTczNTIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASNYX5lyVCOZLzFZzrIKmeZ2jwURmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEBMAoGCCqGSM49BAMCA0cAMEQCIGDNtgYenCImLRqsHZbYxwgpsjZlMd2iaIMsuDa80w36AiBjGxRZ8J5jMAVXIsjYm39IiDuQibiNYNHZeVkCswQQ3zBFAiAUcYmbzDmH5i6CAsmznDPBkDP3NANS26gPyrAX25Iw5AIhAIJnfWc9iRkzreb2F-Xb3i4kfnBCP9WteASm09OWHvhx\",\"clientData\":\"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoib3BzWHFVaWZEcmlBQW1XY2xpbmZiUzBlLVVTWTBDZ3lKSGVfT3RkN3o4byIsImNpZF9wdWJrZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJIelF3bGZYWDdRNFM1TXRDQ25aVU5CdzNSTXpQTzl0T3lXakJxUmw0dEo4IiwieSI6IlhWZ3VHRkxJWngxZlhnM3dOcWZkYm43NWhpNC1fNy1CeGhNbGp3NDJIdDQifSwib3JpZ2luIjoiaHR0cDovL2V4YW1wbGUuY29tIn0\"}"; @Test public void testGetters() throws Exception { RegisterResponse registerResponse = new RegisterResponse(REGISTRATION_DATA_BASE64, CLIENT_DATA_SIGN_BASE64); assertEquals(CLIENT_DATA_SIGN, registerResponse.getClientData().toString()); assertEquals(SERVER_CHALLENGE_SIGN_BASE64, registerResponse.getRequestId()); assertEquals(REGISTRATION_DATA_BASE64, registerResponse.getRegistrationData()); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); RegisterResponse registerResponse = RegisterResponse.fromJson(JSON); RegisterResponse registerResponse2 = objectMapper.readValue(registerResponse.toJson(), RegisterResponse.class); assertEquals(registerResponse, registerResponse2); assertEquals(registerResponse.getRequestId(), registerResponse2.getRequestId()); assertEquals(registerResponse.toJson(), objectMapper.writeValueAsString(registerResponse)); } @Test public void testJavaSerializer() throws Exception { RegisterResponse registerResponse = RegisterResponse.fromJson(JSON); RegisterResponse registerResponse2 = TestUtils.clone(registerResponse); assertEquals(registerResponse, registerResponse2); assertEquals(registerResponse.getRequestId(), registerResponse2.getRequestId()); } @Test(expected = IllegalArgumentException.class) public void fromJsonDetectsTooLongJsonContent() throws U2fBadInputException { RegisterResponse.fromJson(makeLongJson(20000)); fail("fromJson did not detect too long JSON content."); } @Test public void fromJsonAllowsShortJsonContent() throws U2fBadInputException { assertNotNull(RegisterResponse.fromJson(makeLongJson(19999))); } private String makeLongJson(int totalLength) { final String jsonPrefix = "{\"registrationData\":\""; final String jsonSuffix = "\",\"clientData\":\"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoib3BzWHFVaWZEcmlBQW1XY2xpbmZiUzBlLVVTWTBDZ3lKSGVfT3RkN3o4byIsImNpZF9wdWJrZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJIelF3bGZYWDdRNFM1TXRDQ25aVU5CdzNSTXpQTzl0T3lXakJxUmw0dEo4IiwieSI6IlhWZ3VHRkxJWngxZlhnM3dOcWZkYm43NWhpNC1fNy1CeGhNbGp3NDJIdDQifSwib3JpZ2luIjoiaHR0cDovL2V4YW1wbGUuY29tIn0\"}"; final int infixLength = totalLength - jsonPrefix.length() - jsonSuffix.length(); return jsonPrefix + String.format("%0" + infixLength + "d", 0).replace("0", "a") + jsonSuffix; } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/SignRequestDataTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.yubico.u2f.TestUtils; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.crypto.ChallengeGenerator; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.NoEligibleDevicesException; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.APP_ID_SIGN; import static com.yubico.u2f.testdata.TestVectors.SERVER_CHALLENGE_SIGN_BASE64; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SignRequestDataTest { public static final String JSON = "{\"signRequests\":[{\"challenge\":\"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o\",\"appId\":\"https://gstatic.com/securitykey/a/example.com\",\"keyHandle\":\"KlUt_bdHftZf2EEz-GGWAQsiFbV9p10xW3uej-LjklpgGVUbq2HRZZFlnLrwC0lQ96v-ZmDi4Ab3aGi3ctcMJQ\",\"version\":\"U2F_V2\"}]}"; @Test public void testGetters() throws Exception { DeviceRegistration device = mock(DeviceRegistration.class); U2fPrimitives primitives = mock(U2fPrimitives.class); ChallengeGenerator challengeGenerator = mock(ChallengeGenerator.class); byte[] challenge = U2fB64Encoding.decode(SERVER_CHALLENGE_SIGN_BASE64); when(challengeGenerator.generateChallenge()).thenReturn(challenge); SignRequest signRequest = SignRequest.fromJson(SignRequestTest.JSON); when(primitives.startSignature(APP_ID_SIGN, device, challenge)).thenReturn(signRequest); SignRequestData requestData = new SignRequestData(APP_ID_SIGN, ImmutableList.of(device), primitives, challengeGenerator); assertEquals(SERVER_CHALLENGE_SIGN_BASE64, requestData.getRequestId()); SignRequest signRequest2 = Iterables.getOnlyElement(requestData.getSignRequests()); assertEquals(signRequest, signRequest2); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); SignRequestData requestData = SignRequestData.fromJson(JSON); SignRequestData requestData2 = objectMapper.readValue(requestData.toJson(), SignRequestData.class); assertEquals(requestData.getRequestId(), requestData2.getRequestId()); assertEquals(requestData, requestData2); assertEquals(requestData.toJson(), objectMapper.writeValueAsString(requestData)); } @Test public void testJavaSerializer() throws Exception { SignRequestData requestData = SignRequestData.fromJson(JSON); SignRequestData requestData2 = TestUtils.clone(requestData); assertEquals(requestData.getRequestId(), requestData2.getRequestId()); assertEquals(requestData, requestData2); } @Test(expected = IllegalArgumentException.class) public void getSignRequestChecksResponseId() throws Exception { SignRequestData requestData = SignRequestData.fromJson(JSON); final String clientDataJson = "{\"typ\":\"navigator.id.getAssertion\",\"challenge\":\"OpsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o\",\"cid_pubkey\":{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8\",\"y\":\"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4\"},\"origin\":\"http://example.com\"}"; final String signResponseJson = "{\"clientData\":\"" + U2fB64Encoding.encode(clientDataJson.getBytes("UTF-8")) + "\",\"signatureData\":\"\",\"keyHandle\":\"KlUt_bdHftZf2EEz-GGWAQsiFbV9p10xW3uej-LjklpgGVUbq2HRZZFlnLrwC0lQ96v-ZmDi4Ab3aGi3ctcMJQ\"}"; requestData.getSignRequest(SignResponse.fromJson(signResponseJson)); fail("getSignRequest did not detect wrong request ID."); } @Test public void testFailureModesAreIdentifiable() throws Exception { byte[] challenge = U2fB64Encoding.decode(SERVER_CHALLENGE_SIGN_BASE64); ChallengeGenerator challengeGenerator = mock(ChallengeGenerator.class); when(challengeGenerator.generateChallenge()).thenReturn(challenge); try { new SignRequestData(APP_ID_SIGN, ImmutableList.of(), mock(U2fPrimitives.class), challengeGenerator); } catch (NoEligibleDevicesException e) { assertFalse(e.hasDevices()); } DeviceRegistration compromisedDevice = mock(DeviceRegistration.class); when(compromisedDevice.isCompromised()).thenReturn(true); try { new SignRequestData(APP_ID_SIGN, ImmutableList.of(compromisedDevice), mock(U2fPrimitives.class), challengeGenerator); } catch (NoEligibleDevicesException e) { assertTrue(e.hasDevices()); } } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/SignRequestTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.TestUtils; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertEquals; public class SignRequestTest { public static final String JSON = "{\"challenge\":\"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o\",\"appId\":\"https://gstatic.com/securitykey/a/example.com\",\"keyHandle\":\"KlUt_bdHftZf2EEz-GGWAQsiFbV9p10xW3uej-LjklpgGVUbq2HRZZFlnLrwC0lQ96v-ZmDi4Ab3aGi3ctcMJQ\",\"version\":\"U2F_V2\"}"; @Test public void testGetters() throws Exception { SignRequest signRequest = SignRequest.builder().challenge(SERVER_CHALLENGE_SIGN_BASE64).appId(APP_ID_SIGN).keyHandle(KEY_HANDLE_BASE64).build(); assertEquals(SERVER_CHALLENGE_SIGN_BASE64, signRequest.getChallenge()); assertEquals(SERVER_CHALLENGE_SIGN_BASE64, signRequest.getRequestId()); assertEquals(APP_ID_SIGN, signRequest.getAppId()); assertEquals(KEY_HANDLE_BASE64, signRequest.getKeyHandle()); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); SignRequest signRequest = SignRequest.fromJson(JSON); SignRequest signRequest2 = objectMapper.readValue(signRequest.toJson(), SignRequest.class); assertEquals(signRequest.getRequestId(), signRequest2.getRequestId()); assertEquals(signRequest, signRequest2); assertEquals(signRequest.toJson(), objectMapper.writeValueAsString(signRequest)); } @Test public void testJavaSerializer() throws Exception { SignRequest signRequest = SignRequest.fromJson(JSON); SignRequest signRequest2 = TestUtils.clone(signRequest); assertEquals(signRequest.getRequestId(), signRequest2.getRequestId()); assertEquals(signRequest, signRequest2); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/SignResponseTest.java ================================================ package com.yubico.u2f.data.messages; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.TestUtils; import com.yubico.u2f.exceptions.U2fBadInputException; import org.junit.Test; import static com.yubico.u2f.testdata.TestVectors.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; public class SignResponseTest { public static final String JSON = "{\"clientData\":\"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoib3BzWHFVaWZEcmlBQW1XY2xpbmZiUzBlLVVTWTBDZ3lKSGVfT3RkN3o4byIsImNpZF9wdWJrZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJIelF3bGZYWDdRNFM1TXRDQ25aVU5CdzNSTXpQTzl0T3lXakJxUmw0dEo4IiwieSI6IlhWZ3VHRkxJWngxZlhnM3dOcWZkYm43NWhpNC1fNy1CeGhNbGp3NDJIdDQifSwib3JpZ2luIjoiaHR0cDovL2V4YW1wbGUuY29tIn0\",\"signatureData\":\"\",\"keyHandle\":\"KlUt_bdHftZf2EEz-GGWAQsiFbV9p10xW3uej-LjklpgGVUbq2HRZZFlnLrwC0lQ96v-ZmDi4Ab3aGi3ctcMJQ\"}"; @Test public void testGetters() throws Exception { SignResponse signResponse = new SignResponse(CLIENT_DATA_SIGN_BASE64, "", KEY_HANDLE_BASE64); assertEquals(CLIENT_DATA_SIGN, signResponse.getClientData().toString()); assertEquals(KEY_HANDLE_BASE64, signResponse.getKeyHandle()); } @Test public void testToAndFromJson() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); SignResponse signResponse = SignResponse.fromJson(JSON); SignResponse signResponse2 = objectMapper.readValue(signResponse.toJson(), SignResponse.class); assertEquals(signResponse, signResponse2); assertEquals(signResponse.getRequestId(), signResponse2.getRequestId()); assertEquals(signResponse.toJson(), objectMapper.writeValueAsString(signResponse)); } @Test public void testJavaSerializer() throws Exception { SignResponse signResponse = SignResponse.fromJson(JSON); SignResponse signResponse2 = TestUtils.clone(signResponse); assertEquals(signResponse, signResponse2); assertEquals(signResponse.getRequestId(), signResponse2.getRequestId()); } @Test(expected = IllegalArgumentException.class) public void fromJsonDetectsTooLongJsonContent() throws U2fBadInputException { SignResponse.fromJson(makeLongJson(20000)); fail("fromJson did not detect too long JSON content."); } @Test public void fromJsonAllowsShortJsonContent() throws U2fBadInputException { assertNotNull(SignResponse.fromJson(makeLongJson(19999))); } private String makeLongJson(int totalLength) { final String jsonPrefix = "{\"clientData\":\"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoib3BzWHFVaWZEcmlBQW1XY2xpbmZiUzBlLVVTWTBDZ3lKSGVfT3RkN3o4byIsImNpZF9wdWJrZXkiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJIelF3bGZYWDdRNFM1TXRDQ25aVU5CdzNSTXpQTzl0T3lXakJxUmw0dEo4IiwieSI6IlhWZ3VHRkxJWngxZlhnM3dOcWZkYm43NWhpNC1fNy1CeGhNbGp3NDJIdDQifSwib3JpZ2luIjoiaHR0cDovL2V4YW1wbGUuY29tIn0\",\"signatureData\":\"\",\"keyHandle\":\""; final String jsonSuffix = "\"}"; final int infixLength = totalLength - jsonPrefix.length() - jsonSuffix.length(); return jsonPrefix + String.format("%0" + infixLength + "d", 0).replace("0", "a") + jsonSuffix; } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/json/JsonSerializableTest.java ================================================ package com.yubico.u2f.data.messages.json; import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.Test; import static org.junit.Assert.assertEquals; public class JsonSerializableTest { private static class Thing extends JsonSerializable { @JsonProperty final String foo; private Thing(@JsonProperty("foo") String foo) { this.foo = foo; } } @Test public void toStringReturnsJson() { assertEquals("{\"foo\":\"bar\"}", new Thing("bar").toString()); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/key/Client.java ================================================ package com.yubico.u2f.data.messages.key; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.RegisterRequest; import com.yubico.u2f.data.messages.RegisterResponse; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.softkey.SoftKey; import java.nio.ByteBuffer; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; public class Client { public static final byte REGISTRATION_RESERVED_BYTE_VALUE = (byte) 0x05; public static final ImmutableSet TRUSTED_DOMAINS = ImmutableSet.of("http://example.com"); public static final String APP_ID = "my-app"; private final BouncyCastleCrypto crypto = new BouncyCastleCrypto(); private final SoftKey key; private final U2fPrimitives u2f = new U2fPrimitives(); private final ObjectMapper objectMapper = new ObjectMapper(); public Client(SoftKey key) { this.key = key; } public static byte[] encodeRegisterResponse(RawRegisterResponse rawRegisterResponse) throws U2fBadInputException { byte[] userPublicKey = rawRegisterResponse.userPublicKey; byte[] keyHandle = rawRegisterResponse.keyHandle; X509Certificate attestationCertificate = rawRegisterResponse.attestationCertificate; byte[] signature = rawRegisterResponse.signature; byte[] attestationCertificateBytes; try { attestationCertificateBytes = attestationCertificate.getEncoded(); } catch (CertificateEncodingException e) { throw new U2fBadInputException("Error when encoding attestation certificate.", e); } if (keyHandle.length > 255) { throw new U2fBadInputException("keyHandle length cannot be longer than 255 bytes!"); } byte[] result = new byte[1 + userPublicKey.length + 1 + keyHandle.length + attestationCertificateBytes.length + signature.length]; ByteBuffer.wrap(result) .put(REGISTRATION_RESERVED_BYTE_VALUE) .put(userPublicKey) .put((byte) keyHandle.length) .put(keyHandle) .put(attestationCertificateBytes) .put(signature); return result; } public static RegisterResponse encodeTokenRegistrationResponse(String clientDataJson, RawRegisterResponse registerResponse) throws U2fBadInputException { byte[] rawRegisterResponse = Client.encodeRegisterResponse(registerResponse); String rawRegisterResponseBase64 = U2fB64Encoding.encode(rawRegisterResponse); String clientDataBase64 = U2fB64Encoding.encode(clientDataJson.getBytes()); return new RegisterResponse(rawRegisterResponseBase64, clientDataBase64); } public DeviceRegistration register() throws Exception { RegisterRequest registerRequest = u2f.startRegistration(APP_ID); Map clientData = new HashMap(); clientData.put("typ", "navigator.id.finishEnrollment"); clientData.put("challenge", registerRequest.getChallenge()); clientData.put("origin", "http://example.com"); String clientDataJson = objectMapper.writeValueAsString(clientData); byte[] clientParam = crypto.hash(clientDataJson); byte[] appParam = crypto.hash(registerRequest.getAppId()); RawRegisterResponse rawRegisterResponse = key.register(new com.yubico.u2f.softkey.messages.RegisterRequest(appParam, clientParam)); // client encodes data RegisterResponse tokenResponse = Client.encodeTokenRegistrationResponse(clientDataJson, rawRegisterResponse); return u2f.finishRegistration(registerRequest, tokenResponse, TRUSTED_DOMAINS); } public SignResponse sign(DeviceRegistration registeredDevice, SignRequest startedSignature) throws Exception { Map clientData = new HashMap(); clientData.put("typ", "navigator.id.getAssertion"); clientData.put("challenge", startedSignature.getChallenge()); clientData.put("origin", "http://example.com"); String clientDataJson = objectMapper.writeValueAsString(clientData); byte[] clientParam = crypto.hash(clientDataJson); byte[] appParam = crypto.hash(startedSignature.getAppId()); com.yubico.u2f.softkey.messages.SignRequest signRequest = new com.yubico.u2f.softkey.messages.SignRequest((byte) 0x01, clientParam, appParam, U2fB64Encoding.decode(registeredDevice.getKeyHandle())); RawSignResponse rawSignResponse = key.sign(signRequest); String clientDataBase64 = U2fB64Encoding.encode(clientDataJson.getBytes()); ByteArrayDataOutput authData = ByteStreams.newDataOutput(); authData.write(rawSignResponse.getUserPresence()); authData.writeInt((int) rawSignResponse.getCounter()); authData.write(rawSignResponse.getSignature()); return new SignResponse( clientDataBase64, U2fB64Encoding.encode(authData.toByteArray()), startedSignature.getKeyHandle() ); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/key/CodecTestUtils.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.data.messages.key; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.yubico.u2f.exceptions.U2fBadInputException; import java.security.cert.CertificateEncodingException; public class CodecTestUtils { public static byte[] encodeSignResponse(RawSignResponse rawSignResponse) { ByteArrayDataOutput encoded = ByteStreams.newDataOutput(); encoded.write(rawSignResponse.getUserPresence()); encoded.writeInt((int) rawSignResponse.getCounter()); encoded.write(rawSignResponse.getSignature()); return encoded.toByteArray(); } public static byte[] encodeRegisterResponse(RawRegisterResponse rawRegisterResponse) throws U2fBadInputException { byte[] keyHandle = rawRegisterResponse.keyHandle; if (keyHandle.length > 255) { throw new U2fBadInputException("keyHandle length cannot be longer than 255 bytes!"); } try { ByteArrayDataOutput encoded = ByteStreams.newDataOutput(); encoded.write(RawRegisterResponse.REGISTRATION_RESERVED_BYTE_VALUE); encoded.write(rawRegisterResponse.userPublicKey); encoded.write((byte) keyHandle.length); encoded.write(keyHandle); encoded.write(rawRegisterResponse.attestationCertificate.getEncoded()); encoded.write(rawRegisterResponse.signature); return encoded.toByteArray(); } catch (CertificateEncodingException e) { throw new U2fBadInputException("Error when encoding attestation certificate.", e); } } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/key/util/CertificateParserTest.java ================================================ package com.yubico.u2f.data.messages.key.util; import java.security.cert.CertificateException; import org.junit.Test; import static org.junit.Assert.assertNotNull; public class CertificateParserTest { private static final String ATTESTATION_CERT = "MIICGzCCAQWgAwIBAgIEdaP2dTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NzM2Nzk3MzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZo35Damtpl81YdmcbhEuXKAr7xDcQzAy5n3ftAAhtBbu8EeGU4ynfSgLonckqX6J2uXLBppTNE3v2bt+Yf8MLoxIwEDAOBgorBgEEAYLECgECBAAwCwYJKoZIhvcNAQELA4IBAQG9LbiNPgs0sQYOHAJcg+lMk+HCsiWRlYVnbT4I/5lnqU907vY17XYAORd432bU3Nnhsbkvjz76kQJGXeNAF4DPANGGlz8JU+LNEVE2PWPGgEM0GXgB7mZN5Sinfy1AoOdO+3c3bfdJQuXlUxHbo+nDpxxKpzq9gr++RbokF1+0JBkMbaA/qLYL4WdhY5NvaOyMvYpO3sBxlzn6FcP67hlotGH1wU7qhCeh+uur7zDeAWVh7c4QtJOXHkLJQfV3Z7ZMvhkIA6jZJAX99hisABU/SSa5DtgX7AfsHwa04h69AAAWDUzSk3HgOXbUd1FaSOPdlVFkG2N2JllFHykyO3zO"; private static final String PEM_ATTESTATION_CERT = "-----BEGIN CERTIFICATE-----\n" + ATTESTATION_CERT + "\n-----END CERTIFICATE-----\n"; @Test public void parsePemDoesNotReturnNull() throws CertificateException { assertNotNull(CertificateParser.parsePem(PEM_ATTESTATION_CERT)); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/data/messages/key/util/U2fB64EncodingTest.java ================================================ package com.yubico.u2f.data.messages.key.util; import org.junit.Test; import com.yubico.u2f.exceptions.U2fBadInputException; import static org.junit.Assert.assertEquals; public class U2fB64EncodingTest { @Test public void encodeTest() { byte[] input = "Test".getBytes(); String base64Data = U2fB64Encoding.encode(input); // No padding. assertEquals("VGVzdA", base64Data); } @Test public void decodeTest() throws U2fBadInputException { String base64Data = "VGVzdA"; String base64DataWithPadding = "VGVzdA=="; String base64DataEmpty = ""; // Verify that Base64 data with and without padding ('=') are decoded correctly. String out1 = new String(U2fB64Encoding.decode(base64Data)); String out2 = new String(U2fB64Encoding.decode(base64DataWithPadding)); String out3 = new String(U2fB64Encoding.decode(base64DataEmpty)); assertEquals(out1, out2); assertEquals(out1, "Test"); assertEquals(out3, ""); } @Test(expected = U2fBadInputException.class) public void decodeBadAlphabetTest() throws U2fBadInputException { U2fB64Encoding.decode("****"); } @Test(expected = U2fBadInputException.class) public void decodeBadPaddingTest() throws U2fBadInputException { U2fB64Encoding.decode("A==="); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/json/SerializationTest.java ================================================ package com.yubico.u2f.json; import com.fasterxml.jackson.databind.ObjectMapper; import com.yubico.u2f.data.messages.SignResponse; import org.junit.Test; import static com.google.common.base.Preconditions.checkNotNull; public class SerializationTest { @Test public void tokenSignResponse() throws Exception { String response = "{ \"signatureData\": \"AQAAAAUwRAIgB1Q5iWRzC4zkZE2eIqoJZsXXCcg_6FVbZk-sMtLXcz4CIHxWaQsjLc-vD_kZLeg-p7IQ1HAmAFgiTk_dq6Q6iGcu\", \"clientData\": \"eyAiY2hhbGxlbmdlIjogIkQ1VG1CaEQzbTg0c3BRd3FfVm81VWZFSm8xV2JXTnBnRHdvZ0dWcmtBd00iLCAib3JpZ2luIjogImh0dHA6XC9cL2V4YW1wbGUuY29tIiwgInR5cCI6ICJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiB9\", \"keyHandle\": \"fSgg0l0JefF0GAFGAi9cOf5iL1nnzSswSmgpathyRRhsZ8QTzxPH1WAu8TqTbadfnNHOnINoF0UkMjKrxKVZLA\" }"; SignResponse ar = new ObjectMapper().readValue(response, SignResponse.class); checkNotNull(ar.getKeyHandle()); checkNotNull(ar.getClientData()); checkNotNull(ar.getSignatureData()); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/softkey/SoftKey.java ================================================ // Copyright 2014 Google Inc. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd package com.yubico.u2f.softkey; import com.yubico.u2f.data.messages.key.RawSignResponse; import com.yubico.u2f.data.messages.key.RawRegisterResponse; import com.yubico.u2f.data.messages.key.util.ByteInputStream; import com.yubico.u2f.softkey.messages.SignRequest; import com.yubico.u2f.softkey.messages.RegisterRequest; import com.yubico.u2f.testdata.GnubbyKey; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECParameterSpec; import java.io.IOException; import java.security.*; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; public final class SoftKey implements Cloneable { private final X509Certificate attestationCertificate; private final PrivateKey certificatePrivateKey; private final Map dataStore; private long deviceCounter = 0; public SoftKey() { this( new HashMap(), 0, GnubbyKey.ATTESTATION_CERTIFICATE, GnubbyKey.ATTESTATION_CERTIFICATE_PRIVATE_KEY ); } public SoftKey( Map dataStore, long deviceCounter, X509Certificate attestationCertificate, PrivateKey certificatePrivateKey ) { this.dataStore = dataStore; this.deviceCounter = deviceCounter; this.attestationCertificate = attestationCertificate; this.certificatePrivateKey = certificatePrivateKey; } @Override public SoftKey clone() { return new SoftKey( this.dataStore, this.deviceCounter, this.attestationCertificate, this.certificatePrivateKey ); } public RawRegisterResponse register(RegisterRequest registerRequest) throws Exception { byte[] applicationSha256 = registerRequest.getApplicationSha256(); byte[] challengeSha256 = registerRequest.getChallengeSha256(); // generate ECC key SecureRandom random = new SecureRandom(); ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1"); KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA"); g.initialize(ecSpec, random); KeyPair keyPair = g.generateKeyPair(); byte[] keyHandle = new byte[64]; random.nextBytes(keyHandle); dataStore.put(new String(keyHandle), keyPair); byte[] userPublicKey = stripMetaData(keyPair.getPublic().getEncoded()); byte[] signedData = RawRegisterResponse.packBytesToSign(applicationSha256, challengeSha256, keyHandle, userPublicKey); byte[] signature = sign(signedData, certificatePrivateKey); return new RawRegisterResponse(userPublicKey, keyHandle, attestationCertificate, signature); } private byte[] stripMetaData(byte[] a) { ByteInputStream bis = new ByteInputStream(a); try { bis.read(3); bis.read(bis.readUnsigned() + 1); int keyLength = bis.readUnsigned(); bis.read(1); return bis.read(keyLength - 1); } catch (IOException e) { throw new AssertionError(e); } } public RawSignResponse sign(SignRequest signRequest) throws Exception { byte[] applicationSha256 = signRequest.getApplicationSha256(); byte[] challengeSha256 = signRequest.getChallengeSha256(); byte[] keyHandle = signRequest.getKeyHandle(); KeyPair keyPair = checkNotNull(dataStore.get(new String(keyHandle))); long counter = ++deviceCounter; byte[] signedData = RawSignResponse.packBytesToSign(applicationSha256, RawSignResponse.USER_PRESENT_FLAG, counter, challengeSha256); byte[] signature = sign(signedData, keyPair.getPrivate()); return new RawSignResponse(RawSignResponse.USER_PRESENT_FLAG, counter, signature); } private byte[] sign(byte[] signedData, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance("SHA256withECDSA"); signature.initSign(privateKey); signature.update(signedData); return signature.sign(); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/softkey/SoftKeyTest.java ================================================ package com.yubico.u2f.softkey; import com.yubico.u2f.U2fPrimitives; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.ClientData; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.key.Client; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import com.yubico.u2f.exceptions.InvalidDeviceCounterException; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.exceptions.U2fRegistrationException; import com.yubico.u2f.testdata.AcmeKey; import com.yubico.u2f.testdata.GnubbyKey; import java.security.KeyPair; import java.util.HashMap; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static org.hamcrest.core.Is.isA; import static org.junit.Assert.assertEquals; public class SoftKeyTest { public static final String APP_ID = "my-app"; private U2fPrimitives u2f; @Rule public ExpectedException expectedException = ExpectedException.none(); @Before public void setup() { u2f = new U2fPrimitives(); } @Test public void shouldRegister() throws Exception { Client client = createClient(); client.register(); } @Test public void shouldSign() throws Exception { Client client = createClient(); DeviceRegistration registeredDevice = client.register(); signUsing(client, registeredDevice); } // Tests FIDO Security Measure [SM-3] @Test public void shouldProvideAttestationCert() throws Exception { Client client = createClient(); DeviceRegistration deviceRegistration = client.register(); assertEquals("CN=Gnubby Pilot", deviceRegistration.getAttestationCertificate().getIssuerDN().getName()); } @Test public void shouldVerifyAttestationCert() throws Throwable { expectedException.expectCause(isA(U2fBadInputException.class)); SoftKey key = new SoftKey( new HashMap(), 0, AcmeKey.ATTESTATION_CERTIFICATE, GnubbyKey.ATTESTATION_CERTIFICATE_PRIVATE_KEY ); new Client(key).register(); } // Tests FIDO Security Measure [SM-15] @Test(expected = InvalidDeviceCounterException.class) public void shouldProtectAgainstClonedDevices() throws Exception { SoftKey key = new SoftKey(); Client client = new Client(key); SoftKey clonedKey = key.clone(); Client clientUsingClone = new Client(clonedKey); DeviceRegistration registeredDevice = client.register(); signUsing(client, registeredDevice); signUsing(clientUsingClone, registeredDevice); } @Test public void shouldVerifyChallenge() throws Throwable { expectedException.expectCause(isA(U2fBadInputException.class)); Client client = createClient(); DeviceRegistration registeredDevice = client.register(); SignRequest signRequest = u2f.startSignature(APP_ID, registeredDevice); SignResponse originalResponse = client.sign(registeredDevice, signRequest); SignResponse tamperedResponse = new SignResponse( tamperChallenge(originalResponse.getClientData()), originalResponse.getSignatureData(), originalResponse.getKeyHandle() ); u2f.finishSignature(signRequest, tamperedResponse, registeredDevice); } private String tamperChallenge(ClientData clientData) { byte[] rawClientData = clientData.asJson().getBytes(); rawClientData[50] += 1; return U2fB64Encoding.encode(rawClientData); } @Test public void shouldVerifySignature() throws Throwable { expectedException.expectCause(isA(U2fBadInputException.class)); Client client = createClient(); DeviceRegistration registeredDevice = client.register(); SignRequest signRequest = u2f.startSignature(APP_ID, registeredDevice); SignResponse originalResponse = client.sign(registeredDevice, signRequest); SignResponse tamperedResponse = new SignResponse( U2fB64Encoding.encode(originalResponse.getClientData().asJson().getBytes()), tamperSignature(originalResponse.getSignatureData()), originalResponse.getKeyHandle() ); u2f.finishSignature(signRequest, tamperedResponse, registeredDevice); } @Test(expected = RuntimeException.class) public void shouldThrowSeparateExceptionForMalformedSignature() throws Exception { Client client = createClient(); DeviceRegistration registeredDevice = client.register(); SignRequest signRequest = u2f.startSignature(APP_ID, registeredDevice); SignResponse originalResponse = client.sign(registeredDevice, signRequest); SignResponse tamperedResponse = new SignResponse( U2fB64Encoding.encode(originalResponse.getClientData().asJson().getBytes()), makeSignatureMalformed(originalResponse.getSignatureData()), originalResponse.getKeyHandle() ); u2f.finishSignature(signRequest, tamperedResponse, registeredDevice); } private String makeSignatureMalformed(String signature) { return signature.substring(0, 5) + "47" + signature.substring(7); } private String tamperSignature(String signature) { return signature.substring(0, 24) + "47" + signature.substring(26); } private Client createClient() { return new Client(new SoftKey()); } private void signUsing(Client client, DeviceRegistration registeredDevice) throws Exception { SignRequest signRequest = u2f.startSignature(APP_ID, registeredDevice); SignResponse signResponse = client.sign(registeredDevice, signRequest); u2f.finishSignature(signRequest, signResponse, registeredDevice); } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/softkey/messages/RegisterRequest.java ================================================ package com.yubico.u2f.softkey.messages; public class RegisterRequest { private final byte[] challengeSha256; private final byte[] applicationSha256; public RegisterRequest(byte[] applicationSha256, byte[] challengeSha256) { this.challengeSha256 = challengeSha256; this.applicationSha256 = applicationSha256; } /** * The challenge parameter is the SHA-256 hash of the Client Data, a * stringified JSON datastructure that the FIDO Client prepares. Among other * things, the Client Data contains the challenge from the relying party * (hence the name of the parameter). See below for a detailed explanation of * Client Data. */ public byte[] getChallengeSha256() { return challengeSha256; } /** * The application parameter is the SHA-256 hash of the application identity * of the application requesting the registration */ public byte[] getApplicationSha256() { return applicationSha256; } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/softkey/messages/SignRequest.java ================================================ package com.yubico.u2f.softkey.messages; public class SignRequest { private final byte control; private final byte[] challengeSha256; private final byte[] applicationSha256; private final byte[] keyHandle; public SignRequest(byte control, byte[] challengeSha256, byte[] applicationSha256, byte[] keyHandle) { this.control = control; this.challengeSha256 = challengeSha256; this.applicationSha256 = applicationSha256; this.keyHandle = keyHandle; } /** * The FIDO Client will set the control byte to one of the following values: * 0x07 ("check-only") * 0x03 ("enforce-user-presence-and-sign") */ public byte getControl() { return control; } /** * The challenge parameter is the SHA-256 hash of the Client Data, a * stringified JSON datastructure that the FIDO Client prepares. Among other * things, the Client Data contains the challenge from the relying party * (hence the name of the parameter). See below for a detailed explanation of * Client Data. */ public byte[] getChallengeSha256() { return challengeSha256; } /** * The application parameter is the SHA-256 hash of the application identity * of the application requesting the registration */ public byte[] getApplicationSha256() { return applicationSha256; } /** * The key handle obtained during registration. */ public byte[] getKeyHandle() { return keyHandle; } } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/testdata/AcmeKey.java ================================================ package com.yubico.u2f.testdata; import com.yubico.u2f.TestUtils; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import java.security.cert.X509Certificate; import static com.yubico.u2f.TestUtils.fetchCertificate; public class AcmeKey { public static final X509Certificate ATTESTATION_CERTIFICATE = fetchCertificate(GnubbyKey.class.getResourceAsStream("acme/attestation-certificate.der")); public static final String CLIENT_DATA_BASE64 = TestVectors.CLIENT_DATA_REGISTRATION_BASE64; public static final byte[] REGISTRATION_DATA = TestUtils.HEX.decode( "0504478e16bbdbbb741a660a000314a8b6bd63095196ed704c52eebc0fa02a61" + "8f19ff59df18451a11cee43defd9a29b5710f63dfc671f752b1b0c6ca76c8427" + "af2d403c2415e1760d1108105720c6069a9039c99d09f76909c36d9efc350937" + "31f85f55ac6d73ea69de7d9005ae9507b95e149e19676272fc202d949a3ab151" + "b96870308201443081eaa0030201020209019189ffffffff5183300a06082a86" + "48ce3d040302301b3119301706035504031310476e756262792048534d204341" + "2030303022180f32303132303630313030303030305a180f3230363230353331" + "3233353935395a30303119301706035504031310476f6f676c6520476e756262" + "7920763031133011060355042d030a00019189ffffffff51833059301306072a" + "8648ce3d020106082a8648ce3d030107034200041f1302f12173a9cbea83d06d" + "755411e582a87fbb5850eddcf3607ec759a4a12c3cb392235e8d5b17caee1b34" + "e5b5eb548649696257f0ea8efb90846f88ad5f72300a06082a8648ce3d040302" + "0349003046022100b4caea5dc60fbf9f004ed84fc4f18522981c1c303155c082" + "74e889f3f10c5b23022100faafb4f10b92f4754e3b08b5af353f78485bc903ec" + "e7ea911264fc1673b6598f3046022100f3be1bf12cbf0be7eab5ea32f3664edb" + "18a24d4999aac5aa40ff39cf6f34c9ed022100ce72631767367467dfe2aecf6a" + "5a4eba9779fac65f5ca8a2c325b174ee4769ac"); public static final String REGISTRATION_DATA_BASE64 = U2fB64Encoding .encode(REGISTRATION_DATA); public static final String KEY_HANDLE = "PCQV4XYNEQgQVyDGBpqQOcmdCfdpCcNtnvw1CTcx-F9VrG1z6mnefZAFrpUHuV4UnhlnYnL8IC2UmjqxUblocA"; public static final String USER_PUBLIC_KEY_B64 = "BEeOFrvbu3QaZgoAAxSotr1jCVGW7XBMUu68D6AqYY8Z_1nfGEUaEc7kPe_ZoptXEPY9_GcfdSsbDGynbIQnry0"; } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/testdata/GnubbyKey.java ================================================ package com.yubico.u2f.testdata; import java.security.PrivateKey; import java.security.cert.X509Certificate; import static com.yubico.u2f.TestUtils.fetchCertificate; import static com.yubico.u2f.TestUtils.parsePrivateKey; public class GnubbyKey { public static final X509Certificate ATTESTATION_CERTIFICATE = fetchCertificate(GnubbyKey.class.getResourceAsStream("gnubby/attestation-certificate.der")); public static final PrivateKey ATTESTATION_CERTIFICATE_PRIVATE_KEY = parsePrivateKey(GnubbyKey.class.getResourceAsStream("gnubby/attestation-certificate-private-key.hex")); } ================================================ FILE: u2flib-server-core/src/test/java/com/yubico/u2f/testdata/TestVectors.java ================================================ /* * Copyright 2014 Yubico. * Copyright 2014 Google Inc. All rights reserved. * * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd */ package com.yubico.u2f.testdata; import com.google.common.collect.ImmutableSet; import com.yubico.u2f.TestUtils; import com.yubico.u2f.crypto.BouncyCastleCrypto; import com.yubico.u2f.crypto.Crypto; import com.yubico.u2f.data.messages.key.util.U2fB64Encoding; import java.util.Set; final public class TestVectors { private final static Crypto crypto = new BouncyCastleCrypto(); //Test vectors from FIDO U2F: Raw Message Formats - Draft 4 public static final int COUNTER_VALUE = 1; public static final Set TRUSTED_DOMAINS = ImmutableSet.of("http://example.com"); public static final String APP_ID_ENROLL = "http://example.com"; public static final byte[] APP_ID_ENROLL_SHA256 = crypto.hash(APP_ID_ENROLL); public static final String APP_ID_SIGN = "https://gstatic.com/securitykey/a/example.com"; public static final byte[] APP_ID_SIGN_SHA256 = crypto.hash(APP_ID_SIGN); public static final String ORIGIN = "http://example.com"; public static final String SERVER_CHALLENGE_REGISTER_BASE64 = "vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo"; public static final String SERVER_CHALLENGE_SIGN_BASE64 = "opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o"; public static final String CHANNEL_ID_STRING = "{" + "\"kty\":\"EC\"," + "\"crv\":\"P-256\"," + "\"x\":\"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8\"," + "\"y\":\"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4\"" + "}"; public static final String CLIENT_DATA_REGISTER = String.format( "{" + "\"typ\":\"navigator.id.finishEnrollment\"," + "\"challenge\":\"%s\"," + "\"cid_pubkey\":%s," + "\"origin\":\"%s\"}", SERVER_CHALLENGE_REGISTER_BASE64, CHANNEL_ID_STRING, ORIGIN); public static final String CLIENT_DATA_REGISTRATION_BASE64 = TestUtils.BASE64.encode(CLIENT_DATA_REGISTER.getBytes()); public static final byte[] CLIENT_DATA_ENROLL_SHA256 = crypto.hash(CLIENT_DATA_REGISTER .getBytes()); public static final String CLIENT_DATA_SIGN = String.format( "{" + "\"typ\":\"navigator.id.getAssertion\"," + "\"challenge\":\"%s\"," + "\"cid_pubkey\":%s," + "\"origin\":\"%s\"}", SERVER_CHALLENGE_SIGN_BASE64, CHANNEL_ID_STRING, ORIGIN); public static final String CLIENT_DATA_SIGN_BASE64 = U2fB64Encoding.encode(CLIENT_DATA_SIGN.getBytes()); public static final byte[] CLIENT_DATA_SIGN_SHA256 = TestUtils.HEX.decode( "ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57"); public static final byte[] REGISTRATION_REQUEST_DATA = TestUtils.HEX.decode( "4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb" + "f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4"); public static final byte[] REGISTRATION_RESPONSE_DATA = TestUtils.HEX.decode( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b" + "657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2" + "f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2" + "e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772" + "d70c253082013c3081e4a003020102020a47901280001155957352300a06082a" + "8648ce3d0403023017311530130603550403130c476e756262792050696c6f74" + "301e170d3132303831343138323933325a170d3133303831343138323933325a" + "3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34" + "373930313238303030313135353935373335323059301306072a8648ce3d0201" + "06082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c" + "1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23" + "abaf0203b4b8911ba0569994e101300a06082a8648ce3d040302034700304402" + "2060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30d" + "fa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b3" + "0410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80f" + "cab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5" + "ad7804a6d3d3961ef871"); public static final String REGISTRATION_DATA_BASE64 = U2fB64Encoding.encode(REGISTRATION_RESPONSE_DATA); public static final byte[] REGISTRATION_RESPONSE_DATA_WITH_DIFFERENT_APP_ID = TestUtils.HEX.decode( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b" + "657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2" + "f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2" + "e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772" + "d70c253082013c3081e4a003020102020a47901280001155957352300a06082a" + "8648ce3d0403023017311530130603550403130c476e756262792050696c6f74" + "301e170d3132303831343138323933325a170d3133303831343138323933325a" + "3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34" + "373930313238303030313135353935373335323059301306072a8648ce3d0201" + "06082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c" + "1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23" + "abaf0203b4b8911ba0569994e101300a06082a8648ce3d040302034700304402" + "2060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30d" + "fa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b3" + "0410df3046022100d2b4702fea46b322c5addd921b3f4f0fb15c69737fe7441e" + "b764c03dc8f49d09022100eef7dcdf6070d8e5a45ed6be18dfc036ebf8b4faaa" + "ce7287b56e7fac1d2cb552"); public static final String REGISTRATION_DATA_WITH_DIFFERENT_APP_ID_BASE64 = U2fB64Encoding.encode(REGISTRATION_RESPONSE_DATA_WITH_DIFFERENT_APP_ID); public static final byte[] REGISTRATION_RESPONSE_DATA_WITH_DIFFERENT_CLIENT_DATA_TYPE = TestUtils.HEX.decode( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b" + "657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2" + "f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2" + "e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772" + "d70c253082013c3081e4a003020102020a47901280001155957352300a06082a" + "8648ce3d0403023017311530130603550403130c476e756262792050696c6f74" + "301e170d3132303831343138323933325a170d3133303831343138323933325a" + "3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34" + "373930313238303030313135353935373335323059301306072a8648ce3d0201" + "06082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c" + "1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23" + "abaf0203b4b8911ba0569994e101300a06082a8648ce3d040302034700304402" + "2060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30d" + "fa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b3" + "0410df30450220176386c89021f4335d953c56a0c831f98380dc198c95794a85" + "b08f0c4ba849ff022100a10114749d0c28e13a9ffe6dde6e622c33163b249ac1" + "ffb1c8e25b3cc4907e3c"); public static final String REGISTRATION_DATA_WITH_DIFFERENT_CLIENT_DATA_TYPE_BASE64 = U2fB64Encoding.encode(REGISTRATION_RESPONSE_DATA_WITH_DIFFERENT_CLIENT_DATA_TYPE); public static final byte[] KEY_HANDLE = TestUtils.HEX.decode( "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a" + "6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25"); public static final String KEY_HANDLE_BASE64 = U2fB64Encoding.encode(KEY_HANDLE); public static final byte[] USER_PUBLIC_KEY_REGISTER_HEX = TestUtils.HEX.decode( "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b65" + "7c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6" + "d9"); public static final String USER_PUBLIC_KEY_SIGN_HEX = "BNNo8bZlut48M6IPHkKcd1DVAzZgwBkRnSmqS6erwEqnyApGu-EcqMtWdNdPMfipA_a60QX7ardK7-9NuLACXh0"; public static final byte[] SIGN_RESPONSE_DATA = TestUtils.HEX.decode( "0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030c" + "e43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f" + "53c7b22272ec10047a923f"); public static final String SIGN_RESPONSE_DATA_BASE64 = U2fB64Encoding.encode(SIGN_RESPONSE_DATA); public static final byte[] EXPECTED_REGISTER_SIGNED_BYTES = TestUtils.HEX.decode( "00f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1" + "c44142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfa" + "cb2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e392" + "5a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c" + "2504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b" + "657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2" + "f6d9"); public static final byte[] EXPECTED_SIGN_SIGNED_BYTES = TestUtils.HEX.decode( "4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca" + "0100000001ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c482" + "1b3b9dbc57"); public static final byte[] SIGNATURE_REGISTER = TestUtils.HEX.decode( "304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017" + "db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804" + "a6d3d3961ef871"); public static final byte[] SIGNATURE_SIGN = TestUtils.HEX.decode( "304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de8" + "70b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272" + "ec10047a923f"); public static final byte[] SIGN_RESPONSE_INVALID_USER_PRESENCE = TestUtils.HEX.decode( "00000000013045022100adf3521ceb4e143fb3966d3017510bfbc9085a44ff13c6945aadd8" + "e26ec5cc00022004916d120830f2ee44ab3c6c58c80a3dd6f5a09b01599e686d" + "ea2e7288903cae"); public static final String SIGN_RESPONSE_INVALID_USER_PRESENCE_BASE64 = U2fB64Encoding.encode(SIGN_RESPONSE_INVALID_USER_PRESENCE); } ================================================ FILE: u2flib-server-core/src/test/resources/com/yubico/u2f/testdata/acme/attestation-certificate.der ================================================ MIIBRDCB6qADAgECAgkBkYn/////UYMwCgYIKoZIzj0EAwIwGzEZMBcGA1UEAxMQR251YmJ5IEhTTSBDQSAwMDAiGA8yMDEyMDYwMTAwMDAwMFoYDzIwNjIwNTMxMjM1OTU5WjAwMRkwFwYDVQQDExBHb29nbGUgR251YmJ5IHYwMRMwEQYDVQQtAwoAAZGJ/////1GDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHxMC8SFzqcvqg9BtdVQR5YKof7tYUO3c82B+x1mkoSw8s5IjXo1bF8ruGzTltetUhklpYlfw6o77kIRviK1fcjAKBggqhkjOPQQDAgNJADBGAiEAtMrqXcYPv58ATthPxPGFIpgcHDAxVcCCdOiJ8/EMWyMCIQD6r7TxC5L0dU47CLWvNT94SFvJA+zn6pESZPwWc7ZZjw== ================================================ FILE: u2flib-server-core/src/test/resources/com/yubico/u2f/testdata/gnubby/attestation-certificate-private-key.hex ================================================ f3fccc0d00d8031954f90864d43c247f4bf5f0665c6b50cc17749a27d1cf7664 ================================================ FILE: u2flib-server-core/src/test/resources/com/yubico/u2f/testdata/gnubby/attestation-certificate.der ================================================ MIIBPDCB5KADAgECAgpHkBKAABFVlXNSMAoGCCqGSM49BAMCMBcxFTATBgNVBAMTDEdudWJieSBQaWxvdDAeFw0xMjA4MTQxODI5MzJaFw0xMzA4MTQxODI5MzJaMDExLzAtBgNVBAMTJlBpbG90R251YmJ5LTAuNC4xLTQ3OTAxMjgwMDAxMTU1OTU3MzUyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjWF+ZclQjmS8xWc6yCpnmdo8FEZoLCWMRj//31jf0vo+bDeLU9eVxKTf+0GZ7deGLyOrrwIDtLiRG6BWmZThATAKBggqhkjOPQQDAgNHADBEAiBgzbYGHpwiJi0arB2W2McIKbI2ZTHdomiDLLg2vNMN+gIgYxsUWfCeYzAFVyLI2Jt/SIg7kIm4jWDR2XlZArMEEN8= ================================================ FILE: u2flib-server-demo/README ================================================ == u2flib-server-demo A simple self-contained demo server supporting multiple devices per user. The central part is the https://github.com/Yubico/java-u2flib-server/blob/master/u2flib-server-demo/src/main/java/demo/Resource.java[Resource] class. === 1. Clone git clone https://github.com/Yubico/java-u2flib-server.git === 2. Run ./gradlew run === 3. Try it out Then point a https://www.yubico.com/support/knowledge-base/categories/articles/browsers-support-u2f/[U2F compatible web browser] to link:https://localhost:8443/assets/registerIndex.html[https://localhost:8443/assets/registerIndex.html]. NOTE: Since U2F requires a HTTPS connection, this demo server uses a dummy certificate. This will cause your browser to show a warning, which it is safe to bypass. === Troubleshooting [qanda] .Q&A Why do I get https://developers.yubico.com/U2F/Libraries/Client_error_codes.html[error code 2]?:: You are accessing the server using a URL that does not match the server's https://developers.yubico.com/U2F/App_ID.html[App ID]. The App ID is set to `https://localhost:8443` by default, but you can change this in the https://github.com/Yubico/java-u2flib-server/blob/master/u2flib-server-demo/src/main/java/demo/Resource.java[Resource] class. ================================================ FILE: u2flib-server-demo/build.gradle ================================================ description = 'U2F demo' apply plugin: 'application' mainClassName = 'demo.App' dependencies { compile( project(':u2flib-server-core'), project(':u2flib-server-attestation'), [group: 'io.dropwizard', name: 'dropwizard-core', version:'1.3.7'], [group: 'io.dropwizard', name: 'dropwizard-assets', version:'1.3.7'], [group: 'io.dropwizard', name: 'dropwizard-views', version:'1.3.7'], [group: 'io.dropwizard', name: 'dropwizard-views-freemarker', version:'1.3.7'], [group: 'org.freemarker', name: 'freemarker', version:'2.3.28'], ) } project.ext.serverConfigFile = file('config.yml') run { inputs.file serverConfigFile args 'server', project.serverConfigFile.path } ================================================ FILE: u2flib-server-demo/config.yml ================================================ server: applicationConnectors: - type: https port: 8443 keyStorePath: keystore.jks keyStorePassword: p@ssw0rd validateCerts: false adminConnectors: - type: http port: 8081 logging: level: INFO loggers: javax.ws.rs.ext.MessageBodyWriter: DEBUG appenders: - type: console ================================================ FILE: u2flib-server-demo/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: u2flib-server-demo/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: u2flib-server-demo/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: u2flib-server-demo/pom.xml ================================================ 4.0.0 u2flib-server-parent com.yubico 0.19.12 u2flib-server-demo U2F demo com.yubico u2flib-server-core 0.19.12 com.yubico u2flib-server-attestation 0.19.12 io.dropwizard dropwizard-core 1.3.7 io.dropwizard dropwizard-assets 1.3.7 io.dropwizard dropwizard-views 1.3.7 io.dropwizard dropwizard-views-freemarker 1.3.7 org.freemarker freemarker 2.3.28 org.apache.maven.plugins maven-shade-plugin 1.6 true *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA package shade ${project.artifactId} demo.App ================================================ FILE: u2flib-server-demo/src/main/java/demo/App.java ================================================ package demo; import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import io.dropwizard.views.ViewBundle; public class App extends Application { @Override public void initialize(Bootstrap bootstrap) { bootstrap.addBundle(new ViewBundle()); bootstrap.addBundle(new AssetsBundle()); } @Override public void run(Config config, Environment environment) throws Exception { environment.jersey().register(new Resource()); } public static void main(String... args) throws Exception { new App().run(args); } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/Config.java ================================================ package demo; import io.dropwizard.Configuration; public class Config extends Configuration { } ================================================ FILE: u2flib-server-demo/src/main/java/demo/Resource.java ================================================ package demo; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.yubico.u2f.U2F; import com.yubico.u2f.attestation.Attestation; import com.yubico.u2f.attestation.MetadataService; import com.yubico.u2f.data.DeviceRegistration; import com.yubico.u2f.data.messages.SignRequest; import com.yubico.u2f.data.messages.SignRequestData; import com.yubico.u2f.data.messages.SignResponse; import com.yubico.u2f.data.messages.RegisterRequestData; import com.yubico.u2f.data.messages.RegisterResponse; import com.yubico.u2f.exceptions.DeviceCompromisedException; import com.yubico.u2f.exceptions.NoEligibleDevicesException; import com.yubico.u2f.exceptions.U2fAuthenticationException; import com.yubico.u2f.exceptions.U2fBadConfigurationException; import com.yubico.u2f.exceptions.U2fBadInputException; import com.yubico.u2f.exceptions.U2fRegistrationException; import demo.view.AuthenticationView; import demo.view.FinishAuthenticationView; import demo.view.FinishRegistrationView; import demo.view.RegistrationView; import io.dropwizard.views.View; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @Path("/") @Produces(MediaType.TEXT_HTML) public class Resource { public static final String APP_ID = "https://localhost:8443"; private final Map requestStorage = new HashMap(); private final LoadingCache> userStorage = CacheBuilder.newBuilder().build(new CacheLoader>() { @Override public Map load(String key) throws Exception { return new HashMap(); } }); private final U2F u2f = new U2F(); private final MetadataService metadataService = new MetadataService(); @Path("startRegistration") @GET public View startRegistration(@QueryParam("username") String username) throws U2fBadConfigurationException, U2fBadInputException { RegisterRequestData registerRequestData = u2f.startRegistration(APP_ID, getRegistrations(username)); requestStorage.put(registerRequestData.getRequestId(), registerRequestData.toJson()); return new RegistrationView(registerRequestData.toJson(), username); } @Path("finishRegistration") @POST public View finishRegistration(@FormParam("tokenResponse") String response, @FormParam("username") String username) throws CertificateException, U2fBadInputException, U2fRegistrationException { RegisterResponse registerResponse = RegisterResponse.fromJson(response); RegisterRequestData registerRequestData = RegisterRequestData.fromJson(requestStorage.remove(registerResponse.getRequestId())); DeviceRegistration registration = u2f.finishRegistration(registerRequestData, registerResponse); Attestation attestation = metadataService.getAttestation(registration.getAttestationCertificate()); addRegistration(username, registration); return new FinishRegistrationView(attestation, registration); } @Path("startAuthentication") @GET public View startAuthentication(@QueryParam("username") String username) throws U2fBadConfigurationException, U2fBadInputException { try { SignRequestData signRequestData = u2f.startSignature(APP_ID, getRegistrations(username)); requestStorage.put(signRequestData.getRequestId(), signRequestData.toJson()); return new AuthenticationView(signRequestData, username); } catch (NoEligibleDevicesException e) { return new AuthenticationView(new SignRequestData(APP_ID, "", Collections.emptyList()), username); } } @Path("finishAuthentication") @POST public View finishAuthentication(@FormParam("tokenResponse") String response, @FormParam("username") String username) throws U2fBadInputException { SignResponse signResponse = SignResponse.fromJson(response); SignRequestData authenticateRequest = SignRequestData.fromJson(requestStorage.remove(signResponse.getRequestId())); DeviceRegistration registration = null; try { registration = u2f.finishSignature(authenticateRequest, signResponse, getRegistrations(username)); } catch (DeviceCompromisedException e) { registration = e.getDeviceRegistration(); return new FinishAuthenticationView(false, "Device possibly compromised and therefore blocked: " + e.getMessage()); } catch (U2fAuthenticationException e) { return new FinishAuthenticationView(false, "Authentication failed: " + e.getCause().getMessage()); } finally { userStorage.getUnchecked(username).put(registration.getKeyHandle(), registration.toJson()); } return new FinishAuthenticationView(true); } private Iterable getRegistrations(String username) throws U2fBadInputException { List registrations = new ArrayList(); for (String serialized : userStorage.getUnchecked(username).values()) { registrations.add(DeviceRegistration.fromJson(serialized)); } return registrations; } private void addRegistration(String username, DeviceRegistration registration) { userStorage.getUnchecked(username).put(registration.getKeyHandle(), registration.toJson()); } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/U2fDemoException.java ================================================ package demo; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; public class U2fDemoException extends WebApplicationException { public U2fDemoException() { super(Response.status(Response.Status.NOT_FOUND).build()); } public U2fDemoException(String message) { super(Response.status(Response.Status.NOT_FOUND). entity(message).type("text/plain").build()); } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/view/AuthenticationView.java ================================================ package demo.view; import com.yubico.u2f.data.messages.SignRequestData; import io.dropwizard.views.View; import static com.google.common.base.Preconditions.checkNotNull; public class AuthenticationView extends View { private final SignRequestData data; private final String username; public SignRequestData getData() { return data; } public String getDataJson() { return data.toJson(); } public String getUsername() { return username; } public AuthenticationView(SignRequestData data, String username) { super("authenticate.ftl"); this.data = checkNotNull(data); this.username = checkNotNull(username); } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/view/FinishAuthenticationView.java ================================================ package demo.view; import io.dropwizard.views.View; import java.util.Collections; import java.util.List; import lombok.Getter; @Getter public class FinishAuthenticationView extends View { private final boolean success; private final List messages; public FinishAuthenticationView(boolean success, List messages) { super("finishAuthentication.ftl"); this.success = success; this.messages = messages; } public FinishAuthenticationView(boolean success, String message) { this(success, Collections.singletonList(message)); } public FinishAuthenticationView(boolean success) { this(success, Collections.emptyList()); } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/view/FinishRegistrationView.java ================================================ package demo.view; import com.yubico.u2f.attestation.Attestation; import com.yubico.u2f.data.DeviceRegistration; import io.dropwizard.views.View; import lombok.Getter; @Getter public class FinishRegistrationView extends View { private Attestation attestation; private DeviceRegistration registration; public FinishRegistrationView(Attestation attestation, DeviceRegistration registration) { super("finishRegistration.ftl"); this.attestation = attestation; this.registration = registration; } } ================================================ FILE: u2flib-server-demo/src/main/java/demo/view/RegistrationView.java ================================================ package demo.view; import io.dropwizard.views.View; import static com.google.common.base.Preconditions.checkNotNull; public class RegistrationView extends View { private final String username; private final String data; public String getUsername() { return username; } public String getData() { return data; } public RegistrationView(String data, String username) { super("register.ftl"); this.data = checkNotNull(data); this.username = checkNotNull(username); } } ================================================ FILE: u2flib-server-demo/src/main/resources/assets/loginIndex.html ================================================

Login

Navigation

================================================ FILE: u2flib-server-demo/src/main/resources/assets/registerIndex.html ================================================

Register

Navigation

================================================ FILE: u2flib-server-demo/src/main/resources/assets/u2f-api-1.1.js ================================================ //Copyright 2014-2015 Google Inc. All rights reserved. //Use of this source code is governed by a BSD-style //license that can be found in the LICENSE file or at //https://developers.google.com/open-source/licenses/bsd /** * @fileoverview The U2F api. */ 'use strict'; /** * Namespace for the U2F api. * @type {Object} */ var u2f = u2f || {}; /** * FIDO U2F Javascript API Version * @number */ var js_api_version; /** * The U2F extension id * @const {string} */ // The Chrome packaged app extension ID. // Uncomment this if you want to deploy a server instance that uses // the package Chrome app and does not require installing the U2F Chrome extension. u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; // The U2F Chrome extension ID. // Uncomment this if you want to deploy a server instance that uses // the U2F Chrome extension to authenticate. // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; /** * Message types for messsages to/from the extension * @const * @enum {string} */ u2f.MessageTypes = { 'U2F_REGISTER_REQUEST': 'u2f_register_request', 'U2F_REGISTER_RESPONSE': 'u2f_register_response', 'U2F_SIGN_REQUEST': 'u2f_sign_request', 'U2F_SIGN_RESPONSE': 'u2f_sign_response', 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' }; /** * Response status codes * @const * @enum {number} */ u2f.ErrorCodes = { 'OK': 0, 'OTHER_ERROR': 1, 'BAD_REQUEST': 2, 'CONFIGURATION_UNSUPPORTED': 3, 'DEVICE_INELIGIBLE': 4, 'TIMEOUT': 5 }; /** * A message for registration requests * @typedef {{ * type: u2f.MessageTypes, * appId: ?string, * timeoutSeconds: ?number, * requestId: ?number * }} */ u2f.U2fRequest; /** * A message for registration responses * @typedef {{ * type: u2f.MessageTypes, * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), * requestId: ?number * }} */ u2f.U2fResponse; /** * An error object for responses * @typedef {{ * errorCode: u2f.ErrorCodes, * errorMessage: ?string * }} */ u2f.Error; /** * Data object for a single sign request. * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} */ u2f.Transport; /** * Data object for a single sign request. * @typedef {Array} */ u2f.Transports; /** * Data object for a single sign request. * @typedef {{ * version: string, * challenge: string, * keyHandle: string, * appId: string * }} */ u2f.SignRequest; /** * Data object for a sign response. * @typedef {{ * keyHandle: string, * signatureData: string, * clientData: string * }} */ u2f.SignResponse; /** * Data object for a registration request. * @typedef {{ * version: string, * challenge: string * }} */ u2f.RegisterRequest; /** * Data object for a registration response. * @typedef {{ * version: string, * keyHandle: string, * transports: Transports, * appId: string * }} */ u2f.RegisterResponse; /** * Data object for a registered key. * @typedef {{ * version: string, * keyHandle: string, * transports: ?Transports, * appId: ?string * }} */ u2f.RegisteredKey; /** * Data object for a get API register response. * @typedef {{ * js_api_version: number * }} */ u2f.GetJsApiVersionResponse; //Low level MessagePort API support /** * Sets up a MessagePort to the U2F extension using the * available mechanisms. * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback */ u2f.getMessagePort = function(callback) { if (typeof chrome != 'undefined' && chrome.runtime) { // The actual message here does not matter, but we need to get a reply // for the callback to run. Thus, send an empty signature request // in order to get a failure response. var msg = { type: u2f.MessageTypes.U2F_SIGN_REQUEST, signRequests: [] }; chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { if (!chrome.runtime.lastError) { // We are on a whitelisted origin and can talk directly // with the extension. u2f.getChromeRuntimePort_(callback); } else { // chrome.runtime was available, but we couldn't message // the extension directly, use iframe u2f.getIframePort_(callback); } }); } else if (u2f.isAndroidChrome_()) { u2f.getAuthenticatorPort_(callback); } else if (u2f.isIosChrome_()) { u2f.getIosPort_(callback); } else { // chrome.runtime was not available at all, which is normal // when this origin doesn't have access to any extensions. u2f.getIframePort_(callback); } }; /** * Detect chrome running on android based on the browser's useragent. * @private */ u2f.isAndroidChrome_ = function() { var userAgent = navigator.userAgent; return userAgent.indexOf('Chrome') != -1 && userAgent.indexOf('Android') != -1; }; /** * Detect chrome running on iOS based on the browser's platform. * @private */ u2f.isIosChrome_ = function() { return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; }; /** * Connects directly to the extension via chrome.runtime.connect. * @param {function(u2f.WrappedChromeRuntimePort_)} callback * @private */ u2f.getChromeRuntimePort_ = function(callback) { var port = chrome.runtime.connect(u2f.EXTENSION_ID, {'includeTlsChannelId': true}); setTimeout(function() { callback(new u2f.WrappedChromeRuntimePort_(port)); }, 0); }; /** * Return a 'port' abstraction to the Authenticator app. * @param {function(u2f.WrappedAuthenticatorPort_)} callback * @private */ u2f.getAuthenticatorPort_ = function(callback) { setTimeout(function() { callback(new u2f.WrappedAuthenticatorPort_()); }, 0); }; /** * Return a 'port' abstraction to the iOS client app. * @param {function(u2f.WrappedIosPort_)} callback * @private */ u2f.getIosPort_ = function(callback) { setTimeout(function() { callback(new u2f.WrappedIosPort_()); }, 0); }; /** * A wrapper for chrome.runtime.Port that is compatible with MessagePort. * @param {Port} port * @constructor * @private */ u2f.WrappedChromeRuntimePort_ = function(port) { this.port_ = port; }; /** * Format and return a sign request compliant with the JS API version supported by the extension. * @param {Array} signRequests * @param {number} timeoutSeconds * @param {number} reqId * @return {Object} */ u2f.formatSignRequest_ = function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { if (js_api_version === undefined || js_api_version < 1.1) { // Adapt request to the 1.0 JS API var signRequests = []; for (var i = 0; i < registeredKeys.length; i++) { signRequests[i] = { version: registeredKeys[i].version, challenge: challenge, keyHandle: registeredKeys[i].keyHandle, appId: appId }; } return { type: u2f.MessageTypes.U2F_SIGN_REQUEST, signRequests: signRequests, timeoutSeconds: timeoutSeconds, requestId: reqId }; } // JS 1.1 API return { type: u2f.MessageTypes.U2F_SIGN_REQUEST, appId: appId, challenge: challenge, registeredKeys: registeredKeys, timeoutSeconds: timeoutSeconds, requestId: reqId }; }; /** * Format and return a register request compliant with the JS API version supported by the extension.. * @param {Array} signRequests * @param {Array} signRequests * @param {number} timeoutSeconds * @param {number} reqId * @return {Object} */ u2f.formatRegisterRequest_ = function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { if (js_api_version === undefined || js_api_version < 1.1) { // Adapt request to the 1.0 JS API for (var i = 0; i < registerRequests.length; i++) { registerRequests[i].appId = appId; } var signRequests = []; for (var i = 0; i < registeredKeys.length; i++) { signRequests[i] = { version: registeredKeys[i].version, challenge: registerRequests[0], keyHandle: registeredKeys[i].keyHandle, appId: appId }; } return { type: u2f.MessageTypes.U2F_REGISTER_REQUEST, signRequests: signRequests, registerRequests: registerRequests, timeoutSeconds: timeoutSeconds, requestId: reqId }; } // JS 1.1 API return { type: u2f.MessageTypes.U2F_REGISTER_REQUEST, appId: appId, registerRequests: registerRequests, registeredKeys: registeredKeys, timeoutSeconds: timeoutSeconds, requestId: reqId }; }; /** * Posts a message on the underlying channel. * @param {Object} message */ u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { this.port_.postMessage(message); }; /** * Emulates the HTML 5 addEventListener interface. Works only for the * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedChromeRuntimePort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name == 'message' || name == 'onmessage') { this.port_.onMessage.addListener(function(message) { // Emulate a minimal MessageEvent object handler({'data': message}); }); } else { console.error('WrappedChromeRuntimePort only supports onMessage'); } }; /** * Wrap the Authenticator app with a MessagePort interface. * @constructor * @private */ u2f.WrappedAuthenticatorPort_ = function() { this.requestId_ = -1; this.requestObject_ = null; } /** * Launch the Authenticator intent. * @param {Object} message */ u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { var intentUrl = u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + ';end'; document.location = intentUrl; }; /** * Tells what type of port this is. * @return {String} port type */ u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { return "WrappedAuthenticatorPort_"; }; /** * Emulates the HTML 5 addEventListener interface. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name == 'message') { var self = this; /* Register a callback to that executes when * chrome injects the response. */ window.addEventListener( 'message', self.onRequestUpdate_.bind(self, handler), false); } else { console.error('WrappedAuthenticatorPort only supports message'); } }; /** * Callback invoked when a response is received from the Authenticator. * @param function({data: Object}) callback * @param {Object} message message Object */ u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = function(callback, message) { var messageObject = JSON.parse(message.data); var intentUrl = messageObject['intentURL']; var errorCode = messageObject['errorCode']; var responseObject = null; if (messageObject.hasOwnProperty('data')) { responseObject = /** @type {Object} */ ( JSON.parse(messageObject['data'])); } callback({'data': responseObject}); }; /** * Base URL for intents to Authenticator. * @const * @private */ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; /** * Wrap the iOS client app with a MessagePort interface. * @constructor * @private */ u2f.WrappedIosPort_ = function() {}; /** * Launch the iOS client app request * @param {Object} message */ u2f.WrappedIosPort_.prototype.postMessage = function(message) { var str = JSON.stringify(message); var url = "u2f://auth?" + encodeURI(str); location.replace(url); }; /** * Tells what type of port this is. * @return {String} port type */ u2f.WrappedIosPort_.prototype.getPortType = function() { return "WrappedIosPort_"; }; /** * Emulates the HTML 5 addEventListener interface. * @param {string} eventName * @param {function({data: Object})} handler */ u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { var name = eventName.toLowerCase(); if (name !== 'message') { console.error('WrappedIosPort only supports message'); } }; /** * Sets up an embedded trampoline iframe, sourced from the extension. * @param {function(MessagePort)} callback * @private */ u2f.getIframePort_ = function(callback) { // Create the iframe var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; var iframe = document.createElement('iframe'); iframe.src = iframeOrigin + '/u2f-comms.html'; iframe.setAttribute('style', 'display:none'); document.body.appendChild(iframe); var channel = new MessageChannel(); var ready = function(message) { if (message.data == 'ready') { channel.port1.removeEventListener('message', ready); callback(channel.port1); } else { console.error('First event on iframe port was not "ready"'); } }; channel.port1.addEventListener('message', ready); channel.port1.start(); iframe.addEventListener('load', function() { // Deliver the port to the iframe and initialize iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); }); }; //High-level JS API /** * Default extension response timeout in seconds. * @const */ u2f.EXTENSION_TIMEOUT_SEC = 30; /** * A singleton instance for a MessagePort to the extension. * @type {MessagePort|u2f.WrappedChromeRuntimePort_} * @private */ u2f.port_ = null; /** * Callbacks waiting for a port * @type {Array} * @private */ u2f.waitingForPort_ = []; /** * A counter for requestIds. * @type {number} * @private */ u2f.reqCounter_ = 0; /** * A map from requestIds to client callbacks * @type {Object.} * @private */ u2f.callbackMap_ = {}; /** * Creates or retrieves the MessagePort singleton to use. * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback * @private */ u2f.getPortSingleton_ = function(callback) { if (u2f.port_) { callback(u2f.port_); } else { if (u2f.waitingForPort_.length == 0) { u2f.getMessagePort(function(port) { u2f.port_ = port; u2f.port_.addEventListener('message', /** @type {function(Event)} */ (u2f.responseHandler_)); // Careful, here be async callbacks. Maybe. while (u2f.waitingForPort_.length) u2f.waitingForPort_.shift()(u2f.port_); }); } u2f.waitingForPort_.push(callback); } }; /** * Handles response messages from the extension. * @param {MessageEvent.} message * @private */ u2f.responseHandler_ = function(message) { var response = message.data; var reqId = response['requestId']; if (!reqId || !u2f.callbackMap_[reqId]) { console.error('Unknown or missing requestId in response.'); return; } var cb = u2f.callbackMap_[reqId]; delete u2f.callbackMap_[reqId]; cb(response['responseData']); }; /** * Dispatches an array of sign requests to available U2F tokens. * If the JS API version supported by the extension is unknown, it first sends a * message to the extension to find out the supported API version and then it sends * the sign request. * @param {string=} appId * @param {string=} challenge * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.SignResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { if (js_api_version === undefined) { // Send a message to get the extension to JS API version, then send the actual sign request. u2f.getApiVersion( function (response) { js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; console.log("Extension JS API Version: ", js_api_version); u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); }); } else { // We know the JS API version. Send the actual sign request in the supported API version. u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); } }; /** * Dispatches an array of sign requests to available U2F tokens. * @param {string=} appId * @param {string=} challenge * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.SignResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); port.postMessage(req); }); }; /** * Dispatches register requests to available U2F tokens. An array of sign * requests identifies already registered tokens. * If the JS API version supported by the extension is unknown, it first sends a * message to the extension to find out the supported API version and then it sends * the register request. * @param {string=} appId * @param {Array} registerRequests * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.RegisterResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { if (js_api_version === undefined) { // Send a message to get the extension to JS API version, then send the actual register request. u2f.getApiVersion( function (response) { js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; console.log("Extension JS API Version: ", js_api_version); u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds); }); } else { // We know the JS API version. Send the actual register request in the supported API version. u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds); } }; /** * Dispatches register requests to available U2F tokens. An array of sign * requests identifies already registered tokens. * @param {string=} appId * @param {Array} registerRequests * @param {Array} registeredKeys * @param {function((u2f.Error|u2f.RegisterResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); var req = u2f.formatRegisterRequest_( appId, registeredKeys, registerRequests, timeoutSeconds, reqId); port.postMessage(req); }); }; /** * Dispatches a message to the extension to find out the supported * JS API version. * If the user is on a mobile phone and is thus using Google Authenticator instead * of the Chrome extension, don't send the request and simply return 0. * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback * @param {number=} opt_timeoutSeconds */ u2f.getApiVersion = function(callback, opt_timeoutSeconds) { u2f.getPortSingleton_(function(port) { // If we are using Android Google Authenticator or iOS client app, // do not fire an intent to ask which JS API version to use. if (port.getPortType) { var apiVersion; switch (port.getPortType()) { case 'WrappedIosPort_': case 'WrappedAuthenticatorPort_': apiVersion = 1.1; break; default: apiVersion = 0; break; } callback({ 'js_api_version': apiVersion }); return; } var reqId = ++u2f.reqCounter_; u2f.callbackMap_[reqId] = callback; var req = { type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), requestId: reqId }; port.postMessage(req); }); }; ================================================ FILE: u2flib-server-demo/src/main/resources/demo/view/authenticate.ftl ================================================ Java U2F Demo <#list data.getSignRequests() as signRequests>

Touch your U2F token to authenticate.

<#break> <#else>

No devices are registered for this account.

<#include "navigation.ftl"> ================================================ FILE: u2flib-server-demo/src/main/resources/demo/view/finishAuthentication.ftl ================================================ Java U2F Demo <#if success>

Successfully authenticated!

<#list messages as message>

${message}

<#include "navigation.ftl"> ================================================ FILE: u2flib-server-demo/src/main/resources/demo/view/finishRegistration.ftl ================================================ Java U2F Demo

Successfully registered device:

<#list attestation.getVendorProperties()>

Vendor metadata

<#items as key, value>
                ${key}: ${value}
            
<#else>

No vendor metadata present!

<#list attestation.getDeviceProperties()>

Device metadata

<#items as key, value>
                ${key}: ${value}
            
<#else>

No device metadata present!

<#list attestation.getTransports()>

Device transports: <#items as item>${item}<#sep>,

<#else>

No device transports reported!

Registration data

 ${registration} 
<#include "navigation.ftl"> ================================================ FILE: u2flib-server-demo/src/main/resources/demo/view/navigation.ftl ================================================

Navigation

================================================ FILE: u2flib-server-demo/src/main/resources/demo/view/register.ftl ================================================ Java U2F Demo

Touch your U2F token.

<#include "navigation.ftl">