summaryrefslogtreecommitdiff
path: root/wallet/src/androidMain/kotlin/net/taler
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/androidMain/kotlin/net/taler')
-rw-r--r--wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt23
-rw-r--r--wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt132
-rw-r--r--wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/RsaBlinding.kt136
3 files changed, 291 insertions, 0 deletions
diff --git a/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt
new file mode 100644
index 0000000..45cbfc3
--- /dev/null
+++ b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt
@@ -0,0 +1,23 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin
+
+internal actual class DbFactory {
+ actual fun openDb(): Db {
+ return FakeDb()
+ }
+}
diff --git a/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt
new file mode 100644
index 0000000..502b8a0
--- /dev/null
+++ b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt
@@ -0,0 +1,132 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.crypto
+
+import com.goterl.lazycode.lazysodium.LazySodiumJava
+import com.goterl.lazycode.lazysodium.SodiumJava
+import com.goterl.lazycode.lazysodium.interfaces.Hash
+import com.goterl.lazycode.lazysodium.interfaces.Hash.State512
+import com.goterl.lazycode.lazysodium.interfaces.KeyExchange
+import com.goterl.lazycode.lazysodium.interfaces.Sign
+import com.goterl.lazycode.lazysodium.utils.Key
+
+internal actual object CryptoFactory {
+ internal actual fun getCrypto(): Crypto = CryptoJvmImpl
+}
+
+internal object CryptoJvmImpl : CryptoImpl() {
+
+ private val sodium = LazySodiumJava(SodiumJava())
+
+ override fun sha256(input: ByteArray): ByteArray {
+ val output = ByteArray(Hash.SHA256_BYTES)
+ sodium.cryptoHashSha256(output, input, input.size.toLong())
+ return output
+ }
+
+ override fun sha512(input: ByteArray): ByteArray {
+ val output = ByteArray(Hash.SHA512_BYTES)
+ sodium.cryptoHashSha512(output, input, input.size.toLong())
+ return output
+ }
+
+ override fun getHashSha512State(): HashSha512State {
+ return JvmHashSha512State()
+ }
+
+ override fun getRandomBytes(num: Int): ByteArray {
+ return sodium.randomBytesBuf(num)
+ }
+
+ override fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray {
+ return sodium.cryptoSignSeedKeypair(eddsaPrivateKey).publicKey.asBytes
+ }
+
+ override fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray {
+ return sodium.cryptoScalarMultBase(Key.fromBytes(ecdhePrivateKey)).asBytes
+ }
+
+ override fun createEddsaKeyPair(): EddsaKeyPair {
+ val privateKey = sodium.randomBytesBuf(KeyExchange.SEEDBYTES)
+ val publicKey = eddsaGetPublic(privateKey)
+ return EddsaKeyPair(privateKey, publicKey)
+ }
+
+ override fun createEcdheKeyPair(): EcdheKeyPair {
+ val privateKey = sodium.randomBytesBuf(KeyExchange.SEEDBYTES)
+ val publicKey = ecdheGetPublic(privateKey)
+ return EcdheKeyPair(privateKey, publicKey)
+ }
+
+ override fun eddsaSign(msg: ByteArray, eddsaPrivateKey: ByteArray): ByteArray {
+ val privateKey = sodium.cryptoSignSeedKeypair(eddsaPrivateKey).secretKey.asBytes
+ val signatureBytes = ByteArray(Sign.BYTES)
+ sodium.cryptoSignDetached(signatureBytes, msg, msg.size.toLong(), privateKey)
+ return signatureBytes
+ }
+
+ override fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean {
+ return sodium.cryptoSignVerifyDetached(sig, msg, msg.size, eddsaPub)
+ }
+
+ override fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray {
+ val ph = sha512(eddsaPrivateKey)
+ val a = ph.copyOfRange(0, 32)
+ val x = sodium.cryptoScalarMult(Key.fromBytes(a), Key.fromBytes(ecdhePublicKey)).asBytes
+ return sha512(x)
+ }
+
+ override fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray {
+ val curve25519Pub = ByteArray(KeyExchange.PUBLICKEYBYTES)
+ sodium.convertPublicKeyEd25519ToCurve25519(curve25519Pub, eddsaPublicKey)
+ val x = sodium.cryptoScalarMult(Key.fromBytes(ecdhePrivateKey), Key.fromBytes(curve25519Pub)).asBytes
+ return sha512(x)
+ }
+
+ override fun rsaBlind(hm: ByteArray, bks: ByteArray, rsaPubEnc: ByteArray): ByteArray {
+ return RsaBlinding.rsaBlind(hm, bks, rsaPubEnc)
+ }
+
+ override fun rsaUnblind(sig: ByteArray, rsaPubEnc: ByteArray, bks: ByteArray): ByteArray {
+ return RsaBlinding.rsaUnblind(sig, rsaPubEnc, bks)
+ }
+
+ override fun rsaVerify(hm: ByteArray, rsaSig: ByteArray, rsaPubEnc: ByteArray): Boolean {
+ return RsaBlinding.rsaVerify(hm, rsaSig, rsaPubEnc)
+ }
+
+ private class JvmHashSha512State : HashSha512State {
+ private val state = State512()
+
+ init {
+ check(sodium.cryptoHashSha512Init(state)) { "Error doing cryptoHashSha512Init" }
+ }
+
+ override fun update(data: ByteArray): HashSha512State {
+ sodium.cryptoHashSha512Update(state, data, data.size.toLong())
+ return this
+ }
+
+ override fun final(): ByteArray {
+ val output = ByteArray(Hash.SHA512_BYTES)
+ sodium.cryptoHashSha512Final(state, output)
+ return output
+ }
+
+ }
+
+}
diff --git a/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/RsaBlinding.kt b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/RsaBlinding.kt
new file mode 100644
index 0000000..458a089
--- /dev/null
+++ b/wallet/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/RsaBlinding.kt
@@ -0,0 +1,136 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.kotlin.crypto
+
+import java.math.BigInteger
+import kotlin.math.abs
+import kotlin.math.ceil
+import kotlin.math.floor
+
+internal object RsaBlinding {
+
+ fun rsaBlind(hm: ByteArray, bks: ByteArray, rsaPubEnc: ByteArray): ByteArray {
+ val rsaPub = rsaPubDecode(rsaPubEnc)
+ val data = rsaFullDomainHash(hm, rsaPub)
+ val r = rsaBlindingKeyDerive(rsaPub, bks)
+ val rE = r.modPow(rsaPub.e, rsaPub.n)
+ val bm = rE.multiply(data).mod(rsaPub.n)
+ return bm.toByteArrayWithoutSign()
+ }
+
+ fun rsaUnblind(sig: ByteArray, rsaPubEnc: ByteArray, bks: ByteArray): ByteArray {
+ val rsaPub = rsaPubDecode(rsaPubEnc)
+ val blindedSig = BigInteger(1, sig)
+ val r = rsaBlindingKeyDerive(rsaPub, bks)
+ val rInv = r.modInverse(rsaPub.n)
+ val s = blindedSig.multiply(rInv).mod(rsaPub.n)
+ return s.toByteArrayWithoutSign()
+ }
+
+ fun rsaVerify(hm: ByteArray, rsaSig: ByteArray, rsaPubEnc: ByteArray): Boolean {
+ val rsaPub = rsaPubDecode(rsaPubEnc)
+ val d = rsaFullDomainHash(hm, rsaPub)
+ val sig = BigInteger(1, rsaSig)
+ val sigE = sig.modPow(rsaPub.e, rsaPub.n)
+ return sigE == d
+ }
+
+ private fun rsaBlindingKeyDerive(rsaPub: RsaPublicKey, bks: ByteArray): BigInteger {
+ val salt = "Blinding KDF extrator HMAC key".encodeToByteArray()
+ val info = "Blinding KDF".encodeToByteArray()
+ return kdfMod(rsaPub.n, bks, salt, info)
+ }
+
+ private fun rsaPubDecode(publicKey: ByteArray): RsaPublicKey {
+ val modulusLength = abs((publicKey[0].toInt() shl 8) or publicKey[1].toInt())
+ val exponentLength = abs((publicKey[2].toInt() shl 8) or publicKey[3].toInt())
+ if (4 + exponentLength + modulusLength != publicKey.size) {
+ throw Error("invalid RSA public key (format wrong)")
+ }
+ val modulus = publicKey.copyOfRange(4, 4 + modulusLength)
+ val exponent = publicKey.copyOfRange(
+ 4 + modulusLength,
+ 4 + modulusLength + exponentLength
+ )
+ return RsaPublicKey(BigInteger(1, modulus), BigInteger(1, exponent))
+ }
+
+ private fun rsaFullDomainHash(hm: ByteArray, rsaPublicKey: RsaPublicKey): BigInteger {
+ val info = "RSA-FDA FTpsW!".encodeToByteArray()
+ val salt = rsaPubEncode(rsaPublicKey)
+ val r = kdfMod(rsaPublicKey.n, hm, salt, info)
+ rsaGcdValidate(r, rsaPublicKey.n)
+ return r
+ }
+
+ private fun rsaPubEncode(rsaPublicKey: RsaPublicKey): ByteArray {
+ val mb = rsaPublicKey.n.toByteArrayWithoutSign()
+ val eb = rsaPublicKey.e.toByteArrayWithoutSign()
+ val out = ByteArray(4 + mb.size + eb.size)
+ out[0] = ((mb.size ushr 8) and 0xff).toByte()
+ out[1] = (mb.size and 0xff).toByte()
+ out[2] = ((eb.size ushr 8) and 0xff).toByte()
+ out[3] = (eb.size and 0xff).toByte()
+ mb.copyInto(out, destinationOffset = 4)
+ eb.copyInto(out, destinationOffset = 4 + mb.size)
+ return out
+ }
+
+ private fun kdfMod(n: BigInteger, ikm: ByteArray, salt: ByteArray, info: ByteArray): BigInteger {
+ val nBits = n.bitLength()
+ val bufLen = floor((nBits.toDouble() - 1) / 8 + 1).toInt()
+ val mask = (1 shl (8 - (bufLen * 8 - nBits))) - 1
+ var counter = 0
+ while (true) {
+ val ctx = ByteArray(info.size + 2)
+ info.copyInto(ctx)
+ ctx[ctx.size - 2] = ((counter ushr 8) and 0xff).toByte()
+ ctx[ctx.size - 1] = (counter and 0xff).toByte()
+ val buf = CryptoJvmImpl.kdf(bufLen, ikm, salt, ctx)
+ val arr = buf.copyOf()
+ arr[0] = (arr[0].toInt() and mask).toByte()
+ val r = BigInteger(1, arr)
+ if (r < n) return r
+ counter++
+ }
+ }
+
+ /**
+ * Test for malicious RSA key.
+ *
+ * Assuming n is an RSA modulous and r is generated using a call to
+ * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a
+ * malicious RSA key designed to deanomize the user.
+ *
+ * @param r KDF result
+ * @param n RSA modulus of the public key
+ */
+ private fun rsaGcdValidate(r: BigInteger, n: BigInteger) {
+ if (r.gcd(n) != BigInteger.ONE) throw Error("malicious RSA public key")
+ }
+
+ // TODO check that this strips *only* the sign correctly
+ private fun BigInteger.toByteArrayWithoutSign(): ByteArray = this.toByteArray().let {
+ val byteLength = ceil(this.bitLength().toDouble() / 8).toInt()
+ val signBitPosition = ceil((this.bitLength() + 1).toDouble() / 8).toInt()
+ val start = signBitPosition - byteLength
+ it.copyOfRange(start, it.size) // stripping least significant byte (sign)
+ }
+
+}
+
+internal class RsaPublicKey(val n: BigInteger, val e: BigInteger)