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": "Gary Gregory"
|
||||
},
|
||||
{
|
||||
"name": "Stian Soiland-Reyes"
|
||||
},
|
||||
@@ -1114,6 +1111,10 @@
|
||||
{
|
||||
"name": "Yoav Shapira"
|
||||
},
|
||||
{
|
||||
"organisationUrl": "https://www.apache.org/",
|
||||
"name": "Gary Gregory"
|
||||
},
|
||||
{
|
||||
"name": "Rob Tompkins"
|
||||
},
|
||||
@@ -1148,15 +1149,15 @@
|
||||
"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.",
|
||||
"scm": {
|
||||
"connection": "scm:svn:http://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
||||
"url": "http://svn.apache.org/viewvc/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
||||
"developerConnection": "scm:svn:https://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": "https://gitbox.apache.org/repos/asf?p=commons-beanutils.git",
|
||||
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf?p=commons-beanutils.git"
|
||||
},
|
||||
"name": "Apache Commons BeanUtils",
|
||||
"website": "https://commons.apache.org/proper/commons-beanutils/",
|
||||
"website": "https://commons.apache.org/proper/commons-beanutils",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
@@ -1326,8 +1327,8 @@
|
||||
"name": "Rodney Waldhoff"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "1.3.0",
|
||||
"description": "Apache Commons Logging is a thin adapter allowing configurable bridging to other,\n well known logging systems.",
|
||||
"artifactVersion": "1.3.5",
|
||||
"description": "Apache Commons Logging is a thin adapter allowing configurable bridging to other,\n well-known logging systems.",
|
||||
"scm": {
|
||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-logging",
|
||||
"url": "https://gitbox.apache.org/repos/asf/commons-logging",
|
||||
@@ -1399,15 +1400,15 @@
|
||||
"name": "Benedikt Ritter"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "1.8.0",
|
||||
"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.",
|
||||
"artifactVersion": "1.10.1",
|
||||
"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": {
|
||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator",
|
||||
"url": "https://gitbox.apache.org/repos/asf/commons-validator",
|
||||
"developerConnection": "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?p=commons-validator.git/commons-validator",
|
||||
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator.git/commons-validator"
|
||||
},
|
||||
"name": "Apache Commons Validator",
|
||||
"website": "http://commons.apache.org/proper/commons-validator/",
|
||||
"website": "https://commons.apache.org/proper/commons-validator/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
@@ -1460,90 +1461,6 @@
|
||||
"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",
|
||||
"funding": [
|
||||
@@ -2583,6 +2500,27 @@
|
||||
"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",
|
||||
"funding": [
|
||||
@@ -5141,6 +5079,11 @@
|
||||
"url": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
|
||||
"name": "GNU Lesser General Public License"
|
||||
},
|
||||
"73252b46f36df25ef51a7994de439aea": {
|
||||
"hash": "73252b46f36df25ef51a7994de439aea",
|
||||
"url": "https://www.bouncycastle.org/licence.html",
|
||||
"name": "Bouncy Castle Licence"
|
||||
},
|
||||
"8cd94e3ff25fb90fa794464eee297b17": {
|
||||
"hash": "8cd94e3ff25fb90fa794464eee297b17",
|
||||
"url": "https://www.mozilla.org/en-US/MPL/1.1/",
|
||||
|
||||
@@ -10,7 +10,6 @@ kotlinx-coroutines = "1.10.2"
|
||||
material3 = "1.11.0-alpha01"
|
||||
serialization = "1.9.0"
|
||||
cryptoRand = "0.6.0"
|
||||
curve25519Kotlin = "0.0.8"
|
||||
koin = "4.2.0-beta2"
|
||||
ktor = "3.3.3"
|
||||
conveyor = "1.13"
|
||||
@@ -28,6 +27,7 @@ sonner = "0.3.9"
|
||||
materialKolor = "4.1.1"
|
||||
nativeTray = "1.1.0"
|
||||
mps = "1.3.0"
|
||||
bc = "1.84"
|
||||
|
||||
# Files
|
||||
kmpIo = "0.3.0"
|
||||
@@ -87,7 +87,7 @@ kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-
|
||||
|
||||
# cryto
|
||||
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
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies {
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
|
||||
implementation(libs.crypto.rand)
|
||||
implementation(libs.curve25519.kotlin)
|
||||
implementation(libs.bouncycastle)
|
||||
|
||||
implementation(libs.human.readable)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
@@ -30,7 +30,7 @@ publishing {
|
||||
register<MavenPublication>("release") {
|
||||
groupId = "com.zaneschepke.wireguardautotunnel"
|
||||
artifactId = "amneziawg-parser"
|
||||
version = "1.0.7"
|
||||
version = "1.1.0"
|
||||
from(components["java"])
|
||||
pom {
|
||||
name.set("AmneziaWG Parser")
|
||||
|
||||
@@ -14,9 +14,13 @@ import kotlinx.serialization.Serializable
|
||||
data class Config(
|
||||
@SerialName("Interface") val `interface`: InterfaceSection,
|
||||
@SerialName("Peer") val peers: List<PeerSection> = emptyList(),
|
||||
val name: String? = null,
|
||||
val headerComments: List<String> = emptyList(),
|
||||
) {
|
||||
|
||||
fun withName(newName: String?): Config =
|
||||
copy(name = newName?.trim()?.takeIf { it.isNotBlank() })
|
||||
|
||||
@Throws(ConfigParseException::class)
|
||||
fun validate() {
|
||||
`interface`.validate()
|
||||
@@ -25,6 +29,7 @@ data class Config(
|
||||
|
||||
fun asQuickString(): String =
|
||||
buildString {
|
||||
name?.let { appendLine("# Name = $it") }
|
||||
headerComments.forEach { appendLine(it) }
|
||||
ConfigFormatter.appendInterfaceSection(this, `interface`)
|
||||
peers.forEach { ConfigFormatter.appendPeerSection(this, it) }
|
||||
@@ -92,8 +97,28 @@ data class Config(
|
||||
val parts = raw.split("=", limit = 2)
|
||||
|
||||
if (parts.size == 2) {
|
||||
val key = parts[0].trim()
|
||||
var value = parts[1].trim()
|
||||
val rawKey = parts[0].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) {
|
||||
when (key) {
|
||||
@@ -107,14 +132,13 @@ data class Config(
|
||||
}
|
||||
}
|
||||
|
||||
// remove whitespaces
|
||||
// Remove whitespaces
|
||||
if (
|
||||
key in
|
||||
listOf(
|
||||
"PrivateKey",
|
||||
"PublicKey",
|
||||
"PresharedKey",
|
||||
"PreSharedKey",
|
||||
"H1",
|
||||
"H2",
|
||||
"H3",
|
||||
@@ -123,12 +147,55 @@ data class Config(
|
||||
) {
|
||||
value = value.replace(Regex("\\s+"), "")
|
||||
}
|
||||
currentSectionMap?.put(key, value)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
headerComments = headerComments,
|
||||
headerComments = cleanedHeaderComments,
|
||||
name = extractedName,
|
||||
`interface` = buildInterface(interfaceMap, scripts.build(), interfaceComments),
|
||||
peers = peerMaps.map { (map, comments) -> buildPeer(map, comments) },
|
||||
)
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright © 2026 WG Tunnel.
|
||||
// Adapted from WireGuard LLC.
|
||||
|
||||
// com.zaneschepke.wireguardautotunnel.parser.crypto.Key.kt
|
||||
package com.zaneschepke.wireguardautotunnel.parser.crypto
|
||||
|
||||
import io.github.andreypfau.curve25519.x25519.X25519
|
||||
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
|
||||
|
||||
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) {
|
||||
|
||||
fun getBytes(): ByteArray = key.copyOf()
|
||||
|
||||
fun toBase64(): String {
|
||||
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()
|
||||
}
|
||||
fun toBase64(): String = Base64.encode(key).decodeToString()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@@ -39,113 +18,32 @@ class Key private constructor(private val key: ByteArray) {
|
||||
return key.contentEquals(other.key)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
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
|
||||
}
|
||||
override fun hashCode(): Int = key.contentHashCode()
|
||||
|
||||
companion object {
|
||||
fun fromBase64(str: String): Key {
|
||||
val input = str.toCharArray()
|
||||
if (input.size != Format.BASE64.length || input[Format.BASE64.length - 1] != '=') {
|
||||
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)
|
||||
}
|
||||
val bytes = Base64.decode(str)
|
||||
if (bytes.size != 32) throw KeyFormatException(Format.BINARY, Type.LENGTH)
|
||||
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 {
|
||||
val privateKey = ByteArray(Format.BINARY.length)
|
||||
CryptoRand.nextBytes(privateKey)
|
||||
privateKey[0] = privateKey[0] and 248.toByte()
|
||||
privateKey[31] = privateKey[31] and 127.toByte()
|
||||
privateKey[31] = privateKey[31] or 64.toByte()
|
||||
return Key(privateKey)
|
||||
val priv = ByteArray(32)
|
||||
CryptoRand.nextBytes(priv)
|
||||
priv[0] = priv[0] and 248.toByte()
|
||||
priv[31] = (priv[31].toInt() and 127 or 64).toByte()
|
||||
return Key(priv)
|
||||
}
|
||||
|
||||
fun generatePublicKey(privateKey: Key): Key {
|
||||
val publicKey = ByteArray(Format.BINARY.length)
|
||||
X25519.x25519(privateKey.getBytes(), output = publicKey)
|
||||
return Key(publicKey)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
val pub = ByteArray(32)
|
||||
X25519.scalarMultBase(privateKey.getBytes(), 0, pub, 0)
|
||||
return Key(pub)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,3 +58,10 @@ class Key private constructor(private val key: ByteArray) {
|
||||
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