mirror of
https://github.com/wgtunnel/desktop.git
synced 2026-06-02 00:29:09 +02:00
fix: parser duplicate keys, key rotation, name comment
This commit is contained in:
@@ -1087,9 +1087,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Geir Magnusson Jr."
|
"name": "Geir Magnusson Jr."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Gary Gregory"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Stian Soiland-Reyes"
|
"name": "Stian Soiland-Reyes"
|
||||||
},
|
},
|
||||||
@@ -1114,6 +1111,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Yoav Shapira"
|
"name": "Yoav Shapira"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"organisationUrl": "https://www.apache.org/",
|
||||||
|
"name": "Gary Gregory"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Rob Tompkins"
|
"name": "Rob Tompkins"
|
||||||
},
|
},
|
||||||
@@ -1148,15 +1149,15 @@
|
|||||||
"name": "Benedikt Ritter"
|
"name": "Benedikt Ritter"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"artifactVersion": "1.9.4",
|
"artifactVersion": "1.11.0",
|
||||||
"description": "Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.",
|
"description": "Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.",
|
||||||
"scm": {
|
"scm": {
|
||||||
"connection": "scm:svn:http://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
"connection": "scm:git:https://gitbox.apache.org/repos/asf?p=commons-beanutils.git",
|
||||||
"url": "http://svn.apache.org/viewvc/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
"url": "https://gitbox.apache.org/repos/asf?p=commons-beanutils.git",
|
||||||
"developerConnection": "scm:svn:https://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3"
|
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf?p=commons-beanutils.git"
|
||||||
},
|
},
|
||||||
"name": "Apache Commons BeanUtils",
|
"name": "Apache Commons BeanUtils",
|
||||||
"website": "https://commons.apache.org/proper/commons-beanutils/",
|
"website": "https://commons.apache.org/proper/commons-beanutils",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
"Apache-2.0"
|
"Apache-2.0"
|
||||||
],
|
],
|
||||||
@@ -1326,8 +1327,8 @@
|
|||||||
"name": "Rodney Waldhoff"
|
"name": "Rodney Waldhoff"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"artifactVersion": "1.3.0",
|
"artifactVersion": "1.3.5",
|
||||||
"description": "Apache Commons Logging is a thin adapter allowing configurable bridging to other,\n well known logging systems.",
|
"description": "Apache Commons Logging is a thin adapter allowing configurable bridging to other,\n well-known logging systems.",
|
||||||
"scm": {
|
"scm": {
|
||||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-logging",
|
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-logging",
|
||||||
"url": "https://gitbox.apache.org/repos/asf/commons-logging",
|
"url": "https://gitbox.apache.org/repos/asf/commons-logging",
|
||||||
@@ -1399,15 +1400,15 @@
|
|||||||
"name": "Benedikt Ritter"
|
"name": "Benedikt Ritter"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"artifactVersion": "1.8.0",
|
"artifactVersion": "1.10.1",
|
||||||
"description": "Apache Commons Validator provides the building blocks for both client side validation and server side data validation.\n It may be used standalone or with a framework like Struts.",
|
"description": "Apache Commons Validator provides the building blocks for both client-side and server-side data validation.\n It may be used standalone or with a framework like Struts.",
|
||||||
"scm": {
|
"scm": {
|
||||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator",
|
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator.git/commons-validator",
|
||||||
"url": "https://gitbox.apache.org/repos/asf/commons-validator",
|
"url": "https://gitbox.apache.org/repos/asf?p=commons-validator.git/commons-validator",
|
||||||
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator"
|
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator.git/commons-validator"
|
||||||
},
|
},
|
||||||
"name": "Apache Commons Validator",
|
"name": "Apache Commons Validator",
|
||||||
"website": "http://commons.apache.org/proper/commons-validator/",
|
"website": "https://commons.apache.org/proper/commons-validator/",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
"Apache-2.0"
|
"Apache-2.0"
|
||||||
],
|
],
|
||||||
@@ -1460,90 +1461,6 @@
|
|||||||
"Apache-2.0"
|
"Apache-2.0"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uniqueId": "io.github.andreypfau:curve25519-kotlin",
|
|
||||||
"funding": [
|
|
||||||
|
|
||||||
],
|
|
||||||
"developers": [
|
|
||||||
{
|
|
||||||
"name": "Andrey Pfau"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"artifactVersion": "0.0.8",
|
|
||||||
"description": "A pure Kotlin/Multiplatform implementation of group operations on Curve25519.",
|
|
||||||
"scm": {
|
|
||||||
"url": "https://github.com/andreypfau/curve25519-kotlin"
|
|
||||||
},
|
|
||||||
"name": "curve25519-kotlin",
|
|
||||||
"website": "https://github.com/andreypfau/curve25519-kotlin",
|
|
||||||
"licenses": [
|
|
||||||
"Apache-2.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uniqueId": "io.github.andreypfau:kotlinx-crypto-digest",
|
|
||||||
"funding": [
|
|
||||||
|
|
||||||
],
|
|
||||||
"developers": [
|
|
||||||
{
|
|
||||||
"name": "Andrey Pfau"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"artifactVersion": "0.0.4",
|
|
||||||
"description": "A multiplatform Kotlin library providing basic cryptographic functions and primitives",
|
|
||||||
"scm": {
|
|
||||||
"url": "https://github.com/andreypfau/kotlinx-crypto"
|
|
||||||
},
|
|
||||||
"name": "kotlinx-crypto-digest",
|
|
||||||
"website": "https://github.com/andreypfau/kotlinx-crypto",
|
|
||||||
"licenses": [
|
|
||||||
"Apache-2.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uniqueId": "io.github.andreypfau:kotlinx-crypto-sha2",
|
|
||||||
"funding": [
|
|
||||||
|
|
||||||
],
|
|
||||||
"developers": [
|
|
||||||
{
|
|
||||||
"name": "Andrey Pfau"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"artifactVersion": "0.0.4",
|
|
||||||
"description": "A multiplatform Kotlin library providing basic cryptographic functions and primitives",
|
|
||||||
"scm": {
|
|
||||||
"url": "https://github.com/andreypfau/kotlinx-crypto"
|
|
||||||
},
|
|
||||||
"name": "kotlinx-crypto-sha2",
|
|
||||||
"website": "https://github.com/andreypfau/kotlinx-crypto",
|
|
||||||
"licenses": [
|
|
||||||
"Apache-2.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uniqueId": "io.github.andreypfau:kotlinx-crypto-subtle",
|
|
||||||
"funding": [
|
|
||||||
|
|
||||||
],
|
|
||||||
"developers": [
|
|
||||||
{
|
|
||||||
"name": "Andrey Pfau"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"artifactVersion": "0.0.4",
|
|
||||||
"description": "A multiplatform Kotlin library providing basic cryptographic functions and primitives",
|
|
||||||
"scm": {
|
|
||||||
"url": "https://github.com/andreypfau/kotlinx-crypto"
|
|
||||||
},
|
|
||||||
"name": "kotlinx-crypto-subtle",
|
|
||||||
"website": "https://github.com/andreypfau/kotlinx-crypto",
|
|
||||||
"licenses": [
|
|
||||||
"Apache-2.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uniqueId": "io.github.dokar3:sonner",
|
"uniqueId": "io.github.dokar3:sonner",
|
||||||
"funding": [
|
"funding": [
|
||||||
@@ -2583,6 +2500,27 @@
|
|||||||
"name": "The Apache Software Foundation"
|
"name": "The Apache Software Foundation"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"uniqueId": "org.bouncycastle:bcprov-jdk18on",
|
||||||
|
"funding": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"developers": [
|
||||||
|
{
|
||||||
|
"name": "The Legion of the Bouncy Castle Inc."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artifactVersion": "1.84",
|
||||||
|
"description": "The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains the JCA/JCE provider and low-level API for the BC Java version 1.84 for Java 1.8 and later.",
|
||||||
|
"scm": {
|
||||||
|
"url": "https://github.com/bcgit/bc-java"
|
||||||
|
},
|
||||||
|
"name": "Bouncy Castle Provider",
|
||||||
|
"website": "https://www.bouncycastle.org/download/bouncy-castle-java/",
|
||||||
|
"licenses": [
|
||||||
|
"73252b46f36df25ef51a7994de439aea"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"uniqueId": "org.checkerframework:checker-qual",
|
"uniqueId": "org.checkerframework:checker-qual",
|
||||||
"funding": [
|
"funding": [
|
||||||
@@ -5141,6 +5079,11 @@
|
|||||||
"url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
|
"url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
|
||||||
"name": "GNU Lesser General Public License"
|
"name": "GNU Lesser General Public License"
|
||||||
},
|
},
|
||||||
|
"73252b46f36df25ef51a7994de439aea": {
|
||||||
|
"hash": "73252b46f36df25ef51a7994de439aea",
|
||||||
|
"url": "https://www.bouncycastle.org/licence.html",
|
||||||
|
"name": "Bouncy Castle Licence"
|
||||||
|
},
|
||||||
"8cd94e3ff25fb90fa794464eee297b17": {
|
"8cd94e3ff25fb90fa794464eee297b17": {
|
||||||
"hash": "8cd94e3ff25fb90fa794464eee297b17",
|
"hash": "8cd94e3ff25fb90fa794464eee297b17",
|
||||||
"url": "https://www.mozilla.org/en-US/MPL/1.1/",
|
"url": "https://www.mozilla.org/en-US/MPL/1.1/",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ kotlinx-coroutines = "1.10.2"
|
|||||||
material3 = "1.11.0-alpha01"
|
material3 = "1.11.0-alpha01"
|
||||||
serialization = "1.9.0"
|
serialization = "1.9.0"
|
||||||
cryptoRand = "0.6.0"
|
cryptoRand = "0.6.0"
|
||||||
curve25519Kotlin = "0.0.8"
|
|
||||||
koin = "4.2.0-beta2"
|
koin = "4.2.0-beta2"
|
||||||
ktor = "3.3.3"
|
ktor = "3.3.3"
|
||||||
conveyor = "1.13"
|
conveyor = "1.13"
|
||||||
@@ -28,6 +27,7 @@ sonner = "0.3.9"
|
|||||||
materialKolor = "4.1.1"
|
materialKolor = "4.1.1"
|
||||||
nativeTray = "1.1.0"
|
nativeTray = "1.1.0"
|
||||||
mps = "1.3.0"
|
mps = "1.3.0"
|
||||||
|
bc = "1.84"
|
||||||
|
|
||||||
# Files
|
# Files
|
||||||
kmpIo = "0.3.0"
|
kmpIo = "0.3.0"
|
||||||
@@ -87,7 +87,7 @@ kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-
|
|||||||
|
|
||||||
# cryto
|
# cryto
|
||||||
crypto-rand = { module = "org.kotlincrypto.random:crypto-rand", version.ref = "cryptoRand" }
|
crypto-rand = { module = "org.kotlincrypto.random:crypto-rand", version.ref = "cryptoRand" }
|
||||||
curve25519-kotlin = { module = "io.github.andreypfau:curve25519-kotlin", version.ref = "curve25519Kotlin" }
|
bouncycastle = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bc" }
|
||||||
|
|
||||||
# DI
|
# DI
|
||||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.serialization.core)
|
implementation(libs.kotlinx.serialization.core)
|
||||||
|
|
||||||
implementation(libs.crypto.rand)
|
implementation(libs.crypto.rand)
|
||||||
implementation(libs.curve25519.kotlin)
|
implementation(libs.bouncycastle)
|
||||||
|
|
||||||
implementation(libs.human.readable)
|
implementation(libs.human.readable)
|
||||||
implementation(libs.kotlinx.datetime)
|
implementation(libs.kotlinx.datetime)
|
||||||
@@ -30,7 +30,7 @@ publishing {
|
|||||||
register<MavenPublication>("release") {
|
register<MavenPublication>("release") {
|
||||||
groupId = "com.zaneschepke.wireguardautotunnel"
|
groupId = "com.zaneschepke.wireguardautotunnel"
|
||||||
artifactId = "amneziawg-parser"
|
artifactId = "amneziawg-parser"
|
||||||
version = "1.0.7"
|
version = "1.1.0"
|
||||||
from(components["java"])
|
from(components["java"])
|
||||||
pom {
|
pom {
|
||||||
name.set("AmneziaWG Parser")
|
name.set("AmneziaWG Parser")
|
||||||
|
|||||||
@@ -14,9 +14,13 @@ import kotlinx.serialization.Serializable
|
|||||||
data class Config(
|
data class Config(
|
||||||
@SerialName("Interface") val `interface`: InterfaceSection,
|
@SerialName("Interface") val `interface`: InterfaceSection,
|
||||||
@SerialName("Peer") val peers: List<PeerSection> = emptyList(),
|
@SerialName("Peer") val peers: List<PeerSection> = emptyList(),
|
||||||
|
val name: String? = null,
|
||||||
val headerComments: List<String> = emptyList(),
|
val headerComments: List<String> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
fun withName(newName: String?): Config =
|
||||||
|
copy(name = newName?.trim()?.takeIf { it.isNotBlank() })
|
||||||
|
|
||||||
@Throws(ConfigParseException::class)
|
@Throws(ConfigParseException::class)
|
||||||
fun validate() {
|
fun validate() {
|
||||||
`interface`.validate()
|
`interface`.validate()
|
||||||
@@ -25,6 +29,7 @@ data class Config(
|
|||||||
|
|
||||||
fun asQuickString(): String =
|
fun asQuickString(): String =
|
||||||
buildString {
|
buildString {
|
||||||
|
name?.let { appendLine("# Name = $it") }
|
||||||
headerComments.forEach { appendLine(it) }
|
headerComments.forEach { appendLine(it) }
|
||||||
ConfigFormatter.appendInterfaceSection(this, `interface`)
|
ConfigFormatter.appendInterfaceSection(this, `interface`)
|
||||||
peers.forEach { ConfigFormatter.appendPeerSection(this, it) }
|
peers.forEach { ConfigFormatter.appendPeerSection(this, it) }
|
||||||
@@ -92,8 +97,28 @@ data class Config(
|
|||||||
val parts = raw.split("=", limit = 2)
|
val parts = raw.split("=", limit = 2)
|
||||||
|
|
||||||
if (parts.size == 2) {
|
if (parts.size == 2) {
|
||||||
val key = parts[0].trim()
|
val rawKey = parts[0].trim()
|
||||||
var value = parts[1].trim()
|
val lowerKey = rawKey.lowercase()
|
||||||
|
|
||||||
|
// Normalize wireguard keys
|
||||||
|
val key =
|
||||||
|
when (lowerKey) {
|
||||||
|
"allowedips" -> "AllowedIPs"
|
||||||
|
"address" -> "Address"
|
||||||
|
"dns" -> "DNS"
|
||||||
|
"presharedkey" -> "PresharedKey"
|
||||||
|
"privatekey" -> "PrivateKey"
|
||||||
|
"publickey" -> "PublicKey"
|
||||||
|
"listenport" -> "ListenPort"
|
||||||
|
"persistentkeepalive" -> "PersistentKeepalive"
|
||||||
|
"mtu" -> "MTU"
|
||||||
|
"table" -> "Table"
|
||||||
|
"saveconfig" -> "SaveConfig"
|
||||||
|
else -> rawKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip inline comments before trimming
|
||||||
|
var value = parts[1].substringBefore("#").substringBefore(";").trim()
|
||||||
|
|
||||||
if (currentSectionMap === interfaceMap) {
|
if (currentSectionMap === interfaceMap) {
|
||||||
when (key) {
|
when (key) {
|
||||||
@@ -107,14 +132,13 @@ data class Config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove whitespaces
|
// Remove whitespaces
|
||||||
if (
|
if (
|
||||||
key in
|
key in
|
||||||
listOf(
|
listOf(
|
||||||
"PrivateKey",
|
"PrivateKey",
|
||||||
"PublicKey",
|
"PublicKey",
|
||||||
"PresharedKey",
|
"PresharedKey",
|
||||||
"PreSharedKey",
|
|
||||||
"H1",
|
"H1",
|
||||||
"H2",
|
"H2",
|
||||||
"H3",
|
"H3",
|
||||||
@@ -123,12 +147,55 @@ data class Config(
|
|||||||
) {
|
) {
|
||||||
value = value.replace(Regex("\\s+"), "")
|
value = value.replace(Regex("\\s+"), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (key) {
|
||||||
|
"AllowedIPs",
|
||||||
|
"Address",
|
||||||
|
"DNS" -> {
|
||||||
|
val existing = currentSectionMap?.get(key)
|
||||||
|
currentSectionMap?.put(
|
||||||
|
key,
|
||||||
|
if (existing.isNullOrEmpty()) value else "$existing, $value",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"PresharedKey" -> {
|
||||||
|
currentSectionMap?.put("PresharedKey", value)
|
||||||
|
currentSectionMap?.put("PreSharedKey", value)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
currentSectionMap?.put(key, value)
|
currentSectionMap?.put(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val extractedName =
|
||||||
|
headerComments.firstOrNull()?.let { firstComment ->
|
||||||
|
val content = firstComment.trimStart('#', ' ', '\t').trim()
|
||||||
|
|
||||||
|
when {
|
||||||
|
content.startsWith("Name", ignoreCase = true) -> {
|
||||||
|
content
|
||||||
|
.substringAfter("Name", "")
|
||||||
|
.trimStart('=', ' ', '\t')
|
||||||
|
.trim()
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent name duplicates
|
||||||
|
val cleanedHeaderComments =
|
||||||
|
if (extractedName != null) {
|
||||||
|
headerComments.drop(1)
|
||||||
|
} else {
|
||||||
|
headerComments
|
||||||
|
}
|
||||||
|
|
||||||
return Config(
|
return Config(
|
||||||
headerComments = headerComments,
|
headerComments = cleanedHeaderComments,
|
||||||
|
name = extractedName,
|
||||||
`interface` = buildInterface(interfaceMap, scripts.build(), interfaceComments),
|
`interface` = buildInterface(interfaceMap, scripts.build(), interfaceComments),
|
||||||
peers = peerMaps.map { (map, comments) -> buildPeer(map, comments) },
|
peers = peerMaps.map { (map, comments) -> buildPeer(map, comments) },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,37 +1,16 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// com.zaneschepke.wireguardautotunnel.parser.crypto.Key.kt
|
||||||
// Copyright © 2026 WG Tunnel.
|
|
||||||
// Adapted from WireGuard LLC.
|
|
||||||
|
|
||||||
package com.zaneschepke.wireguardautotunnel.parser.crypto
|
package com.zaneschepke.wireguardautotunnel.parser.crypto
|
||||||
|
|
||||||
import io.github.andreypfau.curve25519.x25519.X25519
|
|
||||||
import kotlin.experimental.and
|
import kotlin.experimental.and
|
||||||
import kotlin.experimental.or
|
import org.bouncycastle.math.ec.rfc7748.X25519
|
||||||
|
import org.bouncycastle.util.encoders.Base64
|
||||||
import org.kotlincrypto.random.CryptoRand
|
import org.kotlincrypto.random.CryptoRand
|
||||||
|
|
||||||
class KeyFormatException : Exception {
|
|
||||||
constructor(
|
|
||||||
format: Key.Format,
|
|
||||||
type: Key.Type,
|
|
||||||
) : super("Invalid key format: $format, type: $type")
|
|
||||||
}
|
|
||||||
|
|
||||||
class Key private constructor(private val key: ByteArray) {
|
class Key private constructor(private val key: ByteArray) {
|
||||||
|
|
||||||
fun getBytes(): ByteArray = key.copyOf()
|
fun getBytes(): ByteArray = key.copyOf()
|
||||||
|
|
||||||
fun toBase64(): String {
|
fun toBase64(): String = Base64.encode(key).decodeToString()
|
||||||
val output = CharArray(Format.BASE64.length)
|
|
||||||
var i = 0
|
|
||||||
while (i < key.size / 3) {
|
|
||||||
encodeBase64(key, i * 3, output, i * 4)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
val endSegment = byteArrayOf(key[i * 3], key[i * 3 + 1], 0)
|
|
||||||
encodeBase64(endSegment, 0, output, i * 4)
|
|
||||||
output[Format.BASE64.length - 1] = '='
|
|
||||||
return output.concatToString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -39,113 +18,32 @@ class Key private constructor(private val key: ByteArray) {
|
|||||||
return key.contentEquals(other.key)
|
return key.contentEquals(other.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int = key.contentHashCode()
|
||||||
var ret = 0
|
|
||||||
var i = 0
|
|
||||||
while (i < key.size / 4) {
|
|
||||||
ret =
|
|
||||||
ret xor
|
|
||||||
((key[i * 4 + 0].toInt() shr 0) +
|
|
||||||
(key[i * 4 + 1].toInt() shr 8) +
|
|
||||||
(key[i * 4 + 2].toInt() shr 16) +
|
|
||||||
(key[i * 4 + 3].toInt() shr 24))
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromBase64(str: String): Key {
|
fun fromBase64(str: String): Key {
|
||||||
val input = str.toCharArray()
|
val bytes = Base64.decode(str)
|
||||||
if (input.size != Format.BASE64.length || input[Format.BASE64.length - 1] != '=') {
|
if (bytes.size != 32) throw KeyFormatException(Format.BINARY, Type.LENGTH)
|
||||||
throw KeyFormatException(Format.BASE64, Type.LENGTH)
|
|
||||||
}
|
|
||||||
val key = ByteArray(Format.BINARY.length)
|
|
||||||
var ret = 0
|
|
||||||
var i = 0
|
|
||||||
while (i < key.size / 3) {
|
|
||||||
val value = decodeBase64(input, i * 4)
|
|
||||||
ret = ret or (value ushr 31)
|
|
||||||
key[i * 3] = ((value ushr 16) and 0xff).toByte()
|
|
||||||
key[i * 3 + 1] = ((value ushr 8) and 0xff).toByte()
|
|
||||||
key[i * 3 + 2] = (value and 0xff).toByte()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
val endSegment = charArrayOf(input[i * 4], input[i * 4 + 1], input[i * 4 + 2], 'A')
|
|
||||||
val value = decodeBase64(endSegment, 0)
|
|
||||||
ret = ret or ((value ushr 31) or (value and 0xff))
|
|
||||||
key[i * 3] = ((value ushr 16) and 0xff).toByte()
|
|
||||||
key[i * 3 + 1] = ((value ushr 8) and 0xff).toByte()
|
|
||||||
|
|
||||||
if (ret != 0) {
|
|
||||||
throw KeyFormatException(Format.BASE64, Type.CONTENTS)
|
|
||||||
}
|
|
||||||
return Key(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromBytes(bytes: ByteArray): Key {
|
|
||||||
if (bytes.size != Format.BINARY.length) {
|
|
||||||
throw KeyFormatException(Format.BINARY, Type.LENGTH)
|
|
||||||
}
|
|
||||||
return Key(bytes)
|
return Key(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fromBytes(bytes: ByteArray): Key {
|
||||||
|
if (bytes.size != 32) throw KeyFormatException(Format.BINARY, Type.LENGTH)
|
||||||
|
return Key(bytes.copyOf())
|
||||||
|
}
|
||||||
|
|
||||||
fun generatePrivateKey(): Key {
|
fun generatePrivateKey(): Key {
|
||||||
val privateKey = ByteArray(Format.BINARY.length)
|
val priv = ByteArray(32)
|
||||||
CryptoRand.nextBytes(privateKey)
|
CryptoRand.nextBytes(priv)
|
||||||
privateKey[0] = privateKey[0] and 248.toByte()
|
priv[0] = priv[0] and 248.toByte()
|
||||||
privateKey[31] = privateKey[31] and 127.toByte()
|
priv[31] = (priv[31].toInt() and 127 or 64).toByte()
|
||||||
privateKey[31] = privateKey[31] or 64.toByte()
|
return Key(priv)
|
||||||
return Key(privateKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generatePublicKey(privateKey: Key): Key {
|
fun generatePublicKey(privateKey: Key): Key {
|
||||||
val publicKey = ByteArray(Format.BINARY.length)
|
val pub = ByteArray(32)
|
||||||
X25519.x25519(privateKey.getBytes(), output = publicKey)
|
X25519.scalarMultBase(privateKey.getBytes(), 0, pub, 0)
|
||||||
return Key(publicKey)
|
return Key(pub)
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeBase64(src: CharArray, srcOffset: Int): Int {
|
|
||||||
var value = 0
|
|
||||||
for (i in 0 until 4) {
|
|
||||||
val c = src[i + srcOffset].code
|
|
||||||
value =
|
|
||||||
value or
|
|
||||||
(-1 +
|
|
||||||
((((('A'.code - 1) - c) and (c - ('Z'.code + 1))) ushr 8) and
|
|
||||||
(c - 64)) +
|
|
||||||
((((('a'.code - 1) - c) and (c - ('z'.code + 1))) ushr 8) and
|
|
||||||
(c - 70)) +
|
|
||||||
((((('0'.code - 1) - c) and (c - ('9'.code + 1))) ushr 8) and (c + 5)) +
|
|
||||||
(((('+'.code - 1) - c) and (c - ('+'.code + 1))) ushr 8 and 63) +
|
|
||||||
(((('/'.code - 1) - c) and (c - ('/'.code + 1))) ushr 8 and 64)) shl
|
|
||||||
(18 - 6 * i)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun encodeBase64(src: ByteArray, srcOffset: Int, dest: CharArray, destOffset: Int) {
|
|
||||||
val input =
|
|
||||||
byteArrayOf(
|
|
||||||
(src[srcOffset].toInt() shr 2 and 63).toByte(),
|
|
||||||
((src[srcOffset].toInt() shl
|
|
||||||
4 or
|
|
||||||
(src[1 + srcOffset].toInt() and 0xff ushr 4)) and 63)
|
|
||||||
.toByte(),
|
|
||||||
((src[1 + srcOffset].toInt() shl
|
|
||||||
2 or
|
|
||||||
(src[2 + srcOffset].toInt() and 0xff ushr 6)) and 63)
|
|
||||||
.toByte(),
|
|
||||||
(src[2 + srcOffset].toInt() and 63).toByte(),
|
|
||||||
)
|
|
||||||
for (i in 0 until 4) {
|
|
||||||
dest[i + destOffset] =
|
|
||||||
(input[i].toInt() + 'A'.code + (((25 - input[i].toInt()) ushr 8) and 6) -
|
|
||||||
(((51 - input[i].toInt()) ushr 8) and 75) -
|
|
||||||
(((61 - input[i].toInt()) ushr 8) and 15) +
|
|
||||||
(((62 - input[i].toInt()) ushr 8) and 3))
|
|
||||||
.toChar()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,3 +58,10 @@ class Key private constructor(private val key: ByteArray) {
|
|||||||
CONTENTS,
|
CONTENTS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class KeyFormatException : Exception {
|
||||||
|
constructor(
|
||||||
|
format: Key.Format,
|
||||||
|
type: Key.Type,
|
||||||
|
) : super("Invalid key format: $format, type: $type")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user