diff options
Diffstat (limited to 'wallet/src/androidMain/kotlin/net/taler')
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) |