diff options
author | MS <ms@taler.net> | 2023-12-21 22:12:32 +0100 |
---|---|---|
committer | MS <ms@taler.net> | 2023-12-21 22:12:32 +0100 |
commit | 77f914d8fb3c65d0a99efc0353183524a663abbe (patch) | |
tree | 24acdfbfb05a94bd86d6b30fb86a9e4672d70ceb | |
parent | 644f04f6aefdb047b4b9a90c7f5f35b8929ffd18 (diff) | |
download | libeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.tar.gz libeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.tar.bz2 libeufin-77f914d8fb3c65d0a99efc0353183524a663abbe.zip |
Addressing #7980
-rw-r--r-- | util/src/main/kotlin/Encoding.kt | 92 | ||||
-rw-r--r-- | util/src/test/kotlin/CryptoUtilTest.kt | 73 |
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)) } } - |