mirror of
https://github.com/portswigger/passkey-raider
synced 2026-08-09 13:04:26 +00:00
init code
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
||||
.gradle
|
||||
gradle/
|
||||
build/
|
||||
lib/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
version '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'net.portswigger.burp.extensions:montoya-api:2023.5'
|
||||
implementation 'com.webauthn4j:webauthn4j-core:0.28.3.RELEASE'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
implementation 'com.intellij:forms_rt:7.0.3'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName.set('Passkey-Raider') // Set the JAR file name
|
||||
archiveClassifier.set('') // No classifier (e.g., no "-all" suffix)
|
||||
//archiveVersion.set('') // Remove version from file name
|
||||
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'burp.PasskeyEditorExtension'
|
||||
)
|
||||
}
|
||||
//duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn shadowJar // Ensure shadowJar runs when you run the build task
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,2 @@
|
||||
rootProject.name = 'Passkey-Raider'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2023. PortSwigger Ltd. All rights reserved.
|
||||
*
|
||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
||||
* and Burp Suite Professional, provided that this usage does not violate the
|
||||
* license terms for those products.
|
||||
*/
|
||||
|
||||
package burp;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
|
||||
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
||||
|
||||
class MyHttpRequestEditorProvider implements HttpRequestEditorProvider
|
||||
{
|
||||
private final SettingForm settingForm;
|
||||
private final MontoyaApi api;
|
||||
|
||||
MyHttpRequestEditorProvider(SettingForm settingForm, MontoyaApi api)
|
||||
{
|
||||
this.settingForm = settingForm;
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext creationContext)
|
||||
{
|
||||
return new MyExtensionProvidedHttpRequestEditor(settingForm, api, creationContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2023. PortSwigger Ltd. All rights reserved.
|
||||
*
|
||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
||||
* and Burp Suite Professional, provided that this usage does not violate the
|
||||
* license terms for those products.
|
||||
*/
|
||||
|
||||
package burp;
|
||||
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.proxy.http.InterceptedRequest;
|
||||
import burp.api.montoya.proxy.http.ProxyRequestHandler;
|
||||
import burp.api.montoya.proxy.http.ProxyRequestReceivedAction;
|
||||
import burp.api.montoya.proxy.http.ProxyRequestToBeSentAction;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.ToNumberPolicy;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static burp.api.montoya.core.HighlightColor.BLUE;
|
||||
|
||||
class MyProxyHttpRequestHandler implements ProxyRequestHandler {
|
||||
|
||||
private final SettingForm settingForm;
|
||||
private final MontoyaApi api;
|
||||
private final Util util;
|
||||
Gson gsonPrettyPrinting;
|
||||
|
||||
MyProxyHttpRequestHandler(SettingForm settingForm, MontoyaApi api)
|
||||
{
|
||||
this.settingForm = settingForm;
|
||||
this.api = api;
|
||||
this.util = new Util(api);
|
||||
gsonPrettyPrinting = new GsonBuilder().setPrettyPrinting().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyRequestReceivedAction handleRequestReceived(InterceptedRequest interceptedRequest) {
|
||||
//Drop all post requests
|
||||
/*if (interceptedRequest.method().equals("POST")) {
|
||||
return ProxyRequestReceivedAction.drop();
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
/*if (interceptedRequest.url().equalsIgnoreCase(settingForm.registrationURL) || interceptedRequest.url().equalsIgnoreCase(settingForm.authenticationURL)) {
|
||||
return ProxyRequestReceivedAction.continueWith(interceptedRequest, interceptedRequest.annotations().withHighlightColor(BLUE));
|
||||
}*/
|
||||
|
||||
|
||||
if (interceptedRequest.url().equalsIgnoreCase(settingForm.registrationURL)) {
|
||||
/*
|
||||
- decode
|
||||
- change pub key
|
||||
- encode
|
||||
*/
|
||||
String requestBody = interceptedRequest.bodyToString();
|
||||
|
||||
Pattern patternAttestationObject = Pattern.compile(settingForm.registrationRegexAttestationObject);
|
||||
Matcher matcherAttestationObject = patternAttestationObject.matcher(requestBody);
|
||||
|
||||
if (matcherAttestationObject.find()) {
|
||||
String attestationObjectValue = matcherAttestationObject.group(1);
|
||||
api.logging().logToOutput("\n============= " + settingForm.registrationURL + " =============");
|
||||
api.logging().logToOutput("attestationObjectValue: " + attestationObjectValue);
|
||||
|
||||
Map<String, Object> attestationObject = util.decodeAttestationObject(attestationObjectValue);
|
||||
|
||||
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
Map<String, Object> coseKey = gsonPrettyPrinting.fromJson(settingForm.coseKeyJsonString, mapType);
|
||||
|
||||
((Map<String, Object>) ((Map<String, Object>) attestationObject.get("authenticatorData")).get("attestedCredentialData")).put("coseKey", coseKey);
|
||||
|
||||
String modifiedAttestationObjectB64 = util.encodeAttestationObject(attestationObject);
|
||||
requestBody = requestBody.replaceAll(attestationObjectValue, modifiedAttestationObjectB64);
|
||||
|
||||
|
||||
/*Map<String, Object> authenticatorDataMap = (Map<String, Object>) attestationObject.get("authenticatorData");
|
||||
Map<String, Object> attestedCredentialDataMap = (Map<String, Object>) authenticatorDataMap.get("attestedCredentialData");
|
||||
Map<String, Object> coseKeyMap = (Map<String, Object>) attestedCredentialDataMap.get("coseKey");*/
|
||||
|
||||
return ProxyRequestReceivedAction.continueWith(interceptedRequest.withBody(requestBody), interceptedRequest.annotations().withHighlightColor(BLUE));
|
||||
}
|
||||
} else if (interceptedRequest.url().equalsIgnoreCase(settingForm.authenticationURL)) {
|
||||
return ProxyRequestReceivedAction.continueWith(interceptedRequest, interceptedRequest.annotations().withHighlightColor(BLUE));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//If the content type is json, highlight the request and follow burp rules for interception
|
||||
/*if (interceptedRequest.contentType() == JSON) {
|
||||
return ProxyRequestReceivedAction.continueWith(interceptedRequest, interceptedRequest.annotations().withHighlightColor(RED));
|
||||
}*/
|
||||
|
||||
//Intercept all other requests
|
||||
//return ProxyRequestReceivedAction.intercept(interceptedRequest);
|
||||
|
||||
return ProxyRequestReceivedAction.continueWith(interceptedRequest);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyRequestToBeSentAction handleRequestToBeSent(InterceptedRequest interceptedRequest) {
|
||||
//Do nothing with the user modified request, continue as normal.
|
||||
api.logging().logToOutput("\n============= handleRequestToBeSent =============");
|
||||
|
||||
return ProxyRequestToBeSentAction.continueWith(interceptedRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2023. PortSwigger Ltd. All rights reserved.
|
||||
*
|
||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
||||
* and Burp Suite Professional, provided that this usage does not violate the
|
||||
* license terms for those products.
|
||||
*/
|
||||
|
||||
package burp;
|
||||
|
||||
import burp.api.montoya.proxy.http.InterceptedResponse;
|
||||
import burp.api.montoya.proxy.http.ProxyResponseHandler;
|
||||
import burp.api.montoya.proxy.http.ProxyResponseReceivedAction;
|
||||
import burp.api.montoya.proxy.http.ProxyResponseToBeSentAction;
|
||||
|
||||
import static burp.api.montoya.core.HighlightColor.BLUE;
|
||||
|
||||
class MyProxyHttpResponseHandler implements ProxyResponseHandler {
|
||||
@Override
|
||||
public ProxyResponseReceivedAction handleResponseReceived(InterceptedResponse interceptedResponse) {
|
||||
//Highlight all responses that have username in them
|
||||
/*if (interceptedResponse.bodyToString().contains("username")) {
|
||||
return ProxyResponseReceivedAction.continueWith(interceptedResponse, interceptedResponse.annotations().withHighlightColor(BLUE));
|
||||
}*/
|
||||
|
||||
return ProxyResponseReceivedAction.continueWith(interceptedResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyResponseToBeSentAction handleResponseToBeSent(InterceptedResponse interceptedResponse) {
|
||||
return ProxyResponseToBeSentAction.continueWith(interceptedResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2023. PortSwigger Ltd. All rights reserved.
|
||||
*
|
||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
||||
* and Burp Suite Professional, provided that this usage does not violate the
|
||||
* license terms for those products.
|
||||
*/
|
||||
|
||||
package burp;
|
||||
|
||||
import burp.api.montoya.BurpExtension;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.persistence.PersistedObject;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class PasskeyEditorExtension implements BurpExtension
|
||||
{
|
||||
@Override
|
||||
public void initialize(MontoyaApi api)
|
||||
{
|
||||
api.extension().setName("Passkey Raider");
|
||||
|
||||
SettingForm settingForm = new SettingForm(api);
|
||||
//api.userInterface().registerSuiteTab("Passkey Raider", settingForm.getUI());
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//api.userInterface().registerSuiteTab("Passkey Raider", new SettingForm(api).getUI());
|
||||
api.userInterface().registerSuiteTab("Passkey Raider", settingForm.getUI());
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
api.userInterface().registerHttpRequestEditorProvider(new MyHttpRequestEditorProvider(settingForm, api));
|
||||
|
||||
//Register proxy handlers with Burp.
|
||||
api.proxy().registerRequestHandler(new MyProxyHttpRequestHandler(settingForm, api));
|
||||
api.proxy().registerResponseHandler(new MyProxyHttpResponseHandler());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="burp.SettingForm">
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="10" left="10" bottom="10" right="10"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="887" height="713"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<maximumSize width="400" height="2147483647"/>
|
||||
<minimumSize width="400" height="503"/>
|
||||
<preferredSize width="400" height="503"/>
|
||||
</properties>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="ee15" binding="coseKeyPanel" layout-manager="GridLayoutManager" row-count="4" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="10" left="10" bottom="10" right="10"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<maximum-size width="800" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched" title="COSE Key"/>
|
||||
<children>
|
||||
<component id="877d8" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Key Type"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="b5b9a" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Algorithm"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="ac5a1" binding="keyTypePanel" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched"/>
|
||||
<children>
|
||||
<component id="8cbf9" class="javax.swing.JRadioButton" binding="RSARadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="true"/>
|
||||
<text value="RSA"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="e5b6b" class="javax.swing.JRadioButton" binding="EC2RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="EC2"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="504be" class="javax.swing.JRadioButton" binding="OKPRadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="OKP"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="78c41" binding="algorithmPanel" layout-manager="GridLayoutManager" row-count="1" column-count="8" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched"/>
|
||||
<children>
|
||||
<component id="dbfa1" class="javax.swing.JRadioButton" binding="ES256RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text value="ES256"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="758c8" class="javax.swing.JRadioButton" binding="edDSARadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="EdDSA"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="999e7" class="javax.swing.JRadioButton" binding="RS384RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="4" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="RS384"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f3857" class="javax.swing.JRadioButton" binding="RS1RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="RS1"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="710d4" class="javax.swing.JRadioButton" binding="RS512RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="5" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="RS512"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="fef5e" class="javax.swing.JRadioButton" binding="ES384RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="6" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="ES384"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="a7855" class="javax.swing.JRadioButton" binding="ES512RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="7" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="ES512"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="42bbb" class="javax.swing.JRadioButton" binding="RS256RadioButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="true"/>
|
||||
<text value="RS256"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<component id="f2a6b" class="javax.swing.JButton" binding="generateButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="3" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Generate"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="6258b">
|
||||
<constraints>
|
||||
<grid row="3" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<component id="2f971" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Generated COSE Key"/>
|
||||
</properties>
|
||||
</component>
|
||||
<scrollpane id="79003">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="3" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="d9f75" class="javax.swing.JTextArea" binding="coseKeyField">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<lineWrap value="true"/>
|
||||
<requestFocusEnabled value="true"/>
|
||||
<rows value="10"/>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="7f403" binding="registrationPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="10" left="10" bottom="10" right="10"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<maximum-size width="800" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched" title="Passkey Registration"/>
|
||||
<children>
|
||||
<component id="955d2" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Passkey Registration URL:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d814f" class="javax.swing.JTextField" binding="registrationUrlField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="a68fb" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Regex to extract Registration's clientDataJSON:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="bc13b" class="javax.swing.JTextField" binding="registrationClientDataJSONField">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="4a5f" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Regex to extract Registration's attestationObject:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="67cbd" class="javax.swing.JTextField" binding="registrationAttestationObjectField">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="9b1cd" binding="authenticationPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="10" left="10" bottom="10" right="10"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<maximum-size width="800" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched" title="Passkey Authentication"/>
|
||||
<children>
|
||||
<component id="7f1dc" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Passkey Authentication URL:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="5d9b7" class="javax.swing.JTextField" binding="authenticationUrlField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="b81ae" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Regex to extract Authentication's clientDataJSON:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="52fbe" class="javax.swing.JTextField" binding="authenticationClientDataJSONField">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="56b41" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Regex to extract Authentication's authenticatorData:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="99e34" class="javax.swing.JTextField" binding="authenticationAuthenticatorDataField">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="eb3a9" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Regex to extract Authentication's signature:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="4411c" class="javax.swing.JTextField" binding="authenticationSignatureField">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="41f1c" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<maximum-size width="800" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="c3c9d" class="javax.swing.JButton" binding="saveButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Save"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="78fc2">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<vspacer id="1ea8f">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<buttonGroups>
|
||||
<group name="groupKeyType">
|
||||
<member id="8cbf9"/>
|
||||
<member id="e5b6b"/>
|
||||
<member id="504be"/>
|
||||
</group>
|
||||
<group name="groupAlgorithm">
|
||||
<member id="dbfa1"/>
|
||||
<member id="f3857"/>
|
||||
<member id="758c8"/>
|
||||
<member id="999e7"/>
|
||||
<member id="710d4"/>
|
||||
<member id="fef5e"/>
|
||||
<member id="a7855"/>
|
||||
<member id="42bbb"/>
|
||||
</group>
|
||||
</buttonGroups>
|
||||
</form>
|
||||
@@ -0,0 +1,442 @@
|
||||
package burp;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.persistence.PersistedObject;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.ToNumberPolicy;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.intellij.uiDesigner.core.GridConstraints;
|
||||
import com.intellij.uiDesigner.core.GridLayoutManager;
|
||||
import com.intellij.uiDesigner.core.Spacer;
|
||||
import com.webauthn4j.data.attestation.authenticator.*;
|
||||
import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier;
|
||||
import com.webauthn4j.util.ECUtil;
|
||||
import com.webauthn4j.util.RSAUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import java.awt.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingForm {
|
||||
private JPanel mainPanel;
|
||||
private JPanel registrationPanel;
|
||||
private JTextField registrationUrlField;
|
||||
private JTextField registrationClientDataJSONField;
|
||||
private JTextField registrationAttestationObjectField;
|
||||
private JPanel authenticationPanel;
|
||||
private JTextField authenticationSignatureField;
|
||||
private JTextField authenticationAuthenticatorDataField;
|
||||
private JTextField authenticationClientDataJSONField;
|
||||
private JTextField authenticationUrlField;
|
||||
private JPanel coseKeyPanel;
|
||||
private JTextArea coseKeyField;
|
||||
private JPanel keyTypePanel;
|
||||
private JRadioButton RSARadioButton;
|
||||
private JRadioButton EC2RadioButton;
|
||||
private JRadioButton OKPRadioButton;
|
||||
private JPanel algorithmPanel;
|
||||
private JRadioButton RS256RadioButton;
|
||||
private JRadioButton ES256RadioButton;
|
||||
private JRadioButton edDSARadioButton;
|
||||
private JRadioButton RS384RadioButton;
|
||||
private JRadioButton RS1RadioButton;
|
||||
private JRadioButton RS512RadioButton;
|
||||
private JRadioButton ES384RadioButton;
|
||||
private JRadioButton ES512RadioButton;
|
||||
private JButton generateButton;
|
||||
private JButton saveButton;
|
||||
|
||||
private final MontoyaApi api;
|
||||
private final Util util;
|
||||
Gson gsonPrettyPrinting;
|
||||
Type mapType;
|
||||
|
||||
private PersistedObject settingData;
|
||||
|
||||
public String registrationURL = "";
|
||||
public String registrationRegexClientDataJSON = "\"clientDataJSON\":\"([^\"]+)";
|
||||
public String registrationRegexAttestationObject = "\"attestationObject\":\"([^\"]+)";
|
||||
|
||||
public String authenticationURL = "";
|
||||
public String authenticationRegexClientDataJSON = "\"clientDataJSON\":\"([^\"]+)";
|
||||
public String authenticationRegexAuthenticatorData = "\"authenticatorData\":\"([^\"]+)";
|
||||
public String authenticationRegexSignature = "\"signature\":\"([^\"]+)";
|
||||
public String coseKeyJsonString = "";
|
||||
public COSEKey coseKey = null;
|
||||
|
||||
|
||||
String keyType = "";
|
||||
String algorithm = "";
|
||||
|
||||
SettingForm(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.util = new Util(api);
|
||||
mapType = new TypeToken<Map<String, Object>>() {
|
||||
}.getType();
|
||||
gsonPrettyPrinting = new GsonBuilder().setPrettyPrinting().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
||||
|
||||
settingData = api.persistence().extensionData();
|
||||
registrationURL = settingData.getString("registrationURL") != null ? settingData.getString("registrationURL") : registrationURL;
|
||||
registrationRegexClientDataJSON = settingData.getString("registrationClientDataJSON") != null ? settingData.getString("registrationClientDataJSON") : registrationRegexClientDataJSON;
|
||||
registrationRegexAttestationObject = settingData.getString("registrationAttestationObject") != null ? settingData.getString("registrationAttestationObject") : registrationRegexAttestationObject;
|
||||
authenticationURL = settingData.getString("authenticationURL") != null ? settingData.getString("authenticationURL") : authenticationURL;
|
||||
authenticationRegexClientDataJSON = settingData.getString("authenticationClientDataJSON") != null ? settingData.getString("authenticationClientDataJSON") : authenticationRegexClientDataJSON;
|
||||
authenticationRegexAuthenticatorData = settingData.getString("authenticationAuthenticatorData") != null ? settingData.getString("authenticationAuthenticatorData") : authenticationRegexAuthenticatorData;
|
||||
authenticationRegexSignature = settingData.getString("authenticationSignature") != null ? settingData.getString("authenticationSignature") : authenticationRegexSignature;
|
||||
|
||||
try {
|
||||
coseKeyJsonString = settingData.getString("coseKeyJsonString") != null ? settingData.getString("coseKeyJsonString") : generateCOSEKey();
|
||||
|
||||
Map<String, Object> coseKeyJson = gsonPrettyPrinting.fromJson(coseKeyJsonString, mapType);
|
||||
coseKey = util.COSEKeyJsonToObject(coseKeyJson);
|
||||
switch ((String) coseKeyJson.get("keyType")) {
|
||||
case "RSA" -> RSARadioButton.setSelected(true);
|
||||
case "EC2" -> EC2RadioButton.setSelected(true);
|
||||
default -> {
|
||||
OKPRadioButton.setSelected(true);
|
||||
enableAlgorithms(false);
|
||||
edDSARadioButton.setSelected(true);
|
||||
}
|
||||
}
|
||||
switch ((String) coseKeyJson.get("algorithm")) {
|
||||
case "ES256" -> ES256RadioButton.setSelected(true);
|
||||
case "RS1" -> RS1RadioButton.setSelected(true);
|
||||
case "EdDSA" -> edDSARadioButton.setSelected(true);
|
||||
case "RS384" -> RS384RadioButton.setSelected(true);
|
||||
case "RS512" -> RS512RadioButton.setSelected(true);
|
||||
case "ES384" -> ES384RadioButton.setSelected(true);
|
||||
case "ES512" -> ES512RadioButton.setSelected(true);
|
||||
default -> RS256RadioButton.setSelected(true);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
printSetting(true);
|
||||
|
||||
registrationUrlField.setText(registrationURL);
|
||||
registrationClientDataJSONField.setText(registrationRegexClientDataJSON);
|
||||
registrationAttestationObjectField.setText(registrationRegexAttestationObject);
|
||||
|
||||
authenticationUrlField.setText(authenticationURL);
|
||||
authenticationClientDataJSONField.setText(authenticationRegexClientDataJSON);
|
||||
authenticationAuthenticatorDataField.setText(authenticationRegexAuthenticatorData);
|
||||
authenticationSignatureField.setText(authenticationRegexSignature);
|
||||
|
||||
coseKeyField.setText(coseKeyJsonString);
|
||||
RSARadioButton.addActionListener(e -> {
|
||||
if (RSARadioButton.isSelected()) {
|
||||
enableAlgorithms(true);
|
||||
}
|
||||
});
|
||||
EC2RadioButton.addActionListener(e -> {
|
||||
if (EC2RadioButton.isSelected()) {
|
||||
enableAlgorithms(true);
|
||||
}
|
||||
});
|
||||
OKPRadioButton.addActionListener(e -> {
|
||||
if (OKPRadioButton.isSelected()) {
|
||||
enableAlgorithms(false);
|
||||
edDSARadioButton.setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
generateButton.addActionListener(e -> {
|
||||
try {
|
||||
coseKeyField.setText(generateCOSEKey());
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
saveButton.addActionListener(e -> {
|
||||
registrationURL = registrationUrlField.getText().trim();
|
||||
registrationRegexClientDataJSON = registrationClientDataJSONField.getText().trim();
|
||||
registrationRegexAttestationObject = registrationAttestationObjectField.getText().trim();
|
||||
authenticationURL = authenticationUrlField.getText().trim();
|
||||
authenticationRegexClientDataJSON = authenticationClientDataJSONField.getText().trim();
|
||||
authenticationRegexAuthenticatorData = authenticationAuthenticatorDataField.getText().trim();
|
||||
authenticationRegexSignature = authenticationSignatureField.getText().trim();
|
||||
coseKeyJsonString = coseKeyField.getText();
|
||||
Map<String, Object> coseKeyJson = gsonPrettyPrinting.fromJson(coseKeyJsonString, mapType);
|
||||
coseKey = util.COSEKeyJsonToObject(coseKeyJson);
|
||||
|
||||
settingData.setString("registrationURL", registrationURL);
|
||||
settingData.setString("registrationClientDataJSON", registrationRegexClientDataJSON);
|
||||
settingData.setString("registrationAttestationObject", registrationRegexAttestationObject);
|
||||
|
||||
settingData.setString("authenticationURL", authenticationURL);
|
||||
settingData.setString("authenticationClientDataJSON", authenticationRegexClientDataJSON);
|
||||
settingData.setString("authenticationAuthenticatorData", authenticationRegexAuthenticatorData);
|
||||
settingData.setString("authenticationSignature", authenticationRegexSignature);
|
||||
|
||||
settingData.setString("coseKeyJsonString", coseKeyJsonString);
|
||||
|
||||
printSetting(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void enableAlgorithms(boolean b) {
|
||||
RS256RadioButton.setEnabled(b);
|
||||
ES256RadioButton.setEnabled(b);
|
||||
RS384RadioButton.setEnabled(b);
|
||||
RS1RadioButton.setEnabled(b);
|
||||
RS512RadioButton.setEnabled(b);
|
||||
ES384RadioButton.setEnabled(b);
|
||||
ES512RadioButton.setEnabled(b);
|
||||
}
|
||||
|
||||
public JPanel getUI() {
|
||||
return this.mainPanel;
|
||||
}
|
||||
|
||||
private String generateCOSEKey() throws NoSuchAlgorithmException {
|
||||
api.logging().logToOutput("\n============= generateCOSEKey =============");
|
||||
|
||||
if (RSARadioButton.isSelected()) {
|
||||
keyType = "RSA";
|
||||
} else if (EC2RadioButton.isSelected()) {
|
||||
keyType = "EC2";
|
||||
} else if (OKPRadioButton.isSelected()) {
|
||||
keyType = "EdDSA";
|
||||
}
|
||||
|
||||
if (RS256RadioButton.isSelected()) {
|
||||
algorithm = "RS256";
|
||||
} else if (ES256RadioButton.isSelected()) {
|
||||
algorithm = "ES256";
|
||||
} else if (edDSARadioButton.isSelected()) {
|
||||
algorithm = "EdDSA";
|
||||
} else if (RS384RadioButton.isSelected()) {
|
||||
algorithm = "RS384";
|
||||
} else if (RS1RadioButton.isSelected()) {
|
||||
algorithm = "RS1";
|
||||
} else if (RS512RadioButton.isSelected()) {
|
||||
algorithm = "RS512";
|
||||
} else if (ES384RadioButton.isSelected()) {
|
||||
algorithm = "ES384";
|
||||
} else if (ES512RadioButton.isSelected()) {
|
||||
algorithm = "ES512";
|
||||
}
|
||||
|
||||
coseKey = switch (keyType) {
|
||||
case "EdDSA" -> EdDSACOSEKey.create(Util.createEdDSAKeyPair());
|
||||
case "RSA" -> switch (algorithm) {
|
||||
case "RS256" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.RS256);
|
||||
case "ES256" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.ES256);
|
||||
case "RS1" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.RS1);
|
||||
case "EdDSA" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.EdDSA);
|
||||
case "RS384" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.RS384);
|
||||
case "RS512" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.RS512);
|
||||
case "ES384" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.ES384);
|
||||
case "ES512" -> RSACOSEKey.create(RSAUtil.createKeyPair(), COSEAlgorithmIdentifier.ES512);
|
||||
default -> coseKey;
|
||||
};
|
||||
case "EC2" -> switch (algorithm) {
|
||||
case "RS256" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.RS256);
|
||||
case "ES256" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.ES256);
|
||||
case "RS1" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.RS1);
|
||||
case "EdDSA" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.EdDSA);
|
||||
case "RS384" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.RS384);
|
||||
case "RS512" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.RS512);
|
||||
case "ES384" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.ES384);
|
||||
case "ES512" -> EC2COSEKey.create(ECUtil.createKeyPair(), COSEAlgorithmIdentifier.ES512);
|
||||
default -> coseKey;
|
||||
};
|
||||
default -> null;
|
||||
};
|
||||
Map<String, Object> coseKeyJson = util.COSEKeyObjectToJson(coseKey);
|
||||
|
||||
api.logging().logToOutput("\nKey Type: " + keyType);
|
||||
api.logging().logToOutput("\nAlgorithm: " + algorithm);
|
||||
util.logPrettyJson("COSE Key: ", coseKeyJson);
|
||||
|
||||
return gsonPrettyPrinting.toJson(coseKeyJson);
|
||||
}
|
||||
|
||||
private void printSetting(boolean isLoad) {
|
||||
if (isLoad)
|
||||
api.logging().logToOutput("\n============= Load Setting =============");
|
||||
else
|
||||
api.logging().logToOutput("\n============= Save Setting =============");
|
||||
api.logging().logToOutput("Passkey Registration URL: " + registrationURL);
|
||||
api.logging().logToOutput("Regex to extract Registration's clientDataJSON: " + registrationRegexClientDataJSON);
|
||||
api.logging().logToOutput("Regex to extract Registration's attestationObject: " + registrationRegexAttestationObject);
|
||||
api.logging().logToOutput("Passkey Authentication URL: " + authenticationURL);
|
||||
api.logging().logToOutput("Regex to extract Authentication's clientDataJSON: " + authenticationRegexClientDataJSON);
|
||||
api.logging().logToOutput("Regex to extract Authentication's authenticatorData: " + authenticationRegexAuthenticatorData);
|
||||
api.logging().logToOutput("Regex to extract Authentication's signature: " + authenticationRegexSignature);
|
||||
api.logging().logToOutput("COSE Key: " + coseKeyJsonString);
|
||||
}
|
||||
|
||||
{
|
||||
// GUI initializer generated by IntelliJ IDEA GUI Designer
|
||||
// >>> IMPORTANT!! <<<
|
||||
// DO NOT EDIT OR ADD ANY CODE HERE!
|
||||
$$$setupUI$$$();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method generated by IntelliJ IDEA GUI Designer
|
||||
* >>> IMPORTANT!! <<<
|
||||
* DO NOT edit this method OR call it in your code!
|
||||
*
|
||||
* @noinspection ALL
|
||||
*/
|
||||
private void $$$setupUI$$$() {
|
||||
mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new GridLayoutManager(4, 2, new Insets(10, 10, 10, 10), -1, -1));
|
||||
mainPanel.setMaximumSize(new Dimension(400, 2147483647));
|
||||
mainPanel.setMinimumSize(new Dimension(400, 503));
|
||||
mainPanel.setPreferredSize(new Dimension(400, 503));
|
||||
coseKeyPanel = new JPanel();
|
||||
coseKeyPanel.setLayout(new GridLayoutManager(4, 4, new Insets(10, 10, 10, 10), -1, -1));
|
||||
mainPanel.add(coseKeyPanel, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, new Dimension(800, -1), 0, false));
|
||||
coseKeyPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "COSE Key", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
|
||||
final JLabel label1 = new JLabel();
|
||||
label1.setText("Key Type");
|
||||
coseKeyPanel.add(label1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
final JLabel label2 = new JLabel();
|
||||
label2.setText("Algorithm");
|
||||
coseKeyPanel.add(label2, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
keyTypePanel = new JPanel();
|
||||
keyTypePanel.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1));
|
||||
coseKeyPanel.add(keyTypePanel, new GridConstraints(1, 1, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
|
||||
keyTypePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
|
||||
RSARadioButton = new JRadioButton();
|
||||
RSARadioButton.setSelected(true);
|
||||
RSARadioButton.setText("RSA");
|
||||
keyTypePanel.add(RSARadioButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
EC2RadioButton = new JRadioButton();
|
||||
EC2RadioButton.setText("EC2");
|
||||
keyTypePanel.add(EC2RadioButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
OKPRadioButton = new JRadioButton();
|
||||
OKPRadioButton.setText("OKP");
|
||||
keyTypePanel.add(OKPRadioButton, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
algorithmPanel = new JPanel();
|
||||
algorithmPanel.setLayout(new GridLayoutManager(1, 8, new Insets(0, 0, 0, 0), -1, -1));
|
||||
coseKeyPanel.add(algorithmPanel, new GridConstraints(2, 1, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
|
||||
algorithmPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
|
||||
ES256RadioButton = new JRadioButton();
|
||||
ES256RadioButton.setSelected(false);
|
||||
ES256RadioButton.setText("ES256");
|
||||
algorithmPanel.add(ES256RadioButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
edDSARadioButton = new JRadioButton();
|
||||
edDSARadioButton.setText("EdDSA");
|
||||
algorithmPanel.add(edDSARadioButton, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
RS384RadioButton = new JRadioButton();
|
||||
RS384RadioButton.setText("RS384");
|
||||
algorithmPanel.add(RS384RadioButton, new GridConstraints(0, 4, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
RS1RadioButton = new JRadioButton();
|
||||
RS1RadioButton.setText("RS1");
|
||||
algorithmPanel.add(RS1RadioButton, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
RS512RadioButton = new JRadioButton();
|
||||
RS512RadioButton.setText("RS512");
|
||||
algorithmPanel.add(RS512RadioButton, new GridConstraints(0, 5, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
ES384RadioButton = new JRadioButton();
|
||||
ES384RadioButton.setText("ES384");
|
||||
algorithmPanel.add(ES384RadioButton, new GridConstraints(0, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
ES512RadioButton = new JRadioButton();
|
||||
ES512RadioButton.setText("ES512");
|
||||
algorithmPanel.add(ES512RadioButton, new GridConstraints(0, 7, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
RS256RadioButton = new JRadioButton();
|
||||
RS256RadioButton.setSelected(true);
|
||||
RS256RadioButton.setText("RS256");
|
||||
algorithmPanel.add(RS256RadioButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
generateButton = new JButton();
|
||||
generateButton.setText("Generate");
|
||||
coseKeyPanel.add(generateButton, new GridConstraints(3, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
final Spacer spacer1 = new Spacer();
|
||||
coseKeyPanel.add(spacer1, new GridConstraints(3, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
|
||||
final JLabel label3 = new JLabel();
|
||||
label3.setText("Generated COSE Key");
|
||||
coseKeyPanel.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
final JScrollPane scrollPane1 = new JScrollPane();
|
||||
coseKeyPanel.add(scrollPane1, new GridConstraints(0, 1, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
|
||||
coseKeyField = new JTextArea();
|
||||
coseKeyField.setLineWrap(true);
|
||||
coseKeyField.setRequestFocusEnabled(true);
|
||||
coseKeyField.setRows(10);
|
||||
coseKeyField.setText("");
|
||||
scrollPane1.setViewportView(coseKeyField);
|
||||
registrationPanel = new JPanel();
|
||||
registrationPanel.setLayout(new GridLayoutManager(3, 2, new Insets(10, 10, 10, 10), -1, -1));
|
||||
mainPanel.add(registrationPanel, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, new Dimension(800, -1), 0, false));
|
||||
registrationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Passkey Registration", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
|
||||
final JLabel label4 = new JLabel();
|
||||
label4.setText("Passkey Registration URL:");
|
||||
registrationPanel.add(label4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
registrationUrlField = new JTextField();
|
||||
registrationPanel.add(registrationUrlField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JLabel label5 = new JLabel();
|
||||
label5.setText("Regex to extract Registration's clientDataJSON:");
|
||||
registrationPanel.add(label5, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
registrationClientDataJSONField = new JTextField();
|
||||
registrationPanel.add(registrationClientDataJSONField, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JLabel label6 = new JLabel();
|
||||
label6.setText("Regex to extract Registration's attestationObject:");
|
||||
registrationPanel.add(label6, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
registrationAttestationObjectField = new JTextField();
|
||||
registrationPanel.add(registrationAttestationObjectField, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
authenticationPanel = new JPanel();
|
||||
authenticationPanel.setLayout(new GridLayoutManager(4, 2, new Insets(10, 10, 10, 10), -1, -1));
|
||||
mainPanel.add(authenticationPanel, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, new Dimension(800, -1), 0, false));
|
||||
authenticationPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Passkey Authentication", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
|
||||
final JLabel label7 = new JLabel();
|
||||
label7.setText("Passkey Authentication URL:");
|
||||
authenticationPanel.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
authenticationUrlField = new JTextField();
|
||||
authenticationPanel.add(authenticationUrlField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JLabel label8 = new JLabel();
|
||||
label8.setText("Regex to extract Authentication's clientDataJSON:");
|
||||
authenticationPanel.add(label8, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
authenticationClientDataJSONField = new JTextField();
|
||||
authenticationPanel.add(authenticationClientDataJSONField, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JLabel label9 = new JLabel();
|
||||
label9.setText("Regex to extract Authentication's authenticatorData:");
|
||||
authenticationPanel.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
authenticationAuthenticatorDataField = new JTextField();
|
||||
authenticationPanel.add(authenticationAuthenticatorDataField, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JLabel label10 = new JLabel();
|
||||
label10.setText("Regex to extract Authentication's signature:");
|
||||
authenticationPanel.add(label10, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
authenticationSignatureField = new JTextField();
|
||||
authenticationPanel.add(authenticationSignatureField, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false));
|
||||
final JPanel panel1 = new JPanel();
|
||||
panel1.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1));
|
||||
mainPanel.add(panel1, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, new Dimension(800, -1), 0, false));
|
||||
saveButton = new JButton();
|
||||
saveButton.setText("Save");
|
||||
panel1.add(saveButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
|
||||
final Spacer spacer2 = new Spacer();
|
||||
panel1.add(spacer2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
|
||||
final Spacer spacer3 = new Spacer();
|
||||
panel1.add(spacer3, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
|
||||
ButtonGroup buttonGroup;
|
||||
buttonGroup = new ButtonGroup();
|
||||
buttonGroup.add(RSARadioButton);
|
||||
buttonGroup.add(EC2RadioButton);
|
||||
buttonGroup.add(OKPRadioButton);
|
||||
buttonGroup = new ButtonGroup();
|
||||
buttonGroup.add(ES256RadioButton);
|
||||
buttonGroup.add(RS1RadioButton);
|
||||
buttonGroup.add(edDSARadioButton);
|
||||
buttonGroup.add(RS384RadioButton);
|
||||
buttonGroup.add(RS512RadioButton);
|
||||
buttonGroup.add(ES384RadioButton);
|
||||
buttonGroup.add(ES512RadioButton);
|
||||
buttonGroup.add(RS256RadioButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection ALL
|
||||
*/
|
||||
public JComponent $$$getRootComponent$$$() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
package burp;
|
||||
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.params.ParsedHttpParameter;
|
||||
|
||||
import burp.api.montoya.utilities.Base64EncodingOptions;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.google.gson.ToNumberPolicy;
|
||||
import com.webauthn4j.converter.AttestedCredentialDataConverter;
|
||||
import com.webauthn4j.converter.AuthenticatorDataConverter;
|
||||
import com.webauthn4j.converter.CollectedClientDataConverter;
|
||||
import com.webauthn4j.converter.jackson.deserializer.cbor.TPMSAttestDeserializer;
|
||||
import com.webauthn4j.converter.jackson.deserializer.cbor.TPMTPublicDeserializer;
|
||||
import com.webauthn4j.converter.jackson.serializer.cbor.TPMSAttestSerializer;
|
||||
import com.webauthn4j.converter.jackson.serializer.cbor.TPMTPublicSerializer;
|
||||
import com.webauthn4j.converter.util.ObjectConverter;
|
||||
import com.webauthn4j.converter.AttestationObjectConverter;
|
||||
import com.webauthn4j.data.attestation.authenticator.*;
|
||||
import com.webauthn4j.data.attestation.statement.*;
|
||||
import com.webauthn4j.data.attestation.AttestationObject;
|
||||
import com.webauthn4j.data.extension.CredentialProtectionPolicy;
|
||||
import com.webauthn4j.data.extension.UvmEntries;
|
||||
import com.webauthn4j.data.extension.authenticator.AuthenticationExtensionAuthenticatorOutput;
|
||||
import com.webauthn4j.data.extension.authenticator.AuthenticationExtensionsAuthenticatorOutputs;
|
||||
import com.webauthn4j.data.extension.authenticator.ExtensionAuthenticatorOutput;
|
||||
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
|
||||
import com.webauthn4j.data.jws.JWAIdentifier;
|
||||
import com.webauthn4j.data.jws.JWS;
|
||||
import com.webauthn4j.data.jws.JWSFactory;
|
||||
import com.webauthn4j.data.jws.JWSHeader;
|
||||
import com.webauthn4j.util.*;
|
||||
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static burp.api.montoya.core.ByteArray.byteArray;
|
||||
|
||||
public class Util {
|
||||
private HttpRequestResponse requestResponse;
|
||||
private final MontoyaApi api;
|
||||
private final Gson gsonPrettyPrinting;
|
||||
private final Gson gson;
|
||||
|
||||
private ParsedHttpParameter parsedHttpParameter;
|
||||
|
||||
private final ObjectConverter objectConverter;
|
||||
private final AttestationObjectConverter attestationObjectConverter;
|
||||
private final AuthenticatorDataConverter authenticatorDataConverter;
|
||||
private final AttestedCredentialDataConverter attestedCredentialDataConverter;
|
||||
|
||||
public static final byte BIT_UP = 0;
|
||||
public static final byte BIT_UV = 2;
|
||||
public static final byte BIT_BE = 3;
|
||||
public static final byte BIT_BS = 4;
|
||||
public static final byte BIT_AT = 6;
|
||||
public static final byte BIT_ED = 7;
|
||||
|
||||
private final CollectedClientDataConverter collectedClientDataConverter;
|
||||
|
||||
Util(MontoyaApi api) {
|
||||
this.api = api;
|
||||
objectConverter = new ObjectConverter();
|
||||
attestationObjectConverter = new AttestationObjectConverter(objectConverter);
|
||||
authenticatorDataConverter = new AuthenticatorDataConverter(objectConverter);
|
||||
attestedCredentialDataConverter = new AttestedCredentialDataConverter(objectConverter);
|
||||
collectedClientDataConverter = new CollectedClientDataConverter(objectConverter);
|
||||
gsonPrettyPrinting = new GsonBuilder().setPrettyPrinting().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
||||
gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
||||
|
||||
}
|
||||
|
||||
public static KeyPair createEdDSAKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519");
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
public void logPrettyJson(String text, Map<String, Object> jsonObject) {
|
||||
try {
|
||||
api.logging().logToOutput("\n" + text + gsonPrettyPrinting.toJson(jsonObject));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Error logPrettyJson: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] hexStringToByteArray(String hex) {
|
||||
if (hex == null) {
|
||||
return null;
|
||||
}
|
||||
if (hex.length() % 2 != 0) {
|
||||
throw new IllegalArgumentException("Invalid hex string");
|
||||
}
|
||||
int length = hex.length();
|
||||
byte[] bytes = new byte[length / 2];
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
|
||||
+ Character.digit(hex.charAt(i + 1), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public Map<String, Object> COSEKeyObjectToJson(COSEKey coseKey) {
|
||||
Map<String, Object> coseKeyJson = new HashMap<>();
|
||||
api.logging().logToOutput("coseKey: " + coseKey);
|
||||
|
||||
coseKeyJson.put("keyId", coseKey.getKeyId());
|
||||
coseKeyJson.put("algorithm", coseKey.getAlgorithm().toString());
|
||||
coseKeyJson.put("keyOps", coseKey.getKeyOps());
|
||||
api.logging().logToOutput("getKeyOps: " + coseKey.getKeyOps());
|
||||
|
||||
// https://www.iana.org/assignments/cose/cose.xhtml#key-type
|
||||
if (coseKey instanceof EdDSACOSEKey) {
|
||||
coseKeyJson.put("keyType", "OKP");
|
||||
coseKeyJson.put("curve", ((EdDSACOSEKey) coseKey).getCurve());
|
||||
coseKeyJson.put("x", ArrayUtil.toHexString(((EdDSACOSEKey) coseKey).getX()));
|
||||
coseKeyJson.put("d", ArrayUtil.toHexString(((EdDSACOSEKey) coseKey).getD()));
|
||||
} else if (coseKey instanceof EC2COSEKey) {
|
||||
coseKeyJson.put("keyType", "EC2");
|
||||
coseKeyJson.put("curve", ((EC2COSEKey) coseKey).getCurve());
|
||||
coseKeyJson.put("x", ArrayUtil.toHexString(((EC2COSEKey) coseKey).getX()));
|
||||
coseKeyJson.put("y", ArrayUtil.toHexString(((EC2COSEKey) coseKey).getY()));
|
||||
coseKeyJson.put("d", ArrayUtil.toHexString(((EC2COSEKey) coseKey).getD()));
|
||||
} else if (coseKey instanceof RSACOSEKey) {
|
||||
coseKeyJson.put("keyType", "RSA");
|
||||
coseKeyJson.put("n", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getN()));
|
||||
coseKeyJson.put("e", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getE()));
|
||||
coseKeyJson.put("d", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getD()));
|
||||
coseKeyJson.put("p", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getP()));
|
||||
coseKeyJson.put("q", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getQ()));
|
||||
coseKeyJson.put("dP", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getDP()));
|
||||
coseKeyJson.put("dQ", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getDQ()));
|
||||
coseKeyJson.put("qInv", ArrayUtil.toHexString(((RSACOSEKey) coseKey).getQInv()));
|
||||
}
|
||||
return coseKeyJson;
|
||||
}
|
||||
|
||||
public COSEKey COSEKeyJsonToObject(Map<String, Object> coseKeyJson) {
|
||||
api.logging().logToOutput("keyId: " + coseKeyJson.get("keyId"));
|
||||
byte[] keyId = hexStringToByteArray((String) coseKeyJson.get("keyId"));
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 5");
|
||||
String algorithmString = (String) coseKeyJson.get("algorithm");
|
||||
|
||||
// https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
||||
/*if (Objects.equals(algorithmString, "RS256")) {
|
||||
algorithm = COSEAlgorithmIdentifier.RS256;
|
||||
} else if (Objects.equals(algorithmString, "ES256")) {
|
||||
algorithm = COSEAlgorithmIdentifier.ES256;
|
||||
} else if (Objects.equals(algorithmString, "RS1")) {
|
||||
algorithm = COSEAlgorithmIdentifier.RS1;
|
||||
} else if (Objects.equals(algorithmString, "EdDSA")) {
|
||||
algorithm = COSEAlgorithmIdentifier.EdDSA;
|
||||
} else if (Objects.equals(algorithmString, "RS384")) {
|
||||
algorithm = COSEAlgorithmIdentifier.RS384;
|
||||
} else if (Objects.equals(algorithmString, "RS512")) {
|
||||
algorithm = COSEAlgorithmIdentifier.RS512;
|
||||
} else if (Objects.equals(algorithmString, "ES384")) {
|
||||
algorithm = COSEAlgorithmIdentifier.ES384;
|
||||
} else if (Objects.equals(algorithmString, "ES512")) {
|
||||
algorithm = COSEAlgorithmIdentifier.ES512;
|
||||
}*/
|
||||
COSEAlgorithmIdentifier algorithm = null;
|
||||
if (algorithmString != null) {
|
||||
algorithm = switch (algorithmString) {
|
||||
case "RS256" -> COSEAlgorithmIdentifier.RS256;
|
||||
case "ES256" -> COSEAlgorithmIdentifier.ES256;
|
||||
case "RS1" -> COSEAlgorithmIdentifier.RS1;
|
||||
case "EdDSA" -> COSEAlgorithmIdentifier.EdDSA;
|
||||
case "RS384" -> COSEAlgorithmIdentifier.RS384;
|
||||
case "RS512" -> COSEAlgorithmIdentifier.RS512;
|
||||
case "ES384" -> COSEAlgorithmIdentifier.ES384;
|
||||
case "ES512" -> COSEAlgorithmIdentifier.ES512;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
List<COSEKeyOperation> keyOps = (List<COSEKeyOperation>) coseKeyJson.get("keyOps");
|
||||
|
||||
COSEKey coseKey = null;
|
||||
Curve curve = null;
|
||||
byte[] x = new byte[0];
|
||||
byte[] d;
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 6");
|
||||
api.logging().logToOutput("algorithm: " + algorithm);
|
||||
|
||||
// https://www.iana.org/assignments/cose/cose.xhtml#key-type
|
||||
//if (coseKeyJson.get("n") != null) {
|
||||
if (Objects.equals(coseKeyJson.get("keyType"), "RSA")) {
|
||||
byte[] n = hexStringToByteArray((String) coseKeyJson.get("n"));
|
||||
byte[] e = hexStringToByteArray((String) coseKeyJson.get("e"));
|
||||
d = hexStringToByteArray((String) coseKeyJson.get("d"));
|
||||
byte[] p = hexStringToByteArray((String) coseKeyJson.get("p"));
|
||||
byte[] q = hexStringToByteArray((String) coseKeyJson.get("q"));
|
||||
byte[] dP = hexStringToByteArray((String) coseKeyJson.get("dP"));
|
||||
byte[] dQ = hexStringToByteArray((String) coseKeyJson.get("dQ"));
|
||||
byte[] qInv = hexStringToByteArray((String) coseKeyJson.get("qInv"));
|
||||
coseKey = new RSACOSEKey(keyId, algorithm, keyOps, n, e, d, p, q, dP, dQ, qInv);
|
||||
} else {
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 66");
|
||||
//curve = (Curve) coseKeyJson.get("curve");
|
||||
|
||||
String curveString = (String) coseKeyJson.get("curve");
|
||||
if (Objects.equals(curveString, "SECP256R1")) {
|
||||
curve = Curve.SECP256R1;
|
||||
} else if (Objects.equals(curveString, "SECP384R1")) {
|
||||
curve = Curve.SECP384R1;
|
||||
} else if (Objects.equals(curveString, "SECP521R1")) {
|
||||
curve = Curve.SECP521R1;
|
||||
} else if (Objects.equals(curveString, "ED25519")) {
|
||||
curve = Curve.ED25519;
|
||||
}
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 666");
|
||||
x = hexStringToByteArray((String) coseKeyJson.get("x"));
|
||||
d = hexStringToByteArray((String) coseKeyJson.get("d"));
|
||||
}
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 7");
|
||||
|
||||
if (Objects.equals(coseKeyJson.get("keyType"), "EC2")) {
|
||||
byte[] y = hexStringToByteArray((String) coseKeyJson.get("y"));
|
||||
coseKey = new EC2COSEKey(keyId, algorithm, keyOps, curve, x, y, d);
|
||||
} else if (Objects.equals(coseKeyJson.get("keyType"), "OKP")) {
|
||||
coseKey = new EdDSACOSEKey(keyId, algorithm, keyOps, curve, x, d);
|
||||
}
|
||||
return coseKey;
|
||||
}
|
||||
|
||||
public String calculateSignature(COSEKey coseKey, byte[] data) {
|
||||
try {
|
||||
//coseKey.getAlgorithm()
|
||||
//COSEAlgorithmIdentifier
|
||||
Signature signature = null;
|
||||
if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.RS256)) {
|
||||
signature = Signature.getInstance("SHA256withRSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.ES256)) {
|
||||
signature = Signature.getInstance("SHA256withECDSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.RS1)) {
|
||||
signature = Signature.getInstance("SHA1withRSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.EdDSA)) {
|
||||
signature = Signature.getInstance("ed25519");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.RS384)) {
|
||||
signature = Signature.getInstance("SHA384withRSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.RS512)) {
|
||||
signature = Signature.getInstance("SHA512withRSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.ES384)) {
|
||||
signature = Signature.getInstance("SHA384withECDSA");
|
||||
} else if (Objects.equals(coseKey.getAlgorithm(), COSEAlgorithmIdentifier.ES512)) {
|
||||
signature = Signature.getInstance("SHA512withECDSA");
|
||||
}
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx signature.getAlgorithm()");
|
||||
api.logging().logToOutput(signature.getAlgorithm());
|
||||
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx coseKey.getPrivateKey()");
|
||||
api.logging().logToOutput(coseKey.getPrivateKey().toString());
|
||||
|
||||
|
||||
signature.initSign(coseKey.getPrivateKey());
|
||||
signature.update(data);
|
||||
|
||||
return Base64UrlUtil.encodeToString(signature.sign());
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Signature calculation error: " + e.getMessage());
|
||||
for (StackTraceElement element : e.getStackTrace()) {
|
||||
api.logging().logToOutput("\tat " + element);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends ExtensionAuthenticatorOutput> Map<String, Object> decodeAuthenticatorData(AuthenticatorData<T> authenticatorData) {
|
||||
String rpIdHash = ArrayUtil.toHexString(authenticatorData.getRpIdHash());
|
||||
Map<String, Object> flagsJson = new HashMap<>();
|
||||
flagsJson.put("userPresent", authenticatorData.isFlagUP());
|
||||
flagsJson.put("userVerified", authenticatorData.isFlagUV());
|
||||
flagsJson.put("attestedCredentialData", authenticatorData.isFlagAT());
|
||||
flagsJson.put("extensionDataIncluded", authenticatorData.isFlagED());
|
||||
long signCount = ((Number) authenticatorData.getSignCount()).longValue();
|
||||
//long signCount = authenticatorData.getSignCount();
|
||||
|
||||
AttestedCredentialData attestedCredentialData = authenticatorData.getAttestedCredentialData();
|
||||
Map<String, Object> attestedCredentialDataJson = new HashMap<>();
|
||||
if (attestedCredentialData != null) {
|
||||
AAGUID aaguid = attestedCredentialData.getAaguid();
|
||||
|
||||
String credentialId = ArrayUtil.toHexString(attestedCredentialData.getCredentialId());
|
||||
|
||||
COSEKey coseKey = attestedCredentialData.getCOSEKey();
|
||||
Map<String, Object> coseKeyJson = COSEKeyObjectToJson(coseKey);
|
||||
|
||||
attestedCredentialDataJson.put("aaguid", aaguid.toString());
|
||||
attestedCredentialDataJson.put("credentialId", credentialId);
|
||||
attestedCredentialDataJson.put("coseKey", coseKeyJson);
|
||||
}
|
||||
|
||||
AuthenticationExtensionsAuthenticatorOutputs<T> extensions = authenticatorData.getExtensions();
|
||||
Map<String, Object> extensionsJson = new HashMap<>();
|
||||
if (extensions != null) {
|
||||
UvmEntries uvm = extensions.getUvm();
|
||||
CredentialProtectionPolicy credProtect = extensions.getCredProtect();
|
||||
Object HMACSecret = extensions.getHMACSecret();
|
||||
extensionsJson.put("uvm", uvm);
|
||||
extensionsJson.put("credProtect", credProtect);
|
||||
extensionsJson.put("HMACSecret", HMACSecret);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webauthn-1/#sec-authenticator-data
|
||||
// https://www.w3.org/TR/webauthn-1/#fig-authData
|
||||
Map<String, Object> authenticatorDataJson = new HashMap<>();
|
||||
authenticatorDataJson.put("rpIdHash", rpIdHash);
|
||||
authenticatorDataJson.put("flags", flagsJson);
|
||||
authenticatorDataJson.put("signCount", signCount);
|
||||
authenticatorDataJson.put("attestedCredentialData", attestedCredentialDataJson);
|
||||
authenticatorDataJson.put("extensions", extensionsJson);
|
||||
|
||||
return authenticatorDataJson;
|
||||
}
|
||||
|
||||
public Map<String, Object> decodeAttestationObject(String attestationObjectB64) {
|
||||
try {
|
||||
api.logging().logToOutput("\n============= decodeAttestationObject =============");
|
||||
//byte[] attestationObjectBytes = java.util.Base64.getUrlDecoder().decode(attestationObjectB64 + "==");
|
||||
//byte[] attestationObjectBytes = Base64UrlUtil.decode(attestationObjectB64 + "==");
|
||||
|
||||
|
||||
// Use WebAuthn4J to parse the attestation object
|
||||
/*byte[] authenticatorDataBytes = attestationObjectConverter.extractAuthenticatorData(attestationObjectBytes);
|
||||
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> authenticatorData = authenticatorDataConverter.convert(authenticatorDataBytes);
|
||||
api.logging().logToOutput("Authenticator Data: " + authenticatorData);
|
||||
|
||||
byte[] attestedCredentialDataBytes = authenticatorDataConverter.extractAttestedCredentialData(authenticatorDataBytes);
|
||||
AttestedCredentialData attestedCredentialData = attestedCredentialDataConverter.convert(attestedCredentialDataBytes);
|
||||
api.logging().logToOutput("Attested Credential Data: " + attestedCredentialData);
|
||||
|
||||
byte[] attestationStatementBytes = attestationObjectConverter.extractAttestationStatement(attestationObjectBytes);
|
||||
|
||||
AndroidKeyAttestationStatement attestationStatement = objectConverter.getCborConverter().readValue(attestationStatementBytes, AndroidKeyAttestationStatement.class);
|
||||
api.logging().logToOutput("AndroidKey Attestation Statement: " + attestationStatement.toString());
|
||||
|
||||
long counter = authenticatorData.getSignCount();*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AttestationObject attestationObject = attestationObjectConverter.convert(attestationObjectB64);
|
||||
if (attestationObject != null) {
|
||||
//api.logging().logToOutput(attestationObject.toString());
|
||||
|
||||
// ----------------------- authenticatorData
|
||||
AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authenticatorData = attestationObject.getAuthenticatorData();
|
||||
Map<String, Object> authenticatorDataJson = decodeAuthenticatorData(authenticatorData);
|
||||
|
||||
|
||||
|
||||
// ----------------------- attestationStatement
|
||||
AttestationStatement attestationStatement = attestationObject.getAttestationStatement();
|
||||
String attestationStatementFormat = attestationStatement.getFormat();
|
||||
Map<String, Object> attestationStatementJson = new HashMap<>();
|
||||
attestationStatementJson.put("format", attestationStatementFormat);
|
||||
// api.logging().logToOutput("attestationStatement: " + attestationStatement);
|
||||
|
||||
AttestationCertificatePath x5c = null;
|
||||
if (attestationStatement instanceof AndroidKeyAttestationStatement) {
|
||||
attestationStatementJson.put("alg", ((AndroidKeyAttestationStatement) attestationStatement).getAlg().toString());
|
||||
attestationStatementJson.put("sig", ArrayUtil.toHexString(((AndroidKeyAttestationStatement) attestationStatement).getSig()));
|
||||
x5c = ((AndroidKeyAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("x5cString", ((AndroidKeyAttestationStatement) attestationStatement).getX5c().toString());
|
||||
} else if (attestationStatement instanceof AndroidSafetyNetAttestationStatement) {
|
||||
x5c = ((AndroidSafetyNetAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("x5cString", ((AndroidSafetyNetAttestationStatement) attestationStatement).getX5c().toString());
|
||||
attestationStatementJson.put("ver", ((AndroidSafetyNetAttestationStatement) attestationStatement).getVer());
|
||||
|
||||
Map<String, Object> responseJson = new HashMap<>();
|
||||
Response response = ((AndroidSafetyNetAttestationStatement) attestationStatement).getResponse().getPayload();
|
||||
responseJson.put("nonce", response.getNonce());
|
||||
responseJson.put("timestampMs", response.getTimestampMs());
|
||||
responseJson.put("apkPackageName", response.getApkPackageName());
|
||||
responseJson.put("apkCertificateDigestSha256", response.getApkCertificateDigestSha256());
|
||||
responseJson.put("apkDigestSha256", response.getApkDigestSha256());
|
||||
responseJson.put("ctsProfileMatch", response.getCtsProfileMatch());
|
||||
responseJson.put("basicIntegrity", response.getBasicIntegrity());
|
||||
responseJson.put("advice", response.getAdvice());
|
||||
responseJson.put("error", response.getError());
|
||||
|
||||
// attestationStatementJson.put("response", ((AndroidSafetyNetAttestationStatement) attestationStatement).getResponse().toString());
|
||||
attestationStatementJson.put("response", responseJson);
|
||||
} else if (attestationStatement instanceof AppleAnonymousAttestationStatement) {
|
||||
x5c = ((AppleAnonymousAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("x5cString", ((AppleAnonymousAttestationStatement) attestationStatement).getX5c().toString());
|
||||
} else if (attestationStatement instanceof FIDOU2FAttestationStatement) {
|
||||
x5c = ((FIDOU2FAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("sig", ArrayUtil.toHexString(((FIDOU2FAttestationStatement) attestationStatement).getSig()));
|
||||
attestationStatementJson.put("x5cString", ((FIDOU2FAttestationStatement) attestationStatement).getX5c().toString());
|
||||
} else if (attestationStatement instanceof PackedAttestationStatement) {
|
||||
x5c = ((PackedAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("alg", ((PackedAttestationStatement) attestationStatement).getAlg().toString());
|
||||
attestationStatementJson.put("sig", ArrayUtil.toHexString(((PackedAttestationStatement) attestationStatement).getSig()));
|
||||
attestationStatementJson.put("x5cString", ((PackedAttestationStatement) attestationStatement).getX5c().toString());
|
||||
} else if (attestationStatement instanceof TPMAttestationStatement) {
|
||||
x5c = ((TPMAttestationStatement) attestationStatement).getX5c();
|
||||
attestationStatementJson.put("ver", ((TPMAttestationStatement) attestationStatement).getVer());
|
||||
attestationStatementJson.put("alg", ((TPMAttestationStatement) attestationStatement).getAlg().toString());
|
||||
attestationStatementJson.put("x5cString", ((TPMAttestationStatement) attestationStatement).getX5c().toString());
|
||||
attestationStatementJson.put("sig", ArrayUtil.toHexString(((TPMAttestationStatement) attestationStatement).getSig()));
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(TPMSAttest.class, new TPMSAttestSerializer());
|
||||
//module.addDeserializer(TPMSAttest.class, new TPMSAttestDeserializer());
|
||||
module.addSerializer(TPMTPublic.class, new TPMTPublicSerializer());
|
||||
//module.addDeserializer(TPMTPublic.class, new TPMTPublicDeserializer());
|
||||
objectMapper.registerModule(module);
|
||||
|
||||
// ----- certInfo
|
||||
TPMSAttest certInfo = ((TPMAttestationStatement) attestationStatement).getCertInfo();
|
||||
String serializedCertInfo = objectMapper.writeValueAsString(certInfo);
|
||||
|
||||
/*api.logging().logToOutput("certInfo: " + certInfo);
|
||||
api.logging().logToOutput("serializedCertInfo: " + serializedCertInfo);
|
||||
TPMSAttest deserializedCertInfo = objectMapper.readValue(serializedCertInfo, TPMSAttest.class);
|
||||
api.logging().logToOutput("deserializedCertInfo: " + deserializedCertInfo);
|
||||
api.logging().logToOutput("certInfo == deserializedCertInfo: " + deserializedCertInfo.equals(certInfo));*/
|
||||
|
||||
// attestationStatementJson.put("certInfo", ((TPMAttestationStatement) attestationStatement).getCertInfo());
|
||||
attestationStatementJson.put("certInfo", serializedCertInfo);
|
||||
|
||||
// ----- pubArea
|
||||
TPMTPublic pubArea = ((TPMAttestationStatement) attestationStatement).getPubArea();
|
||||
String serializedPubArea = objectMapper.writeValueAsString(pubArea);
|
||||
|
||||
/*api.logging().logToOutput("pubArea: " + pubArea);
|
||||
api.logging().logToOutput("serializedPubArea: " + serializedPubArea);
|
||||
TPMTPublic deserializedPubArea = objectMapper.readValue(serializedPubArea, TPMTPublic.class);
|
||||
api.logging().logToOutput("deserializedPubArea: " + deserializedPubArea);
|
||||
api.logging().logToOutput("pubArea == deserializedPubArea: " + deserializedPubArea.equals(pubArea));*/
|
||||
|
||||
//attestationStatementJson.put("pubArea", ((TPMAttestationStatement) attestationStatement).getPubArea());
|
||||
attestationStatementJson.put("pubArea", serializedPubArea);
|
||||
}
|
||||
if (!(attestationStatement instanceof NoneAttestationStatement)) {
|
||||
// List<X509Certificate> certificates = new ArrayList<>(); // java.lang.reflect.InaccessibleObjectException: Unable to make field private byte[] sun.security.x509.X509CertImpl.signedCert accessible: module java.base does not "opens sun.security.x509" to unnamed module @3b4a1a5f
|
||||
|
||||
List<String> certificates = new ArrayList<>();
|
||||
int x5c_size = x5c.size();
|
||||
for (int i = 0; i < x5c_size; i++) {
|
||||
// certificates.add(x5c.get(i));
|
||||
certificates.add(ArrayUtil.toHexString(x5c.get(i).getEncoded()));
|
||||
}
|
||||
attestationStatementJson.put("x5c", certificates);
|
||||
}
|
||||
|
||||
Map<String, Object> attestationObjectJson = new HashMap<>();
|
||||
attestationObjectJson.put("authenticatorData", authenticatorDataJson);
|
||||
attestationObjectJson.put("attestationStatement", attestationStatementJson);
|
||||
attestationObjectJson.put("fmt", attestationObject.getFormat());
|
||||
|
||||
return attestationObjectJson;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*api.logging().logToOutput("Format: " + attestationObject.getFormat());
|
||||
api.logging().logToOutput("Authenticator Data: " + attestationObject.getAuthenticatorData());
|
||||
api.logging().logToOutput("Attestation Statement: " + attestationObject.getAttestationStatement());
|
||||
|
||||
byte[] attestationObjectBytes_2 = attestationObjectConverter.convertToBytes(attestationObject);
|
||||
AttestationObject attestationObject_2 = attestationObjectConverter.convert(attestationObjectBytes_2);
|
||||
api.logging().logToOutput("\nFormat: " + attestationObject_2.getFormat());
|
||||
api.logging().logToOutput("Authenticator Data: " + attestationObject_2.getAuthenticatorData());
|
||||
api.logging().logToOutput("Attestation Statement: " + attestationObject_2.getAttestationStatement());*/
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Error decoding attestation object: " + e.getMessage());
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
public AuthenticatorData encodeAuthenticatorData(Map<String, Object> authenticatorDataJson) {
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 1");
|
||||
|
||||
String rpIdHashString = (String) authenticatorDataJson.get("rpIdHash");
|
||||
byte[] rpIdHash = hexStringToByteArray(rpIdHashString);
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 2");
|
||||
|
||||
Map<String, Object> flagsJson = (Map<String, Object>) authenticatorDataJson.get("flags");
|
||||
|
||||
//api.logging().logToOutput("flagsJson: " + flagsJson);
|
||||
|
||||
byte flags = 0;
|
||||
if ((boolean) flagsJson.getOrDefault("userPresent", false)) {
|
||||
flags = (byte) (flags | (1 << BIT_UP));
|
||||
}
|
||||
if ((boolean) flagsJson.getOrDefault("userVerified", false)) {
|
||||
flags = (byte) (flags | (1 << BIT_UV));
|
||||
}
|
||||
if ((boolean) flagsJson.getOrDefault("attestedCredentialData", false)) {
|
||||
flags = (byte) (flags | (1 << BIT_AT));
|
||||
}
|
||||
if ((boolean) flagsJson.getOrDefault("extensionDataIncluded", false)) {
|
||||
flags = (byte) (flags | (1 << BIT_ED));
|
||||
}
|
||||
/*api.logging().logToOutput("flags: " + flags);
|
||||
api.logging().logToOutput("flags: " + String.format("%02X", flags));
|
||||
api.logging().logToOutput("flags: " + String.format("%8s", Integer.toBinaryString(flags & 0xFF)).replace(' ', '0'));*/
|
||||
|
||||
|
||||
//long signCount = (long) authenticatorDataJson.get("signCount");
|
||||
long signCount = ((Number) authenticatorDataJson.get("signCount")).longValue();
|
||||
|
||||
|
||||
// ----------------------- attestedCredentialData
|
||||
Map<String, Object> attestedCredentialDataJson = (Map<String, Object>) authenticatorDataJson.get("attestedCredentialData");
|
||||
AttestedCredentialData attestedCredentialData = null;
|
||||
if (attestedCredentialDataJson != null && !attestedCredentialDataJson.isEmpty()) {
|
||||
AAGUID aaguid = new AAGUID((String) attestedCredentialDataJson.get("aaguid"));
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 3");
|
||||
byte[] credentialId = hexStringToByteArray((String) attestedCredentialDataJson.get("credentialId"));
|
||||
api.logging().logToOutput("xxxxxxxxxxxxxxx 4");
|
||||
|
||||
Map<String, Object> coseKeyJson = (Map<String, Object>) attestedCredentialDataJson.get("coseKey");
|
||||
COSEKey coseKey = COSEKeyJsonToObject(coseKeyJson);
|
||||
|
||||
attestedCredentialData = new AttestedCredentialData(aaguid, credentialId, coseKey);
|
||||
}
|
||||
|
||||
// ----------------------- extensions
|
||||
Map<String, Object> extensionsJson = (Map<String, Object>) authenticatorDataJson.get("extensions");
|
||||
AuthenticationExtensionsAuthenticatorOutputs extensions = null;
|
||||
if (extensionsJson != null && !extensionsJson.isEmpty()) {
|
||||
AuthenticationExtensionsAuthenticatorOutputs.BuilderForRegistration builder = new AuthenticationExtensionsAuthenticatorOutputs.BuilderForRegistration();
|
||||
UvmEntries uvm = (UvmEntries) extensionsJson.get("uvm");
|
||||
CredentialProtectionPolicy credProtect = (CredentialProtectionPolicy) extensionsJson.get("credProtect");
|
||||
Boolean HMACSecret = (Boolean) extensionsJson.get("HMACSecret");
|
||||
builder.setUvm(uvm);
|
||||
builder.setCredProtect(credProtect);
|
||||
builder.setHMACCreateSecret(HMACSecret);
|
||||
extensions = builder.build();
|
||||
}
|
||||
|
||||
return new AuthenticatorData(rpIdHash, flags, signCount, attestedCredentialData, extensions);
|
||||
}
|
||||
|
||||
public String encodeAttestationObject(Map<String, Object> attestationObjectJson) {
|
||||
try {
|
||||
api.logging().logToOutput("\n============= encodeAttestationObject =============");
|
||||
|
||||
// ----------------------- authenticatorData
|
||||
api.logging().logToOutput("\nxxxxxxxxxxxxxxx encodeAttestationObject 1");
|
||||
|
||||
Map<String, Object> authenticatorDataJson = (Map<String, Object>) attestationObjectJson.get("authenticatorData");
|
||||
AuthenticatorData authenticatorData = encodeAuthenticatorData(authenticatorDataJson);
|
||||
api.logging().logToOutput("\nxxxxxxxxxxxxxxx encodeAttestationObject 2");
|
||||
api.logging().logToOutput(String.valueOf(authenticatorData));
|
||||
|
||||
|
||||
|
||||
// ----------------------- attestationStatement
|
||||
Map<String, Object> attestationStatementJson = (Map<String, Object>) attestationObjectJson.get("attestationStatement");
|
||||
|
||||
String format = (String) attestationStatementJson.get("format");
|
||||
String algString = (String) attestationStatementJson.get("alg");
|
||||
COSEAlgorithmIdentifier alg = null;
|
||||
if (algString != null) {
|
||||
alg = switch (algString) {
|
||||
case "RS256" -> COSEAlgorithmIdentifier.RS256;
|
||||
case "ES256" -> COSEAlgorithmIdentifier.ES256;
|
||||
case "RS1" -> COSEAlgorithmIdentifier.RS1;
|
||||
case "EdDSA" -> COSEAlgorithmIdentifier.EdDSA;
|
||||
case "RS384" -> COSEAlgorithmIdentifier.RS384;
|
||||
case "RS512" -> COSEAlgorithmIdentifier.RS512;
|
||||
case "ES384" -> COSEAlgorithmIdentifier.ES384;
|
||||
case "ES512" -> COSEAlgorithmIdentifier.ES512;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
AttestationCertificatePath x5c = null;
|
||||
if (!Objects.equals(format, "none")) {
|
||||
List<String> certificateStrings = (List<String>) attestationStatementJson.get("x5c");
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
for (String certificateString : certificateStrings) {
|
||||
byte[] encodedCertificate = hexStringToByteArray(certificateString);
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
certificates.add((X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCertificate)));
|
||||
}
|
||||
x5c = new AttestationCertificatePath(certificates);
|
||||
}
|
||||
|
||||
AttestationStatement attestationStatement = null;
|
||||
if (Objects.equals(format, "none")) {
|
||||
attestationStatement = new NoneAttestationStatement();
|
||||
} else if (Objects.equals(format, "android-key")) {
|
||||
byte[] sig = hexStringToByteArray((String) attestationStatementJson.get("sig"));
|
||||
attestationStatement = new AndroidKeyAttestationStatement(alg, sig, x5c);
|
||||
|
||||
} else if (Objects.equals(format, "android-safetynet")) {
|
||||
String ver = (String) attestationStatementJson.get("ver");
|
||||
Map<String, Object> responseJson = (Map<String, Object>) attestationStatementJson.get("response");
|
||||
|
||||
String nonce = (String) responseJson.get("nonce");
|
||||
Long timestampMs = (Long) responseJson.get("timestampMs");
|
||||
String apkPackageName = (String) responseJson.get("apkPackageName");
|
||||
String[] apkCertificateDigestSha256 = (String[]) responseJson.get("apkCertificateDigestSha256");
|
||||
String apkDigestSha256 = (String) responseJson.get("apkDigestSha256");
|
||||
Boolean ctsProfileMatch = (Boolean) responseJson.get("ctsProfileMatch");
|
||||
Boolean basicIntegrity = (Boolean) responseJson.get("basicIntegrity");
|
||||
String advice = (String) responseJson.get("advice");
|
||||
String error = (String) responseJson.get("error");
|
||||
|
||||
Response responseObject = new Response(nonce, timestampMs, apkPackageName, apkCertificateDigestSha256, apkDigestSha256, ctsProfileMatch, basicIntegrity, advice, error);
|
||||
JWS<Response> response = new JWSFactory().create(new JWSHeader(JWAIdentifier.ES256, x5c.createCertPath()), responseObject, new byte[32]);
|
||||
|
||||
attestationStatement = new AndroidSafetyNetAttestationStatement(ver, response);
|
||||
|
||||
} else if (Objects.equals(format, "apple")) {
|
||||
attestationStatement = new AppleAnonymousAttestationStatement(x5c);
|
||||
|
||||
} else if (Objects.equals(format, "fido-u2f")) {
|
||||
byte[] sig = hexStringToByteArray((String) attestationStatementJson.get("sig"));
|
||||
attestationStatement = new FIDOU2FAttestationStatement(x5c, sig);
|
||||
|
||||
} else if (Objects.equals(format, "packed")) {
|
||||
byte[] sig = hexStringToByteArray((String) attestationStatementJson.get("sig"));
|
||||
attestationStatement = new PackedAttestationStatement(alg, sig, x5c);
|
||||
|
||||
} else if (Objects.equals(format, "tpm")) {
|
||||
String ver = (String) attestationStatementJson.get("ver");
|
||||
byte[] sig = hexStringToByteArray((String) attestationStatementJson.get("sig"));
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
//module.addSerializer(TPMSAttest.class, new TPMSAttestSerializer());
|
||||
module.addDeserializer(TPMSAttest.class, new TPMSAttestDeserializer());
|
||||
//module.addSerializer(TPMTPublic.class, new TPMTPublicSerializer());
|
||||
module.addDeserializer(TPMTPublic.class, new TPMTPublicDeserializer());
|
||||
objectMapper.registerModule(module);
|
||||
|
||||
String serializedCertInfo = (String) attestationStatementJson.get("certInfo");
|
||||
TPMSAttest certInfo = objectMapper.readValue(serializedCertInfo, TPMSAttest.class);
|
||||
|
||||
String serializedPubArea = (String) attestationStatementJson.get("pubArea");
|
||||
TPMTPublic pubArea = objectMapper.readValue(serializedPubArea, TPMTPublic.class);
|
||||
|
||||
attestationStatement = new TPMAttestationStatement(ver, alg, x5c, sig, certInfo, pubArea);
|
||||
}
|
||||
|
||||
|
||||
|
||||
AttestationObject attestationObject = new AttestationObject((AuthenticatorData<RegistrationExtensionAuthenticatorOutput>) authenticatorData, attestationStatement);
|
||||
return attestationObjectConverter.convertToBase64urlString(attestationObject);
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Error encoding attestation object: " + e.getMessage());
|
||||
for (StackTraceElement element : e.getStackTrace()) {
|
||||
api.logging().logToOutput("\tat " + element);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public Map<String, Object> decodeClientDataJSON(String clientDataJSONB64) {
|
||||
try {
|
||||
api.logging().logToOutput("\n============= decodeClientDataJSON =============");
|
||||
/*
|
||||
byte[] clientDataJSONBytes = Base64UrlUtil.decode(clientDataJSONB64 + "==");
|
||||
CollectedClientData collectedClientData = clientDataJSONBytes == null ? null : collectedClientDataConverter.convert(clientDataJSONBytes);
|
||||
Map<String, Object> collectedClientson = new HashMap<>();
|
||||
collectedClientson.put("type", collectedClientData.getType());
|
||||
collectedClientson.put("challenge", collectedClientData.getChallenge());
|
||||
collectedClientson.put("origin", collectedClientData.getOrigin());
|
||||
collectedClientson.put("crossOrigin", collectedClientData.getCrossOrigin());
|
||||
collectedClientson.put("tokenBinding", collectedClientData.getTokenBinding());
|
||||
return collectedClientson;*/
|
||||
|
||||
String clientDataJSONString = new String(Base64UrlUtil.decode(clientDataJSONB64));
|
||||
//Gson gson = new Gson();
|
||||
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
return gsonPrettyPrinting.fromJson(clientDataJSONString, mapType);
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Error decoding ClientDataJSON: " + e.getMessage());
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
public String encodeClientDataJSON(Map<String, Object> clientData) {
|
||||
try {
|
||||
api.logging().logToOutput("\n============= encodeClientDataJSON =============");
|
||||
//Gson gson = new Gson();
|
||||
String jsonString = gson.toJson(clientData);
|
||||
return new String(Base64UrlUtil.encode(jsonString.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("Error encoding ClientDataJSON: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user