init code

This commit is contained in:
Peeranat Thantaletong
2025-01-13 13:48:31 +07:00
parent 2e26c32c18
commit 22c8f32958
13 changed files with 2613 additions and 0 deletions
+45
View File
@@ -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
+36
View File
@@ -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
}
Vendored
+234
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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());
}
}
+378
View File
@@ -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>
+442
View File
@@ -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;
}
}
+712
View File
@@ -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;
}
}