Commit d4258765 authored by 0Tyler's avatar 0Tyler

Initial commit

parents
target/
.settings/
.classpath
.project
src/main/java/META-INF/
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="gaedemo" />
</profile>
</annotationProcessing>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8.0_221" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# WebAuthnDemo
An example Java Relying Party implementation of the [WebAuthn
specification](https://w3c.github.io/webauthn/).
### Install
The demo is written on top of Google App Engine. Run
```sh
$ mvn appengine:devserver
```
for a local development server instance.
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="google-app-engine" name="Google App Engine">
<configuration />
</facet>
<facet type="app-engine-standard" name="Google App Engine Standard">
<configuration />
</facet>
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<groupId>com.google.webauthn</groupId>
<artifactId>gaedemo</artifactId>
<!-- [START set_versions] -->
<properties>
<appengine.app.version>1</appengine.app.version>
<appengine.sdk.version>1.9.73</appengine.sdk.version>
<objectify.version>5.1.22</objectify.version>
<guava.version>30.0-jre</guava.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<prerequisites>
<maven>3.1.0</maven>
</prerequisites>
<!-- [END set_versions] -->
<dependencies>
<!-- Compile/runtime dependencies -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-datastore</artifactId>
<version>1.71.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.google.endpoints</groupId>
<artifactId>endpoints-framework</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-1.0-sdk</artifactId>
<version>${appengine.sdk.version}</version>
</dependency>
<dependency>
<groupId>co.nstant.in</groupId>
<artifactId>cbor</artifactId>
<version>0.8</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.google.endpoints</groupId>
<artifactId>endpoints-management-control-appengine</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>com.google.endpoints</groupId>
<artifactId>endpoints-framework-auth</artifactId>
<version>1.0.11</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- [START Objectify_Dependencies] -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.objectify</groupId>
<artifactId>objectify</artifactId>
<version>${objectify.version}</version>
</dependency>
<!-- [END Objectify_Dependencies] -->
<!-- Test Dependencies -->
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-testing</artifactId>
<version>${appengine.sdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-stubs</artifactId>
<version>${appengine.sdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.67</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-junit</artifactId>
<version>2.0.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.28.0</version>
<exclusions>
<exclusion> <!-- declare the exclusion here -->
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
<build>
<!-- for hot reload of the web application -->
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>display-dependency-updates</goal>
<goal>display-plugin-updates</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archiveClasses>true</archiveClasses>
<webResources>
<!-- in order to interpolate version from pom into appengine-web.xml -->
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<filtering>true</filtering>
<targetPath>WEB-INF</targetPath>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>${appengine.sdk.version}</version>
<configuration>
<enableJarClasses>false</enableJarClasses>
<!-- Comment in the below snippet to bind to all IPs instead of
just localhost -->
<address>0.0.0.0</address>
<port>8080</port>
<!-- Comment in the below snippet to enable local debugging with
a remote debugger like those included with Eclipse or IntelliJ -->
<jvmFlags>
<jvmFlag>-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n</jvmFlag>
<jvmFlag>-Dappengine.fullscan.seconds=5</jvmFlag>
</jvmFlags>
<devserverVersion>2-alpha</devserverVersion>
<automaticRestart>true</automaticRestart>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>errorprone</id>
<activation>
<jdk>[1.8,</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<compilerId>javac-with-errorprone</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<showWarnings>true</showWarnings>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac-errorprone</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
mvn appengine:devserver
\ No newline at end of file
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.webauthn.gaedemo.crypto;
import com.google.webauthn.gaedemo.objects.Algorithm;
import org.jose4j.jws.BaseSignatureAlgorithm;
import org.jose4j.jws.EcdsaUsingShaAlgorithm;
import org.jose4j.jws.RsaUsingShaAlgorithm;
import java.util.HashMap;
import java.util.Map;
public class AlgorithmIdentifierMapper {
private static final Map<Algorithm, BaseSignatureAlgorithm> map = new HashMap<>();
static {
map.put(Algorithm.ES256, new EcdsaUsingShaAlgorithm.EcdsaP256UsingSha256());
map.put(Algorithm.ES384, new EcdsaUsingShaAlgorithm.EcdsaP384UsingSha384());
map.put(Algorithm.ES512, new EcdsaUsingShaAlgorithm.EcdsaP521UsingSha512());
map.put(Algorithm.RS256, new RsaUsingShaAlgorithm.RsaSha256());
map.put(Algorithm.RS384, new RsaUsingShaAlgorithm.RsaSha384());
map.put(Algorithm.RS512, new RsaUsingShaAlgorithm.RsaSha512());
map.put(Algorithm.PS256, new RsaUsingShaAlgorithm.RsaPssSha256());
map.put(Algorithm.PS384, new RsaUsingShaAlgorithm.RsaPssSha384());
map.put(Algorithm.PS512, new RsaUsingShaAlgorithm.RsaPssSha512());
}
public static BaseSignatureAlgorithm get(Algorithm algorithm) {
return map.get(algorithm);
}
}
/*
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.webauthn.gaedemo.crypto;
import com.google.webauthn.gaedemo.objects.CablePairingData;
import com.google.webauthn.gaedemo.objects.CableSessionData;
import org.bouncycastle.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Random;
public class Cable {
private static final byte[] HMAC_TAG_CLIENT_EID =
"client".getBytes(StandardCharsets.UTF_8);
private static final byte[] HMAC_TAG_AUTHENTICATOR_EID =
"authenticator".getBytes(StandardCharsets.UTF_8);
private static final byte[] HKDF_INFO_SESSION_PRE_KEY =
"FIDO caBLE v1 sessionPreKey".getBytes(StandardCharsets.UTF_8);
private final Random random;
public Cable() {
this(new SecureRandom());
}
Cable(Random random) {
this.random = random;
}
public CableSessionData generateSessionData(CablePairingData pairingData) {
byte[] nonce = new byte[8];
random.nextBytes(nonce);
byte[] clientEidHash = Crypto.hmacSha256(pairingData.irk,
Arrays.concatenate(nonce, HMAC_TAG_CLIENT_EID), 8);
byte[] clientEid = Arrays.concatenate(nonce, clientEidHash);
byte[] authenticatorEid = Crypto.hmacSha256(pairingData.irk,
Arrays.concatenate(clientEid, HMAC_TAG_AUTHENTICATOR_EID), 16);
byte[] sessionPreKey = Crypto.hkdfSha256(pairingData.lk, nonce,
HKDF_INFO_SESSION_PRE_KEY, 32);
return new CableSessionData(pairingData.version, clientEid, authenticatorEid, sessionPreKey);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.crypto;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import javax.crypto.KeyAgreement;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.HKDFParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.jose4j.jws.EcdsaUsingShaAlgorithm;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.RsaKey;
public class Crypto {
private static int EC_COORDINATE_LENGTH = 32;
private static int EC_PUBLIC_KEY_LENGTH = 65;
private static byte EC_PUBLIC_KEY_PREFIX = 0x4;
static {
Security.addProvider(new BouncyCastleProvider());
}
public static byte[] sha256Digest(byte[] input) {
SHA256Digest digest = new SHA256Digest();
digest.update(input, 0, input.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
public static byte[] hmacSha256(byte[] key, byte[] data, int outputLength) {
HMac hmac = new HMac(new SHA256Digest());
hmac.init(new KeyParameter(key));
hmac.update(data, 0, data.length);
byte[] output = new byte[hmac.getMacSize()];
hmac.doFinal(output, 0);
return Arrays.copyOf(output, outputLength);
}
public static byte[] hkdfSha256(byte[] ikm, byte[] salt, byte[] info, int outputLength) {
byte[] output = new byte[outputLength];
HKDFParameters params = new HKDFParameters(ikm, salt, info);
HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
hkdf.init(params);
hkdf.generateBytes(output, 0, outputLength);
return output;
}
public static byte[] digest(byte[] input, String alg) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(alg);
return digest.digest(input);
}
public static boolean verifySignature(PublicKey publicKey, byte[] signedBytes, byte[] signature)
throws WebAuthnException {
try {
Signature ecdsaSignature = Signature.getInstance("SHA256withECDSA");
ecdsaSignature.initVerify(publicKey);
ecdsaSignature.update(signedBytes);
return ecdsaSignature.verify(signature);
} catch (InvalidKeyException e) {
throw new WebAuthnException("Error when verifying signature", e);
} catch (SignatureException e) {
throw new WebAuthnException("Error when verifying signature", e);
} catch (NoSuchAlgorithmException e) {
throw new WebAuthnException("Error when verifying signature", e);
}
}
// TODO add test for this.
public static boolean verifySignature(X509Certificate attestationCertificate, byte[] signedBytes,
byte[] signature) throws WebAuthnException {
return verifySignature(attestationCertificate.getPublicKey(), signedBytes, signature);
}
public static boolean verifySignature(X509Certificate attestationCertificate, byte[] signedBytes,
byte[] signature, String signatureAlgorithm) throws WebAuthnException {
return verifySignature(attestationCertificate.getPublicKey(), signedBytes, signature,
signatureAlgorithm);
}
public static boolean verifySignature(PublicKey publicKey, byte[] message, byte[] signature,
String signatureAlgorithm) throws WebAuthnException {
if (signatureAlgorithm == null || signatureAlgorithm.isEmpty()) {
throw new WebAuthnException("Signature algorithm is null or empty");
}
try {
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initVerify(publicKey);
sig.update(message);
return sig.verify(signature);
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new WebAuthnException("Error when verifying signature", e);
}
}
public static PublicKey decodePublicKey(byte[] encodedPublicKey) throws WebAuthnException {
return decodePublicKey(Arrays.copyOfRange(encodedPublicKey, 1, 1 + EC_COORDINATE_LENGTH),
Arrays.copyOfRange(encodedPublicKey, 1 + EC_COORDINATE_LENGTH, encodedPublicKey.length));
}
public static PublicKey decodePublicKey(byte[] x, byte[] y) throws WebAuthnException {
try {
X9ECParameters curve = SECNamedCurves.getByName("secp256r1");
ECPoint point;
try {
byte[] encodedPublicKey = Bytes.concat(new byte[] {0x04}, x, y);
point = curve.getCurve().decodePoint(encodedPublicKey);
} catch (RuntimeException e) {
throw new WebAuthnException("Couldn't parse user public key", e);
}
return KeyFactory.getInstance("ECDSA").generatePublic(new ECPublicKeySpec(point,
new ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), curve.getH())));
} catch (InvalidKeySpecException e) {
throw new WebAuthnException("Error when decoding public key", e);
} catch (NoSuchAlgorithmException e) {
throw new WebAuthnException("Error when decoding public key", e);
}
}
public static PublicKey getRSAPublicKey(RsaKey rsaKey) throws WebAuthnException {
BigInteger modulus = new BigInteger(Bytes.concat(new byte[] {0}, rsaKey.getN()));
BigInteger publicExponent = new BigInteger(rsaKey.getE());
try {
return getRSAPublicKey(modulus, publicExponent);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new WebAuthnException("Error when generate RSA public key", e);
}
}
public static byte[] compressECPublicKey(ECPublicKey publicKey) {
byte[] x = publicKey.getW().getAffineX().toByteArray();
byte[] y = publicKey.getW().getAffineY().toByteArray();
byte[] output = new byte[EC_PUBLIC_KEY_LENGTH];
System.arraycopy(y, y.length - EC_COORDINATE_LENGTH, output,
output.length - EC_COORDINATE_LENGTH, EC_COORDINATE_LENGTH);
System.arraycopy(x, x.length - EC_COORDINATE_LENGTH, output, 1, EC_COORDINATE_LENGTH);
output[0] = EC_PUBLIC_KEY_PREFIX;
return output;
}
public static PublicKey getECPublicKey(EccKey eccKey) throws WebAuthnException {
BigInteger x = new BigInteger(eccKey.getX());
BigInteger y = new BigInteger(eccKey.getY());
java.security.spec.ECPoint w = new java.security.spec.ECPoint(x, y);
EcdsaUsingShaAlgorithm algorithm =
(EcdsaUsingShaAlgorithm) AlgorithmIdentifierMapper.get(eccKey.getAlg());
String curveName = algorithm.getCurveName();
try {
return getECPublicKey(w, curveName);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new WebAuthnException("Error when generate EC public key", e);
}
}
public static PublicKey getRSAPublicKey(BigInteger n, BigInteger e)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec keySpec = new RSAPublicKeySpec(n, e);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
public static PublicKey getECPublicKey(java.security.spec.ECPoint w, String stdCurveName)
throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(stdCurveName);
java.security.spec.ECParameterSpec params = new ECNamedCurveSpec(parameterSpec.getName(),
parameterSpec.getCurve(), parameterSpec.getG(), parameterSpec.getN(), parameterSpec.getH(),
parameterSpec.getSeed());
KeySpec keySpec = new java.security.spec.ECPublicKeySpec(w, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(keySpec);
}
public static KeyPair generateKeyPair() {
try {
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
gen.initialize(spec);
KeyPair keyPair = gen.generateKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
public static byte[] getS(PrivateKey privateKey, byte[] publicKey) {
try {
KeyAgreement agreement = KeyAgreement.getInstance("ECDH");
agreement.init(privateKey);
agreement.doPhase(decodePublicKey(publicKey), true);
return agreement.generateSecret();
} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException
| WebAuthnException e) {
throw new RuntimeException(e);
}
}
}
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.crypto;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.util.Base64;
import com.google.api.client.util.Key;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
/**
* Sample code to verify the device attestation statement offline.
*/
public class OfflineVerify {
private static final DefaultHostnameVerifier HOSTNAME_VERIFIER = new DefaultHostnameVerifier();
/**
* Class for parsing the JSON data.
*/
public static class AttestationStatement extends JsonWebSignature.Payload {
@Key
private String nonce;
@Key
private long timestampMs;
@Key
private String apkPackageName;
@Key
private String apkDigestSha256;
@Key
private boolean ctsProfileMatch;
public byte[] getNonce() {
return Base64.decodeBase64(nonce);
}
public long getTimestampMs() {
return timestampMs;
}
public String getApkPackageName() {
return apkPackageName;
}
public byte[] getApkDigestSha256() {
return Base64.decodeBase64(apkDigestSha256);
}
public boolean isCtsProfileMatch() {
return ctsProfileMatch;
}
}
public static AttestationStatement parseAndVerify(String signedAttestationStatement) {
// Parse JSON Web Signature format.
JsonWebSignature jws;
try {
jws = JsonWebSignature.parser(JacksonFactory.getDefaultInstance())
.setPayloadClass(AttestationStatement.class).parse(signedAttestationStatement);
} catch (IOException e) {
System.err
.println("Failure: " + signedAttestationStatement + " is not valid JWS " + "format.");
return null;
}
// Verify the signature of the JWS and retrieve the signature certificate.
X509Certificate cert;
try {
cert = jws.verifySignature();
if (cert == null) {
System.err.println("Failure: Signature verification failed.");
return null;
}
} catch (GeneralSecurityException e) {
System.err.println("Failure: Error during cryptographic verification of the JWS signature.");
return null;
}
// Verify the hostname of the certificate.
if (!verifyHostname("attest.android.com", cert)) {
System.err
.println("Failure: Certificate isn't issued for the hostname attest.android" + ".com.");
return null;
}
// Extract and use the payload data.
AttestationStatement stmt = (AttestationStatement) jws.getPayload();
return stmt;
}
/**
* Verifies that the certificate matches the specified hostname. Uses the
* {@link DefaultHostnameVerifier} from the Apache HttpClient library to confirm that the hostname
* matches the certificate.
*
* @param hostname
* @param leafCert
* @return
*/
private static boolean verifyHostname(String hostname, X509Certificate leafCert) {
try {
// Check that the hostname matches the certificate. This method throws an exception if
// the cert could not be verified.
HOSTNAME_VERIFIER.verify(hostname, leafCert);
return true;
} catch (SSLException e) {
return false;
}
}
private static void process(String signedAttestationStatement) {
AttestationStatement stmt = parseAndVerify(signedAttestationStatement);
if (stmt == null) {
System.err.println("Failure: Failed to parse and verify the attestation statement.");
return;
}
System.out.println("Successfully verified the attestation statement. The content is:");
System.out.println("Nonce: " + Arrays.toString(stmt.getNonce()));
System.out.println("Timestamp: " + stmt.getTimestampMs() + " ms");
System.out.println("APK package name: " + stmt.getApkPackageName());
System.out.println("APK digest SHA256: " + Arrays.toString(stmt.getApkDigestSha256()));
System.out.println("CTS profile match: " + stmt.isCtsProfileMatch());
System.out.println("\n** This sample only shows how to verify the authenticity of an "
+ "attestation response. Next, you must check that the server response matches the "
+ "request by comparing the nonce, package name, timestamp and digest.");
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: OfflineVerify <signed attestation statement>");
return;
}
process(args[0]);
}
}
package com.google.webauthn.gaedemo.endpoints;
/**
* Contains the client IDs and scopes for allowed clients consuming FIDO2 API.
*/
public class Constants {
// Scopes: https://developers.google.com/identity/protocols/googlescopes
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
public static final String OPENID_SCOPE = "openid";
// TODO: configure Client IDs for android and web clients.
// ClientIds:
// https://cloud.google.com/endpoints/docs/frameworks/java/creating-client-ids
public static final String WEB_CLIENT_ID =
"762961289381-hbbpkaqgi1kelev5mquj4dg4n8glr59p.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID =
"762961289381-d27cfmh1st5m681lo2r7837hg0a6b0pm.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID2 =
"762961289381-87bpacb39s1pi3tsi2ahlsqvsmuh3odr.apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String APP_ID = "webauthndemo.appspot.com";
}
package com.google.webauthn.gaedemo.endpoints;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Named;
import javax.servlet.ServletException;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.users.User;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement;
import com.google.webauthn.gaedemo.objects.AttestationObject;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.FidoU2fAttestationStatement;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialCreationOptions;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialRequestOptions;
import com.google.webauthn.gaedemo.server.AndroidSafetyNetServer;
import com.google.webauthn.gaedemo.server.PackedServer;
import com.google.webauthn.gaedemo.server.Server;
import com.google.webauthn.gaedemo.server.U2fServer;
import com.google.webauthn.gaedemo.storage.Credential;
import com.google.webauthn.gaedemo.storage.SessionData;
/**
* An endpoint class for handling FIDO2 requests.
*
* Google Cloud Endpoints generate APIs and client libraries from API backend, to simplify client
* access to data from other applications.
* https://cloud.google.com/endpoints/docs/frameworks/java/get-started-frameworks-java
*/
@Api(
name = "fido2RequestHandler",
version = "v1",
scopes = {Constants.EMAIL_SCOPE, Constants.OPENID_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.ANDROID_CLIENT_ID2},
audiences = {Constants.ANDROID_AUDIENCE},
namespace = @ApiNamespace(
ownerName = "gaedemo.webauthn.google.com", ownerDomain = "gaedemo.webauthn.google.com")
)
public class Fido2RequestHandler {
@ApiMethod(name = "getRegistrationRequest", path="get/register")
public List<String> getRegistrationRequest(User user) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions(
user.getNickname() /* userName */, user.getEmail() /* userId */,
Constants.APP_ID /* rpId */, Constants.APP_ID /* rpName */);
SessionData session = new SessionData(options.challenge, Constants.APP_ID);
session.save(user.getEmail());
JsonObject sessionJson = session.getJsonObject();
JsonObject optionsJson = options.getJsonObject();
optionsJson.add("session", sessionJson);
List<String> resultList = new ArrayList<String>();
resultList.add(optionsJson.toString());
return resultList;
}
@ApiMethod(name = "processRegistrationResponse")
public List<String> processRegistrationResponse(
@Named("responseData") String responseData, User user)
throws OAuthRequestException, ResponseException, ServletException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
Gson gson = new Gson();
JsonElement element = gson.fromJson(responseData, JsonElement.class);
JsonObject object = element.getAsJsonObject();
String clientDataJSON = object.get("clientDataJSON").getAsString();
String attestationObject = object.get("attestationObject").getAsString();
AuthenticatorAttestationResponse attestation =
new AuthenticatorAttestationResponse(clientDataJSON, attestationObject);
// TODO
String credentialId = BaseEncoding.base64Url().encode(
attestation.getAttestationObject().getAuthenticatorData().getAttData().getCredentialId());
String type = null;
String session = null;
PublicKeyCredential cred = new PublicKeyCredential(credentialId, type,
BaseEncoding.base64Url().decode(credentialId), attestation);
switch (cred.getAttestationType()) {
case FIDOU2F:
U2fServer.registerCredential(cred, user.getEmail(),
session, Constants.APP_ID, Constants.APP_ID);
break;
case ANDROIDSAFETYNET:
AndroidSafetyNetServer.registerCredential(
cred, user.getEmail(), session, Constants.APP_ID);
break;
case PACKED:
PackedServer.registerCredential(cred, user.getEmail(), session, Constants.APP_ID);
break;
default:
// This should never happen.
}
Credential credential = new Credential(cred);
credential.save(user.getEmail());
List<String> resultList = new ArrayList<String>();
resultList.add(credential.toJson());
return resultList;
}
@ApiMethod(name = "getSignRequest", path="get/sign")
public List<String> getSignRequest(User user) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
PublicKeyCredentialRequestOptions assertion =
new PublicKeyCredentialRequestOptions(Constants.APP_ID);
SessionData session = new SessionData(assertion.challenge, Constants.APP_ID);
session.save(user.getEmail());
assertion.populateAllowList(user.getEmail());
JsonObject assertionJson = assertion.getJsonObject();
JsonObject sessionJson = session.getJsonObject();
assertionJson.add("session", sessionJson);
List<String> resultList = new ArrayList<String>();
resultList.add(assertionJson.toString());
return resultList;
}
@ApiMethod(name = "processSignResponse")
public List<String> processSignResponse(
@Named("responseData") String responseData, User user)
throws OAuthRequestException, ResponseException, ServletException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
Gson gson = new Gson();
JsonElement element = gson.fromJson(responseData, JsonElement.class);
JsonObject object = element.getAsJsonObject();
String clientDataJSON = object.get("clientDataJSON").getAsString();
String authenticatorData = object.get("authenticatorData").getAsString();
String credentialId = object.get("credentialId").getAsString();
String signature = object.get("signature").getAsString();
AuthenticatorAssertionResponse assertion =
new AuthenticatorAssertionResponse(clientDataJSON, authenticatorData, signature);
// TODO
String type = null;
String session = null;
PublicKeyCredential cred = new PublicKeyCredential(credentialId, type,
BaseEncoding.base64Url().decode(credentialId), assertion);
Credential savedCredential;
try {
savedCredential = Server.validateAndFindCredential(cred, user.getEmail(), session);
} catch (ResponseException e) {
throw new ServletException("Unable to validate assertion", e);
}
Server.verifyAssertion(cred, user.getEmail(), session, savedCredential);
List<String> resultList = new ArrayList<String>();
resultList.add(savedCredential.toJson());
return resultList;
}
@ApiMethod(name = "getAllSecurityKeys", path = "getAllSecurityKeys")
public String[] getAllSecurityKeys(User user) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
List<Credential> savedCreds = Credential.load(user.getEmail());
JsonArray result = new JsonArray();
for (Credential c : savedCreds) {
JsonObject cJson = new JsonObject();
cJson.addProperty("handle", BaseEncoding.base64Url().encode(c.getCredential().rawId));
// TODO
/*
try {
cJson.addProperty("publicKey", Integer.toHexString(
Crypto.decodePublicKey(ecc.getX(), ecc.getY()).hashCode()));
} catch (WebAuthnException e) {
e.printStackTrace();
continue;
}
*/
AttestationObject attObj =
((AuthenticatorAttestationResponse) c.getCredential().getResponse())
.getAttestationObject();
if (attObj.getAttestationStatement() instanceof FidoU2fAttestationStatement) {
cJson.addProperty("name", "FIDO U2F Authenticator");
} else if (attObj.getAttestationStatement() instanceof AndroidSafetyNetAttestationStatement) {
cJson.addProperty("name", "Android SafetyNet");
}
cJson.addProperty("date", c.getDate().toString());
cJson.addProperty("id", c.id);
result.add(cJson);
}
return new String[] {result.toString()};
}
@ApiMethod(name = "removeSecurityKey")
public String[] removeSecurityKey(User user, @Named("publicKey") String publicKey)
throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("User is not authenticated");
}
Credential.remove(user.getEmail(), publicKey);
return new String[] {"OK"};
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.exceptions;
public class ResponseException extends Exception {
/**
*
*/
private static final long serialVersionUID = 6341176597489797230L;
/**
* @param string
*/
public ResponseException(String string) {
super(string);
}
/**
* @param string
*/
public ResponseException(String string, Throwable cause) {
super(string, cause);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.exceptions;
public class WebAuthnException extends Exception {
/**
*
*/
private static final long serialVersionUID = 3285394858024616570L;
/**
* @param string
*/
public WebAuthnException(String string) {
super(string);
}
public WebAuthnException(String message, Throwable cause) {
super(message, cause);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
/**
* Algorithm enum differentiating between the supported asymmetric key algorithms
*/
public enum Algorithm {
ES256("ES256"), ES384("ES384"), ES512("ES512"), RS256("RS256"), RS384("RS384"), RS512(
"RS512"), PS256("PS256"), PS384("PS384"), PS512("PS512"),
UNDEFINED("undefined");
final private String name;
/**
* @param name The string representation of the algorithm name
*/
private Algorithm(String name) {
this.name = name;
}
/**
* @param alg The Algorithm to check
* @return If the Algorithm is an ECC Algorithm
*/
public static boolean isEccAlgorithm(Algorithm alg) {
return alg == ES256 || alg == ES384 || alg == ES512;
}
/**
* @param alg The Algorithm to check
* @return If the Algorithm is an RSA Algorithm
*/
public static boolean isRsaAlgorithm(Algorithm alg) {
return alg == RS256 || alg == RS384 || alg == RS512 || alg == PS256 || alg == PS384
|| alg == PS512;
}
/**
* @param s Input string to decode
* @return Transport corresponding to the input string
*/
public static Algorithm decode(String s) {
for (Algorithm t : Algorithm.values()) {
if (t.name.equals(s)) {
return t;
}
}
// COSE Algorithm Identifiers
if (s.equals("-7")) {
return ES256;
}
throw new IllegalArgumentException(s + " not a valid Algorithm");
}
/**
* @param alg Input integer to decode
* @return Transport corresponding to the input string
*/
public static Algorithm decode(int alg) {
switch (alg) {
case -7:
return ES256;
case -35:
return ES384;
case -36:
return ES512;
case -37:
return PS256;
case -38:
return PS384;
case -39:
return PS512;
case -257:
return RS256;
case -258:
return RS384;
case -259:
return RS512;
case -260:
return ES256;
case -261:
return ES512;
}
return Algorithm.UNDEFINED;
}
public int encodeToInt() {
switch (this) {
case ES256:
return -7;
case ES384:
return -35;
case ES512:
return -36;
case PS256:
return -37;
case PS384:
return -38;
case PS512:
return -39;
case RS256:
return -257;
case RS384:
return -258;
case RS512:
return -259;
default:
}
return -1;
}
@Override
public String toString() {
return name;
}
public Object toReadableString() {
return name;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.util.Arrays;
import java.util.Objects;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.googlecode.objectify.annotation.Subclass;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnicodeString;
/**
* Object representation of the Android SafetyNet attestation statement
*/
@Subclass
public class AndroidSafetyNetAttestationStatement extends AttestationStatement {
String ver;
byte[] response;
public AndroidSafetyNetAttestationStatement() {
this.ver = null;
this.response = null;
}
/**
* Decodes a cbor representation of an AndroidSafetyNetAttestationStatement into the object
* representation
*
* @param attStmt Cbor DataItem representation of the attestation statement to decode
* @return Decoded AndroidSafetyNetAttestationStatement
* @throws ResponseException Input was not a valid AndroidSafetyNetAttestationStatement DataItem
*/
public static AndroidSafetyNetAttestationStatement decode(DataItem attStmt)
throws ResponseException {
AndroidSafetyNetAttestationStatement result = new AndroidSafetyNetAttestationStatement();
Map given = (Map) attStmt;
for (DataItem data : given.getKeys()) {
if (data instanceof UnicodeString) {
if (((UnicodeString) data).getString().equals("ver")) {
UnicodeString version = (UnicodeString) given.get(data);
result.ver = version.getString();
} else if (((UnicodeString) data).getString().equals("response")) {
result.response = ((ByteString) (given.get(data))).getBytes();
}
}
}
if (result.response == null || result.ver == null)
throw new ResponseException("Invalid JWT Cbor");
return result;
}
@Override
DataItem encode() throws CborException {
Map map = new Map();
map.put(new UnicodeString("ver"), new UnicodeString(ver));
map.put(new UnicodeString("response"), new ByteString(response));
return map;
}
@Override
public int hashCode() {
return Objects.hash(ver, Arrays.hashCode(response));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AndroidSafetyNetAttestationStatement) {
AndroidSafetyNetAttestationStatement other = (AndroidSafetyNetAttestationStatement) obj;
try {
if (!ver.equals(other.ver)) {
return false;
}
} catch (NullPointerException e) {
return false;
}
return Arrays.equals(response, other.response);
}
return false;
}
/**
* Return the Google Play Services version used to create the SafetyNet attestation
*
* @return the version
*/
public String getVer() {
return ver;
}
/**
* @return the response bytes
*/
public byte[] getResponse() {
return response;
}
@Override
public String getName() {
return "Android SafetyNet";
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public enum Attachment {
PLATFORM("platform"), CROSS_PLATFORM("cross-platform");
final String name;
private Attachment(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public enum AttestationConveyancePreference {
NONE("none"), INDIRECT("indirect"), DIRECT("direct");
private final String val;
AttestationConveyancePreference(String s) {
this.val = s;
}
/**
* @param s
* @return AuthenticatorAttachment corresponding to the input string
*/
public static AttestationConveyancePreference decode(String s) {
for (AttestationConveyancePreference a : AttestationConveyancePreference.values()) {
if (a.val.equals(s)) {
return a;
}
}
throw new IllegalArgumentException(s + " not a valid AttestationConveyancePreference");
}
@Override
public String toString() {
return this.val;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import co.nstant.in.cbor.CborException;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
/**
* Object representation of the attestation data
*/
public class AttestationData {
byte[] aaguid;
byte[] credentialId;
CredentialPublicKey publicKey;
public AttestationData() {
aaguid = new byte[16];
credentialId = new byte[] {};
publicKey = new EccKey();
}
/**
* @param aaguid Authenticator Attestation GUID
* @param credentialId Credential ID
* @param publicKey CredentialPublicKey
*/
public AttestationData(byte[] aaguid, byte[] credentialId, CredentialPublicKey publicKey) {
this.aaguid = aaguid;
this.credentialId = credentialId;
this.publicKey = publicKey;
}
/**
* Decodes the input byte array into the AttestationData object.
*
* @param data
* @return AttestationData object created from the byte sequence
* @throws ResponseException
* @throws CborException
*/
public static AttestationData decode(byte[] data) throws ResponseException, CborException {
AttestationData result = new AttestationData();
// The attested credential data is formatted as follows:
// [16 bytes] AAGUID
// [2 bytes] Credential ID length (L)
// [L bytes] Credential ID
// [Remaining bytes] Credential (COSE) Public Key
int index = 0;
if (data.length < 18) {
throw new ResponseException("Invalid input");
}
System.arraycopy(data, 0, result.aaguid, 0, 16);
index += 16;
int length = (data[index++] & 0xFF) << 8;
length += data[index++] & 0xFF;
result.credentialId = new byte[length];
System.arraycopy(data, index, result.credentialId, 0, length);
index += length;
byte[] cbor = new byte[data.length - index];
System.arraycopy(data, index, cbor, 0, data.length - index);
result.publicKey = CredentialPublicKey.decode(cbor);
return result;
}
/**
* @return Encoded byte array created from AttestationData object
* @throws CborException
*/
public byte[] encode() throws CborException {
return Bytes.concat(aaguid, ByteBuffer.allocate(2).putShort((short) credentialId.length).array(), credentialId,
publicKey.encode());
}
/**
*
*/
@Override
public boolean equals(Object obj) {
try {
AttestationData other = (AttestationData) obj;
return Arrays.equals(encode(), other.encode());
} catch (NullPointerException | CborException | ClassCastException e) {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(aaguid), Arrays.hashCode(credentialId), publicKey);
}
/**
* @return the aaguid
*/
public byte[] getAaguid() {
return aaguid;
}
/**
* @return the credentialId
*/
public byte[] getCredentialId() {
return credentialId;
}
/**
* @return the publicKey
*/
public CredentialPublicKey getPublicKey() {
return publicKey;
}
}
package com.google.webauthn.gaedemo.objects;
public interface AttestationExtension {
public enum Type {
CABLE,
}
public Type getType();
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnicodeString;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.io.ByteArrayOutputStream;
import java.util.List;
public class AttestationObject {
AuthenticatorData authData;
String fmt;
AttestationStatement attStmt;
public AttestationObject() {
}
/**
* @param authData
* @param fmt
* @param attStmt
*/
public AttestationObject(AuthenticatorData authData, String fmt, AttestationStatement attStmt) {
this.authData = authData;
this.fmt = fmt;
this.attStmt = attStmt;
}
/**
* @param attestationObject
* @return AttestationObject created from the provided byte array
* @throws CborException
* @throws ResponseException
*/
public static AttestationObject decode(byte[] attestationObject)
throws CborException, ResponseException {
AttestationObject result = new AttestationObject();
List<DataItem> dataItems = CborDecoder.decode(attestationObject);
if (dataItems.size() == 1 && dataItems.get(0) instanceof Map) {
DataItem attStmt = null;
Map attObjMap = (Map) dataItems.get(0);
for (DataItem key : attObjMap.getKeys()) {
if (key instanceof UnicodeString) {
switch(((UnicodeString) key).getString()) {
case "fmt":
UnicodeString value = (UnicodeString) attObjMap.get(key);
result.fmt = value.getString();
break;
case "authData":
byte[] authData = ((ByteString) attObjMap.get(key)).getBytes();
result.authData = AuthenticatorData.decode(authData);
break;
case "attStmt":
attStmt = attObjMap.get(key);
break;
}
}
}
if (attStmt != null) {
result.attStmt = AttestationStatement.decode(result.fmt, attStmt);
}
}
return result;
}
/**
* @return Encoded byte array containing AttestationObject data
* @throws CborException
*/
public byte[] encode() throws CborException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
List<DataItem> cbor =
new CborBuilder().addMap().put("fmt", fmt).put("authData", authData.encode())
.put(new UnicodeString("attStmt"), attStmt.encode()).end().build();
new CborEncoder(output).encode(cbor);
return output.toByteArray();
}
/**
* @return the authData
*/
public AuthenticatorData getAuthenticatorData() {
return authData;
}
/**
* @return the fmt
*/
public String getFormat() {
return fmt;
}
/**
* @return the attStmt
*/
public AttestationStatement getAttestationStatement() {
return attStmt;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
public abstract class AttestationStatement {
/**
* @param fmt
* @param attStmt
* @return Attestation statement of provided format
*/
public static AttestationStatement decode(String fmt, DataItem attStmt) {
switch (fmt) {
case "fido-u2f":
return FidoU2fAttestationStatement.decode(attStmt);
case "packed":
return PackedAttestationStatement.decode(attStmt);
case "android-safetynet":
try {
return AndroidSafetyNetAttestationStatement.decode(attStmt);
} catch (ResponseException e) {
break;
}
case "none":
return new NoneAttestationStatement();
}
return null;
}
/**
* @return Encoded AttestationStatement
* @throws CborException
*/
abstract DataItem encode() throws CborException;
public abstract String getName();
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public enum AttestationStatementEnum {
FIDOU2F, ANDROIDSAFETYNET, PACKED, NONE;
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.List;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.crypto.Crypto;
public class AuthenticationExtensionsClientInputs {
public List<CableSessionData> cableAuthentication;
public KeyPair rpPublicKey;
JsonObject registrationExtensions;
/**
* @param parameter
* @return
*/
public static AuthenticationExtensionsClientInputs parse(String parameter) {
Gson gson = new Gson();
return gson.fromJson(parameter, AuthenticationExtensionsClientInputs.class);
}
/**
* Add Cable Session Data extension.
* @param cableSessionData
*/
public void addCableSessionData(CableSessionData cableSessionData) {
if (cableAuthentication == null) {
cableAuthentication = new ArrayList<>();
}
cableAuthentication.add(cableSessionData);
}
/**
* @return JSONObject formatted extension object.
*/
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
if (cableAuthentication != null) {
JsonArray cableSessionDatas = new JsonArray();
for (CableSessionData sessionData : cableAuthentication) {
cableSessionDatas.add(sessionData.getJsonObject());
}
result.add("cableAuthentication", cableSessionDatas);
}
return result;
}
/**
* Add cable registration data extension.
* @return
*/
public KeyPair addCableRegistrationData() {
if (registrationExtensions == null) {
registrationExtensions = new JsonObject();
}
KeyPair keyPair = Crypto.generateKeyPair();
JsonObject cableRegistration = new JsonObject();
JsonArray versionArray = new JsonArray();
versionArray.add(1L);
cableRegistration.add("versions", versionArray);
cableRegistration.addProperty("rpPublicKey", BaseEncoding.base64()
.encode(Crypto.compressECPublicKey((ECPublicKey) keyPair.getPublic())));
registrationExtensions.add("cableRegistration", cableRegistration);
return keyPair;
}
/**
* @return registration extensions.
*/
public JsonObject getRegistrationExtensions() {
return registrationExtensions;
}
}
package com.google.webauthn.gaedemo.objects;
import com.google.gson.JsonElement;
public class AuthenticationExtensionsClientOutputs {
public AuthenticationExtensionsClientOutputs() {}
public void parseExtensions(JsonElement json) {}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.nio.charset.StandardCharsets;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.googlecode.objectify.annotation.Subclass;
@Subclass
public class AuthenticatorAssertionResponse extends AuthenticatorResponse {
private static class AssertionResponseJson {
String clientDataJSON;
String authenticatorData;
String signature;
String userHandle;
}
byte[] authDataBytes;
AuthenticatorData authData;
byte[] signature;
byte[] userHandle;
public AuthenticatorAssertionResponse() {}
public AuthenticatorAssertionResponse(String clientDataJSON, String authenticatorData,
String signatureString) throws ResponseException {
clientData = CollectedClientData.decode(clientDataJSON);
clientDataBytes = clientDataJSON.getBytes(StandardCharsets.UTF_8);
authData = AuthenticatorData.decode(BaseEncoding.base64().decode(authenticatorData));
signature = BaseEncoding.base64().decode(signatureString);
}
/**
* @param data
* @throws ResponseException
*/
public AuthenticatorAssertionResponse(JsonElement data) throws ResponseException {
Gson gson = new Gson();
try {
AssertionResponseJson parsedObject = gson.fromJson(data, AssertionResponseJson.class);
clientDataBytes = BaseEncoding.base64().decode(parsedObject.clientDataJSON);
clientData = gson.fromJson(new String(clientDataBytes, StandardCharsets.UTF_8),
CollectedClientData.class);
authDataBytes = BaseEncoding.base64().decode(parsedObject.authenticatorData);
authData = AuthenticatorData.decode(authDataBytes);
signature = BaseEncoding.base64().decode(parsedObject.signature);
userHandle = BaseEncoding.base64().decode(parsedObject.userHandle);
} catch (JsonSyntaxException e) {
throw new ResponseException("Response format incorrect");
}
}
/**
* @return byte array of authData
*/
public byte[] getAuthDataBytes() {
return authDataBytes;
}
/**
* @return the authData
*/
public AuthenticatorData getAuthenticatorData() {
return authData;
}
/**
* @return the signature
*/
public byte[] getSignature() {
return signature;
}
/**
* @return the userHandle
*/
public byte[] getUserHandle() {
return userHandle;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
/**
*
*/
public enum AuthenticatorAttachment {
PLATFORM("platform"), CROSS_PLATFORM("cross-platform");
private final String name;
/**
* @param name
*/
private AuthenticatorAttachment(String name) {
this.name = name;
}
/**
* @param s
* @return AuthenticatorAttachment corresponding to the input string
*/
public static AuthenticatorAttachment decode(String s) {
for (AuthenticatorAttachment a : AuthenticatorAttachment.values()) {
if (a.name.equals(s)) {
return a;
}
}
throw new IllegalArgumentException(s + " not a valid AuthenticatorAttachment");
}
@Override
public String toString() {
return name;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.nio.charset.StandardCharsets;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.googlecode.objectify.annotation.Subclass;
import co.nstant.in.cbor.CborException;
@Subclass
public class AuthenticatorAttestationResponse extends AuthenticatorResponse {
private static class AttestationResponseJson {
String clientDataJSON;
String attestationObject;
String[] transports;
}
public AttestationObject decodedObject;
public String[] transports;
/**
*
*/
public AuthenticatorAttestationResponse() {}
/**
* Create AuthenticatorAttestationResponse from member objects.
* @param clientDataJSON
* @param attestationObject
* @throws ResponseException
*/
public AuthenticatorAttestationResponse(String clientDataJSON, String attestationObject)
throws ResponseException {
clientData = CollectedClientData.decode(clientDataJSON);
clientDataBytes = clientDataJSON.getBytes(StandardCharsets.UTF_8);
try {
decodedObject = AttestationObject.decode(BaseEncoding.base64().decode(attestationObject));
} catch (CborException e) {
throw new ResponseException("Cannot decode attestation object");
}
}
/**
* @param data
* @throws ResponseException
*/
public AuthenticatorAttestationResponse(JsonElement data) throws ResponseException {
Gson gson = new Gson();
AttestationResponseJson parsedObject = gson.fromJson(data, AttestationResponseJson.class);
clientDataBytes = BaseEncoding.base64().decode(parsedObject.clientDataJSON);
byte[] attestationObject = BaseEncoding.base64().decode(parsedObject.attestationObject);
try {
decodedObject = AttestationObject.decode(attestationObject);
} catch (CborException e) {
throw new ResponseException("Cannot decode attestation object");
}
clientData = gson.fromJson(new String(clientDataBytes, StandardCharsets.UTF_8),
CollectedClientData.class);
transports = parsedObject.transports;
}
/**
* @return json encoded representation of the AuthenticatorAttestationResponse
*/
public String encode() {
JsonObject json = new JsonObject();
json.addProperty("clientDataJSON", BaseEncoding.base64().encode(clientDataBytes));
try {
json.addProperty("attestationObject", BaseEncoding.base64().encode(decodedObject.encode()));
} catch (CborException e) {
return null;
}
return json.getAsString();
}
/**
* @return the decodedObject
*/
public AttestationObject getAttestationObject() {
return decodedObject;
}
/**
* @return the list of transports supported by the credential
*/
public String[] getTransports() {
return transports;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnicodeString;
public class AuthenticatorData {
private byte[] rpIdHash;
private byte flags;
private int signCount;
// optional
AttestationData attData;
private byte[] extensions;
/**
* @param rpIdHash
* @param flags
* @param signCount
* @param attData
*/
public AuthenticatorData(byte[] rpIdHash, byte flags, int signCount, AttestationData attData, byte[] extensions) {
this.rpIdHash = rpIdHash;
this.flags = flags;
this.signCount = signCount;
this.attData = attData;
this.extensions = extensions;
}
AuthenticatorData() {
rpIdHash = new byte[32];
attData = new AttestationData();
this.extensions = null;
}
AuthenticatorData(byte[] rpIdHash, byte flags, int signCount) {
this.rpIdHash = rpIdHash;
this.flags = flags;
this.signCount = signCount;
this.attData = null;
this.extensions = null;
}
/**
* @return the rpIdHash
*/
public byte[] getRpIdHash() {
return rpIdHash;
}
/**
* @return the flags
*/
public byte getFlags() {
return flags;
}
/**
* @return the UP bit of the flags
*/
public boolean isUP() {
return (flags & 1) != 0;
}
/**
* @return the UV bit of the flags
*/
public boolean isUV() {
return (flags & 1 << 2) != 0;
}
/**
* @return the AT bit of the flags
*/
public boolean hasAttestationData() {
return (flags & 1 << 6) != 0;
}
/**
* @return the ED bit of the flags
*/
public boolean hasExtensionData() {
return (flags & 1 << 7) != 0;
}
/**
* @return the Attestation extensions
*/
public HashMap<String, AttestationExtension> getExtensionData() {
return parseExtensions(this.extensions);
}
/**
* @return the signCount
*/
public int getSignCount() {
return signCount;
}
/**
* @return the attData
*/
public AttestationData getAttData() {
return attData;
}
/**
* @param authData
* @return Decoded AuthenticatorData object
* @throws ResponseException
*/
public static AuthenticatorData decode(byte[] authData) throws ResponseException {
if (authData.length < 37) {
throw new ResponseException("Invalid input");
}
int index = 0;
byte[] rpIdHash = new byte[32];
System.arraycopy(authData, 0, rpIdHash, 0, 32);
index += 32;
byte flags = authData[index++];
int signCount = Ints.fromBytes(authData[index++], authData[index++], authData[index++], authData[index++]);
int definedIndex = index;
AttestationData attData = null;
// Bit 6 determines whether attestation data was included
if ((flags & 1 << 6) != 0) {
byte[] remainder = new byte[authData.length - index];
System.arraycopy(authData, index, remainder, 0, authData.length - index);
try {
attData = AttestationData.decode(remainder);
} catch (CborException e) {
throw new ResponseException("Error decoding");
}
}
byte[] extensions = null;
// Bit 7 determines whether extensions are included.
if ((flags & 1 << 7) != 0) {
try {
int start = definedIndex + attData.encode().length;
if (authData.length > start) {
byte[] remainder = new byte[authData.length - start];
System.arraycopy(authData, start, remainder, 0, authData.length - start);
extensions = remainder;
}
} catch (CborException e) {
throw new ResponseException("Error decoding authenticator extensions");
}
}
return new AuthenticatorData(rpIdHash, flags, signCount, attData, extensions);
}
/**
* Parse Attestation extensions
*
* @return extension map
*/
private HashMap<String, AttestationExtension> parseExtensions(byte[] extensions) {
HashMap<String, AttestationExtension> extensionMap = new HashMap<>();
try {
List<DataItem> dataItems = CborDecoder.decode(extensions);
if (dataItems.size() < 1 || !(dataItems.get(0) instanceof Map)) {
return extensionMap;
}
Map map = (Map) dataItems.get(0);
for (DataItem data : map.getKeys()) {
if (data instanceof UnicodeString) {
if (((UnicodeString) data).getString().equals(CableRegistrationData.KEY)) {
CableRegistrationData decodedCableData = CableRegistrationData.parseFromCbor(map.get(data));
extensionMap.put(CableRegistrationData.KEY, decodedCableData);
}
}
}
} catch (CborException e) {
e.printStackTrace();
}
return extensionMap;
}
/**
* @return Encoded byte array
* @throws CborException
*/
public byte[] encode() throws CborException {
byte[] flags = { this.flags };
byte[] signCount = ByteBuffer.allocate(4).putInt(this.signCount).array();
byte[] result;
if (this.attData != null) {
byte[] attData = this.attData.encode();
result = Bytes.concat(rpIdHash, flags, signCount, attData);
} else {
result = Bytes.concat(rpIdHash, flags, signCount);
}
if (this.extensions != null) {
result = Bytes.concat(result, extensions);
}
return result;
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(rpIdHash), flags, signCount, attData, Arrays.hashCode(extensions));
}
@Override
public boolean equals(Object obj) {
try {
return Arrays.equals(encode(), ((AuthenticatorData) obj).encode());
} catch (CborException | ClassCastException e) {
return false;
}
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public class AuthenticatorResponse {
protected CollectedClientData clientData;
protected byte[] clientDataBytes;
/**
* @return the clientData
*/
public CollectedClientData getClientData() {
return clientData;
}
/**
* @return the clientData string
*/
public byte[] getClientDataBytes() {
return clientDataBytes;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.JsonObject;
public class AuthenticatorSelectionCriteria {
public AuthenticatorAttachment authenticatorAttachment;
public boolean requireResidentKey;
public UserVerificationRequirement userVerification;
public AuthenticatorSelectionCriteria() {
authenticatorAttachment = null;
requireResidentKey = false;
userVerification = UserVerificationRequirement.PREFERRED;
}
public AuthenticatorSelectionCriteria(AuthenticatorAttachment authenticatorAttachment,
boolean requireResidentKey, UserVerificationRequirement userVerification) {
this.authenticatorAttachment = authenticatorAttachment;
this.requireResidentKey = requireResidentKey;
this.userVerification = userVerification;
}
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
if (authenticatorAttachment != null) {
result.addProperty("authenticatorAttachment", authenticatorAttachment.toString());
}
result.addProperty("requireResidentKey", requireResidentKey);
if (userVerification != null) {
result.addProperty("userVerification", userVerification.toString());
}
return result;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public enum AuthenticatorTransport {
USB("usb"), NFC("nfc"), BLE("ble"), INTERNAL("internal"), CABLE("cable"), LIGHTNING("lightning");
final private String name;
/**
* @param name
*/
private AuthenticatorTransport(String name) {
this.name = name;
}
/**
* @param s
* @return Transport corresponding to the input string
*/
public static AuthenticatorTransport decode(String s) {
for (AuthenticatorTransport t : AuthenticatorTransport.values()) {
if (t.name.equals(s)) {
return t;
}
}
throw new IllegalArgumentException(s + " not a valid Transport");
}
@Override
public String toString() {
return name;
}
}
/*
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import org.bouncycastle.util.Arrays;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.crypto.Crypto;
public class CablePairingData {
public int version;
public byte[] irk;
public byte[] lk;
private static int HKDF_SHA_LENGTH = 64;
private static int K_LENGTH = 32;
public CablePairingData(int version, byte[] irk, byte[] lk) {
this.version = version;
this.irk = irk;
this.lk = lk;
}
public CablePairingData() {}
/**
* @param cableData
* @param sessionKeyPair
* @return
*/
public static CablePairingData generatePairingData(CableRegistrationData cableData,
KeyPair sessionKeyPair) {
byte[] sharedSecret = Crypto.getS(sessionKeyPair.getPrivate(), cableData.publicKey);
byte[] info = "FIDO caBLE v1 pairing data".getBytes(StandardCharsets.US_ASCII);
byte[] version = ByteBuffer.allocate(4).putInt(cableData.versions.get(0)).array();
byte[] result = Crypto.hkdfSha256(sharedSecret, Crypto.sha256Digest(Bytes.concat(version,
Crypto.compressECPublicKey((ECPublicKey) sessionKeyPair.getPublic()), cableData.publicKey)),
info, HKDF_SHA_LENGTH);
return new CablePairingData(cableData.versions.get(0), Arrays.copyOf(result, K_LENGTH),
Arrays.copyOfRange(result, K_LENGTH, 2 * K_LENGTH));
}
}
package com.google.webauthn.gaedemo.objects;
import java.util.ArrayList;
import java.util.List;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnicodeString;
import co.nstant.in.cbor.model.UnsignedInteger;
public class CableRegistrationData implements AttestationExtension {
public static final String KEY = "cableRegistration";
List<Integer> versions;
Integer maxVersion;
byte[] publicKey;
public CableRegistrationData() {
}
public static CableRegistrationData parseFromCbor(DataItem cborCableData) {
CableRegistrationData cableData = new CableRegistrationData();
Map cborMap = (Map) cborCableData;
for (DataItem data : cborMap.getKeys()) {
if (data instanceof UnicodeString) {
switch (((UnicodeString) data).getString()) {
case "version":
cableData.versions = new ArrayList<>();
cableData.versions.add(((UnsignedInteger) cborMap.get(data)).getValue().intValue());
break;
case "maxVersion":
cableData.maxVersion = ((UnsignedInteger) cborMap.get(data)).getValue().intValue();
break;
case "authenticatorPublicKey":
cableData.publicKey = ((ByteString) cborMap.get(data)).getBytes();
break;
}
}
}
return cableData;
}
@Override
public Type getType() {
return Type.CABLE;
}
}
/*
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonObject;
import java.util.Arrays;
import java.util.Objects;
public class CableSessionData {
public int version;
public byte[] clientEid;
public byte[] authenticatorEid;
public byte[] sessionPreKey;
public CableSessionData() {}
public CableSessionData(int version, byte[] clientEid, byte[] authenticatorEid,
byte[] sessionPreKey) {
this.version = version;
this.clientEid = clientEid;
this.authenticatorEid = authenticatorEid;
this.sessionPreKey = sessionPreKey;
}
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
result.addProperty("version", version);
result.addProperty("clientEid", BaseEncoding.base64().encode(clientEid));
result.addProperty("authenticatorEid", BaseEncoding.base64().encode(authenticatorEid));
result.addProperty("sessionPreKey", BaseEncoding.base64().encode(sessionPreKey));
return result;
}
@Override
public int hashCode() {
return Objects.hash(version, Arrays.hashCode(clientEid), Arrays.hashCode(authenticatorEid),
Arrays.hashCode(sessionPreKey));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CableSessionData that = (CableSessionData) o;
return version == that.version && Arrays.equals(clientEid, that.clientEid)
&& Arrays.equals(authenticatorEid, that.authenticatorEid)
&& Arrays.equals(sessionPreKey, that.sessionPreKey);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.webauthn.gaedemo.crypto.Crypto;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
public class CollectedClientData {
String type;
String challenge;
String origin;
String hashAlgorithm;
String tokenBindingId;
AuthenticationExtensionsClientInputs clientExtensions;
AuthenticationExtensionsClientInputs authenticatorExtensions;
CollectedClientData() {}
/**
* @param json
* @return Decoded CollectedClientData object
*/
public static CollectedClientData decode(String json) {
Gson gson = new Gson();
try {
return gson.fromJson(json, CollectedClientData.class);
} catch (JsonSyntaxException e) {
return null;
}
}
/**
* @return json encoded representation of CollectedClientData
*/
public String encode() {
Gson gson = new Gson();
return gson.toJson(this);
}
public byte[] getHash() {
String json = encode();
try {
return Crypto.digest(json.getBytes(StandardCharsets.UTF_8), hashAlgorithm);
} catch (NoSuchAlgorithmException e) {
return Crypto.sha256Digest(json.getBytes(StandardCharsets.UTF_8));
}
}
/**
* @return the challenge
*/
public String getChallenge() {
return challenge;
}
/**
* @return the origin
*/
public String getOrigin() {
return origin;
}
/**
* @return the hashAlg
*/
public String getHashAlg() {
return hashAlgorithm;
}
/**
* @return the tokenBinding
*/
public String getTokenBinding() {
return tokenBindingId;
}
public String getType() {
return type;
}
public AuthenticationExtensionsClientInputs getClientExtensions() {
return clientExtensions;
}
public AuthenticationExtensionsClientInputs getAuthenticatorExtensions() {
return authenticatorExtensions;
}
@Override
public int hashCode() {
return Objects.hash(type, challenge, origin, hashAlgorithm, tokenBindingId, clientExtensions,
authenticatorExtensions);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CollectedClientData)) {
return false;
}
return encode().equals(((CollectedClientData)obj).encode());
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.security.InvalidParameterException;
import java.util.List;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.NegativeInteger;
import co.nstant.in.cbor.model.UnsignedInteger;
public abstract class CredentialPublicKey {
Algorithm alg;
int kty;
byte[] cborEncodedKey;
static final int CRV_LABEL = -1;
static final int X_LABEL = -2;
static final int Y_LABEL = -3;
static final int N_LABEL = -1;
static final int E_LABEL = -2;
static final int KTY_LABEL = 1;
static final int ALG_LABEL = 3;
/**
* @return Cbor encoded byte array
* @throws CborException
*/
public abstract byte[] encode() throws CborException;
/**
* @return human-readable hex string of the key
*/
@Override
public abstract String toString();
/**
* Get algorithm info
*
* @return algorithm
*/
public Algorithm getAlg() {
return alg;
}
/**
* @param cbor
* @return CredentialPublicKey object decoded from cbor byte array
* @throws CborException
*/
public static CredentialPublicKey decode(byte[] cbor) throws CborException {
List<DataItem> dataItems = CborDecoder.decode(cbor);
if (dataItems.size() < 1 || !(dataItems.get(0) instanceof Map)) {
return null;
}
Map map = (Map) dataItems.get(0);
// If there are 4 keys in the map, the key should be RSA. If there are 5, then it is ECC.
if (map.getKeys().size() == 4) {
RsaKey rsaKey = new RsaKey();
for (DataItem d : map.getKeys()) {
int tmp = 0;
if (d instanceof NegativeInteger) {
tmp = ((NegativeInteger) d).getValue().intValue();
} else if (d instanceof UnsignedInteger) {
tmp = ((UnsignedInteger) d).getValue().intValue();
}
switch (tmp) {
case N_LABEL:
if (map.get(d) instanceof ByteString) {
rsaKey.n = ((ByteString) map.get(d)).getBytes();
} else {
throw new InvalidParameterException("Public key 'N' invalid type");
}
break;
case E_LABEL:
if (map.get(d) instanceof ByteString) {
rsaKey.e = ((ByteString) map.get(d)).getBytes();
} else {
throw new InvalidParameterException("Public key 'E' invalid type");
}
break;
case KTY_LABEL:
if (map.get(d) instanceof UnsignedInteger) {
rsaKey.kty = ((UnsignedInteger) map.get(d)).getValue().intValue();
} else {
throw new InvalidParameterException("Public key 'KTY' invalid type");
}
break;
case ALG_LABEL:
if (map.get(d) instanceof NegativeInteger) {
rsaKey.alg = Algorithm.decode(((NegativeInteger) map.get(d)).getValue().intValue());
if (!Algorithm.isRsaAlgorithm(rsaKey.alg))
throw new InvalidParameterException("Unsupported RSA algorithm");
} else {
throw new InvalidParameterException("Public key 'ALG' invalid type");
}
break;
}
}
return rsaKey;
} else if (map.getKeys().size() == 5) {
EccKey eccKey = new EccKey();
for (DataItem d : map.getKeys()) {
int tmp = 0;
if (d instanceof NegativeInteger) {
tmp = ((NegativeInteger) d).getValue().intValue();
} else if (d instanceof UnsignedInteger) {
tmp = ((UnsignedInteger) d).getValue().intValue();
}
switch (tmp) {
case CRV_LABEL:
if (map.get(d) instanceof UnsignedInteger) {
eccKey.crv = ((UnsignedInteger) map.get(d)).getValue().intValue();
} else {
throw new InvalidParameterException("Public key 'CRV' invalid type");
}
break;
case X_LABEL:
if (map.get(d) instanceof ByteString) {
eccKey.x = ((ByteString) map.get(d)).getBytes();
} else {
throw new InvalidParameterException("Public key 'X' invalid type");
}
break;
case Y_LABEL:
if (map.get(d) instanceof ByteString) {
eccKey.y = ((ByteString) map.get(d)).getBytes();
} else {
throw new InvalidParameterException("Public key 'Y' invalid type");
}
break;
case KTY_LABEL:
if (map.get(d) instanceof UnsignedInteger) {
eccKey.kty = ((UnsignedInteger) map.get(d)).getValue().intValue();
} else {
throw new InvalidParameterException("Public key 'KTY' invalid type");
}
break;
case ALG_LABEL:
if (map.get(d) instanceof NegativeInteger) {
eccKey.alg = Algorithm.decode(((NegativeInteger) map.get(d)).getValue().intValue());
if (!Algorithm.isEccAlgorithm(eccKey.alg))
throw new InvalidParameterException("Unsupported ECC algorithm");
} else {
throw new InvalidParameterException("Public key 'ALG' invalid type");
}
break;
}
}
return eccKey;
}
throw new InvalidParameterException("Unsupported COSE public key sent");
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import com.google.common.primitives.Bytes;
import com.googlecode.objectify.annotation.Subclass;
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.NegativeInteger;
import co.nstant.in.cbor.model.UnsignedInteger;
@Subclass
public class EccKey extends CredentialPublicKey {
byte[] x, y;
int crv;
EccKey() {
x = null;
y = null;
alg = Algorithm.UNDEFINED;
}
public EccKey(Algorithm alg, byte[] x, byte[] y) {
this.alg = alg;
this.x = x;
this.y = y;
}
/**
* @param x
* @param y
*/
public EccKey(byte[] x, byte[] y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
return Arrays.hashCode(Bytes.concat(x, y));
}
@Override
public boolean equals(Object obj) {
try {
if (obj instanceof EccKey) {
EccKey other = (EccKey) obj;
if (Arrays.equals(x, other.x) && Arrays.equals(y, other.y) && alg == other.alg) {
return true;
}
}
} catch (NullPointerException e) {
}
return false;
}
@Override
public byte[] encode() throws CborException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
List<DataItem> dataItems =
new CborBuilder().addMap().put(new UnsignedInteger(KTY_LABEL), new UnsignedInteger(kty))
.put(new UnsignedInteger(ALG_LABEL), new NegativeInteger(alg.encodeToInt()))
.put(new NegativeInteger(CRV_LABEL), new UnsignedInteger(crv))
.put(new NegativeInteger(X_LABEL), new ByteString(x))
.put(new NegativeInteger(Y_LABEL), new ByteString(y)).end().build();
new CborEncoder(output).encode(dataItems);
return output.toByteArray();
}
/**
* @return the x
*/
public byte[] getX() {
return x;
}
/**
* @return the y
*/
public byte[] getY() {
return y;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("alg:");
b.append(alg.toReadableString());
b.append(" x:");
b.append(DatatypeConverter.printHexBinary(x));
b.append(" y:");
b.append(DatatypeConverter.printHexBinary(y));
return b.toString();
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.UnicodeString;
import com.googlecode.objectify.annotation.Subclass;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Subclass
public class FidoU2fAttestationStatement extends AttestationStatement {
public byte[] sig;
public byte[] attestnCert;
public List<byte[]> caCert;
/**
* @param sig
* @param attestnCert
* @param caCert
*/
public FidoU2fAttestationStatement(byte[] sig, byte[] attestnCert, List<byte[]> caCert) {
super();
this.sig = sig;
this.attestnCert = attestnCert;
this.caCert = caCert;
}
public FidoU2fAttestationStatement() {
}
/**
* @param attStmt
* @return Decoded FidoU2fAttestationStatement
*/
public static FidoU2fAttestationStatement decode(DataItem attStmt) {
FidoU2fAttestationStatement result = new FidoU2fAttestationStatement();
Map given = null;
if (attStmt instanceof ByteString) {
byte[] temp = ((ByteString) attStmt).getBytes();
List<DataItem> dataItems = null;
try {
dataItems = CborDecoder.decode(temp);
} catch (Exception e) {
}
given = (Map) dataItems.get(0);
} else {
given = (Map) attStmt;
}
for (DataItem data : given.getKeys()) {
if (data instanceof UnicodeString) {
if (((UnicodeString) data).getString().equals("x5c")) {
Array array = (Array) given.get(data);
List<DataItem> list = array.getDataItems();
if (list.size() > 0) {
result.attestnCert = ((ByteString) list.get(0)).getBytes();
}
result.caCert = new ArrayList<byte[]>();
for (int i = 1; i < list.size(); i++) {
result.caCert.add(((ByteString) list.get(i)).getBytes());
}
} else if (((UnicodeString) data).getString().equals("sig")) {
result.sig = ((ByteString) (given.get(data))).getBytes();
}
}
}
return result;
}
@Override
DataItem encode() throws CborException {
Map result = new Map();
Array x5c = new Array();
x5c.add(new ByteString(attestnCert));
for (byte[] cert : this.caCert) {
x5c.add(new ByteString(cert));
}
result.put(new UnicodeString("x5c"), x5c);
result.put(new UnicodeString("sig"), new ByteString(sig));
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FidoU2fAttestationStatement)) {
return false;
}
FidoU2fAttestationStatement other = (FidoU2fAttestationStatement) obj;
if (!Arrays.equals(attestnCert, other.attestnCert)) {
return false;
}
if (!Arrays.equals(sig, other.sig)) {
return false;
}
if (caCert.size() == other.caCert.size()) {
return false;
}
for (int i = 0; i < caCert.size(); i++) {
if (!Arrays.equals(caCert.get(i), other.caCert.get(i))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(sig), Arrays.hashCode(attestnCert), caCert);
}
@Override
public String getName() {
return "FIDO U2F Authenticator";
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.googlecode.objectify.annotation.Subclass;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
@Subclass
public final class NoneAttestationStatement extends AttestationStatement {
public NoneAttestationStatement() {}
@Override
DataItem encode() throws CborException {
Map result = new Map();
return result;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NoneAttestationStatement)
return true;
return false;
}
@Override
public String getName() {
return "NONE ATTESTATION";
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import com.googlecode.objectify.annotation.Subclass;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.Map;
import co.nstant.in.cbor.model.NegativeInteger;
import co.nstant.in.cbor.model.UnicodeString;
@Subclass
public class PackedAttestationStatement extends AttestationStatement {
public byte[] sig;
public byte[] attestnCert;
public List<byte[]> caCert;
public Algorithm alg;
public byte[] ecdaaKeyId;
/**
* @param sig
* @param attestnCert
* @param caCert
*/
public PackedAttestationStatement(byte[] sig, byte[] attestnCert, List<byte[]> caCert, String alg) {
super();
this.sig = sig;
this.attestnCert = attestnCert;
this.caCert = caCert;
this.alg = Algorithm.decode(alg);
this.ecdaaKeyId = null;
}
/**
* @param sig
* @param attestnCert
* @param caCert
*/
public PackedAttestationStatement(byte[] sig, byte[] ecdaaKeyId, String alg) {
super();
this.sig = sig;
this.ecdaaKeyId = ecdaaKeyId;
this.alg = Algorithm.decode(alg);
this.caCert = null;
this.attestnCert = null;
}
public PackedAttestationStatement() {
this.sig = null;
this.attestnCert = null;
this.caCert = null;
this.alg = null;
this.ecdaaKeyId = null;
}
/**
* @param attStmt
* @return Decoded FidoU2fAttestationStatement
*/
public static PackedAttestationStatement decode(DataItem attStmt) {
PackedAttestationStatement result = new PackedAttestationStatement();
Map given = null;
if (attStmt instanceof ByteString) {
byte[] temp = ((ByteString) attStmt).getBytes();
List<DataItem> dataItems = null;
try {
dataItems = CborDecoder.decode(temp);
} catch (Exception e) {
}
given = (Map) dataItems.get(0);
} else {
given = (Map) attStmt;
}
for (DataItem data : given.getKeys()) {
if (data instanceof UnicodeString) {
switch (((UnicodeString) data).getString()) {
case "x5c":
Array array = (Array) given.get(data);
List<DataItem> list = array.getDataItems();
if (list.size() > 0) {
result.attestnCert = ((ByteString) list.get(0)).getBytes();
}
result.caCert = new ArrayList<byte[]>();
for (int i = 1; i < list.size(); i++) {
result.caCert.add(((ByteString) list.get(i)).getBytes());
}
break;
case "sig":
result.sig = ((ByteString) (given.get(data))).getBytes();
break;
case "alg":
int algInt = new BigDecimal(((NegativeInteger) (given.get(data))).getValue()).intValueExact();
result.alg = Algorithm.decode(algInt);
break;
case "ecdaaKeyId":
result.ecdaaKeyId = ((ByteString) (given.get(data))).getBytes();
break;
}
}
}
return result;
}
@Override
DataItem encode() throws CborException {
Map result = new Map();
if (attestnCert != null) {
Array x5c = new Array();
x5c.add(new ByteString(attestnCert));
for (byte[] cert : this.caCert) {
x5c.add(new ByteString(cert));
}
result.put(new UnicodeString("x5c"), x5c);
}
if (ecdaaKeyId != null) {
result.put(new UnicodeString("ecdaaKeyId"), new ByteString(ecdaaKeyId));
}
result.put(new UnicodeString("sig"), new ByteString(sig));
result.put(new UnicodeString("alg"), new UnicodeString(alg.toString()));
return result;
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(sig), Arrays.hashCode(attestnCert), caCert, alg, Arrays.hashCode(ecdaaKeyId));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PackedAttestationStatement)) {
return false;
}
PackedAttestationStatement other = (PackedAttestationStatement) obj;
try {
return encode().equals(other.encode());
} catch (CborException e) {
}
return false;
}
@Override
public String getName() {
return "Packed Attestation";
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.Gson;
public class PublicKeyCredential {
public String id;
public String type;
public byte[] rawId;
AuthenticatorResponse response;
/**
* @param id
* @param type
* @param rawId
* @param response
*/
public PublicKeyCredential(String id, String type, byte[] rawId, AuthenticatorResponse response) {
this.id = id;
this.type = type;
this.rawId = rawId;
this.response = response;
}
/**
*
*/
public PublicKeyCredential() {}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @return the type
*/
public String getType() {
return type;
}
/**
* @return the rawId
*/
public byte[] getRawId() {
return rawId;
}
public AttestationStatementEnum getAttestationType() {
try {
AuthenticatorAttestationResponse attRsp = (AuthenticatorAttestationResponse) response;
AttestationStatement attStmt = attRsp.decodedObject.getAttestationStatement();
if (attStmt instanceof AndroidSafetyNetAttestationStatement) {
return AttestationStatementEnum.ANDROIDSAFETYNET;
} else if (attStmt instanceof FidoU2fAttestationStatement) {
return AttestationStatementEnum.FIDOU2F;
} else if (attStmt instanceof PackedAttestationStatement) {
return AttestationStatementEnum.PACKED;
} else if (attStmt instanceof NoneAttestationStatement) {
return AttestationStatementEnum.NONE;
}
} catch (ClassCastException e) {
return null;
}
return null;
}
/**
* @return the response
*/
public AuthenticatorResponse getResponse() {
return response;
}
/**
* @return json encoded String representation of PublicKeyCredential
*/
public String encode() {
return new Gson().toJson(this);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
public class PublicKeyCredentialCreationOptions {
private static final int CHALLENGE_LENGTH = 32;
private final SecureRandom random = new SecureRandom();
PublicKeyCredentialEntity rp;
PublicKeyCredentialUserEntity user;
public byte[] challenge;
ArrayList<PublicKeyCredentialParameters> pubKeyCredParams;
long timeout;
ArrayList<PublicKeyCredentialDescriptor> excludeCredentials;
protected AuthenticatorSelectionCriteria authenticatorSelection;
protected AttestationConveyancePreference attestation;
protected AuthenticationExtensionsClientInputs extensions;
/**
*
*/
public PublicKeyCredentialCreationOptions() {
pubKeyCredParams = new ArrayList<PublicKeyCredentialParameters>();
excludeCredentials = new ArrayList<PublicKeyCredentialDescriptor>();
extensions = null;
authenticatorSelection = null;
}
/**
* @param userId
* @param rpId
* @param rpName
*/
public PublicKeyCredentialCreationOptions(String userName, String userId, String rpId,
String rpName) {
pubKeyCredParams = new ArrayList<PublicKeyCredentialParameters>();
excludeCredentials = new ArrayList<PublicKeyCredentialDescriptor>();
rp = new PublicKeyCredentialRpEntity(rpId, rpName, null);
byte[] userIdBytes = new byte[32];
random.nextBytes(userIdBytes);
user = new PublicKeyCredentialUserEntity(userName, userIdBytes);
challenge = new byte[CHALLENGE_LENGTH];
random.nextBytes(challenge);
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.ES256));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.ES384));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.ES512));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.RS256));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.RS384));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.RS512));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.PS256));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.PS384));
pubKeyCredParams.add(
new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.PS512));
extensions = null;
}
public void setExtensions(AuthenticationExtensionsClientInputs extensions) {
this.extensions = extensions;
}
public void setCriteria(AuthenticatorSelectionCriteria criteria) {
this.authenticatorSelection = criteria;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void excludeCredential(PublicKeyCredentialDescriptor credential) {
excludeCredentials.add(credential);
}
public void setExcludeCredentials(Collection<PublicKeyCredentialDescriptor> excludeCredentials) {
this.excludeCredentials.clear();
this.excludeCredentials.addAll(excludeCredentials);
}
public void setAttestationConveyancePreference(AttestationConveyancePreference attestation) {
this.attestation = attestation;
}
/**
* @return Encoded JsonObect representation of MakeCredentialOptions
*/
public JsonObject getJsonObject() {
// Required parameters
JsonObject result = new JsonObject();
result.add("rp", rp.getJsonObject());
result.add("user", user.getJsonObject());
result.addProperty("challenge", BaseEncoding.base64().encode(challenge));
JsonArray params = new JsonArray();
for (PublicKeyCredentialParameters param : pubKeyCredParams)
params.add(param.getJsonObject());
result.add("pubKeyCredParams", params);
// Optional parameters
if (this.timeout > 0) {
result.addProperty("timeout", timeout);
}
if (this.excludeCredentials != null && this.excludeCredentials.size() > 0) {
JsonArray excludeParams = new JsonArray();
for (PublicKeyCredentialDescriptor descriptor : excludeCredentials) {
excludeParams.add(descriptor.getJsonObject());
}
result.add("excludeCredentials", excludeParams);
}
if (this.authenticatorSelection != null) {
result.add("authenticatorSelection", authenticatorSelection.getJsonObject());
}
if (this.attestation != null) {
result.addProperty("attestation", this.attestation.toString());
}
if (extensions != null) {
// TODO
}
return result;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.ArrayList;
public class PublicKeyCredentialDescriptor {
/**
* @param type
* @param id
*/
public PublicKeyCredentialDescriptor(PublicKeyCredentialType type, byte[] id) {
this.type = type;
this.id = id;
this.transports = new ArrayList<AuthenticatorTransport>();
}
/**
* @param type
* @param id
* @param transports
*/
public PublicKeyCredentialDescriptor(PublicKeyCredentialType type, byte[] id,
ArrayList<AuthenticatorTransport> transports) {
this.type = type;
this.id = id;
this.transports = transports;
}
private PublicKeyCredentialType type;
private byte[] id;
private ArrayList<AuthenticatorTransport> transports;
/**
* @return Encoded JsonObject representation of PublicKeyCredentialDescriptor
*/
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
result.addProperty("type", type.toString());
result.addProperty("id", BaseEncoding.base64().encode(id));
JsonArray transports = new JsonArray();
if (this.transports != null) {
for (AuthenticatorTransport t : this.transports) {
JsonPrimitive element = new JsonPrimitive(t.toString());
transports.add(element);
}
if (transports.size() > 0) {
result.add("transports", transports);
}
}
return result;
}
/**
* @param transports the transports to set
*/
public void setTransports(ArrayList<AuthenticatorTransport> transports) {
this.transports = transports;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.JsonObject;
public class PublicKeyCredentialEntity {
protected String name;
protected String icon;
/**
* @param name
* @param icon
*/
PublicKeyCredentialEntity(String name, String icon) {
this.name = name;
this.icon = icon;
}
PublicKeyCredentialEntity() {
this.name = null;
this.icon = null;
}
/**
* @return Encoded JsonObject representation of PublicKeyCredentialEntity
*/
public JsonObject getJsonObject() {
JsonObject json = new JsonObject();
json.addProperty("name", name);
if (icon != null)
json.addProperty("icon", icon);
return json;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.JsonObject;
public class PublicKeyCredentialParameters {
private PublicKeyCredentialType type;
private Algorithm algorithm;
/**
* @param type
* @param algorithm
*/
public PublicKeyCredentialParameters(PublicKeyCredentialType type, Algorithm algorithm) {
this.type = type;
this.algorithm = algorithm;
}
/**
* @return JsonObject representation of PublicKeyCredentialParameters
*/
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
result.addProperty("type", type.toString());
result.addProperty("alg", algorithm.encodeToInt());
return result;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.crypto.Cable;
import com.google.webauthn.gaedemo.storage.Credential;
public class PublicKeyCredentialRequestOptions {
private static final int CHALLENGE_LENGTH = 32;
private final SecureRandom random = new SecureRandom();
// Required parameters
public byte[] challenge;
// Optional parameters
public long timeout;
public String rpId;
protected ArrayList<PublicKeyCredentialDescriptor> allowCredentials;
protected UserVerificationRequirement userVerification;
AuthenticationExtensionsClientInputs extensions;
/**
* @param rpId
*/
public PublicKeyCredentialRequestOptions(String rpId) {
challenge = new byte[CHALLENGE_LENGTH];
random.nextBytes(challenge);
allowCredentials = new ArrayList<PublicKeyCredentialDescriptor>();
this.rpId = rpId;
}
/**
* @return JsonObject representation of PublicKeyCredentialRequestOptions
*/
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
result.addProperty("challenge", BaseEncoding.base64().encode(challenge));
if (timeout > 0) {
result.addProperty("timeout", timeout);
}
result.addProperty("rpId", rpId);
JsonArray allowCredentials = new JsonArray();
for (PublicKeyCredentialDescriptor credential : this.allowCredentials) {
allowCredentials.add(credential.getJsonObject());
}
result.add("allowCredentials", allowCredentials);
if (extensions != null) {
result.add("extensions", extensions.getJsonObject());
}
return result;
}
public List<PublicKeyCredentialDescriptor> getAllowCredentials() {
return allowCredentials;
}
/**
* @param currentUser
*/
public void populateAllowList(String currentUser) {
List<Credential> credentialList = Credential.load(currentUser);
for (Credential c : credentialList) {
PublicKeyCredential storedCred = c.getCredential();
if (storedCred == null)
continue;
PublicKeyCredentialDescriptor pkcd =
new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, storedCred.rawId);
if (((AuthenticatorAttestationResponse) storedCred.getResponse()).getTransports() != null) {
ArrayList<AuthenticatorTransport> transportList = new ArrayList<>();
for (String transport : ((AuthenticatorAttestationResponse) storedCred.getResponse()).getTransports()) {
transportList.add(AuthenticatorTransport.decode(transport));
}
pkcd.setTransports(transportList);
}
allowCredentials.add(pkcd);
Cable cableCrypto = new Cable();
CablePairingData cablePairingData = c.getCablePairingData();
if (cablePairingData != null) {
if (extensions == null) {
extensions = new AuthenticationExtensionsClientInputs();
}
extensions.addCableSessionData(cableCrypto.generateSessionData(cablePairingData));
}
}
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
public class PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity {
protected String id;
/**
* @param id
* @param name
* @param icon
*/
PublicKeyCredentialRpEntity(String id, String name, String icon) {
super(name, icon);
this.id = id;
}
PublicKeyCredentialRpEntity() {
super(null, null);
this.id = null;
}
@Override
public JsonObject getJsonObject() {
Gson gson = new Gson();
return (JsonObject) gson.toJsonTree(this);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
public enum PublicKeyCredentialType {
PUBLIC_KEY("public-key");
final String name;
/**
* @param name
*/
private PublicKeyCredentialType(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonObject;
public class PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity {
protected String displayName;
protected byte[] id;
/**
* @param displayName
*/
public PublicKeyCredentialUserEntity(String displayName, byte[] id) {
super();
this.displayName = displayName;
this.name = displayName;
this.id = id;
}
/**
* @return
*/
@Override
public JsonObject getJsonObject() {
JsonObject superJson = super.getJsonObject();
superJson.addProperty("displayName", displayName);
superJson.addProperty("id", BaseEncoding.base64().encode(id));
return superJson;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.NegativeInteger;
import co.nstant.in.cbor.model.UnsignedInteger;
import com.googlecode.objectify.annotation.Subclass;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.xml.bind.DatatypeConverter;
@Subclass
public class RsaKey extends CredentialPublicKey {
byte[] n, e;
RsaKey() {
n = null;
e = null;
alg = Algorithm.UNDEFINED;
}
public RsaKey(Algorithm alg, byte[] n, byte[] e) {
this.alg = alg;
this.n = n;
this.e = e;
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(n), Arrays.hashCode(e));
}
@Override
public boolean equals(Object obj) {
try {
if (obj instanceof RsaKey) {
RsaKey other = (RsaKey) obj;
if (Arrays.equals(n, other.n) && Arrays.equals(e, other.e) && alg == other.alg) {
return true;
}
}
} catch (NullPointerException e) {
}
return false;
}
@Override
public byte[] encode() throws CborException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
List<DataItem> dataItems =
new CborBuilder().addMap().put(new UnsignedInteger(KTY_LABEL), new UnsignedInteger(kty))
.put(new UnsignedInteger(ALG_LABEL), new NegativeInteger(alg.encodeToInt()))
.put(new NegativeInteger(N_LABEL), new ByteString(n))
.put(new NegativeInteger(E_LABEL), new ByteString(e)).end().build();
new CborEncoder(output).encode(dataItems);
return output.toByteArray();
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("alg:");
b.append(alg.toReadableString());
b.append(" n:");
b.append(DatatypeConverter.printHexBinary(n));
b.append("<br />");
b.append(" e:");
b.append(DatatypeConverter.printHexBinary(e));
return b.toString();
}
public byte[] getN() {
return n;
}
public byte[] getE() {
return e;
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.objects;
/**
*
*/
public enum UserVerificationRequirement {
REQUIRED("required"), PREFERRED("preferred"), DISCOURAGED("discouraged");
private final String name;
/**
* @param name
*/
private UserVerificationRequirement(String name) {
this.name = name;
}
/**
* @param s
* @return AuthenticatorAttachment corresponding to the input string
*/
public static UserVerificationRequirement decode(String s) {
for (UserVerificationRequirement a : UserVerificationRequirement.values()) {
if (a.name.equals(s)) {
return a;
}
}
throw new IllegalArgumentException(s + " not a valid AuthenticatorAttachment");
}
@Override
public String toString() {
return name;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.crypto.OfflineVerify;
import com.google.webauthn.gaedemo.crypto.OfflineVerify.AttestationStatement;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.storage.Credential;
import co.nstant.in.cbor.CborException;
public class AndroidSafetyNetServer extends Server {
private static final Logger Log = Logger.getLogger(AndroidSafetyNetServer.class.getName());
/**
* @param cred
* @param rpId
* @param session
* @param currentUser
* @throws ServletException
*/
public static void registerCredential(PublicKeyCredential cred, String currentUser,
String session, String rpId) throws ServletException {
if (!(cred.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Invalid response structure");
}
AuthenticatorAttestationResponse attResponse =
(AuthenticatorAttestationResponse) cred.getResponse();
List<Credential> savedCreds = Credential.load(currentUser);
for (Credential c : savedCreds) {
if (c.getCredential().id.equals(cred.id)) {
throw new ServletException("Credential already registered for this user");
}
}
try {
verifySessionAndChallenge(attResponse, currentUser, session);
} catch (ResponseException e1) {
throw new ServletException("Unable to verify session and challenge data");
}
AndroidSafetyNetAttestationStatement attStmt =
(AndroidSafetyNetAttestationStatement) attResponse.decodedObject.getAttestationStatement();
AttestationStatement stmt =
OfflineVerify.parseAndVerify(new String(attStmt.getResponse(), StandardCharsets.UTF_8));
if (stmt == null) {
Log.info("Failure: Failed to parse and verify the attestation statement.");
throw new ServletException("Failed to verify attestation statement");
}
byte[] clientDataHash = Crypto.sha256Digest(attResponse.getClientDataBytes());
try {
// Nonce was changed from [authenticatorData, clientDataHash] to
// sha256 [authenticatorData, clientDataHash]
// https://github.com/w3c/webauthn/pull/869
byte[] expectedNonce = Crypto.sha256Digest(Bytes.concat(
attResponse.getAttestationObject().getAuthenticatorData().encode(), clientDataHash));
if (!Arrays.equals(expectedNonce, stmt.getNonce())) {
expectedNonce = Bytes.concat(
attResponse.getAttestationObject().getAuthenticatorData().encode(), clientDataHash);
if (!Arrays.equals(expectedNonce, stmt.getNonce())) {
throw new ServletException("Nonce does not match");
}
//
}
} catch (CborException e) {
throw new ServletException("Error encoding authdata");
}
/*
* // Test devices won't pass this. if (!stmt.isCtsProfileMatch()) { throw new
* ServletException("No cts profile match"); }
*/
}
// TODO Remove after switch to generic verification
/**
* @param cred
* @param currentUser
* @param sessionId
* @throws ServletException
*/
public static void verifyAssertion(PublicKeyCredential cred, String currentUser, String sessionId,
Credential savedCredential) throws ServletException {
AuthenticatorAssertionResponse assertionResponse =
(AuthenticatorAssertionResponse) cred.getResponse();
Log.info("-- Verifying signature --");
if (!(savedCredential.getCredential()
.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Stored attestation missing");
}
AuthenticatorAttestationResponse storedAttData =
(AuthenticatorAttestationResponse) savedCredential.getCredential().getResponse();
if (!(storedAttData.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey)) {
throw new ServletException("Ecc key not provided");
}
EccKey publicKey =
(EccKey) storedAttData.decodedObject.getAuthenticatorData().getAttData().getPublicKey();
try {
byte[] clientDataHash = Crypto.sha256Digest(assertionResponse.getClientDataBytes());
byte[] signedBytes =
Bytes.concat(assertionResponse.getAuthenticatorData().encode(), clientDataHash);
if (!Crypto.verifySignature(Crypto.decodePublicKey(publicKey.getX(), publicKey.getY()),
signedBytes, assertionResponse.getSignature())) {
throw new ServletException("Signature invalid");
}
} catch (WebAuthnException e) {
throw new ServletException("Failure while verifying signature", e);
} catch (CborException e) {
throw new ServletException("Failure while verifying authenticator data");
}
if (Integer.compareUnsigned(assertionResponse.getAuthenticatorData().getSignCount(),
savedCredential.getSignCount()) <= 0 && savedCredential.getSignCount() != 0) {
throw new ServletException("Sign count invalid");
}
savedCredential.updateSignCount(assertionResponse.getAuthenticatorData().getSignCount());
Log.info("Signature verified");
}
}
package com.google.webauthn.gaedemo.server;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
public class Datastore {
private static DatastoreService datastore = null;
public static DatastoreService getDatastore() {
if (datastore == null) {
synchronized (Datastore.class) {
if (datastore == null) {
datastore = DatastoreServiceFactory.getDatastoreService();
}
}
}
return datastore;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.FidoU2fAttestationStatement;
import com.google.webauthn.gaedemo.objects.NoneAttestationStatement;
import com.google.webauthn.gaedemo.objects.PackedAttestationStatement;
import com.google.webauthn.gaedemo.objects.RsaKey;
import com.google.webauthn.gaedemo.storage.AttestationSessionData;
import com.google.webauthn.gaedemo.storage.Credential;
import com.google.webauthn.gaedemo.storage.User;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.VoidWork;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class OfyHelper implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
ObjectifyService.run(new VoidWork() {
@Override
public void vrun() {
ObjectifyService.register(User.class);
ObjectifyService.register(Credential.class);
ObjectifyService.register(AttestationSessionData.class);
ObjectifyService.register(AuthenticatorAttestationResponse.class);
ObjectifyService.register(AuthenticatorAssertionResponse.class);
ObjectifyService.register(RsaKey.class);
ObjectifyService.register(EccKey.class);
ObjectifyService.register(FidoU2fAttestationStatement.class);
ObjectifyService.register(PackedAttestationStatement.class);
ObjectifyService.register(AndroidSafetyNetAttestationStatement.class);
ObjectifyService.register(NoneAttestationStatement.class);
}
});
}
@Override
public void contextDestroyed(ServletContextEvent sce) {}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.crypto.AlgorithmIdentifierMapper;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.PackedAttestationStatement;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.objects.RsaKey;
import com.google.webauthn.gaedemo.storage.Credential;
import co.nstant.in.cbor.CborException;
public class PackedServer extends Server {
private static final Logger Log = Logger.getLogger(PackedServer.class.getName());
/**
* @param cred
* @param currentUser
* @param sessionId
* @throws ServletException
*/
@Deprecated
public static void verifyAssertion(PublicKeyCredential cred, String currentUser, String sessionId,
Credential savedCredential) throws ServletException {
AuthenticatorAssertionResponse assertionResponse =
(AuthenticatorAssertionResponse) cred.getResponse();
Log.info("-- Verifying signature --");
if (!(savedCredential.getCredential()
.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Stored attestation missing");
}
AuthenticatorAttestationResponse storedAttData =
(AuthenticatorAttestationResponse) savedCredential.getCredential().getResponse();
if (!(storedAttData.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey)) {
throw new ServletException("U2f-capable key not provided");
}
savedCredential.updateSignCount(assertionResponse.getAuthenticatorData().getSignCount());
Log.info("Signature verified");
}
/**
* @param cred
* @param currentUser
* @param session
* @param origin
* @throws ServletException
*/
public static void registerCredential(PublicKeyCredential cred, String currentUser,
String session, String origin) throws ServletException {
if (!(cred.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Invalid response structure");
}
AuthenticatorAttestationResponse attResponse =
(AuthenticatorAttestationResponse) cred.getResponse();
List<Credential> savedCreds = Credential.load(currentUser);
for (Credential c : savedCreds) {
if (c.getCredential().id.equals(cred.id)) {
throw new ServletException("Credential already registerd for this user");
}
}
try {
verifySessionAndChallenge(attResponse, currentUser, session);
} catch (ResponseException e1) {
throw new ServletException("Unable to verify session and challenge data", e1);
}
byte[] clientDataHash = Crypto.sha256Digest(attResponse.getClientDataBytes());
byte[] rpIdHash = Crypto.sha256Digest(origin.getBytes(StandardCharsets.UTF_8));
if (!Arrays.equals(attResponse.getAttestationObject().getAuthenticatorData().getRpIdHash(),
rpIdHash)) {
throw new ServletException("RPID hash incorrect");
}
if (!(attResponse.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey)
&& !(attResponse.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof RsaKey)) {
throw new ServletException("Supported key not provided");
}
PackedAttestationStatement attStmt =
(PackedAttestationStatement) attResponse.decodedObject.getAttestationStatement();
try {
/*
* Signatures are signed over the concatenation of Authenticator data and Client Data Hash
*/
byte[] signedBytes =
Bytes.concat(attResponse.decodedObject.getAuthenticatorData().encode(), clientDataHash);
StringBuilder buf = new StringBuilder();
for (byte b : signedBytes) {
buf.append(String.format("%02X ", b));
}
Log.info("Signed bytes: " + buf.toString());
String signatureAlgorithm;
try {
signatureAlgorithm = AlgorithmIdentifierMapper.get(
attResponse.decodedObject.getAuthenticatorData().getAttData().getPublicKey().getAlg())
.getJavaAlgorithm();
} catch (Exception e) {
// Default to ES256
signatureAlgorithm = "SHA256withECDSA";
}
// TODO Make attStmt.attestnCert an X509Certificate right off the
// bat.
if (attStmt.attestnCert instanceof byte[]) {
DataInputStream inputStream =
new DataInputStream(new ByteArrayInputStream(attStmt.attestnCert));
X509Certificate attestationCertificate = (X509Certificate) CertificateFactory
.getInstance("X.509").generateCertificate(inputStream);
if (!Crypto.verifySignature(attestationCertificate, signedBytes, attStmt.sig,
signatureAlgorithm)) {
throw new ServletException("Signature invalid");
}
} else {
// Self-attestation.
if (!signatureAlgorithm
.equals(AlgorithmIdentifierMapper.get(attStmt.alg).getJavaAlgorithm())) {
throw new ServletException("Algorithm mismatch");
}
PublicKey publicKey;
if (attResponse.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey) {
publicKey = Crypto.getECPublicKey((EccKey) attResponse.decodedObject
.getAuthenticatorData().getAttData().getPublicKey());
} else {
throw new ServletException("Public Key not supported");
}
if (!Crypto.verifySignature(publicKey, signedBytes, attStmt.sig, signatureAlgorithm)) {
throw new ServletException("Signature invalid");
}
}
} catch (CertificateException e) {
throw new ServletException("Error when parsing attestationCertificate");
} catch (WebAuthnException e) {
throw new ServletException("Failure while verifying signature", e);
} catch (CborException e) {
throw new ServletException("Unable to reencode authenticator data");
}
// TODO Check trust anchors
// TODO Check X.509 certs
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import com.google.gson.Gson;
public class PublicKeyCredentialResponse {
protected boolean success;
protected String message;
protected String handle;
public PublicKeyCredentialResponse(boolean success, String message) {
this.success = success;
this.message = message;
}
public PublicKeyCredentialResponse(boolean success, String message, String handle) {
this.success = success;
this.message = message;
this.handle = handle;
}
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.crypto.AlgorithmIdentifierMapper;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorResponse;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.objects.RsaKey;
import com.google.webauthn.gaedemo.storage.Credential;
import com.google.webauthn.gaedemo.storage.SessionData;
import co.nstant.in.cbor.CborException;
public abstract class Server {
private static final Logger Log = Logger.getLogger(U2fServer.class.getName());
/**
* @param assertionResponse
* @param currentUser
* @param sessionId
* @throws ResponseException
*/
public static void verifySessionAndChallenge(AuthenticatorResponse assertionResponse,
String currentUser, String sessionId) throws ResponseException {
Log.info("-- Verifying provided session and challenge data --");
// TODO: when it's calling from an Android application via Endpoints API, the session ID
// is temporarily null for now.
if (sessionId == null) {
return;
}
long id = 0;
try {
id = Long.valueOf(sessionId);
} catch (NumberFormatException e) {
throw new ResponseException("Provided session id invalid");
}
/* Invalidate old sessions */
SessionData.removeOldSessions(currentUser);
SessionData session = SessionData.load(currentUser, Long.valueOf(id));
if (session == null) {
throw new ResponseException("Session invalid");
}
SessionData.remove(currentUser, Long.valueOf(id));
// Session.getChallenge returns a base64-encoded string
byte[] sessionChallenge = BaseEncoding.base64().decode(session.getChallenge());
// assertionResponse.getClientData().getChallenge() is a base64url-encoded string
byte[] clientSessionChallenge =
BaseEncoding.base64Url().decode(assertionResponse.getClientData().getChallenge());
if (!Arrays.equals(sessionChallenge, clientSessionChallenge)) {
throw new ResponseException("Returned challenge incorrect");
}
Log.info("Successfully verified session and challenge data");
}
/**
* @param cred
* @param currentUser
* @param sessionId
* @return
* @throws ResponseException
*/
public static Credential validateAndFindCredential(PublicKeyCredential cred, String currentUser,
String sessionId) throws ResponseException {
if (!(cred.getResponse() instanceof AuthenticatorAssertionResponse)) {
throw new ResponseException("Invalid authenticator response");
}
AuthenticatorAssertionResponse assertionResponse =
(AuthenticatorAssertionResponse) cred.getResponse();
List<Credential> savedCreds = Credential.load(currentUser);
if (savedCreds == null || savedCreds.size() == 0) {
throw new ResponseException("No credentials registered for this user");
}
try {
verifySessionAndChallenge(assertionResponse, currentUser, sessionId);
} catch (ResponseException e1) {
throw new ResponseException("Unable to verify session and challenge data", e1);
}
Credential credential = null;
for (Credential saved : savedCreds) {
if (saved.getCredential().getId().equals(cred.getId())) {
credential = saved;
break;
}
}
if (credential == null) {
Log.info("Credential not registered with this user");
throw new ResponseException("Received response from credential not associated with user");
}
return credential;
}
/**
* @param cred
* @param currentUser
* @param sessionId
* @throws ServletException
*/
public static void verifyAssertion(PublicKeyCredential cred, String currentUser, String sessionId,
Credential savedCredential) throws ServletException {
AuthenticatorAssertionResponse assertionResponse =
(AuthenticatorAssertionResponse) cred.getResponse();
Log.info("-- Verifying signature --");
if (!(savedCredential.getCredential()
.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Stored attestation missing");
}
AuthenticatorAttestationResponse storedAttData =
(AuthenticatorAttestationResponse) savedCredential.getCredential().getResponse();
try {
PublicKey publicKey;
if (storedAttData.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey) {
publicKey = Crypto.getECPublicKey((EccKey) storedAttData.decodedObject
.getAuthenticatorData().getAttData().getPublicKey());
} else {
publicKey = Crypto.getRSAPublicKey((RsaKey) storedAttData.decodedObject
.getAuthenticatorData().getAttData().getPublicKey());
}
byte[] clientDataHash = Crypto.sha256Digest(assertionResponse.getClientDataBytes());
byte[] signedBytes;
// concat of aData (authDataBytes) and hash of cData (clientDataHash)
try {
signedBytes = Bytes.concat(assertionResponse.getAuthDataBytes(), clientDataHash);
} catch (NullPointerException e) {
try {
signedBytes = Bytes.concat(assertionResponse.getAuthenticatorData().encode(),
clientDataHash);
} catch (CborException e1) {
throw new ServletException("Authenticator data invalid", e);
}
}
String signatureAlgorithm = AlgorithmIdentifierMapper.get(
storedAttData.decodedObject.getAuthenticatorData().getAttData().getPublicKey().getAlg())
.getJavaAlgorithm();
if (!Crypto.verifySignature(publicKey, signedBytes, assertionResponse.getSignature(),
signatureAlgorithm)) {
throw new ServletException("Signature invalid");
}
} catch (WebAuthnException e) {
throw new ServletException("Failure while verifying signature", e);
}
if (Integer.compareUnsigned(assertionResponse.getAuthenticatorData().getSignCount(),
savedCredential.getSignCount()) <= 0) {
throw new ServletException("Sign count invalid");
}
savedCredential.updateSignCount(assertionResponse.getAuthenticatorData().getSignCount());
Log.info("Signature verified");
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.server;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.FidoU2fAttestationStatement;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.storage.Credential;
public class U2fServer extends Server {
private static final Logger Log = Logger.getLogger(U2fServer.class.getName());
/**
* @param cred
* @param currentUser
* @param sessionId
* @throws ServletException
*/
@Deprecated
public static void verifyAssertion(PublicKeyCredential cred, String currentUser, String sessionId,
Credential savedCredential) throws ServletException {
AuthenticatorAssertionResponse assertionResponse =
(AuthenticatorAssertionResponse) cred.getResponse();
Log.info("-- Verifying signature --");
if (!(savedCredential.getCredential()
.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Stored attestation missing");
}
AuthenticatorAttestationResponse storedAttData =
(AuthenticatorAttestationResponse) savedCredential.getCredential().getResponse();
if (!(storedAttData.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey)) {
throw new ServletException("U2f-capable key not provided");
}
EccKey publicKey =
(EccKey) storedAttData.decodedObject.getAuthenticatorData().getAttData().getPublicKey();
try {
/*
* U2F authentication signatures are signed over the concatenation of
*
* 32 byte application parameter hash
*
* 1 byte user presence
*
* 4 byte big-endian representation of the counter
*
* 32 byte challenge parameter (ie SHA256 hash of clientData)
*/
byte[] clientDataHash = Crypto.sha256Digest(assertionResponse.getClientDataBytes());
byte[] signedBytes =
Bytes.concat(storedAttData.getAttestationObject().getAuthenticatorData().getRpIdHash(),
new byte[] {
(assertionResponse.getAuthenticatorData().isUP() == true ? (byte) 1 : (byte) 0)},
ByteBuffer.allocate(4).putInt(assertionResponse.getAuthenticatorData().getSignCount())
.array(),
clientDataHash);
if (!Crypto.verifySignature(Crypto.decodePublicKey(publicKey.getX(), publicKey.getY()),
signedBytes, assertionResponse.getSignature())) {
signedBytes[storedAttData.getAttestationObject().getAuthenticatorData()
.getRpIdHash().length] = assertionResponse.getAuthenticatorData().getFlags();
//TODO Remove this hack.
if (!Crypto.verifySignature(Crypto.decodePublicKey(publicKey.getX(), publicKey.getY()),
signedBytes, assertionResponse.getSignature())) {
throw new ServletException("Signature invalid");
}
}
} catch (WebAuthnException e) {
throw new ServletException("Failure while verifying signature");
}
if (Integer.compareUnsigned(assertionResponse.getAuthenticatorData().getSignCount(),
savedCredential.getSignCount()) <= 0) {
throw new ServletException("Sign count invalid");
}
savedCredential.updateSignCount(assertionResponse.getAuthenticatorData().getSignCount());
Log.info("Signature verified");
}
/**
* @param cred
* @param currentUser
* @param session
* @param originString
* @throws ServletException
*/
public static void registerCredential(PublicKeyCredential cred, String currentUser,
String session, String originString, String rpId) throws ServletException {
if (!(cred.getResponse() instanceof AuthenticatorAttestationResponse)) {
throw new ServletException("Invalid response structure");
}
AuthenticatorAttestationResponse attResponse =
(AuthenticatorAttestationResponse) cred.getResponse();
List<Credential> savedCreds = Credential.load(currentUser);
for (Credential c : savedCreds) {
if (c.getCredential().id.equals(cred.id)) {
throw new ServletException("Credential already registerd for this user");
}
}
try {
verifySessionAndChallenge(attResponse, currentUser, session);
} catch (ResponseException e1) {
throw new ServletException("Unable to verify session and challenge data", e1);
}
byte[] clientDataHash = Crypto.sha256Digest(attResponse.getClientDataBytes());
byte[] rpIdHash = Crypto.sha256Digest(rpId.getBytes(StandardCharsets.UTF_8));
if (!Arrays.equals(attResponse.getAttestationObject().getAuthenticatorData().getRpIdHash(),
rpIdHash)) {
throw new ServletException("RPID hash incorrect");
}
if (!(attResponse.decodedObject.getAuthenticatorData().getAttData()
.getPublicKey() instanceof EccKey)) {
throw new ServletException("U2f-capable key not provided");
}
FidoU2fAttestationStatement attStmt =
(FidoU2fAttestationStatement) attResponse.decodedObject.getAttestationStatement();
EccKey publicKey =
(EccKey) attResponse.decodedObject.getAuthenticatorData().getAttData().getPublicKey();
try {
/*
* U2F registration signatures are signed over the concatenation of
*
* 1 byte RFU (0)
*
* 32 byte application parameter hash
*
* 32 byte challenge parameter
*
* key handle
*
* 65 byte user public key represented as {0x4, X, Y}
*/
byte[] signedBytes = Bytes.concat(new byte[] {0}, rpIdHash, clientDataHash, cred.rawId,
new byte[] {0x04}, publicKey.getX(), publicKey.getY());
// TODO Make attStmt.attestnCert an X509Certificate right off the
// bat.
DataInputStream inputStream =
new DataInputStream(new ByteArrayInputStream(attStmt.attestnCert));
X509Certificate attestationCertificate = (X509Certificate) CertificateFactory
.getInstance("X.509").generateCertificate(inputStream);
if (!Crypto.verifySignature(attestationCertificate, signedBytes, attStmt.sig)) {
throw new ServletException("Signature invalid");
}
} catch (CertificateException e) {
throw new ServletException("Error when parsing attestationCertificate");
} catch (WebAuthnException e) {
throw new ServletException("Failure while verifying signature", e);
}
// TODO Check trust anchors
// TODO Check if self-attestation(/is allowed)
// TODO Check X.509 certs
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet class handling the http traffic for asset links.
*/
public class AssetLinksHttpServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 4479027193794261437L;
private static final String PATH = "/well-known/assetlinks.json";
public AssetLinksHttpServlet() {}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Do anything else that needs doing here
if (request.getRequestURI().toLowerCase().contains(".json")) {
response.setContentType("application/json");
}
// Read and return the resource from the non-hidden folder
String respString = readResource(PATH);
// response.getOutputStream().print(respString);
response.getWriter().print(respString);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
private String readResource(String resName) throws IOException {
InputStream in = getServletContext().getResourceAsStream(resName);
return inputStreamToString(in);
}
private static String inputStreamToString(InputStream in) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.objects.AuthenticationExtensionsClientInputs;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialRequestOptions;
import com.google.webauthn.gaedemo.storage.SessionData;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class BeginGetAssertion
*/
public class BeginGetAssertion extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public BeginGetAssertion() {}
@Override
protected void doGet(HttpServletRequest q, HttpServletResponse p)
throws ServletException, IOException {
doPost(q, p);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
String rpId = Iterables.get(Splitter.on(':').split(request.getHeader("Host")), 0);
// String rpId = (request.isSecure() ? "https://" : "http://") + request.getHeader("Host");
PublicKeyCredentialRequestOptions assertion = new PublicKeyCredentialRequestOptions(rpId);
SessionData session = new SessionData(assertion.challenge, rpId);
session.save(currentUser);
JsonObject sessionJson = session.getJsonObject();
assertion.populateAllowList(currentUser);
JsonObject assertionJson = assertion.getJsonObject();
assertionJson.add("session", sessionJson);
response.setContentType("application/json");
response.getWriter().println(assertionJson.toString());
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.IOException;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.webauthn.gaedemo.objects.AttestationConveyancePreference;
import com.google.webauthn.gaedemo.objects.AuthenticationExtensionsClientInputs;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttachment;
import com.google.webauthn.gaedemo.objects.AuthenticatorSelectionCriteria;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialCreationOptions;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialDescriptor;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialType;
import com.google.webauthn.gaedemo.objects.UserVerificationRequirement;
import com.google.webauthn.gaedemo.storage.CableKeyPair;
import com.google.webauthn.gaedemo.storage.Credential;
import com.google.webauthn.gaedemo.storage.SessionData;
public class BeginMakeCredential extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public BeginMakeCredential() {}
@Override
protected void doGet(HttpServletRequest a, HttpServletResponse b)
throws ServletException, IOException {
doPost(a, b);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Tyler -----------------
// String username = request.getParameter("username");
// User user = new User(username, "localhost");
// // String rpId = (request.isSecure() ? "https://" : "http://") + request.getHeader("Host");
// String rpId = Iterables.get(Splitter.on(':').split(request.getHeader("Host")), 0);
// String rpName = getServletContext().getInitParameter("name");
// rpName = (rpName == null ? "" : rpName);
//
// PublicKeyCredentialCreationOptions options =
// new PublicKeyCredentialCreationOptions(user.getNickname(), user.getEmail(), rpId, rpName);
//
// String hasAdvanced = request.getParameter("advanced");
// if (hasAdvanced.equals("true")) {
// parseAdvancedOptions(request.getParameter("advancedOptions"), options);
// }
//
// SessionData session = new SessionData(options.challenge, rpId);
//
// session.save(userService.getCurrentUser().getEmail());
// JsonObject sessionJson = session.getJsonObject();
// JsonObject optionsJson = options.getJsonObject();
// optionsJson.add("session", sessionJson);
//
// AuthenticationExtensionsClientInputs extensions = new AuthenticationExtensionsClientInputs();
// try {
// KeyPair cableKeyPair = extensions.addCableRegistrationData();
// // Store the KeyPair in storage
// CableKeyPair storedKeyPair = new CableKeyPair(cableKeyPair);
// storedKeyPair.save(session.getId());
// } catch (Exception e) {}
//
// optionsJson.add("extensions", extensions.getRegistrationExtensions());
//
// response.setContentType("application/json");
// response.getWriter().println(optionsJson.toString());
// Tyler -----------------
// Original Code
User user = userService.getCurrentUser();
// String rpId = (request.isSecure() ? "https://" : "http://") + request.getHeader("Host");
String rpId = Iterables.get(Splitter.on(':').split(request.getHeader("Host")), 0);
String rpName = getServletContext().getInitParameter("name");
rpName = (rpName == null ? "" : rpName);
PublicKeyCredentialCreationOptions options =
new PublicKeyCredentialCreationOptions(user.getNickname(), user.getEmail(), rpId, rpName);
String hasAdvanced = request.getParameter("advanced");
if (hasAdvanced.equals("true")) {
parseAdvancedOptions(request.getParameter("advancedOptions"), options);
}
SessionData session = new SessionData(options.challenge, rpId);
session.save(userService.getCurrentUser().getEmail());
JsonObject sessionJson = session.getJsonObject();
JsonObject optionsJson = options.getJsonObject();
optionsJson.add("session", sessionJson);
AuthenticationExtensionsClientInputs extensions = new AuthenticationExtensionsClientInputs();
try {
KeyPair cableKeyPair = extensions.addCableRegistrationData();
// Store the KeyPair in storage
CableKeyPair storedKeyPair = new CableKeyPair(cableKeyPair);
storedKeyPair.save(session.getId());
} catch (Exception e) {}
optionsJson.add("extensions", extensions.getRegistrationExtensions());
response.setContentType("application/json");
response.getWriter().println(optionsJson.toString());
}
private void parseAdvancedOptions(String jsonString, PublicKeyCredentialCreationOptions options) {
JsonElement jsonElement = new JsonParser().parse(jsonString);
JsonObject jsonObject = jsonElement.getAsJsonObject();
Set<Map.Entry<String, JsonElement>> entries = jsonObject.entrySet();
boolean rk = false;
boolean excludeCredentials = false;
UserVerificationRequirement uv = null;
AuthenticatorAttachment attachment = null;
for (Map.Entry<String, JsonElement> entry : entries) {
if (entry.getKey().equals("requireResidentKey")) {
rk = entry.getValue().getAsBoolean();
} else if (entry.getKey().equals("excludeCredentials")) {
excludeCredentials = entry.getValue().getAsBoolean();
if (excludeCredentials) {
List<PublicKeyCredentialDescriptor> credentials = new ArrayList<>();
String currentUser = userService.getCurrentUser().getEmail();
List<Credential> savedCreds = Credential.load(currentUser);
for (Credential c : savedCreds) {
credentials.add(convertCredentialToCredentialDescriptor(c));
}
options.setExcludeCredentials(credentials);
}
} else if (entry.getKey().equals("userVerification")) {
uv = UserVerificationRequirement.decode(entry.getValue().getAsString());
} else if (entry.getKey().equals("authenticatorAttachment")) {
attachment = AuthenticatorAttachment.decode(entry.getValue().getAsString());
} else if (entry.getKey().equals("attestationConveyancePreference")) {
AttestationConveyancePreference conveyance =
AttestationConveyancePreference.decode(entry.getValue().getAsString());
options.setAttestationConveyancePreference(conveyance);
}
}
options.setCriteria(new AuthenticatorSelectionCriteria(attachment, rk, uv));
}
private PublicKeyCredentialDescriptor convertCredentialToCredentialDescriptor(Credential c) {
PublicKeyCredentialType type = PublicKeyCredentialType.PUBLIC_KEY;
byte[] id = c.getCredential().getRawId();
return new PublicKeyCredentialDescriptor(type, id, null);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class Home
*/
public class Cable extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public Cable() {}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nickname = userService.getCurrentUser().getNickname();
String logoutUrl = userService.createLogoutURL(request.getRequestURI());
request.setAttribute("nickname", nickname);
request.setAttribute("logoutUrl", logoutUrl);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/cable.jsp");
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialRequestOptions;
import com.google.webauthn.gaedemo.storage.SessionData;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class CreateSession
*/
public class CreateSession extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
/**
* @see HttpServlet#HttpServlet()
*/
public CreateSession() {}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
String currentUser = userService.getCurrentUser().getEmail();
String rpId = (request.isSecure() ? "https://" : "http://") + request.getHeader("Host");
PublicKeyCredentialRequestOptions assertion = new PublicKeyCredentialRequestOptions(rpId);
SessionData session = new SessionData(assertion.challenge, rpId);
session.save(currentUser);
JsonObject assertionJson = new JsonObject();
assertionJson.add("session", session.getJsonObject());
response.setContentType("application/json");
response.getWriter().println(assertionJson.toString());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.webauthn.gaedemo.storage.CableKeyPair;
import com.google.webauthn.gaedemo.storage.SessionData;
/**
* Servlet implementation class DeleteOldSessions
*/
public class DeleteOldSessions extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public DeleteOldSessions() {}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
SessionData.removeAllOldSessions();
CableKeyPair.removeAllOldKeyPairs();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
SessionData.removeAllOldSessions();
CableKeyPair.removeAllOldKeyPairs();
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.server.PublicKeyCredentialResponse;
import com.google.webauthn.gaedemo.server.Server;
import com.google.webauthn.gaedemo.storage.Credential;
public class FinishGetAssertion extends HttpServlet {
private static final int FINGERPRINT = 2;
private static final int SCREEN_LOCK = 134;
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public FinishGetAssertion() {
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
String data = request.getParameter("data");
if (data == null) {
data = "";
}
String session = request.getParameter("session");
if (session == null) {
session = "";
}
String credentialId = null;
String type = null;
String uvm = null;
JsonElement assertionJson = null;
try {
JsonObject json = new JsonParser().parse(data).getAsJsonObject();
JsonElement idJson = json.get("id");
if (idJson != null) {
credentialId = idJson.getAsString();
}
JsonElement typeJson = json.get("type");
if (typeJson != null) {
type = typeJson.getAsString();
}
JsonElement uvmJson = json.get("uvm");
if (uvmJson != null && uvmJson.isJsonArray()) {
JsonArray uvmArray = uvmJson.getAsJsonArray();
if (uvmJson.isJsonArray()) {
JsonElement uvmElement = uvmArray.get(0);
if(uvmElement != null) {
switch (uvmElement.getAsJsonObject().get("userVerificationMethod").getAsInt()){
case FINGERPRINT:
uvm = "Fingerprint";
break;
case SCREEN_LOCK:
uvm = "Screen Lock";
break;
default:
uvm = "Others";
break;
}
}
}
}
assertionJson = json.get("response");
if (assertionJson == null) {
throw new ServletException("Missing element 'response'");
}
} catch (IllegalStateException e) {
throw new ServletException("Passed data not a json object");
} catch (ClassCastException e) {
throw new ServletException("Invalid input");
} catch (JsonParseException e) {
throw new ServletException("Input not valid json");
}
AuthenticatorAssertionResponse assertion = null;
try {
assertion = new AuthenticatorAssertionResponse(assertionJson);
} catch (ResponseException e) {
throw new ServletException(e.toString());
}
// Recoding of credential ID is needed, because the ID from HTTP servlet request doesn't support
// padding.
String credentialIdRecoded = BaseEncoding.base64Url().encode(
BaseEncoding.base64Url().decode(credentialId));
PublicKeyCredential cred = new PublicKeyCredential(credentialIdRecoded, type,
BaseEncoding.base64Url().decode(credentialId), assertion);
Credential savedCredential;
try {
savedCredential = Server.validateAndFindCredential(cred, currentUser, session);
} catch (ResponseException e) {
throw new ServletException("Unable to validate assertion", e);
}
Server.verifyAssertion(cred, currentUser, session, savedCredential);
savedCredential.setUserVerificationMethod(uvm);
savedCredential.save(currentUser);
response.setContentType("application/json");
String handle = DatatypeConverter.printHexBinary(savedCredential.getCredential().rawId);
PublicKeyCredentialResponse rsp =
new PublicKeyCredentialResponse(true, "Successful assertion", handle);
response.getWriter().println(rsp.toJson());
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.IOException;
import java.security.KeyPair;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.objects.AttestationExtension;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.CablePairingData;
import com.google.webauthn.gaedemo.objects.CableRegistrationData;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.server.AndroidSafetyNetServer;
import com.google.webauthn.gaedemo.server.PackedServer;
import com.google.webauthn.gaedemo.server.PublicKeyCredentialResponse;
import com.google.webauthn.gaedemo.server.U2fServer;
import com.google.webauthn.gaedemo.storage.CableKeyPair;
import com.google.webauthn.gaedemo.storage.Credential;
public class FinishMakeCredential extends HttpServlet {
private static final int FINGERPRINT = 2;
private static final int SCREEN_LOCK = 134;
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public FinishMakeCredential() {}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
String data = request.getParameter("data");
if (data == null) {
data = "";
}
String session = request.getParameter("session");
if (session == null) {
session = "";
}
String credentialId = null;
String type = null;
String uvm = null;
JsonElement makeCredentialResponse = null;
CablePairingData cablePairingData = null;
try {
JsonObject json = new JsonParser().parse(data).getAsJsonObject();
JsonElement idJson = json.get("id");
if (idJson != null) {
credentialId = idJson.getAsString();
}
JsonElement typeJson = json.get("type");
if (typeJson != null) {
type = typeJson.getAsString();
}
JsonElement uvmJson = json.get("uvm");
if (uvmJson != null && uvmJson.isJsonArray()) {
JsonArray uvmArray = uvmJson.getAsJsonArray();
if (uvmJson.isJsonArray()) {
JsonElement uvmElement = uvmArray.get(0);
if (uvmElement != null) {
switch (uvmElement.getAsJsonObject().get("userVerificationMethod").getAsInt()){
case FINGERPRINT:
uvm = "Fingerprint";
break;
case SCREEN_LOCK:
uvm = "Screen Lock";
break;
default:
uvm = "Others";
break;
}
}
}
}
makeCredentialResponse = json.get("response");
} catch (IllegalStateException e) {
throw new ServletException("Passed data not a json object");
} catch (ClassCastException e) {
throw new ServletException("Invalid input");
} catch (JsonParseException e) {
throw new ServletException("Input not valid json");
}
AuthenticatorAttestationResponse attestation = null;
try {
attestation = new AuthenticatorAttestationResponse(makeCredentialResponse);
} catch (ResponseException e) {
throw new ServletException(e);
}
if (attestation.getAttestationObject().getAuthenticatorData().hasExtensionData()) {
Map<String, AttestationExtension> extensionMap =
attestation.getAttestationObject().getAuthenticatorData().getExtensionData();
if (extensionMap.containsKey(CableRegistrationData.KEY)) {
CableRegistrationData cableData =
(CableRegistrationData) extensionMap.get(CableRegistrationData.KEY);
// Get key pair generated during the StartMakeCredential operation
KeyPair sessionKeyPair = CableKeyPair.get(Long.valueOf(session));
cablePairingData = CablePairingData.generatePairingData(cableData, sessionKeyPair);
}
}
// Recoding of credential ID is needed, because the ID from HTTP servlet request doesn't support
// padding.
String credentialIdRecoded =
BaseEncoding.base64Url().encode(BaseEncoding.base64Url().decode(credentialId));
PublicKeyCredential cred = new PublicKeyCredential(credentialIdRecoded, type,
BaseEncoding.base64Url().decode(credentialId), attestation);
String domain = (request.isSecure() ? "https://" : "http://") + request.getHeader("Host");
String rpId = Iterables.get(Splitter.on(':').split(request.getHeader("Host")), 0);
switch (cred.getAttestationType()) {
case FIDOU2F:
U2fServer.registerCredential(cred, currentUser, session, domain, rpId);
break;
case ANDROIDSAFETYNET:
AndroidSafetyNetServer.registerCredential(cred, currentUser, session, rpId);
break;
case PACKED:
PackedServer.registerCredential(cred, currentUser, session, rpId);
break;
case NONE:
break;
}
Credential credential = new Credential(cred);
if (cablePairingData != null) {
credential.setCablePairingData(cablePairingData);
}
credential.setUserVerificationMethod(uvm);
credential.save(currentUser);
PublicKeyCredentialResponse rsp =
new PublicKeyCredentialResponse(true, "Successfully created credential");
response.setContentType("application/json");
response.getWriter().println(rsp.toJson());
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.webauthn.gaedemo.storage.SessionData;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class GetSession
*/
public class GetSession extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
/**
* @see HttpServlet#HttpServlet()
*/
public GetSession() {}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
System.out.println(Long.valueOf(id));
SessionData session =
SessionData.load(userService.getCurrentUser().getEmail(), Long.valueOf(id));
response.setContentType("application/json");
if (session != null) {
response.getWriter().println(session.getJsonObject().toString());
} else {
throw new ServletException("Session not found");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class Home
*/
public class Home extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public Home() {}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nickname = userService.getCurrentUser().getNickname();
String logoutUrl = userService.createLogoutURL(request.getRequestURI());
request.setAttribute("nickname", nickname);
request.setAttribute("logoutUrl", logoutUrl);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp");
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class Home
*/
public class Login extends HttpServlet {
private final Logger log= Logger.getLogger(Login.class.getSimpleName());
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
public Login() {}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nickname = userService.getCurrentUser().getNickname();
String logoutUrl = userService.createLogoutURL(request.getRequestURI());
System.out.println("Login");
log.info("Login");
request.setAttribute("nickname", nickname);
request.setAttribute("logoutUrl", logoutUrl);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/login.jsp");
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.objects.AttestationObject;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.CredentialPublicKey;
import com.google.webauthn.gaedemo.storage.Credential;
/**
* Servlet implementation class RegisteredKeys
*/
public class RegisteredKeys extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
/**
* @see HttpServlet#HttpServlet()
*/
public RegisteredKeys() {}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
List<Credential> savedCreds = Credential.load(currentUser);
JsonArray result = new JsonArray();
response.setContentType("application/json");
for (Credential c : savedCreds) {
JsonObject cJson = new JsonObject();
cJson.addProperty("handle", DatatypeConverter.printHexBinary(c.getCredential().rawId));
cJson.addProperty("base64handle", BaseEncoding.base64Url().encode(c.getCredential().rawId));
CredentialPublicKey publicKey =
((AuthenticatorAttestationResponse) c.getCredential().getResponse())
.getAttestationObject().getAuthenticatorData().getAttData().getPublicKey();
cJson.addProperty("publicKey", publicKey.toString());
AttestationObject attObj =
((AuthenticatorAttestationResponse) c.getCredential().getResponse())
.getAttestationObject();
cJson.addProperty("name", attObj.getAttestationStatement().getName());
cJson.addProperty("date", c.getDate().toString());
cJson.addProperty("id", c.id);
cJson.addProperty("hasCable", c.hasCablePairingData());
if (c.hasUserVerificationMethod()) {
cJson.addProperty("userVerificationMethod", c.getUserVerificationMethod());
}
if (((AuthenticatorAttestationResponse) c.getCredential().getResponse()).getTransports() != null) {
JsonArray transportList = new JsonArray();
for (String transport : ((AuthenticatorAttestationResponse) c.getCredential().getResponse()).getTransports()) {
transportList.add(transport);
}
cJson.add("transports", transportList);
}
result.add(cJson);
}
response.getWriter().print(result.toString());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.webauthn.gaedemo.storage.Credential;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class RemoveCredential
*/
public class RemoveCredential extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
/**
* @see HttpServlet#HttpServlet()
*/
public RemoveCredential() {}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
Credential.remove(currentUser, request.getParameter("id"));
List<Credential> savedCreds = Credential.load(currentUser);
response.setContentType("text/html");
for (Credential c : savedCreds) {
response.getWriter()
.println(c.toJson() + "<a href=RemoveCredential?id=" + c.id + ">[X]</a><br />");
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
Credential.remove(currentUser, request.getParameter("credentialId"));
response.setContentType("application/json");
response.getWriter().println("{}");
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.objects.AttestationData;
import com.google.webauthn.gaedemo.objects.AttestationObject;
import com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse;
import com.google.webauthn.gaedemo.objects.AuthenticatorData;
import com.google.webauthn.gaedemo.objects.EccKey;
import com.google.webauthn.gaedemo.objects.FidoU2fAttestationStatement;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.google.webauthn.gaedemo.storage.Credential;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* Servlet implementation class SaveCredential
*/
public class SaveCredential extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
/**
* @see HttpServlet#HttpServlet()
*/
public SaveCredential() {
super();
}
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String currentUser = userService.getCurrentUser().getEmail();
String id = request.getParameter("id");
Random rand = new Random();
byte[] aaguid = new byte[16];
rand.nextBytes(aaguid);
byte[] x = new byte[5], y = new byte[5];
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
} catch (NoSuchAlgorithmException e) {
throw new ServletException(e);
} catch (NoSuchProviderException e) {
throw new ServletException(e);
}
try {
keyGen.initialize(ecGenSpec, new SecureRandom());
} catch (InvalidAlgorithmParameterException e) {
throw new ServletException(e);
}
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey pub = keyPair.getPublic();
ECPublicKey publicKey = (ECPublicKey) pub;
x = publicKey.getW().getAffineX().toByteArray();
y = publicKey.getW().getAffineY().toByteArray();
EccKey ecc = new EccKey(x, y);
AttestationData attData = new AttestationData(aaguid, aaguid, ecc);
byte[] rpIdHash = Crypto
.sha256Digest(((request.isSecure() ? "https://" : "http://") + request.getHeader("Host"))
.getBytes(StandardCharsets.UTF_8));
AuthenticatorData authData = new AuthenticatorData(rpIdHash, (byte) (1 << 6), 0, attData, null);
FidoU2fAttestationStatement attStmt = new FidoU2fAttestationStatement();
AttestationObject attObj = new AttestationObject(authData, "fido-u2f", attStmt);
AuthenticatorAttestationResponse attRsp = new AuthenticatorAttestationResponse();
attRsp.decodedObject = attObj;
PublicKeyCredential pkc =
new PublicKeyCredential(id, "", id.getBytes(StandardCharsets.UTF_8), attRsp);
Credential credential = new Credential(pkc);
credential.save(currentUser);
response.setContentType("application/json");
response.getWriter().println(credential.toJson());
}
}
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.servlets;
import static org.apache.commons.codec.binary.Hex.decodeHex;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.webauthn.gaedemo.objects.CablePairingData;
import com.google.webauthn.gaedemo.storage.Credential;
import org.apache.commons.codec.DecoderException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* Servlet to update a credential.
*
* Only supports updating caBLE pairing data for now.
*/
public class UpdateCredential extends HttpServlet {
private static final long serialVersionUID = 1L;
private final UserService userService = UserServiceFactory.getUserService();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
long id = Long.valueOf(req.getParameter("id"));
String irkHex = req.getParameter("irk");
String lkHex = req.getParameter("lk");
// Validate
if (irkHex == null || lkHex == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing IRK or LK");
return;
}
byte[] irk, lk;
try {
irk = decodeHex(irkHex.toCharArray());
lk = decodeHex(lkHex.toCharArray());
} catch (DecoderException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Cannot decode IRK/LK (use hex)");
return;
}
if (irk.length != 32 || lk.length != 32) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "IRK and LK must both be 32 bytes long");
return;
}
String currentUser = userService.getCurrentUser().getEmail();
List<Credential> credentials = Credential.load(currentUser);
for (Credential credential : credentials) {
if (id == credential.id) {
credential.setCablePairingData(new CablePairingData(1, irk, lk));
credential.save(currentUser);
resp.setContentType("application/json");
resp.getWriter().println("{}");
return;
}
}
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import static com.googlecode.objectify.ObjectifyService.ofy;
import java.util.Objects;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonObject;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
@Entity
public class AttestationSessionData {
@Parent
Key<User> user;
@Id
public Long id;
String challenge;
String origin;
public AttestationSessionData(byte[] challenge, String origin) {
this.challenge = BaseEncoding.base64().encode(challenge);
this.origin = origin;
}
@Override
public int hashCode() {
return Objects.hash(challenge, origin, id, user);
}
boolean equalsThis(AttestationSessionData other) {
return (this.challenge != null && other.challenge != null
&& this.challenge.equals(other.challenge))
&& (this.origin != null && other.origin != null && this.origin.equals(other.origin));
}
@Override
public boolean equals(Object other) {
if (!(other instanceof AttestationSessionData)) {
return false;
}
return this.equalsThis((AttestationSessionData)other);
}
public void save(String currentUser) {
Key<User> user = Key.create(User.class, currentUser);
this.user = user;
ofy().save().entity(this).now();
}
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
result.addProperty("challenge", challenge);
result.addProperty("origin", origin);
return result;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import com.google.appengine.api.datastore.ShortBlob;
import com.googlecode.objectify.Key;
@Entity
public class Authenticator {
@Parent
Key<User> theUser;
@Id
public Long id;
public ShortBlob keyHandle;
public ShortBlob publicKey;
}
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyPair;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.common.io.BaseEncoding;
import com.google.webauthn.gaedemo.server.Datastore;
public class CableKeyPair {
public static final String KIND = "CableKeyPair";
public static final String KEY_PAIR_PROPERTY = "keypair";
public static final String TIMESTAMP_PROPERTY = "created";
private static final long HOUR_IN_MILLIS = (1 * 60 * 60 * 1000);
private KeyPair keyPair;
private long id;
public CableKeyPair(KeyPair keyPair) {
this.keyPair = keyPair;
}
public long save(Long sessionId) throws IOException {
Key parentKey = KeyFactory.createKey(SessionData.KIND, sessionId);
Entity keyEntity = new Entity(KIND, parentKey);
// Serialize the KeyPair
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(keyPair);
keyEntity.setProperty(KEY_PAIR_PROPERTY, BaseEncoding.base64().encode(baos.toByteArray()));
keyEntity.setProperty(TIMESTAMP_PROPERTY, new Date());
Key stored = Datastore.getDatastore().put(keyEntity);
this.id = stored.getId();
return id;
}
public static KeyPair get(Long sessionId) throws IOException {
Key sessionKey = KeyFactory.createKey(SessionData.KIND, sessionId);
Query query = new Query(KIND).setAncestor(sessionKey);
List<Entity> results =
Datastore.getDatastore().prepare(query).asList(FetchOptions.Builder.withDefaults());
for (Entity result : results) {
if (result.getParent().getId() == sessionId.longValue()) {
byte[] serializedKeyPair =
BaseEncoding.base64().decode((String) result.getProperty(KEY_PAIR_PROPERTY));
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serializedKeyPair));
try {
return (KeyPair) in.readObject();
} catch (ClassNotFoundException e1) {
throw new IOException(e1);
}
}
}
throw new IOException("KeyPair " + String.valueOf(sessionId) + "not found");
}
/**
* Remove all stale sessions older than 1 hour.
*/
public static void removeAllOldKeyPairs() {
Filter filter = new FilterPredicate(TIMESTAMP_PROPERTY, FilterOperator.LESS_THAN_OR_EQUAL,
new Date(System.currentTimeMillis() - HOUR_IN_MILLIS));
Query query = new Query(KIND).setFilter(filter);
List<Entity> results =
Datastore.getDatastore().prepare(query).asList(FetchOptions.Builder.withDefaults());
List<Key> keys = results.stream().map(entity -> entity.getKey()).collect(Collectors.toList());
Datastore.getDatastore().delete(keys);
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import static com.googlecode.objectify.ObjectifyService.ofy;
import java.util.Date;
import java.util.List;
import com.google.gson.Gson;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import com.google.webauthn.gaedemo.objects.CablePairingData;
import com.google.webauthn.gaedemo.objects.PublicKeyCredential;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
@Entity
public class Credential {
@Parent
Key<User> user;
@Id
public Long id;
private Date date;
private int signCount;
private PublicKeyCredential credential;
private CablePairingData cablePairingData;
private String userVerificationMethod;
public Credential() {
signCount = 0;
date = new Date();
}
public Credential(String json) {
signCount = 0;
Gson gson = new Gson();
credential = gson.fromJson(json, PublicKeyCredential.class);
date = new Date();
}
public Credential(PublicKeyCredential credential) {
this.signCount = 0;
this.credential = credential;
this.date = new Date();
}
public void validate() throws ResponseException {
if (credential == null) {
throw new ResponseException("Credentials invalid");
}
}
public String toJson() {
Gson gson = new Gson();
return gson.toJson(this);
}
public void save(String currentUser) {
Key<User> user = Key.create(User.class, currentUser);
this.user = user;
ofy().save().entity(this).now();
}
public static List<Credential> load(String currentUser) {
Key<User> user = Key.create(User.class, currentUser);
List<Credential> credentials = ofy().load().type(Credential.class).ancestor(user).list();
return credentials;
}
public static void remove(String currentUser, String id) {
Key<User> user = Key.create(User.class, currentUser);
ofy().delete().type(Credential.class).parent(user).id(Long.valueOf(id)).now();
}
/**
* @return the credential
*/
public PublicKeyCredential getCredential() {
return credential;
}
/**
* @return the signCount
*/
public int getSignCount() {
return signCount;
}
/**
* @param signCount
*/
public void updateSignCount(int signCount) {
this.signCount = signCount;
ofy().save().entity(this).now();
}
/**
* @return the date
*/
public Date getDate() {
return date;
}
public CablePairingData getCablePairingData() {
return cablePairingData;
}
public void setCablePairingData(CablePairingData cablePairingData) {
this.cablePairingData = cablePairingData;
}
public boolean hasCablePairingData() {
return cablePairingData != null;
}
public void setUserVerificationMethod(String userVerificationMethod) {
this.userVerificationMethod = userVerificationMethod;
}
public boolean hasUserVerificationMethod() {
return userVerificationMethod != null;
}
public String getUserVerificationMethod() {
return userVerificationMethod;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import com.google.webauthn.gaedemo.objects.PublicKeyCredentialType;
import com.google.webauthn.gaedemo.objects.AuthenticatorTransport;
import java.io.Serializable;
import java.util.*;
public class CredentialObject implements Serializable {
private static final long serialVersionUID = 1L;
PublicKeyCredentialType type;
byte[] handle;
ArrayList<AuthenticatorTransport> transports;
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.common.io.BaseEncoding;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.server.Datastore;
public class SessionData {
public static final String KIND = "SessionData";
private static final String CHALLENGE_PROPERTY = "challenge";
private static final String ORIGIN_PROPERTY = "origin";
private static final String TIMESTAMP_PROPERTY = "created";
private static final String ID_PROPERTY = "id";
private static final long HOUR_IN_MILLIS = (1 * 60 * 60 * 1000);
private long id = 0;
private String challenge;
private String origin;
private Date created;
public SessionData() {
this.created = new Date();
}
public SessionData(byte[] challenge, String origin) {
this.challenge = BaseEncoding.base64().encode(challenge);
this.origin = origin;
this.created = new Date();
}
public SessionData(byte[] challenge, String origin, Date date) {
this.challenge = BaseEncoding.base64().encode(challenge);
this.origin = origin;
this.created = date;
}
/**
* @return the challenge
*/
public String getChallenge() {
return challenge;
}
/**
* @return the origin
*/
public String getOrigin() {
return origin;
}
/**
* Save the session into the datastore.
*
* @param currentUser
*/
public void save(String currentUser) {
Key parentKey = KeyFactory.createKey(User.KIND, currentUser);
Entity session = new Entity(KIND, parentKey);
session.setProperty(CHALLENGE_PROPERTY, challenge);
session.setProperty(ORIGIN_PROPERTY, origin);
session.setProperty(TIMESTAMP_PROPERTY, new Date());
Key stored = Datastore.getDatastore().put(session);
this.id = stored.getId();
}
/**
* @return the id
*/
public long getId() {
return id;
}
private static List<Entity> loadEntities(String currentUser) {
Key userKey = KeyFactory.createKey(User.KIND, currentUser);
Query query = new Query(KIND).setAncestor(userKey);
List<Entity> results =
Datastore.getDatastore().prepare(query).asList(FetchOptions.Builder.withDefaults());
return results;
}
/**
* Load all sessions from datastore for a particular user.
*
* @param currentUser
* @return
*/
public static List<SessionData> load(String currentUser) {
List<Entity> sessionEntities = loadEntities(currentUser);
ArrayList<SessionData> sessions = new ArrayList<>();
for (Entity e : sessionEntities) {
SessionData session =
new SessionData(BaseEncoding.base64().decode((String) e.getProperty(CHALLENGE_PROPERTY)),
(String) e.getProperty(ORIGIN_PROPERTY), (Date) e.getProperty(TIMESTAMP_PROPERTY));
sessions.add(session);
}
return sessions;
}
/**
* Remove all sessions for a particular user older than 1 hour.
*
* @param currentUser
*/
public static void removeOldSessions(String currentUser) {
List<Entity> sessionEntities = loadEntities(currentUser);
for (Entity e : sessionEntities) {
Date sessionDate = (Date) e.getProperty(TIMESTAMP_PROPERTY);
if (sessionDate.getTime() < System.currentTimeMillis() - HOUR_IN_MILLIS) {
Datastore.getDatastore().delete(e.getKey());
}
}
}
/**
* Remove all stale sessions older than 1 hour.
*/
public static void removeAllOldSessions() {
Filter filter = new FilterPredicate(TIMESTAMP_PROPERTY, FilterOperator.LESS_THAN_OR_EQUAL,
new Date(System.currentTimeMillis() - HOUR_IN_MILLIS));
Query query = new Query(KIND).setFilter(filter);
List<Entity> results =
Datastore.getDatastore().prepare(query).asList(FetchOptions.Builder.withDefaults());
List<Key> keys = results.stream().map(entity -> entity.getKey()).collect(Collectors.toList());
Datastore.getDatastore().delete(keys);
}
/**
* Query datastore for a session for a particular user via id.
*
* @param currentUser
* @param id
* @return
*/
public static SessionData load(String currentUser, Long id) {
Key userKey = KeyFactory.createKey(User.KIND, currentUser);
Key sessionKey = KeyFactory.createKey(userKey, KIND, id);
try {
Entity e = Datastore.getDatastore().get(sessionKey);
return new SessionData(
BaseEncoding.base64().decode((String) e.getProperty(CHALLENGE_PROPERTY)),
(String) e.getProperty(ORIGIN_PROPERTY), (Date) e.getProperty(TIMESTAMP_PROPERTY));
} catch (EntityNotFoundException e1) {
return null;
}
}
/**
* Remove a particular session for a user via id.
*
* @param currentUser
* @param id
*/
public static void remove(String currentUser, Long id) {
Key userKey = KeyFactory.createKey(User.KIND, currentUser);
Key sessionKey = KeyFactory.createKey(userKey, KIND, id);
Datastore.getDatastore().delete(sessionKey);
}
/**
* @return JsonObject representation of the SessionData object.
*/
public JsonObject getJsonObject() {
JsonObject result = new JsonObject();
if (id != 0L) {
result.addProperty(ID_PROPERTY, id);
}
result.addProperty(CHALLENGE_PROPERTY, challenge);
result.addProperty(ORIGIN_PROPERTY, origin);
result.addProperty(TIMESTAMP_PROPERTY, created.toString());
return result;
}
}
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.webauthn.gaedemo.storage;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
@Entity
public class User {
public static final String KIND = "User";
@Id
public String id;
}
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>webauthndemo</application>
<version>${appengine.app.version}</version>
<threadsafe>true</threadsafe>
<runtime>java8</runtime>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
</system-properties>
</appengine-web-app>
<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/DeleteOldSessions</url>
<description>Hourly session cleanup</description>
<schedule>every 60 minutes</schedule>
</cron>
</cronentries>
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="true">
<datastore-index kind="SessionData" ancestor="true"
source="manual">
<property name="created" direction="asc" />
</datastore-index>
<datastore-index kind="CableKeyPair" ancestor="true"
source="manual">
<property name="created" direction="asc" />
</datastore-index>
</datastore-indexes>
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to WARNING
.level = DEBUG
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<welcome-file-list>
<welcome-file>Home</welcome-file>
</welcome-file-list>
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.google.webauthn.gaedemo.server.OfyHelper</listener-class>
</listener>
<security-constraint>
<web-resource-collection>
<web-resource-name>assetlinks</web-resource-name>
<url-pattern>/.well-known/*</url-pattern>
</web-resource-collection>
<!-- OMIT auth-constraint -->
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>cron</web-resource-name>
<url-pattern>/DeleteOldSessions</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<url-pattern>/_ah/api/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>all-others</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<context-param>
<param-name>name</param-name>
<param-value>webauthn-demo</param-value>
</context-param>
<servlet>
<description></description>
<display-name>FinishMakeCredential</display-name>
<servlet-name>FinishMakeCredential</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.FinishMakeCredential</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FinishMakeCredential</servlet-name>
<url-pattern>/FinishMakeCredential</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>BeginMakeCredential</display-name>
<servlet-name>BeginMakeCredential</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.BeginMakeCredential</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BeginMakeCredential</servlet-name>
<url-pattern>/BeginMakeCredential</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>BeginGetAssertion</display-name>
<servlet-name>BeginGetAssertion</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.BeginGetAssertion</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BeginGetAssertion</servlet-name>
<url-pattern>/BeginGetAssertion</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>RegisteredKeys</display-name>
<servlet-name>RegisteredKeys</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.RegisteredKeys</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegisteredKeys</servlet-name>
<url-pattern>/RegisteredKeys</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>RemoveCredential</display-name>
<servlet-name>RemoveCredential</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.RemoveCredential</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RemoveCredential</servlet-name>
<url-pattern>/RemoveCredential</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>UpdateCredential</display-name>
<servlet-name>UpdateCredential</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.UpdateCredential</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UpdateCredential</servlet-name>
<url-pattern>/UpdateCredential</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>Home</display-name>
<servlet-name>Home</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.Home</servlet-class>
</servlet>
<servlet>
<description></description>
<display-name>Cable</display-name>
<servlet-name>Cable</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.Cable</servlet-class>
</servlet>
<servlet>
<description></description>
<display-name>Login</display-name>
<servlet-name>Login</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Home</servlet-name>
<url-pattern>/Home</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Cable</servlet-name>
<url-pattern>/Cable</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>FinishGetAssertion</display-name>
<servlet-name>FinishGetAssertion</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.FinishGetAssertion</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FinishGetAssertion</servlet-name>
<url-pattern>/FinishGetAssertion</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>CreateSession</display-name>
<servlet-name>CreateSession</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.CreateSession</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CreateSession</servlet-name>
<url-pattern>/CreateSession</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>GetSession</display-name>
<servlet-name>GetSession</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.GetSession</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetSession</servlet-name>
<url-pattern>/GetSession</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>SaveCredential</display-name>
<servlet-name>SaveCredential</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.SaveCredential</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SaveCredential</servlet-name>
<url-pattern>/SaveCredential</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>DeleteOldSessions</display-name>
<servlet-name>DeleteOldSessions</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.DeleteOldSessions</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DeleteOldSessions</servlet-name>
<url-pattern>/DeleteOldSessions</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>EndpointsServlet</servlet-name>
<servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.google.webauthn.gaedemo.endpoints.Fido2RequestHandler</param-value>
</init-param>
<init-param>
<param-name>restricted</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>EndpointsServlet</servlet-name>
<url-pattern>/_ah/api/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>AssetLinks</servlet-name>
<servlet-class>com.google.webauthn.gaedemo.servlets.AssetLinksHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AssetLinks</servlet-name>
<url-pattern>/.well-known/assetlinks.json</url-pattern>
</servlet-mapping>
</web-app>
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet"
href="https://code.getmdl.io/1.3.0/material.teal-pink.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<link href="stylesheets/webauthn.css" rel="stylesheet">
<script src="//code.getmdl.io/1.3.0/material.min.js"></script>
<script src="js/webauthn-cable.js"></script>
<title>WebAuthN Demo</title>
</head>
<body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout-icon"></div>
<div class="mdl-layout__header-row">
<span class="mdl-layout__title">WebAuthN Demo - ${nickname}</span>
<div class="mdl-layout-spacer"></div>
<nav class="mdl-navigation">
<button id="credential-button"
class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect make-button">
Register New Credential</button>
<button id="authenticate-button"
class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect auth-button">
Authenticate</button>
<a href="${logoutUrl}">
<button
class="mdl-button mdl-js-button logout-button mdl-js-ripple-effect">
Logout</button>
</a>
</nav>
</div>
</header>
<main class="mdl-layout__content mdl-color--grey-100">
<div id="active" class="hidden activity-bar">
<h3 class="active-text">Waiting for user touch</h3>
<div
class="mdl-progress mdl-js-progress mdl-progress__indeterminate page-width"></div>
</div>
<div id="error" class="hidden">
<h3 class="error-text" id="error-text">An error has occurred</h3>
</div>
<div id="success" class="hidden">
<h3 class="success-text" id="success-text">Success</h3>
</div>
<div id="advanced-switch" class="advanced-switch">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-advanced">
<input type="checkbox" id="switch-advanced" class="mdl-switch__input">
<span class="mdl-switch__label">Advanced Registration Options</span>
</label>
</div>
<div id="advanced-switch-auth" class="advanced-switch">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-advanced-auth">
<input type="checkbox" id="switch-advanced-auth" class="mdl-switch__input">
<span class="mdl-switch__label">Advanced Authentication Options</span>
</label>
</div>
<div id="advanced" class="advanced hidden">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-rr">
<input type="checkbox" id="switch-rr" class="mdl-switch__input">
<span class="mdl-switch__label">Prevent Reregistration</span>
</label>
<label for="attachment" class="attachment">Attachment Type</label>
<select id="attachment" class="attachment">
<option value="none">N/A</option>
<option value="platform">Platform</option>
<option value="cross-platform">Cross-Platform</option>
</select>
<br />
<label for="conveyance" class="attachment">Conveyance Preference</label>
<select id="conveyance" class="attachment">
<option value="NA">N/A</option>
<option value="none">None</option>
<option value="indirect">Indirect</option>
<option value="direct">Direct</option>
</select>
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-rk">
<input type="checkbox" id="switch-rk" class="mdl-switch__input">
<span class="mdl-switch__label">Require resident key</span>
</label>
<label for="userVerification" class="attachment">User Verification</label>
<select id="userVerification" class="attachment">
<option value="none">None</option>
<option value="required">Required</option>
<option value="preferred">Preferred</option>
<option value="discouraged">Discouraged</option>
</select>
</div>
<div id="advanced-auth" class="advanced hidden">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text" id="cable-client-eid" class="mdl-textfield__input" pattern="[a-fA-F\d]+">
<label class="mdl-textfield__label" for="cable-client-eid">caBLE Client EID</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text" id="cable-authenticator-eid" class="mdl-textfield__input" pattern="[a-fA-F\d]+">
<label class="mdl-textfield__label" for="cable-authenticator-eid">caBLE Authenticator EID</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text" id="cable-session-pre-key" class="mdl-textfield__input" pattern="[a-fA-F\d]+">
<label class="mdl-textfield__label" for="cable-session-pre-key">caBLE Session Pre Key</label>
</div>
</div>
<div id="credentials" class="mdl-grid mdl-grid--no-spacing"></div>
<div id="github" class="github-link">
<a href="https://github.com/google/webauthndemo">GitHub</a>
</div>
</main>
</div>
</body>
</html>
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet"
href="https://code.getmdl.io/1.3.0/material.teal-pink.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<link href="stylesheets/webauthn.css" rel="stylesheet">
<script src="//code.getmdl.io/1.3.0/material.min.js"></script>
<!-- Web Animations polyfill for Edge. -->
<script src="https://rawgit.com/web-animations/web-animations-js/master/web-animations.min.js"></script>
<title>Thanks PL</title>
</head>
<body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<span class="mdl-layout__title">WebAuthn Demo</span>
</div>
<div id="header-buttons" class="mdl-layout__header-row">
<div id="username-input" class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" id="username"/>
<label class="mdl-textfield__label" for="username">Username</label>
</div>
<%-- <button id="isuvpaa-button"--%>
<%-- class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect make-button"--%>
<%-- title="Calls IsUserVerifyingPlatformAuthenticatorAvailable. Returns &quot;true&quot; if the device supports an internal authenticator, and &quot;false&quot; if the device does not. (E.g., fingerprints on Android or TouchID on MacOS)"--%>
<%-- >--%>
<%-- isUVPAA</button>--%>
<%-- <button id="platform-button"--%>
<%-- class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect make-button"--%>
<%-- title="A convenience method to register an internal authenticator. This is equivalent to calling MakeCredential with AttachmentType=Platform"--%>
<%-- >--%>
<%-- Register platform authenticator</button>--%>
<button id="credential-button"
class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect make-button"
title="Calls MakeCredential to register a new credential from a FIDO device"
>
Register new credential</button>
<button id="authenticate-button"
class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect auth-button"
title="Calls GetAssertion to request an assertion from a previously-registered FIDO device"
>
Authenticate</button>
</div>
</header>
<div class="mdl-layout__drawer">
<span class="mdl-layout-title">Advanced Options</span>
<div class="mdl-list">
<div class="mdl-list__item">
<label class="mdl-checkbox mdl-js-checkbox" for="switch-rr">
<input type="checkbox" id="switch-rr" class="mdl-checkbox__input">
<span class="mdl-checkbox__label">Prevent Reregistration</span>
</label>
</div>
<div class="mdl-list__item">
<label for="attachment" class="attachment">Attachment Type</label>
<select id="attachment" class="attachment">
<option value="none">N/A</option>
<option value="platform">Platform</option>
<option value="cross-platform">Cross-Platform</option>
</select>
</div>
<div class="mdl-list__item">
<label for="conveyance" class="attachment">Conveyance Preference</label>
<select id="conveyance" class="attachment">
<option value="NA">N/A</option>
<option value="none">None</option>
<option value="indirect">Indirect</option>
<option value="direct">Direct</option>
</select>
</div>
<div class="mdl-list__item">
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="switch-rk">
<input type="checkbox" id="switch-rk" class="mdl-checkbox__input">
<span class="mdl-checkbox__label">Require resident key</span>
</label>
</div>
<div class="mdl-list__item">
<label for="userVerification" class="attachment">User Verification</label>
<select id="userVerification" class="attachment">
<option value="none">None</option>
<option value="required">Required</option>
<option value="preferred">Preferred</option>
<option value="discouraged">Discouraged</option>
</select>
</div>
<div class="mdl-list__item">
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="switch-uvm">
<input type="checkbox" id="switch-uvm" class="mdl-checkbox__input">
<span class="mdl-checkbox__label">User Verification Method</span>
</label>
</div>
<div class="mdl-list__item">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" pattern="-?[0-9]*(\.[0-9]+)?" id="customTimeout">
<label class="mdl-textfield__label" for="customTimeout">Timeout (milliseconds)</label>
<span class="mdl-textfield__error">Input is not a number!</span>
</div>
</div>
<div class="mdl-list__item">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" pattern="-?[0-9]*(\.[0-9]+)?" id="abortTimeout">
<label class="mdl-textfield__label" for="abortTimeout">AbortTimeout (milliseconds)</label>
<span class="mdl-textfield__error">Input is not a number!</span>
</div>
</div>
<div class="mdl-list__item">
<a href="${logoutUrl}">Logout</a>
</div>
</div>
</div>
<main class="flex-layout">
<div class="mdl-layout__content mdl-color--grey-100 flex-content">
<div id="active" class="hidden activity-bar">
<h3 class="active-text">Waiting for user touch</h3>
<div class="mdl-progress mdl-js-progress mdl-progress__indeterminate page-width"></div>
</div>
<div id="credentials" class="mdl-grid mdl-grid--no-spacing"></div>
</div>
<footer id="github" class="mdl-mini-footer">
<div class="mdl-mini-footer__left-section">
<ul class="mdl-mini-footer__link-list">
<li>${nickname}</li>
</ul>
</div>
<div class="mdl-mini-footer__right-section">
<ul class="mdl-mini-footer__link-list">
<li>
<a href="https://github.com/google/webauthndemo">GitHub</a>
</li>
</ul>
</div>
</footer>
<div id="snack-bar" class="mdl-js-snackbar mdl-snackbar">
<div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button>
</div>
</main>
</div>
<script src="js/webauthn.js"></script>
</body>
</html>
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const $ = q => {
return document.querySelector(q);
};
const show = q => {
$(q).style.display = 'block';
};
const hide = q => {
$(q).style.display = 'none';
};
const isChecked = q => {
return $(q).checked;
};
const onClick = (q, func) => {
$(q).addEventListener('click', func);
};
function addErrorMsg(msg) {
$('#error-text').innerHTML = msg;
show('#error');
};
function addSuccessMsg(msg) {
$('#success-text').innerHTML = msg;
show('#success');
};
function removeMsgs() {
hide('#error');
hide('#success');
};
function _fetch(url, obj) {
let headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
let body;
if (typeof URLSearchParams === "function") {
body = new URLSearchParams();
for (let key in obj) {
body.append(key, obj[key]);
}
// Set body to string value to handle an Edge case
body = body.toString();
} else {
// Add parameters to body manually if browser doesn't support
// URLSearchParams
body = "";
for (let key in obj) {
body += encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]) + "&";
}
}
return fetch(url, {
method: 'POST',
headers: headers,
credentials: 'include',
body: body
}).then(response => {
if (response.status === 200) {
return response.json();
} else {
throw response.statusText;
}
});
};
function credentialListConversion(list) {
return list.map(item => {
const cred = {
type: item.type,
id: strToBin(item.id)
};
if (item.transports) {
cred.transports = list.transports;
}
return cred;
});
}
function getAssertion() {
let _parameters;
_fetch('/BeginGetAssertion').then(parameters => {
const requestOptions = {};
_parameters = parameters;
requestOptions.challenge = strToBin(parameters.challenge);
if ('timeout' in parameters) {
requestOptions.timeout = parameters.timeout;
}
if ('rpId' in parameters) {
requestOptions.rpId = parameters.rpId;
}
if ('allowCredentials' in parameters) {
requestOptions.allowCredentials = credentialListConversion(parameters.allowCredentials);
}
console.log(requestOptions);
return navigator.credentials.get({
"publicKey": requestOptions
});
}).then(assertion => {
const publicKeyCredential = {};
if ('id' in assertion) {
publicKeyCredential.id = assertion.id;
}
if ('type' in assertion) {
publicKeyCredential.type = assertion.type;
}
if ('rawId' in assertion) {
publicKeyCredential.rawId = binToStr(assertion.rawId);
}
if (!assertion.response) {
throw "Get assertion response lacking 'response' attribute";
}
const _response = assertion.response;
publicKeyCredential.response = {
clientDataJSON: binToStr(_response.clientDataJSON),
authenticatorData: binToStr(_response.authenticatorData),
signature: binToStr(_response.signature),
userHandle: binToStr(_response.userHandle)
};
return _fetch('/FinishGetAssertion', {
data: JSON.stringify(publicKeyCredential),
session: _parameters.session.id
});
}).then(result => {
console.log(result);
if (result && result.success) {
let tmpName = $('#username-text').value;
$('#instructions').textContent = 'Thanks, ' + tmpName + '! Login was a success.';
hide('#auth-spinner');
}
}).catch(err => {
hide('#auth-spinner');
console.log(err.toString());
$('#instructions').textContent = `Sorry, an error occurred. [${err.toString()}]`;
$('#next-button').disabled = false;
$('#next-button').textContent = 'Retry';
$('#instructions').style.color = 'red';
});
}
function strToBin(str) {
return Uint8Array.from(atob(str), c => c.charCodeAt(0));
}
function binToStr(bin) {
return btoa(new Uint8Array(bin).reduce(
(s, byte) => s + String.fromCharCode(byte), ''
));
}
function transitionToPassword() {
$('#instruction-text').style.opacity = 0;
window.setTimeout(completePasswordTransition, 800);
$('#next-button').removeEventListener('click', transitionToPassword);
$('#next-button').disabled = true;
}
function completePasswordTransition() {
let tmpName = $('#username-text').value;
hide('#top-box');
$('#instructions').textContent = 'Hello, ' + tmpName + ', please enter a password. For your privacy, this data is never stored nor sent to the server.';
$('#instruction-text').style.opacity = 1;
show('#bottom-box');
onClick('#next-button', transitionToAuthentication);
window.setTimeout(enableButton, 800);
}
function enableButton() {
$('#password-text').addEventListener("input", passwordValidation);
passwordValidation();
}
function passwordValidation() {
if ($('#password-text').value.length < 1) {
$('#next-button').disabled = true;
} else {
$('#next-button').disabled = false;
}
}
function transitionToAuthentication() {
$('#next-button').disabled = true;
$('#instruction-text').style.opacity = 0;
window.setTimeout(waitingForTouch, 800);
getAssertion();
}
function waitingForTouch() {
$('#instructions').style.color = 'gray';
hide('#bottom-box');
show('#auth-spinner');
$('#instructions').textContent = 'Please use your security key or authenticator to finish authentication.';
$('#instruction-text').style.opacity = 1;
}
function usernameValidation() {
if ($('#username-text').value.length < 1) {
$('#next-button').disabled = true;
} else {
$('#next-button').disabled = false;
}
}
document.addEventListener('DOMContentLoaded', () => {
let hiddens = Array.from(document.querySelectorAll('.hidden'));
for (let hidden of hiddens) {
hidden.style.display = 'none';
hidden.classList.remove('hidden');
}
if (navigator.credentials && navigator.credentials.create) {
} else {
addErrorMsg('Your browser doesn\'t support WebAuthn');
fetchCredentials();
}
});
window.addEventListener('load', () => {
$('#outer-card').style.opacity = 1;
show('#top-box');
onClick('#next-button', transitionToPassword);
$('#next-button').disabled = true;
$('#username-text').addEventListener("input", usernameValidation);
usernameValidation();
});
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const $ = q => {
return document.querySelector(q);
};
const show = q => {
$(q).style.display = 'block';
};
const hide = q => {
$(q).style.display = 'none';
};
const isChecked = q => {
return $(q).checked;
};
const onClick = (q, func) => {
$(q).addEventListener('click', func);
};
function addErrorMsg(msg) {
$('#error-text').innerHTML = msg;
show('#error');
};
function addSuccessMsg(msg) {
$('#success-text').innerHTML = msg;
show('#success');
};
function removeMsgs() {
hide('#error');
hide('#success');
};
function _fetch(url, obj) {
let headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
let body;
if (typeof URLSearchParams === "function") {
body = new URLSearchParams();
for (let key in obj) {
body.append(key, obj[key]);
}
// Set body to string value to handle an Edge case
body = body.toString();
} else {
// Add parameters to body manually if browser doesn't support
// URLSearchParams
body = "";
for (let key in obj) {
body += encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]) + "&";
}
}
return fetch(url, {
method: 'POST',
headers: headers,
credentials: 'include',
body: body
}).then(response => {
if (response.status === 200) {
return response.json();
} else {
throw response.statusText;
}
});
};
function fetchCredentials() {
_fetch('/RegisteredKeys').then(response => {
let credentials = '';
for (let i in response) {
let { handle, publicKey, name, date, id } = response[i];
let hasCable = response[i].hasCable ? "Yes" : "No";
let buttonId = `delete${i}`;
credentials +=
`<div class="mdl-cell mdl-cell--1-offset mdl-cell-4-col">
<div class="mdl-card mdl-shadow--4dp" id="${handle}">
<div class="mdl-card__title mdl-card--border">${name}</div>
<div class="mdl-card__supporting-text">Enrolled ${date}</div>
<div class="mdl-card__subtitle-text">Public Key</div>
<div class="mdl-card__supporting-text">${publicKey}</div>
<div class="mdl-card__subtitle-text">Key Handle</div>
<div class="mdl-card__supporting-text">${handle}</div>
<div class="mdl-card__subtitle-text">Supports caBLE</div>
<div class="mdl-card__supporting-text">${hasCable}</div>
<div class="mdl-card__menu">
<button id="${buttonId}"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">delete_forever</i>
</button>
</div>
</div>
</div>
`;
}
$('#credentials').innerHTML = credentials;
for (let i in response) {
let { handle, publicKey, name, date, id } = response[i];
onClick(`#delete${i}`, removeCredential(id));
}
});
}
function removeCredential(id) {
return () => {
_fetch('/RemoveCredential', {
credentialId : id
}).then(() => {
fetchCredentials();
}).catch(err => {
addErrorMsg(`An error occurred during removal [${err.toString()}]`);
});
}
}
function credentialListConversion(list) {
return list.map(item => {
const cred = {
type: item.type,
id: strToBin(item.id)
};
if (item.transports) {
cred.transports = list.transports;
}
return cred;
});
}
function hexToBuffer(hex) {
let array = new Uint8Array(hex.toUpperCase().match(/[\dA-F]{2}/gi).map(function (b) {
return parseInt(b, 16)
}));
return array.buffer;
}
function addCredential() {
removeMsgs();
show('#active');
let _options;
const advancedOptions = {};
if (isChecked('#switch-advanced')) {
if (isChecked('#switch-rk')) {
advancedOptions.requireResidentKey = isChecked('#switch-rk');
}
if (isChecked('#switch-rr')) {
advancedOptions.excludeCredentials = isChecked('#switch-rr');
}
if ($('#userVerification').value != "none") {
advancedOptions.userVerification = $('#userVerification').value;
}
if ($('#attachment').value != "none") {
advancedOptions.authenticatorAttachment = $('#attachment').value;
}
if ($('#conveyance').value != "NA") {
advancedOptions.attestationConveyancePreference = $('#conveyance').value;
}
}
return _fetch('/BeginMakeCredential', {
advanced: isChecked('#switch-advanced'),
advancedOptions: JSON.stringify(advancedOptions)
}).then(options => {
const makeCredentialOptions = {};
_options = options;
makeCredentialOptions.rp = options.rp;
makeCredentialOptions.user = options.user;
makeCredentialOptions.user.id = strToBin(options.user.id);
makeCredentialOptions.challenge = strToBin(options.challenge);
makeCredentialOptions.pubKeyCredParams = options.pubKeyCredParams;
// Optional parameters
if ('timeout' in options) {
makeCredentialOptions.timeout = options.timeout;
}
if ('excludeCredentials' in options) {
makeCredentialOptions.excludeCredentials = credentialListConversion(options.excludeCredentials);
}
if ('authenticatorSelection' in options) {
makeCredentialOptions.authenticatorSelection = options.authenticatorSelection;
}
if ('attestation' in options) {
makeCredentialOptions.attestation = options.attestation;
}
if ('extensions' in options) {
makeCredentialOptions.extensions = options.extensions;
}
console.log(makeCredentialOptions);
return navigator.credentials.create({
"publicKey": makeCredentialOptions
});
}).then(attestation => {
hide('#active');
const publicKeyCredential = {};
if ('id' in attestation) {
publicKeyCredential.id = attestation.id;
}
if ('type' in attestation) {
publicKeyCredential.type = attestation.type;
}
if ('rawId' in attestation) {
publicKeyCredential.rawId = binToStr(attestation.rawId);
}
if (!attestation.response) {
addErrorMsg("Make Credential response lacking 'response' attribute");
}
const response = {};
response.clientDataJSON = binToStr(attestation.response.clientDataJSON);
response.attestationObject = binToStr(attestation.response.attestationObject);
publicKeyCredential.response = response;
return _fetch('/FinishMakeCredential', {
data: JSON.stringify(publicKeyCredential),
session: _options.session.id
});
}).then(parameters => {
console.log(parameters);
if (parameters && parameters.success) {
addSuccessMsg(parameters.message);
fetchCredentials();
} else {
throw 'Unexpected response received.';
}
}).catch(err => {
hide('#active');
console.log(err.toString());
addErrorMsg(`An error occurred during Make Credential operation [${err.toString()}]`);
});
}
function getAssertion() {
removeMsgs();
show('#active');
let _parameters;
_fetch('/BeginGetAssertion').then(parameters => {
const requestOptions = {};
_parameters = parameters;
requestOptions.challenge = strToBin(parameters.challenge);
if ('timeout' in parameters) {
requestOptions.timeout = parameters.timeout;
}
if ('rpId' in parameters) {
requestOptions.rpId = parameters.rpId;
}
if ('allowCredentials' in parameters) {
requestOptions.allowCredentials = credentialListConversion(parameters.allowCredentials);
}
if ('extensions' in parameters) {
requestOptions.extensions = parameters.extensions;
// Convert ByteArrays
if ('cableAuthentication' in requestOptions.extensions) {
var cableSessionDatas = requestOptions.extensions.cableAuthentication;
cableSessionDatas.forEach(sd => {
sd.clientEid = hexToBuffer(sd.clientEid);
sd.authenticatorEid = hexToBuffer(sd.authenticatorEid);
sd.sessionPreKey = hexToBuffer(sd.sessionPreKey);
});
}
}
// Add options if selected
if (isChecked('#switch-advanced-auth')) {
let extensions = {};
if ($('#cable-client-eid').value !== '' ||
$('#cable-authenticator-eid').value !== '' ||
$('#cable-session-pre-key').value !== '') {
let cable = {};
cable.version = 1;
cable.clientEid = hexToBuffer($('#cable-client-eid').value);
cable.authenticatorEid = hexToBuffer($('#cable-authenticator-eid').value);
cable.sessionPreKey = hexToBuffer($('#cable-session-pre-key').value);
extensions.cableAuthentication = [cable];
}
if (Object.keys(extensions).length !== 0) {
requestOptions.extensions = extensions;
}
}
console.log(requestOptions);
return navigator.credentials.get({
"publicKey": requestOptions
});
}).then(assertion => {
hide('#active');
const publicKeyCredential = {};
if ('id' in assertion) {
publicKeyCredential.id = assertion.id;
}
if ('type' in assertion) {
publicKeyCredential.type = assertion.type;
}
if ('rawId' in assertion) {
publicKeyCredential.rawId = binToStr(assertion.rawId);
}
if (!assertion.response) {
throw "Get assertion response lacking 'response' attribute";
}
const _response = assertion.response;
publicKeyCredential.response = {
clientDataJSON: binToStr(_response.clientDataJSON),
authenticatorData: binToStr(_response.authenticatorData),
signature: binToStr(_response.signature),
userHandle: binToStr(_response.userHandle)
};
return _fetch('/FinishGetAssertion', {
data: JSON.stringify(publicKeyCredential),
session: _parameters.session.id
});
}).then(result => {
console.log(result);
if (result && result.success) {
addSuccessMsg(result.message);
if ('handle' in result) {
let card = document.getElementById(result.handle);
card.animate([{
backgroundColor: '#009688'
},{
backgroundColor: 'white'
}], {
duration: 2000,
easing: 'ease-out'
});
}
}
}).catch(err => {
hide('#active');
console.log(err.toString());
addErrorMsg(`An error occurred during Assertion request [${err.toString()}]`);
});
}
function strToBin(str) {
return Uint8Array.from(atob(str), c => c.charCodeAt(0));
}
function binToStr(bin) {
return btoa(new Uint8Array(bin).reduce(
(s, byte) => s + String.fromCharCode(byte), ''
));
}
document.addEventListener('DOMContentLoaded', () => {
let hiddens = Array.from(document.querySelectorAll('.hidden'));
for (let hidden of hiddens) {
hidden.style.display = 'none';
hidden.classList.remove('hidden');
}
if (navigator.credentials && navigator.credentials.create) {
fetchCredentials();
} else {
addErrorMsg('Your browser doesn\'t support WebAuthn');
fetchCredentials();
}
});
window.addEventListener('load', () => {
if (isChecked('#switch-advanced')) {
show('#advanced');
} else {
hide('#advanced');
}
if (isChecked('#switch-advanced-auth')) {
show('#advanced-auth');
} else {
hide('#advanced-auth');
}
onClick('#credential-button', addCredential);
onClick('#authenticate-button', getAssertion);
onClick('#switch-advanced', () => {
if (isChecked('#switch-advanced')) {
show('#advanced');
} else {
hide('#advanced');
}
});
onClick('#switch-advanced-auth', () => {
if (isChecked('#switch-advanced-auth')) {
show('#advanced-auth');
} else {
hide('#advanced-auth');
}
});
});
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const $ = q => {
return document.querySelector(q);
};
const show = q => {
$(q).style.display = 'block';
};
const hide = q => {
$(q).style.display = 'none';
};
const isChecked = q => {
return $(q).checked;
};
const onClick = (q, func) => {
$(q).addEventListener('click', func);
};
const onCheck = (q, on, off) => {
$(q).addEventListener('change', () => {
if($(q).checked) {
on();
} else {
off();
}
});
}
function showErrorMsg(msg) {
$('#snack-bar').MaterialSnackbar.showSnackbar({
message: msg,
timeout: 5000
});
};
function showSuccessMsg(msg) {
$('#snack-bar').MaterialSnackbar.showSnackbar({
message: msg,
timeout: 5000
});
};
function _fetch(url, obj) {
let headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
let body;
if (typeof URLSearchParams === "function") {
body = new URLSearchParams();
for (let key in obj) {
body.append(key, obj[key]);
}
// Set body to string value to handle an Edge case
body = body.toString();
} else {
// Add parameters to body manually if browser doesn't support URLSearchParams
body = "";
for (let key in obj) {
body += encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]) + "&";
}
}
return fetch(url, {
method: 'POST',
headers: headers,
credentials: 'include',
body: body
}).then(response => {
if (response.status === 200) {
return response.json();
} else {
throw response.statusText;
}
});
};
function fetchCredentials() {
_fetch('/RegisteredKeys').then(response => {
let credentials = '';
for (let i in response) {
let { handle, base64handle, publicKey, name, date, id, transports, userVerificationMethod } = response[i];
const trimmedHandle = base64handle.replace(/=/g, '');
let buttonId = `delete${i}`;
credentials +=
`<div class="mdl-cell mdl-cell--1-offset-desktop mdl-cell-4-col">
<div class="mdl-card mdl-shadow--4dp" id="${handle}">
<div class="mdl-card__title mdl-card--border">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-${trimmedHandle}">
<input type="checkbox" id="switch-${trimmedHandle}" class="mdl-switch__input" checked>
<span class="mdl-switch__label">${name}</span>
</label>
</div>
<div class="mdl-card__supporting-text">Enrolled ${date}</div>
<div class="mdl-card__subtitle-text">Public Key</div>
<div class="mdl-card__supporting-text">${publicKey}</div>
<div class="mdl-card__subtitle-text">Key Handle</div>
<div class="mdl-card__supporting-text">${handle}</div>`;
if (userVerificationMethod) {
credentials +=
`<div class="mdl-card__subtitle-text">User Verification Method</div>
<div class="mdl-card__supporting-text">${userVerificationMethod}</div>`;
}
if (transports) {
credentials +=
`<div class="mdl-card__subtitle-text">Transports</div>
<div class="mdl-card__supporting-text">`;
for (const transport of transports) {
credentials += `<input type="checkbox" id="${transport}${trimmedHandle}" value="${transport}${trimmedHandle}" checked>${transport} &nbsp;`;
}
credentials += `</div>`;
}
credentials +=
`<div class="mdl-card__menu">
<button id="${buttonId}"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect"
title="Removes this credential registration from the server"
>
<i class="material-icons">delete_forever</i>
</button>
</div>
</div>
</div>
`;
}
$('#credentials').innerHTML = credentials;
componentHandler.upgradeAllRegistered();
for (let i in response) {
let { handle, base64handle, publicKey, name, date, id } = response[i];
const trimmedHandle = base64handle.replace(/=/g, '');
onClick(`#delete${i}`, removeCredential(id));
onCheck(`#switch-${trimmedHandle}`, brightenCard(handle), dimCard(handle));
}
});
}
function brightenCard(id) {
return () => {
let card = document.getElementById(id);
card.animate([{
backgroundColor: '#ADADAD'
},{
backgroundColor: 'white'
}], {
duration: 200,
easing: 'ease-out',
fill: 'forwards'
});
}
}
function dimCard(id) {
return () => {
let card = document.getElementById(id);
card.animate([{
backgroundColor: 'white'
},{
backgroundColor: '#ADADAD'
}], {
duration: 200,
easing: 'ease-out',
fill: 'forwards'
});
}
}
function removeCredential(id) {
return () => {
_fetch('/RemoveCredential', {
credentialId : id
}).then(() => {
fetchCredentials();
}).catch(err => {
showErrorMsg(`An error occurred during removal [${err.toString()}]`);
});
}
}
function credentialListConversion(list) {
// Filter unchecked credentials
const filteredList = list.filter((element) => {
try {
const base64Id = '#switch-'.concat(element.id.replace(/\+/g, '-')
.replace(/\//g,'_').replace(/=/g,'')).replace(/=/g, '');
if (isChecked(base64Id)) {
return true;
} else {
return false;
}
} catch(e) {
return true;
}
});
return filteredList.map(item => {
const cred = {
type: item.type,
id: strToBin(item.id)
};
if (item.transports) {
const newTransportList = [];
// Filter out unchecked transports
for (transport of item.transports) {
try {
// The transport id is the transport name concatenated with the
// corresponding key handle
const base64Id = '#'.concat(transport, item.id.replace(/\+/g, '-')
.replace(/\//g,'_').replace(/=/g,''));
if (isChecked(base64Id)) {
newTransportList.push(transport);
}
} catch(e) {};
}
if (newTransportList.length) {
cred.transports = newTransportList;
}
}
return cred;
});
}
function serializeUvm(uvm) {
var uvmJson = new Array();
for (var i = 0; i < uvm.length; i ++) {
const uvmEntry = {};
uvmEntry.userVerificationMethod = uvm[i][0];
uvmEntry.keyProtectionType = uvm[i][1];
uvmEntry.atchuvmJsonerProtectionType = uvm[i][2];
uvmJson.push(uvmEntry);
}
return uvmJson;
}
function registerNewCredential() {
const advancedOptions = {};
if (isChecked('#switch-rk')) {
advancedOptions.requireResidentKey = isChecked('#switch-rk');
}
if (isChecked('#switch-rr')) {
advancedOptions.excludeCredentials = isChecked('#switch-rr');
}
if ($('#userVerification').value != "none") {
advancedOptions.userVerification = $('#userVerification').value;
}
if ($('#attachment').value != "none") {
advancedOptions.authenticatorAttachment = $('#attachment').value;
}
if ($('#conveyance').value != "NA") {
advancedOptions.attestationConveyancePreference = $('#conveyance').value;
}
makeCredential(advancedOptions);
}
function registerPlatformAuthenticator() {
const advancedOptions = {};
if (isChecked('#switch-rk')) {
advancedOptions.requireResidentKey = isChecked('#switch-rk');
}
if (isChecked('#switch-rr')) {
advancedOptions.excludeCredentials = isChecked('#switch-rr');
}
advancedOptions.userVerification = 'required';
advancedOptions.authenticatorAttachment = 'platform';
if ($('#conveyance').value != "NA") {
advancedOptions.attestationConveyancePreference = $('#conveyance').value;
}
makeCredential(advancedOptions);
}
function makeCredential(advancedOptions) {
show('#active');
let _options;
// Tyler -----------------
let username = document.querySelector('.mdl-textfield__input').value;
return _fetch('/BeginMakeCredential', {
advanced: true,
advancedOptions: JSON.stringify(advancedOptions),
// Tyler -----------------
username: username
}).then(options => {
const makeCredentialOptions = {};
_options = options;
makeCredentialOptions.rp = options.rp;
makeCredentialOptions.user = options.user;
makeCredentialOptions.user.id = strToBin(options.user.id);
makeCredentialOptions.challenge = strToBin(options.challenge);
makeCredentialOptions.pubKeyCredParams = options.pubKeyCredParams;
// Optional parameters
if ($('#customTimeout').value != '') {
makeCredentialOptions.timeout = $('#customTimeout').value;
}
if ('excludeCredentials' in options) {
makeCredentialOptions.excludeCredentials = credentialListConversion(options.excludeCredentials);
}
if ('authenticatorSelection' in options) {
makeCredentialOptions.authenticatorSelection = options.authenticatorSelection;
}
if ('attestation' in options) {
makeCredentialOptions.attestation = options.attestation;
}
if ('extensions' in options) {
makeCredentialOptions.extensions = options.extensions;
if (makeCredentialOptions.extensions.cableRegistration) {
makeCredentialOptions.extensions.cableRegistration.rpPublicKey =
strToBin(makeCredentialOptions.extensions.cableRegistration.rpPublicKey);
}
}
if (isChecked('#switch-uvm')) {
makeCredentialOptions.extensions.uvm = true;
}
console.log('sending attestation request:');
console.log(makeCredentialOptions);
if ($('#abortTimeout').value != '') {
authAbortController = new AbortController();
authAbortSignal = authAbortController.signal;
setTimeout(function(){ authAbortController.abort(); }, $('#abortTimeout').value);
return navigator.credentials.create({
"publicKey": makeCredentialOptions,
"signal": authAbortSignal
});
}
return navigator.credentials.create({
"publicKey": makeCredentialOptions
});
}).then(attestation => {
hide('#active');
console.log('received attestation response:');
console.log(attestation);
if ($('#abortTimeout').value != '') {
clearTimeout();
}
const publicKeyCredential = {};
if ('id' in attestation) {
publicKeyCredential.id = attestation.id;
}
if ('type' in attestation) {
publicKeyCredential.type = attestation.type;
}
if ('rawId' in attestation) {
publicKeyCredential.rawId = binToStr(attestation.rawId);
}
if (!attestation.response) {
showErrorMsg("Make Credential response lacking 'response' attribute");
}
const response = {};
response.clientDataJSON = binToStr(attestation.response.clientDataJSON);
response.attestationObject = binToStr(attestation.response.attestationObject);
// Check for included extensions
if (attestation.getClientExtensionResults) {
publicKeyCredential.extensions = attestation.getClientExtensionResults();
if (attestation.getClientExtensionResults().uvm != null) {
publicKeyCredential.uvm = serializeUvm(attestation.getClientExtensionResults().uvm);
}
}
// Check if transports are included in the registration response.
if (attestation.response.getTransports) {
response.transports = attestation.response.getTransports();
}
publicKeyCredential.response = response;
return _fetch('/FinishMakeCredential', {
data: JSON.stringify(publicKeyCredential),
session: _options.session.id
});
}).then(parameters => {
console.log(parameters);
if (parameters && parameters.success) {
showSuccessMsg(parameters.message);
fetchCredentials();
} else {
throw 'Unexpected response received.';
}
}).catch(err => {
hide('#active');
console.log(err.toString());
showErrorMsg(`An error occurred during Make Credential operation [${err.toString()}]`);
});
}
function isUVPAA() {
try {
eval(PublicKeyCredential);
} catch(err) {
showErrorMsg(`UVPAA failed: [${err.toString()}]`);
return;
}
if (PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then(response => {
if (response === true) {
showSuccessMsg(`User verifying platform authenticator is available.`);
} else {
showErrorMsg(`User verifying platform authenticator is NOT available.`);
}
}).catch(err => {
showErrorMsg(`UVPAA failed: [${err.toString()}]`);
});
} else {
showErrorMsg(`User verifying platform authenticator is not available on this browser.`);
}
}
function getAssertion() {
show('#active');
let _parameters;
_fetch('/BeginGetAssertion').then(parameters => {
const requestOptions = {};
_parameters = parameters;
requestOptions.challenge = strToBin(parameters.challenge);
if ($('#customTimeout').value != '') {
requestOptions.timeout = $('#customTimeout').value;
}
if ('rpId' in parameters) {
requestOptions.rpId = parameters.rpId;
}
if ('allowCredentials' in parameters) {
requestOptions.allowCredentials = credentialListConversion(parameters.allowCredentials);
}
if ($('#userVerification').value != "none") {
requestOptions.userVerification = $('#userVerification').value;
}
if ('extensions' in parameters) {
requestOptions.extensions = {};
let cableData = [];
if ('cableAuthentication' in parameters.extensions) {
for (cableElement of parameters.extensions.cableAuthentication){
let cableExtension = {
'version': cableElement.version,
'clientEid': strToBin(cableElement.clientEid),
'authenticatorEid': strToBin(cableElement.authenticatorEid),
'sessionPreKey': strToBin(cableElement.sessionPreKey),
};
cableData.push(cableExtension);
}
requestOptions.extensions['cableAuthentication'] = cableData;
}
}
if (isChecked('#switch-uvm')) {
if (requestOptions.extensions == null) {
requestOptions.extensions = {};
}
requestOptions.extensions.uvm = true;
}
console.log('sending assertion request:');
console.log(requestOptions);
if ($('#abortTimeout').value != '') {
authAbortController = new AbortController();
authAbortSignal = authAbortController.signal;
setTimeout(function(){ authAbortController.abort(); }, $('#abortTimeout').value);
return navigator.credentials.get({
"publicKey": requestOptions,
"signal": authAbortSignal
});
}
return navigator.credentials.get({
"publicKey": requestOptions
});
}).then(assertion => {
hide('#active');
if ($('#abortTimeout').value != '') {
clearTimeout();
}
console.log('received assertion response:');
console.log(assertion);
const publicKeyCredential = {};
if ('id' in assertion) {
publicKeyCredential.id = assertion.id;
}
if ('type' in assertion) {
publicKeyCredential.type = assertion.type;
}
if ('rawId' in assertion) {
publicKeyCredential.rawId = binToStr(assertion.rawId);
}
if (!assertion.response) {
throw "Get assertion response lacking 'response' attribute";
}
if (assertion.getClientExtensionResults) {
if (assertion.getClientExtensionResults().uvm != null) {
publicKeyCredential.uvm = serializeUvm(assertion.getClientExtensionResults().uvm);
}
}
const _response = assertion.response;
publicKeyCredential.response = {
clientDataJSON: binToStr(_response.clientDataJSON),
authenticatorData: binToStr(_response.authenticatorData),
signature: binToStr(_response.signature),
userHandle: binToStr(_response.userHandle)
};
return _fetch('/FinishGetAssertion', {
data: JSON.stringify(publicKeyCredential),
session: _parameters.session.id
});
}).then(result => {
console.log(result);
if (result && result.success) {
showSuccessMsg(result.message);
if ('handle' in result) {
setTimeout(function(){ fetchCredentials(); }, 2000);
let card = document.getElementById(result.handle);
let prevColor =
getComputedStyle(card).backgroundColor;
card.animate([{
backgroundColor: '#009688'
},{
backgroundColor: prevColor
}], {
duration: 2000,
easing: 'ease-out'
});
}
}
}).catch(err => {
hide('#active');
console.log(err.toString());
showErrorMsg(`An error occurred during Assertion request [${err.toString()}]`);
});
}
function strToBin(str) {
return Uint8Array.from(atob(str), c => c.charCodeAt(0));
}
function binToStr(bin) {
return btoa(new Uint8Array(bin).reduce(
(s, byte) => s + String.fromCharCode(byte), ''
));
}
document.addEventListener('DOMContentLoaded', () => {
let hiddens = Array.from(document.querySelectorAll('.hidden'));
for (let hidden of hiddens) {
hidden.style.display = 'none';
hidden.classList.remove('hidden');
}
if (navigator.credentials && navigator.credentials.create) {
fetchCredentials();
} else {
showErrorMsg('Your browser doesn\'t support WebAuthn');
fetchCredentials();
}
});
window.addEventListener('load', () => {
onClick('#credential-button', registerNewCredential);
onClick('#platform-button', registerPlatformAuthenticator);
onClick('#authenticate-button', getAssertion);
onClick('#isuvpaa-button', isUVPAA);
});
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet"
href="https://code.getmdl.io/1.3.0/material.teal-pink.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<link href="stylesheets/webauthn.css" rel="stylesheet">
<script src="//code.getmdl.io/1.3.0/material.min.js"></script>
<script src="js/login.js"></script>
<title>WebAuthN Login</title>
</head>
<body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout-icon"></div>
<div class="mdl-layout__header-row">
<span class="mdl-layout__title">WebAuthN Login Demo</span>
<div class="mdl-layout-spacer"></div>
<nav class="mdl-navigation">
<a href="${logoutUrl}">
<button
class="mdl-button mdl-js-button logout-button mdl-js-ripple-effect">
Logout</button>
</a>
</nav>
</div>
</header>
<main class="mdl-layout__content mdl-color--grey-100">
<div id="outer-card" class="gone username">
<div id="username-card" class="login-card mdl-card mdl-shadow--2dp">
<div class="signin-title">
<div class="mdl-card__title mdl-card--expand">
<h2 class="mdl-card__title-text">Sign in</h2>
</div>
</div>
<div id="instruction-text" class="instruction-text mdl-card__supporting-text">
<div id="instructions">
Please enter a username. For your privacy, this data is never sent to the server.
</div>
<div class="boxes">
<div id="top-box"
class="top-box hidden mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text" id="username-text" maxlength="25"
class="mdl-textfield__input"> <label
class="mdl-textfield__label" for="username-text">Username</label>
</div>
<div id="bottom-box"
class="bottom-box hidden mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="password" id="password-text"
class="mdl-textfield__input"> <label
class="mdl-textfield__label" for="password-text">Password</label>
</div>
</div>
<div id="auth-spinner" class="auth-spinner hidden mdl-spinner mdl-js-spinner is-active"></div>
</div>
<div class="outer-next">
<button id="next-button"
class="next-button mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">
Next</button>
</div>
</div>
</div>
<div id="password" class="hidden password">Password</div>
<div id="touch" class="hidden touch">Touch</div>
<div id="active" class="hidden activity-bar">
<h3 class="active-text">Waiting for user touch</h3>
<div
class="mdl-progress mdl-js-progress mdl-progress__indeterminate page-width"></div>
</div>
<div id="error" class="hidden">
<h3 class="error-text" id="error-text">An error has occurred</h3>
</div>
<div id="success" class="hidden">
<h3 class="success-text" id="success-text">Success</h3>
</div>
</main>
</div>
</body>
</html>
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.flex-layout {
display: flex;
height: 100%;
flex-direction: column;
}
.mdl-snackbar {
line-height: 1;
}
.flex-content {
flex: 1 1 auto;
}
.mdl-mini-footer {
padding-top: 3px;
padding-bottom: 3px;
}
#header-buttons {
padding: 0 16px;
flex-wrap: wrap;
min-height: 56px;
height: auto;
}
#header-buttons > button {
flex: 1 1 auto;
margin-bottom: 8px;
}
#header-buttons > .mdl-textfield {
flex: 1 1 auto;
margin-bottom: 8px;
}
#username-input{
min-width: 64px;
width: 100%;
margin-right: 12px;
margin-left: 12px;
}
.fingerprint {
width: 36px;
height: 36px;
background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M17.81%204.47c-.08%200-.16-.02-.23-.06C15.66%203.42%2014%203%2012.01%203c-1.98%200-3.86.47-5.57%201.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82%202.52%209.86%202%2012.01%202c2.13%200%203.99.47%206.03%201.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5%209.72c-.1%200-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4%202.25-2.5%203.75-3.27C9.98%204.04%2014%204.03%2017.15%205.65c1.5.77%202.76%201.86%203.75%203.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5%201.7-3.4%202.96-.08.14-.23.21-.39.21zm6.25%2012.07c-.13%200-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34%200-2.97%202.54-5.39%205.66-5.39s5.66%202.42%205.66%205.39c0%20.28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57%200-4.66%201.97-4.66%204.39%200%201.44.32%202.77.93%203.85.64%201.15%201.08%201.64%201.85%202.42.19.2.19.51%200%20.71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19%200-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39%200-.28.22-.5.5-.5s.5.22.5.5c0%201.41.72%202.74%201.94%203.56.71.48%201.54.71%202.54.71.24%200%20.64-.03%201.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91%2022c-.04%200-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22%200-1.62%201.38-2.94%203.08-2.94%201.7%200%203.08%201.32%203.08%202.94%200%201.07.93%201.94%202.08%201.94s2.08-.87%202.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84%200-5.44%201.58-6.61%204.03-.39.81-.59%201.76-.59%202.8%200%20.78.07%202.01.67%203.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96%200-1.2.23-2.29.68-3.24%201.33-2.79%204.28-4.6%207.51-4.6%204.55%200%208.25%203.51%208.25%207.83%200%201.62-1.38%202.94-3.08%202.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08%201.94c0%201.71.66%203.31%201.87%204.51.95.94%201.86%201.46%203.27%201.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z%22%20fill%3D%22white%22%2F%3E%3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%2F%3E%3C%2Fsvg%3E%20');
background-size: contain;
background-repeat: no-repeat;
cursor: pointer;
}
.mdl-layout__drawer .mdl-checkbox__label {
vertical-align: top;
}
.mdl-cell {
padding: 30px;
text-align: left;
}
.mdl-card__subtitle-text {
padding-left: .2cm;
font-weight: bold;
}
.mdl-card__supporting-text {
font-style: italic;
overflow-wrap: break-word;
word-wrap: break-word;
padding-bottom: 8px;
padding-top: 8px;
}
.mdl-switch {
width: auto;
}
.mdl-checkbox{
word-wrap:break-word;
word-break: keep-all;
height: auto;
}
.mdl-textfield__input{
padding: 5px;
}
.login-card.mdl-card {
width: 350px;
height: 350px;
margin-left: auto;
margin-right: auto;
margin-top: 20px;
}
.outer-next {
position: absolute;
text-align: right;
bottom: .5cm;
right: .5cm;
}
.login-card.mdl-card__title {
margin-bottom: 2cm;
}
.username-card {
position: relative;
display: block;
margin : 0 auto;
}
.boxes {
position: relative;
height: 50px;
}
.top-box {
position: absolute;
top: 5px;
}
.bottom-box {
position: absolute;
top: 5px;
}
.instructions {
height: 200px;
}
.auth-spinner {
position: relative;
display: block;
margin : 0 auto;
}
.mdl-spinner {
width: 84px;
height: 84px;
}
.mdl-spinner__circle {
border-width: 6px;
}
.signin-title {
height: 75px;
}
.instruction-text {
transition: opacity 0.8s;
}
.gone {
opacity: 0;
}
.next-button.mdl-button {
width: 100px;
}
.advanced-switch {
background-color: white;
padding-top: .2cm;
padding-bottom: .2cm;
padding-left: .2cm;
}
.advanced {
background-color: white;
padding-top: .2cm;
padding-bottom: .2cm;
padding-left: .2cm;
}
.attachment {
font-size: medium;
}
.auth-button {
margin-left: .25cm;
margin-right: .25cm;
}
.make-button {
margin-left: .25cm;
margin-right: .25cm;
}
.isuvpaa-button {
margin-left: .25cm;
margin-right: .25cm;
}
.logout-button {
margin-left: .25cm;
margin-right: .25cm;
color: white;
}
.hidden {
display: none;
}
.activity-bar {
padding-top: 0;
padding-bottom: .2cm;
margin: 0;
}
.page-width {
width: 100%;
}
.active-text {
margin: 0;
background-color: white;
text-align: center;
font-size: medium;
}
[{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://webauthndemo.appspot.com"
}
},
{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.fido.example.fido2apiexample",
"sha256_cert_fingerprints": [
"83:1E:EC:AB:FA:71:87:18:6B:21:07:4B:C9:F1:B4:A7:12:B0:88:9E:E1:3A:4D:83:25:0E:31:BC:A7:78:DF:C4",
"90:61:22:59:30:58:E4:3F:18:13:5C:07:BB:DC:43:F7:06:46:4E:CC:8C:C3:23:61:19:A9:9D:DF:82:13:DC:D7",
"6E:41:E7:95:61:15:FE:34:42:3D:D6:06:25:FC:0E:97:B4:A7:FC:22:C2:FF:64:C4:DE:1E:13:3B:5F:E7:DF:82"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.fido.example.zeropartyexample",
"sha256_cert_fingerprints": [
"19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
]
}
}]
/*
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.webauthn.gaedemo.crypto;
import com.google.webauthn.gaedemo.objects.CablePairingData;
import com.google.webauthn.gaedemo.objects.CableSessionData;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Assert;
import org.junit.Test;
import java.util.Random;
public class CableTest {
@Test
public void testGenerateSessionData_vectors() {
Cable cable = new Cable(new Random() {
private static final long serialVersionUID = -7092153420386472236L;
@Override
public void nextBytes(byte[] bytes) {
for (int i = 0; i < bytes.length; ++i) {
bytes[i] = (byte) 0xAA;
}
}
});
CablePairingData pairingData = new CablePairingData(
1,
Hex.decode("202122232425262728292A2B2C2D2E2F202122232425262728292A2B2C2D2E2F"),
Hex.decode("101112131415161718191A1B1C1D1E1F101112131415161718191A1B1C1D1E1F"));
CableSessionData actual = cable.generateSessionData(pairingData);
CableSessionData expected = new CableSessionData(
1,
Hex.decode("AAAAAAAAAAAAAAAAC50F4E92238F1BE7"),
Hex.decode("75B83487AE3DB1C1159C00EB992C984D"),
Hex.decode("073B97D8D142EA3A04B16BD3DC81553334577A20F398DBBC02FBD18B9354BAD2"));
Assert.assertEquals(expected, actual);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.DataItem;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.security.SecureRandom;
import org.junit.Ignore;
import org.junit.Test;
public class AndroidSafetyNetAttestationStatementTest {
/**
* Test method for
* {@link com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement#encode()} and
* {@link com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement#decode(co.nstant.in.cbor.model.DataItem)}.
*/
@Ignore
@Test
public void testEncode() {
SecureRandom random = new SecureRandom();
AndroidSafetyNetAttestationStatement attStmt = new AndroidSafetyNetAttestationStatement();
attStmt.ver = "10";
attStmt.response = new byte[20];
random.nextBytes(attStmt.response);
try {
DataItem encoded = attStmt.encode();
AndroidSafetyNetAttestationStatement decoded =
AndroidSafetyNetAttestationStatement.decode(encoded);
assertEquals(decoded, attStmt);
} catch (CborException | ResponseException e) {
fail(e.getMessage());
}
}
/**
* Test method for
* {@link com.google.webauthn.gaedemo.objects.AndroidSafetyNetAttestationStatement#equals(java.lang.Object)}.
*/
@Test
@Ignore
public void testEqualsObject() {
AndroidSafetyNetAttestationStatement a = new AndroidSafetyNetAttestationStatement();
AndroidSafetyNetAttestationStatement b = new AndroidSafetyNetAttestationStatement();
assertEquals(a, b);
a.ver = "testString";
assertNotEquals(a, b);
b.ver = "testString";
assertEquals(a, b);
a.response = new byte[] {0, 1, 2, 3};
assertNotEquals(a, b);
b.response = new byte[] {0, 1, 2, 3};
assertEquals(a, b);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import org.junit.Test;
public class AttestationDataTest {
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.AttestationData#encode()}.
*/
@Test
public void testEncode() {
AttestationData attData = new AttestationData();
Random rand = new Random();
rand.nextBytes(attData.aaguid);
attData.credentialId = "testCredentialId".getBytes(StandardCharsets.UTF_8);
RsaKey publicKey = new RsaKey();
publicKey.alg = Algorithm.RS256;
publicKey.e = "e".getBytes(StandardCharsets.UTF_8);
publicKey.n = "n".getBytes(StandardCharsets.UTF_8);
attData.publicKey = publicKey;
AttestationData decoded;
try {
decoded = AttestationData.decode(attData.encode());
assertEquals(decoded, attData);
} catch (ResponseException e) {
fail(e.toString());
} catch (CborException e) {
e.printStackTrace();
fail(e.toString());
}
}
/**
* Test method for
* {@link com.google.webauthn.gaedemo.objects.AttestationData#equals(java.lang.Object)}.
*/
@Test
public void testEquals() {
AttestationData one = new AttestationData();
AttestationData two = new AttestationData();
assertEquals(one, two);
one.aaguid = "aaguid".getBytes(StandardCharsets.UTF_8);
assertNotEquals(one, two);
two.aaguid = "aaguid".getBytes(StandardCharsets.UTF_8);
assertEquals(one, two);
one.credentialId = "credentialId".getBytes(StandardCharsets.UTF_8);
assertNotEquals(one, two);
two.credentialId = "credentialId".getBytes(StandardCharsets.UTF_8);
assertEquals(one, two);
one.publicKey = new RsaKey();
assertNotEquals(one, two);
two.publicKey = new RsaKey();
assertEquals(one, two);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import org.junit.Test;
@SuppressWarnings("unused")
public class AuthenticatorAssertionResponseTest {
final SecureRandom random = new SecureRandom();
/**
* Test method for
* {@link com.google.webauthn.gaedemo.objects.AuthenticatorAssertionResponse#AuthenticatorAssertionResponse(java.lang.String)}.
*
* @throws ResponseException
*/
// @Test
public void testAuthenticatorAssertionResponse() throws ResponseException {
Gson gson = new Gson();
CollectedClientData clientData = new CollectedClientData();
clientData.challenge = "challengeString";
clientData.hashAlgorithm = "SHA-256";
clientData.origin = "https://localhost";
String clientJson =
BaseEncoding.base64().encode(gson.toJson(clientData).getBytes(StandardCharsets.UTF_8));
AuthenticatorData authData = null;
{
byte flags = 1 << 6;
byte[] rpIdHash = new byte[32];
authData = new AuthenticatorData(rpIdHash, flags, 0);
}
String authenticatorBase64 = null;
try {
authenticatorBase64 = BaseEncoding.base64().encode(authData.encode());
} catch (CborException e1) {
fail(e1.toString());
}
byte[] signature = new byte[32];
random.nextBytes(signature);
String signatureBase64 = BaseEncoding.base64().encode(signature);
JsonObject json = new JsonObject();
json.addProperty("clientDataJSON", clientJson);
json.addProperty("authenticatorData", authenticatorBase64);
json.addProperty("signature", signatureBase64);
JsonElement element = gson.fromJson(json.toString(), JsonElement.class);
System.out.println(json.toString());
AuthenticatorAssertionResponse decoded = new AuthenticatorAssertionResponse(element);
assertTrue(Arrays.equals(decoded.signature, signature));
assertEquals(decoded.getClientData(), clientJson);
assertEquals(decoded.getAuthenticatorData(), authData);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import org.junit.Ignore;
import org.junit.Test;
public class AuthenticatorAttestationResponseTest {
final SecureRandom random = new SecureRandom();
/**
* Test method for
* {@link com.google.webauthn.gaedemo.objects.AuthenticatorAttestationResponse#AuthenticatorAttestationResponse}.
* @throws ResponseException
*/
@Test
@Ignore
public void testAuthenticatorAttestationResponse() throws ResponseException {
Gson gson = new Gson();
CollectedClientData clientData = new CollectedClientData();
clientData.challenge = "challengeString";
clientData.hashAlgorithm = "SHA-256";
clientData.origin = "https://localhost";
String clientJson = gson.toJson(clientData);
String clientBase64 =
BaseEncoding.base64Url().encode(clientJson.getBytes(StandardCharsets.UTF_8));
AttestationData attData = new AttestationData();
random.nextBytes(attData.aaguid);
attData.credentialId = new byte[16];
random.nextBytes(attData.credentialId);
EccKey ecc = new EccKey();
ecc.alg = Algorithm.decode("-7");
ecc.x = new byte[4];
ecc.y = new byte[4];
random.nextBytes(ecc.x);
random.nextBytes(ecc.y);
attData.publicKey = ecc;
byte flags = 1 << 6;
byte[] rpIdHash = new byte[32];
AuthenticatorData authData = new AuthenticatorData(rpIdHash, flags, 0, attData, null);
String authenticatorBase64 = null;
try {
authenticatorBase64 = BaseEncoding.base64().encode(authData.encode());
} catch (CborException e1) {
fail(e1.toString());
}
byte[] signature = new byte[32];
random.nextBytes(signature);
String signatureBase64 = BaseEncoding.base64().encode(signature);
JsonObject json = new JsonObject();
json.addProperty("clientDataJSON", clientBase64);
json.addProperty("authenticatorData", authenticatorBase64);
json.addProperty("signature", signatureBase64);
AuthenticatorAssertionResponse decoded =
new AuthenticatorAssertionResponse((JsonElement) json);
assertTrue(Arrays.equals(decoded.signature, signature));
assertEquals(decoded.getClientData(), clientData);
assertEquals(decoded.getAuthenticatorData(), authData);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import com.google.api.client.util.Base64;
import com.google.common.primitives.Bytes;
import com.google.webauthn.gaedemo.exceptions.ResponseException;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import org.junit.Test;
@SuppressWarnings("unused")
public class AuthenticatorDataTest {
private final SecureRandom random = new SecureRandom();
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.AuthenticatorData#decode(byte[])}.
*
* @throws ResponseException
*/
@Test
public void testDecodeWithoutAttestation() throws ResponseException {
byte[] randomRpIdHash = new byte[32];
random.nextBytes(randomRpIdHash);
byte[] flags = {0};
int countUnsignedInt = Integer.parseUnsignedInt("" + (random.nextLong() & 0xffffffffL));
byte[] count = ByteBuffer.allocate(4).putInt(countUnsignedInt).array();
byte[] data = Bytes.concat(randomRpIdHash, flags, count);
AuthenticatorData result = AuthenticatorData.decode(data);
assertArrayEquals(randomRpIdHash, result.getRpIdHash());
assertTrue(Integer.compareUnsigned(countUnsignedInt, result.getSignCount()) == 0);
}
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.AuthenticatorData#decode(byte[])}.
*
* @throws CborException
* @throws ResponseException
*/
@Test
public void testDecodeWithAttestation() throws CborException, ResponseException {
byte[] randomRpIdHash = new byte[32];
random.nextBytes(randomRpIdHash);
byte[] flags = {1 << 6};
AttestationData attData = new AttestationData();
EccKey eccKey = new EccKey(Base64.decodeBase64("NNxD3LBXs6iF1jiBGZ4Qqhd997NKcmDLJyyILL49V90"),
Base64.decodeBase64("MJtVZlRRfTscLy06DSHLBfA8O03pZJ1K01DbCILr0rA"));
random.nextBytes(attData.aaguid);
eccKey.alg = Algorithm.ES256;
attData.publicKey = eccKey;
int countUnsignedInt = Integer.parseUnsignedInt("" + (random.nextLong() & 0xffffffffL));
byte[] count = ByteBuffer.allocate(4).putInt(countUnsignedInt).array();
byte[] data = null;
data = Bytes.concat(randomRpIdHash, flags, count, attData.encode());
AuthenticatorData result = AuthenticatorData.decode(data);
assertTrue(result.getAttData().getPublicKey().equals(eccKey));
assertArrayEquals(randomRpIdHash, result.getRpIdHash());
assertTrue(Integer.compareUnsigned(countUnsignedInt, result.getSignCount()) == 0);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import org.junit.Ignore;
import org.junit.Test;
public class CollectedClientDataTest {
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.CollectedClientData#equals(Object)}.
*/
@Test
@Ignore
public void testEquals() {
CollectedClientData one = new CollectedClientData();
CollectedClientData two = new CollectedClientData();
assertEquals(one, two);
one.challenge = "challenge";
assertNotEquals(one, two);
two.challenge = "challenge";
assertEquals(one, two);
one.hashAlgorithm = "SHA-256";
assertNotEquals(one, two);
two.hashAlgorithm = "SHA-256";
assertEquals(one, two);
one.origin = "https://google.com";
assertNotEquals(one, two);
two.origin = "https://google.com";
assertEquals(one, two);
one.tokenBindingId = "test";
assertNotEquals(one, two);
two.tokenBindingId = "test";
assertEquals(one, two);
}
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.CollectedClientData#encode()}.
*/
@Ignore
@Test
public void testEncode() {
CollectedClientData clientData = new CollectedClientData();
clientData.challenge = "testChallenge";
clientData.hashAlgorithm = "SHA-1";
clientData.origin = "testOrigin";
CollectedClientData decoded = CollectedClientData.decode(clientData.encode());
assertEquals(clientData, decoded);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import org.junit.Test;
public class CredentialPublicKeyTest {
/**
* Test method for {@link com.google.webauthn.gaedemo.objects.CredentialPublicKey#decode(byte[])}.
*/
@Test
public void testDecode() {
EccKey eccKey = new EccKey();
eccKey.alg = Algorithm.decode("-7");
eccKey.x = new byte[] {0, 1, 2, 3};
eccKey.y = new byte[] {0, 2, 4, 6};
RsaKey rsaKey = new RsaKey();
rsaKey.alg = Algorithm.decode("PS512");
rsaKey.e = new byte[] {0, 1, 2, 3};
rsaKey.n = new byte[] {0, 2, 4, 6};
try {
CredentialPublicKey ecc = CredentialPublicKey.decode(eccKey.encode());
CredentialPublicKey rsa = CredentialPublicKey.decode(rsaKey.encode());
assertTrue(ecc instanceof EccKey);
assertEquals(ecc, eccKey);
assertTrue(rsa instanceof RsaKey);
assertEquals(rsa, rsaKey);
} catch (CborException e) {
fail(e.toString());
}
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import com.google.webauthn.gaedemo.crypto.Crypto;
import com.google.webauthn.gaedemo.exceptions.WebAuthnException;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;
import static org.junit.Assert.fail;
public class CryptoTest {
@Test
public void testGetRSAPublicKey() {
byte[] n = Hex.decode("A9E167983F39D55FF2A093415EA6798985C8355D9A915BFB1D01DA197026170FBDA522D035856D7A986614415CCFB7B7083B09C991B81969376DF9651E7BD9A93324A37F3BBBAF460186363432CB07035952FC858B3104B8CC18081448E64F1CFB5D60C4E05C1F53D37F53D86901F105F87A70D1BE83C65F38CF1C2CAA6AA7EB");
byte[] e = Hex.decode("010001");
Algorithm alg = Algorithm.RS256;
RsaKey rsaPublicKey = new RsaKey(alg, n, e);
try {
Crypto.getRSAPublicKey(rsaPublicKey);
} catch (WebAuthnException ex) {
fail("WebAuthnException: " + ex.getMessage());
}
}
@Test
public void testECPublicKey() {
byte[] x = Hex.decode("e383082691558e4b78cd80a5f9fa16f768bba6a93fb8801f8f5ad6f3cddd4f94");
byte[] y = Hex.decode("c9dd9c82ec06bdcdaef0e8602a4900deb08fc3d925c86ae996c6ad4c36f3f179");
Algorithm alg = Algorithm.ES256;
EccKey eccPublicKey = new EccKey(alg, x, y);
try {
Crypto.getECPublicKey(eccPublicKey);
} catch (WebAuthnException ex) {
fail("WebAuthnException: " + ex.getMessage());
}
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.charset.StandardCharsets;
import co.nstant.in.cbor.CborException;
import org.junit.Ignore;
import org.junit.Test;
public class EccKeyTest {
@Test
public void testEquals() {
EccKey one = new EccKey();
EccKey two = new EccKey();
assertEquals(one, two);
one.x = new byte[] {0, 1, 2};
assertNotEquals(one, two);
two.x = new byte[] {0, 1, 2};
assertEquals(one, two);
one.y = one.x;
assertNotEquals(one, two);
two.y = one.x;
assertEquals(one, two);
one.alg = Algorithm.ES256;
assertNotEquals(one, two);
two.alg = Algorithm.ES256;
assertEquals(one, two);
one.alg = null;
assertNotEquals(one, two);
two.alg = null;
assertEquals(one, two);
CredentialPublicKey three = new RsaKey();
CredentialPublicKey four = new EccKey();
assertNotEquals(four, three);
}
@Test
@Ignore
public void testEncode() {
EccKey testKey = new EccKey();
testKey.alg = Algorithm.ES256;
testKey.x = "testX".getBytes(StandardCharsets.UTF_8);
testKey.y = "testY".getBytes(StandardCharsets.UTF_8);
try {
CredentialPublicKey decodedCpk = CredentialPublicKey.decode(testKey.encode());
assertTrue(decodedCpk instanceof EccKey);
assertEquals(decodedCpk, testKey);
testKey.alg = Algorithm.PS256;
decodedCpk = CredentialPublicKey.decode(testKey.encode());
assertTrue(!(decodedCpk instanceof EccKey));
} catch (CborException e) {
fail("CborException: " + e.getMessage());
}
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import co.nstant.in.cbor.CborException;
import java.security.SecureRandom;
import java.util.ArrayList;
import org.junit.Ignore;
import org.junit.Test;
@SuppressWarnings("unused")
public class FidoU2fAttestationStatementTest {
final SecureRandom rand = new SecureRandom();
@Test
@Ignore
public void testEncode() throws CborException {
FidoU2fAttestationStatement attStmt = new FidoU2fAttestationStatement();
attStmt.attestnCert = new byte[32];
rand.nextBytes(attStmt.attestnCert);
attStmt.sig = new byte[64];
rand.nextBytes(attStmt.sig);
attStmt.caCert = new ArrayList<byte[]>();
for (int i = 0; i < 5; i++) {
byte[] cert = new byte[32];
rand.nextBytes(cert);
attStmt.caCert.add(cert);
}
FidoU2fAttestationStatement decoded = FidoU2fAttestationStatement.decode(attStmt.encode());
assertEquals(decoded, attStmt);
}
}
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.google.webauthn.gaedemo.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.charset.StandardCharsets;
import co.nstant.in.cbor.CborException;
import org.junit.Test;
public class RsaKeyTest {
@Test
public void testEquals() {
RsaKey one = new RsaKey();
RsaKey two = new RsaKey();
assertEquals(one, two);
one.alg = Algorithm.UNDEFINED;
two.alg = null;
assertNotEquals(one, two);
two.alg = Algorithm.UNDEFINED;
one.e = new byte[] {0, 1, 2};
assertNotEquals(one, two);
two.e = new byte[] {0, 1, 2};
assertEquals(one, two);
one.n = new byte[] {2, 1, 0};
assertNotEquals(one, two);
two.n = new byte[] {2, 1, 0};
assertEquals(one, two);
CredentialPublicKey three = new RsaKey();
CredentialPublicKey four = new EccKey();
assertNotEquals(four, three);
}
@Test
public void testEncode() {
RsaKey rsaKey = new RsaKey();
rsaKey.alg = Algorithm.PS256;
rsaKey.e = "e".getBytes(StandardCharsets.UTF_8);
rsaKey.n = "n".getBytes(StandardCharsets.UTF_8);
try {
CredentialPublicKey decodedCpk = CredentialPublicKey.decode(rsaKey.encode());
assertTrue(decodedCpk instanceof RsaKey);
assertEquals(decodedCpk, rsaKey);
} catch (CborException e) {
fail("CborException: " + e.getMessage());
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment