add general support from encoding to string and restoring from string various keys formats. + docs.
This commit is contained in:
parent
e8fa634640
commit
cef7e4abed
@ -48,7 +48,7 @@ open class BinaryId protected constructor (
|
||||
/**
|
||||
* Bad format (crc does not match)
|
||||
*/
|
||||
class InvalidException(text: String) : IllegalArgumentException(text)
|
||||
class InvalidException(text: String,reason: Throwable?=null) : IllegalArgumentException(text,reason)
|
||||
|
||||
/**
|
||||
* Attempt to compare binary ids with different magic. In this case only [equals]
|
||||
@ -136,8 +136,9 @@ open class BinaryId protected constructor (
|
||||
* Restore a string representation of existing BinaryId.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun restoreFromString(str: String): BinaryId =
|
||||
fun restoreFromString(str: String): BinaryId = kotlin.runCatching {
|
||||
BinaryId(str.decodeBase64Url().toUByteArray())
|
||||
}.getOrElse { throw InvalidException("can't parse binary id: $str", it) }
|
||||
|
||||
|
||||
fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray())
|
||||
|
@ -18,6 +18,9 @@ import kotlinx.serialization.Serializable
|
||||
*
|
||||
* See [PBKD.Params.deriveKey] for deriving keys from id.
|
||||
*
|
||||
* See [id], and [BinaryId] class for more. Note that for [PublicKey] and [VerifyingPublicKey] [BinaryId.asPublicKey]
|
||||
* and [BinaryId.asVerifyingKey] restore actual keys, providing [BinaryId.magic] has proper value, see [KeysmagicNumber]]
|
||||
*
|
||||
* @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same.
|
||||
* @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper
|
||||
* password, see above.
|
||||
|
@ -3,6 +3,9 @@ package net.sergeych.crypto2
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.sergeych.bipack.decodeFromBipack
|
||||
import net.sergeych.crypto2.VerifyingPublicKey.Companion.toString
|
||||
import net.sergeych.mp_tools.decodeBase64Url
|
||||
|
||||
/**
|
||||
* The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding
|
||||
@ -71,4 +74,40 @@ class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingK
|
||||
Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
|
||||
|
||||
override val id by lazy { KeyId(magic, keyBytes, null, true) }
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Parse any known public key text representation, including what [toString] return (for public keys it is
|
||||
* possible)
|
||||
* @throws IllegalArgumentException the public key isn't recognized
|
||||
*/
|
||||
fun parse(text: String): PublicKey {
|
||||
val s = text.trim()
|
||||
|
||||
fun parseId(t: String): PublicKey{
|
||||
val id = BinaryId.restoreFromString(t)
|
||||
if( id.magic != KeysmagicNumber.defaultAssymmetric.ordinal)
|
||||
throw IllegalArgumentException("invalid magick ${id.magic} for PublicKey")
|
||||
return id.asPublicKey
|
||||
}
|
||||
|
||||
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
|
||||
return when {
|
||||
s.startsWith("\uD83D\uDDDDpub#") -> parseId(s.drop(6))
|
||||
s.startsWith("pub#") -> parseId(s.drop(4))
|
||||
s.startsWith("#") -> parseId(s.drop(1))
|
||||
else -> {
|
||||
// consider it is serialized key in base64 format
|
||||
val data = s.decodeBase64Url().asUByteArray()
|
||||
if (data.size == 32)
|
||||
PublicKey(data)
|
||||
else {
|
||||
runCatching { data.decodeFromBipack<PublicKey>() }.getOrNull()
|
||||
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as PublicKey }
|
||||
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,10 @@ package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
import net.sergeych.bipack.decodeFromBipack
|
||||
import net.sergeych.mp_tools.decodeBase64Compact
|
||||
import net.sergeych.mp_tools.encodeToBase64Compact
|
||||
|
||||
@Serializable
|
||||
sealed class UniversalKey : KeyInstance {
|
||||
@ -11,7 +15,15 @@ sealed class UniversalKey: KeyInstance {
|
||||
@Transient
|
||||
open val magic: KeysmagicNumber = KeysmagicNumber.Unknown
|
||||
|
||||
|
||||
/**
|
||||
* Key ID positively identify key from the point of view of _decrypting or verifying_. So matching [VerifyingKey]
|
||||
* and [SigningKey] will have the same id, same as matching [PublicKey] and [SecretKey].
|
||||
*
|
||||
* KeyId is based on [BinaryId] which includes checksum (crc8) and magick number for additional security,
|
||||
* see [KeysmagicNumber].
|
||||
*
|
||||
* Also "public" keys can be restored from id using [BinaryId.asPublicKey] and [BinaryId.asVerifyingKey].
|
||||
*/
|
||||
override val id by lazy { KeyId(magic, keyBytes, null) }
|
||||
|
||||
// Important: id can be overridden, so we use it, not magic:
|
||||
@ -33,11 +45,33 @@ sealed class UniversalKey: KeyInstance {
|
||||
companion object {
|
||||
fun newSecretKey() = SecretKey.new()
|
||||
fun newSigningKey() = SigningSecretKey.new()
|
||||
|
||||
@Suppress("unused")
|
||||
fun newSymmetricKey() = SymmetricKey.new()
|
||||
|
||||
/**
|
||||
* Parse all known string representations of the universal key
|
||||
* @throws IllegalArgumentException if it can't parse any key.
|
||||
*/
|
||||
fun parseString(text: String): UniversalKey {
|
||||
val s = text.trim()
|
||||
return when {
|
||||
s.startsWith("\uD83D\uDDDDpub#") || s.startsWith("pub#") ->
|
||||
PublicKey.parse(s)
|
||||
s.startsWith("\uD83D\uDDDDver#") || s.startsWith("ver#") ->
|
||||
VerifyingPublicKey.parse(s)
|
||||
else -> {
|
||||
s.decodeBase64Compact().decodeFromBipack<UniversalKey>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : UniversalKey> T.asString() =
|
||||
BipackEncoder.encode<T>(this).encodeToBase64Compact()
|
||||
|
||||
|
||||
open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") :
|
||||
IllegalStateException(text)
|
||||
|
@ -5,6 +5,8 @@ import com.ionspin.kotlin.crypto.signature.Signature
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.sergeych.bipack.decodeFromBipack
|
||||
import net.sergeych.mp_tools.decodeBase64Url
|
||||
|
||||
/**
|
||||
* Public key to verify signatures only
|
||||
@ -53,5 +55,41 @@ class VerifyingPublicKey(override val keyBytes: UByteArray) : UniversalKey(), Ve
|
||||
*/
|
||||
infix fun and(other: Multikey) = Multikey(this) and other
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Parse any known public key text representation, including what [toString] return (for public keys it is
|
||||
* possible)
|
||||
* @throws IllegalArgumentException the public key isn't recognized, in particular [BinaryId.InvalidException]
|
||||
* if the text is corrupt
|
||||
*/
|
||||
fun parse(text: String): VerifyingPublicKey {
|
||||
val s = text.trim()
|
||||
|
||||
fun parseId(t: String): VerifyingPublicKey {
|
||||
// assume it is an id:
|
||||
val id = BinaryId.restoreFromString(t)
|
||||
return if (id.magic == KeysmagicNumber.defaultVerifying.ordinal)
|
||||
id.asVerifyingKey as VerifyingPublicKey
|
||||
else throw IllegalArgumentException("Invalid magick: ${id.magic} when parsing[$t]")
|
||||
}
|
||||
|
||||
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
|
||||
return when {
|
||||
s.startsWith("\uD83D\uDDDDver#") -> parseId(s.drop(6))
|
||||
s.startsWith("ver#") -> parseId(s.drop(4))
|
||||
s.startsWith("#") -> parseId(s.drop(1))
|
||||
else -> {
|
||||
// consider it is serialized key in base64 format
|
||||
val data = s.decodeBase64Url().asUByteArray()
|
||||
if (data.size == 32)
|
||||
VerifyingPublicKey(data)
|
||||
else {
|
||||
runCatching { data.decodeFromBipack<VerifyingPublicKey>() }.getOrNull()
|
||||
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as VerifyingPublicKey }
|
||||
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,11 @@ sealed class KDF {
|
||||
@Suppress("unused")
|
||||
fun deriveMultiple(password: String, count: Int): List<SymmetricKey> =
|
||||
kdfForSize(count).deriveMultipleKeys(password, count)
|
||||
|
||||
/**
|
||||
* Derive single key from password, same as [deriveMultiple] with count=1.
|
||||
*/
|
||||
fun derive(password: String): SymmetricKey = deriveMultiple(password, 1).first()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -416,4 +416,45 @@ class KeysTest {
|
||||
assertEquals(x, BipackDecoder.decode<SecretKey>(y))
|
||||
assertContentEquals(x.keyBytes, BipackDecoder.decode<SecretKey>(y).keyBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringRepresentationAndParse() = runTest {
|
||||
initCrypto()
|
||||
val k1 = SigningSecretKey.new()
|
||||
val k2 = k1.verifyingKey
|
||||
val k3 = SecretKey.new()
|
||||
val k4 = k3.publicKey
|
||||
|
||||
val k5 = UniversalPrivateKey.new()
|
||||
val k6 = k5.publicKey
|
||||
|
||||
assertEquals(32, k2.keyBytes.size)
|
||||
assertContentEquals(k2.keyBytes, k2.id.id.body)
|
||||
|
||||
val k7 = SymmetricKey.new()
|
||||
val k8 = KDF.Complexity.Interactive.derive("super")
|
||||
|
||||
fun testToString(k: UniversalKey) {
|
||||
val s = k.toString()
|
||||
val kx = UniversalKey.parseString(s)
|
||||
assertEquals(kx::class, k::class)
|
||||
assertContentEquals(k.keyBytes, kx.keyBytes)
|
||||
assertEquals(k.id, kx.id)
|
||||
assertEquals(k, kx)
|
||||
}
|
||||
|
||||
fun testAsString(k: UniversalKey) {
|
||||
val s = k.asString()
|
||||
val kx = UniversalKey.parseString(s)
|
||||
assertEquals(kx::class, k::class)
|
||||
assertContentEquals(k.keyBytes, kx.keyBytes)
|
||||
assertEquals(k.id, kx.id)
|
||||
assertEquals(k, kx)
|
||||
}
|
||||
|
||||
testToString(k2)
|
||||
testToString(k4)
|
||||
|
||||
for( i in listOf(k1, k2, k3, k4, k5, k6, k7, k8)) testAsString(i)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user