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,16 +2,28 @@ 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 {
 | 
			
		||||
sealed class UniversalKey : KeyInstance {
 | 
			
		||||
 | 
			
		||||
    abstract val keyBytes: UByteArray
 | 
			
		||||
 | 
			
		||||
    @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,12 +45,34 @@ 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