summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-12-21 22:12:32 +0100
committerMS <ms@taler.net>2023-12-21 22:12:32 +0100
commit77f914d8fb3c65d0a99efc0353183524a663abbe (patch)
tree24acdfbfb05a94bd86d6b30fb86a9e4672d70ceb
parent644f04f6aefdb047b4b9a90c7f5f35b8929ffd18 (diff)
downloadlibeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.tar.gz
libeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.tar.bz2
libeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.zip
Addressing #7980
-rw-r--r--util/src/main/kotlin/Encoding.kt92
-rw-r--r--util/src/test/kotlin/CryptoUtilTest.kt73
2 files changed, 110 insertions, 55 deletions
diff --git a/util/src/main/kotlin/Encoding.kt b/util/src/main/kotlin/Encoding.kt
index 4f3dcabf..26a523f6 100644
--- a/util/src/main/kotlin/Encoding.kt
+++ b/util/src/main/kotlin/Encoding.kt
@@ -31,61 +31,73 @@ object Base32Crockford {
fun encode(data: ByteArray): String {
val sb = StringBuilder()
- val size = data.size
- var bitBuf = 0
- var numBits = 0
- var pos = 0
- while (pos < size || numBits > 0) {
- if (pos < size && numBits < 5) {
- val d = data.getIntAt(pos++)
- bitBuf = (bitBuf shl 8) or d
- numBits += 8
- }
- if (numBits < 5) {
- // zero-padding
- bitBuf = bitBuf shl (5 - numBits)
- numBits = 5
+ var inputChunkBuffer = 0
+ var pendingBitsCount = 0
+ var inputCursor = 0
+ var inputChunkNumber = 0
+
+ while (inputCursor < data.size) {
+ // Read input
+ inputChunkNumber = data.getIntAt(inputCursor++)
+ inputChunkBuffer = (inputChunkBuffer shl 8) or inputChunkNumber
+ pendingBitsCount += 8
+ // Write symbols
+ while (pendingBitsCount >= 5) {
+ val symbolIndex = inputChunkBuffer.ushr(pendingBitsCount - 5) and 31
+ sb.append(encTable[symbolIndex])
+ pendingBitsCount -= 5
}
- val v = bitBuf.ushr(numBits - 5) and 31
- sb.append(encTable[v])
- numBits -= 5
}
- return sb.toString()
+ if (pendingBitsCount >= 5)
+ throw Exception("base32 encoder did not write all the symbols")
+
+ if (pendingBitsCount > 0) {
+ val symbolIndex = (inputChunkNumber shl (5 - pendingBitsCount)) and 31
+ sb.append(encTable[symbolIndex])
+ }
+ val enc = sb.toString()
+ val oneMore = ((data.size * 8) % 5) > 0
+ val expectedLength = if (oneMore) {
+ ((data.size * 8) / 5) + 1
+ } else {
+ (data.size * 8) / 5
+ }
+ if (enc.length != expectedLength)
+ throw Exception("base32 encoding has wrong length")
+ return enc
}
/**
* Decodes the input to its binary representation, throws
* net.taler.wallet.crypto.EncodingException on invalid encodings.
*/
- fun decode(encoded: String, out: ByteArrayOutputStream) {
- val size = encoded.length
- var bitpos = 0
- var bitbuf = 0
- var readPosition = 0
-
- while (readPosition < size || bitpos > 0) {
- //println("at position $readPosition with bitpos $bitpos")
- if (readPosition < size) {
- val v = getValue(encoded[readPosition++])
- bitbuf = (bitbuf shl 5) or v
- bitpos += 5
- }
- while (bitpos >= 8) {
- val d = (bitbuf ushr (bitpos - 8)) and 0xFF
- out.write(d)
- bitpos -= 8
- }
- if (readPosition == size && bitpos > 0) {
- bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
- bitpos = if (bitbuf == 0) 0 else 8
+ fun decode(
+ encoded: String,
+ out: ByteArrayOutputStream
+ ) {
+ var outBitsCount = 0
+ var bitsBuffer = 0
+ var inputCursor = 0
+
+ while (inputCursor < encoded.length) {
+ val decodedNumber = getValue(encoded[inputCursor++])
+ bitsBuffer = (bitsBuffer shl 5) or decodedNumber
+ outBitsCount += 5
+ while (outBitsCount >= 8) {
+ val outputChunk = (bitsBuffer ushr (outBitsCount - 8)) and 0xFF
+ out.write(outputChunk)
+ outBitsCount -= 8 // decrease of written bits.
}
}
+ if ((encoded.length * 5) / 8 != out.size())
+ throw Exception("base32 decoder: wrong output size")
}
fun decode(encoded: String): ByteArray {
val out = ByteArrayOutputStream()
decode(encoded, out)
- return out.toByteArray()
+ val blob = out.toByteArray()
+ return blob
}
private fun getValue(chr: Char): Int {
diff --git a/util/src/test/kotlin/CryptoUtilTest.kt b/util/src/test/kotlin/CryptoUtilTest.kt
index 866866a2..82448c45 100644
--- a/util/src/test/kotlin/CryptoUtilTest.kt
+++ b/util/src/test/kotlin/CryptoUtilTest.kt
@@ -18,17 +18,13 @@
*/
import net.taler.wallet.crypto.Base32Crockford
-import net.taler.wallet.crypto.EncodingException
-import org.bouncycastle.asn1.edec.EdECObjectIdentifiers
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
+import org.junit.Ignore
import org.junit.Test
import tech.libeufin.util.*
-import java.security.KeyFactory
+import java.io.File
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateCrtKey
-import java.security.spec.KeySpec
-import java.security.spec.X509EncodedKeySpec
+import java.util.*
import javax.crypto.EncryptedPrivateKeyInfo
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@@ -150,19 +146,67 @@ class CryptoUtilTest {
fun checkEddsaPublicKey() {
val givenEnc = "XZH3P6NF9DSG3BH0C082X38N2RVK1RV2H24KF76028QBKDM24BCG"
val non32bytes = "N2RVK1RV2H24KF76028QBKDM24BCG"
-
assertTrue(CryptoUtil.checkValidEddsaPublicKey(givenEnc))
assertFalse(CryptoUtil.checkValidEddsaPublicKey(non32bytes))
}
- @Test(expected = EncodingException::class)
- fun base32ToBytesTest() {
- val expectedEncoding = "C9P6YRG" // decodes to 'blob'
- assert(Base32Crockford.decode(expectedEncoding).toString(Charsets.UTF_8) == "blob")
+ @Test
+ fun base32Test() {
val validKey = "4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0"
- val obj = Base32Crockford.decode(validKey)
+ val enc = validKey
+ val obj = Base32Crockford.decode(enc)
+ assertTrue(obj.size == 32)
val roundTrip = Base32Crockford.encode(obj)
- assertEquals(validKey, roundTrip)
+ assertEquals(enc, roundTrip)
+ val invalidShorterKey = "4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCE"
+ val shorterBlob = Base32Crockford.decode(invalidShorterKey)
+ assertTrue(shorterBlob.size < 32) // See #7980
+ }
+
+ @Test
+ fun blobRoundTrip() {
+ val blob = ByteArray(30)
+ Random().nextBytes(blob)
+ val enc = Base32Crockford.encode(blob)
+ val blobAgain = Base32Crockford.decode(enc)
+ assertTrue(blob.contentEquals(blobAgain))
+ }
+
+ /**
+ * Manual test: tests that gnunet-base32 and
+ * libeufin encode to the same string.
+ */
+ @Ignore
+ fun gnunetEncodeCheck() {
+ val blob = ByteArray(30)
+ Random().nextBytes(blob)
+ val b = File("/tmp/libeufin-blob.bin")
+ b.writeBytes(blob)
+ val enc = Base32Crockford.encode(blob)
+ // The following output needs to match the one from
+ // "gnunet-base32 /tmp/libeufin-blob.bin"
+ println(enc)
+ }
+
+ /**
+ * Manual test: tests that gnunet-base32 and
+ * libeufin decode to the same value
+ */
+ @Ignore
+ fun gnunetDecodeCheck() {
+ // condition: "gnunet-base32 -d /tmp/blob.enc" needs to decode to /tmp/blob.bin
+ val blob = File("/tmp/blob.bin").readBytes()
+ val blobEnc = File("/tmp/blob.enc").readText(Charsets.UTF_8)
+ val dec = Base32Crockford.decode(blobEnc)
+ assertTrue(blob.contentEquals(dec))
+ }
+
+ @Test
+ fun emptyBase32Test() {
+ val enc = Base32Crockford.encode(ByteArray(0))
+ assert(enc.isEmpty())
+ val blob = Base32Crockford.decode("")
+ assert(blob.isEmpty())
}
@Test
@@ -171,4 +215,3 @@ class CryptoUtilTest {
assertTrue(CryptoUtil.checkpw("myinsecurepw", x))
}
}
-