/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package stdx.crypto.keys

import std.crypto.digest.Digest
import std.io.*
import std.math.numeric.BigInt
import std.sync.AtomicReference
import stdx.crypto.digest.*
import stdx.crypto.common.*

public enum PadOption {
    OAEP(OAEPOption) | PSS(PSSOption) | PKCS1
}

public struct OAEPOption {
    let label: String
    let hash: Digest
    let mgfHash: Digest

    public init(hash: Digest, mgfHash: Digest, label!: String = "") {
        this.label = label
        this.hash = hash
        this.mgfHash = mgfHash
    }
}

public struct PSSOption {
    let saltLen: Int32
    public init(saltLen: Int32) {
        if (saltLen < 0) {
            throw CryptoException("Salt length can not less than 0.")
        }
        this.saltLen = saltLen
    }
}

const RSA_MIN_MODULUS_BITS: Int32 = 512
const RSA_MAX_MODULUS_BITS: Int32 = 16384
let RSA_MIN_EXPONENT = BigInt(3)
let RSA_MAX_EXPONENT = BigInt(true, Array<Byte>(32, repeat: 255))

public class RSAPrivateKey <: PrivateKey {
    var pkey = CPointer<UInt64>()
    var evpKeyCtx: EVPKEYCTX

    init(blob: DerBlob) {
        pkey = loadPrivateKey(RSA_id, blob.body)
        if (pkey.isNull()) {
            throw CryptoException("Init RSA PrivateKey failed.")
        }
        evpKeyCtx = EVPKEYCTX(pkey)
    }

    public init(bits: Int32) {
        checkBits(bits)
        pkey = unsafe { generateRSA(bits, BigInt.parse("65537")) }
        if (pkey.isNull()) {
            throw CryptoException("Init RSA PrivateKey failed.")
        }
        evpKeyCtx = EVPKEYCTX(pkey)
    }

    public init(bits: Int32, e: BigInt) {
        checkBits(bits)
        checkPublicExponent(e)
        pkey = unsafe { generateRSA(bits, e) }
        if (pkey.isNull()) {
            throw CryptoException("Init RSA PrivateKey failed.")
        }
        evpKeyCtx = EVPKEYCTX(pkey)
    }

    public func encodeToDer(password!: ?String): DerBlob {
        var blob: DerBlob = encodeToDer()
        match (password) {
            case Some(password) => try {
                var key = GeneralPrivateKey.decodeDer(blob)
                key.encodeToDer(password: password)
            } catch (e: CryptoException) {
                throw CryptoException(e.message)
            }
            case None => blob
        }
    }

    public func encodeToDer(): DerBlob {
        var content = getPrivateKeyDer(pkey)
        keepAlive(this)
        DerBlob(content)
    }

    public func encodeToPem(): PemEntry {
        PemEntry(PemEntry.LABEL_RSA_PRIVATE_KEY, encodeToDer())
    }

    /**
     * Encode the key to PemEntry optionally doing encryption using the specified password if any
     * If the passord is None, then the key will be encoded unencrypted.
     * An encrypted key produced by this function is always in PKCS8 format.
     * @throws CryptoException if failed to encode/encrypt or the provided password is empty
     */
    public func encodeToPem(password!: ?String): PemEntry {
        match (password) {
            case Some(password) => PemEntry(PemEntry.LABEL_ENCRYPTED_PRIVATE_KEY, encodeToDer(password: password))
            case None => encodeToPem()
        }
    }

    public static func decodeDer(blob: DerBlob): RSAPrivateKey {
        RSAPrivateKey(blob)
    }

    public static func decodeDer(blob: DerBlob, password!: ?String): RSAPrivateKey {
        match (password) {
            case Some(password) => try {
                var priKey = GeneralPrivateKey.decodeDer(blob, password: password)
                RSAPrivateKey(priKey.encodeToDer())
            } catch (e: CryptoException) {
                throw CryptoException(e.message)
            }
            case None => RSAPrivateKey(blob)
        }
    }

    public static func decodeFromPem(text: String): RSAPrivateKey {
        decodeFromPem(text, password: None)
    }

    public static func decodeFromPem(
        text: String,
        password!: ?String
    ): RSAPrivateKey {
        try {
            var priKey = GeneralPrivateKey.decodeFromPem(text, password: password)
            RSAPrivateKey(priKey.encodeToDer())
        } catch (e: CryptoException) {
            throw CryptoException(e.message)
        }
    }

    public func sign(hash: Digest, digest: Array<Byte>, padType!: PadOption): Array<Byte> {
        let result = match (padType) {
            case PKCS1 => signPKCS1(evpKeyCtx, pkey, hash, digest)
            case PSS(pss) => signPSS(evpKeyCtx, pkey, hash, digest, pss.saltLen)
            case OAEP(_) => throw CryptoException("OAEPOption only use in encrypt or decrypt.")
        }
        keepAlive(this)
        result
    }

    public func decrypt(input: InputStream, output: OutputStream, padType!: PadOption): Unit {
        match (padType) {
            case PKCS1 => unsafe { decryptPKCS1(evpKeyCtx, pkey, input, output) }
            case OAEP(oaep) => unsafe { decryptOAEP(evpKeyCtx, pkey, input, output, oaep.label, oaep.hash, oaep.mgfHash) }
            case PSS(_) => throw CryptoException("PSSOption only use in sign or verify.")
        }
        keepAlive(this)
    }

    public override func toString(): String {
        "RSA PRIVATE KEY"
    }

    ~init() {
        if (!pkey.isNull()) {
            keyFree(pkey)
        }
    }
}

public class RSAPublicKey <: PublicKey {
    var pkey: CPointer<UInt64>
    var evpKeyCtx: EVPKEYCTX

    init(blob: DerBlob) {
        pkey = loadPublicKey(RSA_id, blob.body)
        if (pkey.isNull()) {
            throw CryptoException("Init RSA PublicKey failed.")
        }
        evpKeyCtx = EVPKEYCTX(pkey)
    }

    public init(pri: RSAPrivateKey) {
        var content = getPublicKeyDer(pri.pkey)
        pkey = loadPublicKey(RSA_id, content)
        if (pkey.isNull()) {
            throw CryptoException("Init RSA PublicKey failed.")
        }
        evpKeyCtx = EVPKEYCTX(pkey)
    }

    public func encodeToDer(): DerBlob {
        var content = getPublicKeyDer(pkey)
        keepAlive(this)
        DerBlob(content)
    }
    public func encodeToPem(): PemEntry {
        PemEntry(PemEntry.LABEL_PUBLIC_KEY, encodeToDer())
    }

    public static func decodeDer(blob: DerBlob): RSAPublicKey {
        RSAPublicKey(blob)
    }

    public static func decodeFromPem(text: String): RSAPublicKey {
        try {
            var pubKey = GeneralPublicKey.decodeFromPem(text)
            RSAPublicKey(pubKey.encodeToDer())
        } catch (e: CryptoException | CryptoException) {
            throw CryptoException(e.message)
        }
    }

    public func verify(hash: Digest, digest: Array<Byte>, sig: Array<Byte>, padType!: PadOption): Bool {
        let result = match (padType) {
            case PKCS1 => verifyPKCS1(evpKeyCtx, hash, digest, sig)
            case PSS(pss) => verifyPSS(evpKeyCtx, hash, digest, sig, pss.saltLen)
            case OAEP(_) => throw CryptoException("OAEPOption only use in encrypt or decrypt.")
        }
        result
    }

    public func encrypt(input: InputStream, output: OutputStream, padType!: PadOption): Unit {
        match (padType) {
            case PKCS1 => unsafe { encryptPKCS1(evpKeyCtx, pkey, input, output) }
            case OAEP(oaep) => unsafe { encryptOAEP(evpKeyCtx, pkey, input, output, oaep.label, oaep.hash, oaep.mgfHash) }
            case PSS(_) => throw CryptoException("PSSOption only use in sign or verify.")
        }
        keepAlive(this)
    }

    public override func toString(): String {
        "RSA PUBLIC KEY"
    }

    ~init() {
        if (!pkey.isNull()) {
            keyFree(pkey)
        }
    }
}

func checkBits(bits: Int32) {
    if (bits < RSA_MIN_MODULUS_BITS) {
        throw CryptoException("Key size too small, low security level.")
    }
    if (bits > RSA_MAX_MODULUS_BITS) {
        throw CryptoException("Key size too long.")
    }
}

func checkPublicExponent(e: BigInt) {
    let lastBit = e & BigInt(1)
    if (lastBit == BigInt(0)) {
        throw CryptoException("Public exponent value is invalid because it is an even number.")
    }
    if (e < RSA_MIN_EXPONENT) {
        throw CryptoException("Public exponent value is too small.")
    }
    if (e > RSA_MAX_EXPONENT) {
        throw CryptoException("Public exponent value is too large.")
    }
}

unsafe func generateRSA(bits: Int32, e: BigInt) {
    let engine = CPointer<UInt64>()
    var pkey = CPointer<UInt64>()
    var exp = CPointer<BIGNUM>()
    var ctx = keyCtxNewId(RSA_id, engine)
    if (ctx.isNull()) {
        throw CryptoException("Init RSA PrivateKey failed.")
    }
    let binPtr: CPointerHandle<Byte> = acquireArrayRawData(e.toBytes())
    try (ppkey = LibC.malloc<CPointer<UInt64>>().asResource()) {
        if (ppkey.value.isNull()) {
            throw CryptoException("Init RSA PrivateKey failed, malloc failed.")
        }

        var ret = keygenInit(ctx)
        if (ret != 1) {
            throw CryptoException("Init RSA PrivateKey failed.")
        }
        ret = setRSABits(ctx, bits)
        if (ret != 1) {
            throw CryptoException("Set RSA bits error.")
        }
        exp = bin2bn(binPtr.pointer, Int32(e.toBytes().size), exp)
        if (exp.isNull()) {
            throw CryptoException("Set RSA public exponent error.")
        }
        ret = setRSAPubExp(ctx, exp)
        if (ret != 1) {
            throw CryptoException("Set RSA public exponent error.")
        }
        ppkey.value.write(pkey)
        ret = keyGenerate(ctx, ppkey.value)
        if (ret != 1) {
            throw CryptoException("Generate RSA PrivateKey failed.")
        }
        pkey = ppkey.value.read()
    } finally {
        bnFree(exp)
        keyCtxFree(ctx)
        releaseArrayRawData(binPtr)
    }
    pkey
}

func getPrivateKeyDer(pkey: CPointer<UInt64>) {
    unsafe {
        var keySize = getSize(pkey)
        var readBuf = Array<Byte>(Int64(keySize * 8), repeat: 0)
        let read: CPointerHandle<Byte> = acquireArrayRawData(readBuf)
        var readLen: Int32
        var readPtr = LibC.malloc<CPointer<Byte>>()
        if (readPtr.isNull()) {
            releaseArrayRawData(read)
            throw CryptoException("Init PrivateKey failed.")
        }
        readPtr.write(read.pointer)
        try {
            readLen = privateKey2d(pkey, readPtr)
            if (readLen < 0 || Int64(readLen) > readBuf.size) {
                throw CryptoException("Fail to load private key.")
            }
        } finally {
            LibC.free(readPtr)
            releaseArrayRawData(read)
        }
        readBuf[0..Int64(readLen)]
    }
}

func getPublicKeyDer(pkey: CPointer<UInt64>) {
    unsafe {
        var keySize = getSize(pkey)
        var readBuf = Array<Byte>(Int64(keySize * 2), repeat: 0)
        let read: CPointerHandle<Byte> = acquireArrayRawData(readBuf)
        var readLen: Int32
        var readPtr = LibC.malloc<CPointer<Byte>>()
        if (readPtr.isNull()) {
            releaseArrayRawData(read)
            throw CryptoException("Init PublicKey failed.")
        }
        readPtr.write(read.pointer)
        try {
            readLen = pubKey2d(pkey, readPtr)
            if (readLen < 0 || Int64(readLen) > readBuf.size) {
                throw CryptoException("Fail to load public key.")
            }
        } finally {
            LibC.free(readPtr)
            releaseArrayRawData(read)
        }
        readBuf[0..Int64(readLen)]
    }
}

func loadPrivateKey(id: Int32, der: Array<Byte>) {
    unsafe {
        let derHandle: CPointerHandle<Byte> = acquireArrayRawData(der)
        var pkey = CPointer<UInt64>()
        var ppkey = CPointer<CPointer<UInt64>>()
        var readPtr = LibC.malloc<CPointer<Byte>>()
        if (readPtr.isNull()) {
            releaseArrayRawData(derHandle)
            throw CryptoException("Init PrivateKey failed.")
        }
        readPtr.write(derHandle.pointer)
        try {
            pkey = d2PrivateKey(id, ppkey, readPtr, der.size)
            if (pkey.isNull()) {
                throw CryptoException("Load private key error.")
            }
            if (!keyTypeCheck(id, pkey)) {
                keyFree(pkey)
                throw CryptoException("Private key type error.")
            }
        } finally {
            LibC.free(readPtr)
            releaseArrayRawData(derHandle)
        }
        pkey
    }
}

func loadPublicKey(id: Int32, der: Array<Byte>) {
    unsafe {
        let derHandle: CPointerHandle<Byte> = acquireArrayRawData(der)
        var pkey = CPointer<UInt64>()
        var ppkey = CPointer<CPointer<UInt64>>()
        var readPtr = LibC.malloc<CPointer<Byte>>()
        if (readPtr.isNull()) {
            releaseArrayRawData(derHandle)
            throw CryptoException("Init PublicKey failed.")
        }
        readPtr.write(derHandle.pointer)
        try {
            pkey = d2Pubkey(ppkey, readPtr, der.size)
            if (pkey.isNull()) {
                throw CryptoException("Load PublicKey error.")
            }
            if (!keyTypeCheck(id, pkey)) {
                keyFree(pkey)
                throw CryptoException("Public key type error.")
            }
        } finally {
            LibC.free(readPtr)
            releaseArrayRawData(derHandle)
        }
        return pkey
    }
}

func keyTypeCheck(id: Int32, pkey: CPointer<UInt64>) {
    let pkeyId = getPkeyId(pkey)
    if (id == pkeyId) {
        return true
    }
    // if id is sm2 and getPkeyId return -1(find Provider Key failed) need check name again
    if (pkeyId == -1 && id == NID_sm2) {
        if (pkeyIs(pkey, "SM2")) { // SM2 is type name
            return true
        }
    }
    false
}

func getDigest(hash: Digest) {
    var md = match (hash) {
        case _: MD5 => md5()
        case _: SHA1 => sha1()
        case _: SHA224 => sha224()
        case _: SHA256 => sha256()
        case _: SHA384 => sha384()
        case _: SHA512 => sha512()
        case _: SM3 => sm3()
        case _ => throw CryptoException("Digest type error.")
    }
    return md
}

func signPKCS1(ctx: EVPKEYCTX, pkey: CPointer<UInt64>, hash: Digest, data: Array<Byte>) {
    unsafe {
        var rsaSize = Int64(getSize(pkey))
        var sig: Array<UInt8> = Array<UInt8>(Int64(rsaSize), repeat: 0)
        let sigHandle: CPointerHandle<Byte> = acquireArrayRawData(sig)
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        try (sizePtr = LibC.malloc<UIntNative>().asResource()) {
            if (sizePtr.value.isNull()) {
                throw CryptoException("Sign init error, malloc failed.")
            }

            sizePtr.value.write(UIntNative(rsaSize))
            var ret = signInit(ctx.ptr)
            if (ret <= 0) {
                throw CryptoException("Sign init error.")
            }
            ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PADDING)
            if (ret <= 0) {
                throw CryptoException("Set rsa padding error.")
            }
            ret = setSignatureMD(ctx.ptr, getDigest(hash))
            if (ret <= 0) {
                throw CryptoException("Set signature md error.")
            }
            ret = sign(ctx.ptr, sigHandle.pointer, sizePtr.value, dataHandle.pointer, UIntNative(data.size))
            if (ret != 1 || sizePtr.value.read() > UIntNative(rsaSize)) {
                throw CryptoException("Sign error.")
            }
            rsaSize = Int64(sizePtr.value.read())
        } finally {
            releaseArrayRawData(sigHandle)
            releaseArrayRawData(dataHandle)
        }
        sig[0..rsaSize]
    }
}

func signPSS(ctx: EVPKEYCTX, pkey: CPointer<UInt64>, hash: Digest, data: Array<Byte>, saltLen: Int32) {
    unsafe {
        var rsaSize = Int64(getSize(pkey))
        var sig: Array<UInt8> = Array<UInt8>(rsaSize, repeat: 0)
        let sigHandle: CPointerHandle<Byte> = acquireArrayRawData(sig)
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        try (sizePtr = LibC.malloc<UIntNative>().asResource()) {
            if (sizePtr.value.isNull()) {
                throw CryptoException("Sign init error, malloc failed.")
            }

            sizePtr.value.write(UIntNative(rsaSize))
            var ret = signInit(ctx.ptr)
            if (ret <= 0) {
                throw CryptoException("Sign init error.")
            }
            ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PSS_PADDING)
            if (ret <= 0) {
                throw CryptoException("Set rsa padding error.")
            }
            ret = setSignatureMD(ctx.ptr, getDigest(hash))
            if (ret <= 0) {
                throw CryptoException("Set signature md error.")
            }
            ret = setRSAPssSaltLen(ctx.ptr, saltLen)
            if (ret <= 0) {
                throw CryptoException("Set rsa pss saltlen error.")
            }
            ret = sign(ctx.ptr, sigHandle.pointer, sizePtr.value, dataHandle.pointer, UIntNative(data.size))
            if (ret != 1 || sizePtr.value.read() > UIntNative(rsaSize)) {
                throw CryptoException("Sign error.")
            }
            rsaSize = Int64(sizePtr.value.read())
        } finally {
            releaseArrayRawData(sigHandle)
            releaseArrayRawData(dataHandle)
        }
        sig[0..rsaSize]
    }
}

func verifyPKCS1(ctx: EVPKEYCTX, hash: Digest, data: Array<Byte>, sig: Array<Byte>) {
    unsafe {
        let sigHandle: CPointerHandle<Byte> = acquireArrayRawData(sig)
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        var result: Int32 = 0
        try {
            var ret = verifyInit(ctx.ptr)
            if (ret <= 0) {
                throw CryptoException("Verify init error.")
            }
            ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PADDING)
            if (ret <= 0) {
                throw CryptoException("Set rsa padding error.")
            }
            ret = setSignatureMD(ctx.ptr, getDigest(hash))
            if (ret <= 0) {
                throw CryptoException("Set signature md error.")
            }
            result = verify(ctx.ptr, sigHandle.pointer, UIntNative(sig.size), dataHandle.pointer, UIntNative(data.size))
        } finally {
            releaseArrayRawData(sigHandle)
            releaseArrayRawData(dataHandle)
        }
        if (result != 1) {
            return false
        } else {
            return true
        }
    }
}

func verifyPSS(
    ctx: EVPKEYCTX,
    hash: Digest,
    data: Array<Byte>,
    sig: Array<Byte>,
    saltLen: Int32
) {
    unsafe {
        let sigHandle: CPointerHandle<Byte> = acquireArrayRawData(sig)
        let dataHandle: CPointerHandle<Byte> = acquireArrayRawData(data)
        var result: Int32 = 0
        try {
            var ret = verifyInit(ctx.ptr)
            if (ret <= 0) {
                throw CryptoException("Verify init error.")
            }
            ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PSS_PADDING)
            if (ret <= 0) {
                throw CryptoException("Set rsa padding error.")
            }
            ret = setSignatureMD(ctx.ptr, getDigest(hash))
            if (ret <= 0) {
                throw CryptoException("Set signature md error.")
            }
            ret = setRSAPssSaltLen(ctx.ptr, saltLen)
            if (ret <= 0) {
                throw CryptoException("Set rsa pss saltlen error.")
            }
            result = verify(ctx.ptr, sigHandle.pointer, UIntNative(sig.size), dataHandle.pointer, UIntNative(data.size))
        } finally {
            releaseArrayRawData(sigHandle)
            releaseArrayRawData(dataHandle)
        }
        if (result != 1) {
            return false
        } else {
            return true
        }
    }
}

unsafe func encryptPKCS1(ctx: EVPKEYCTX, pkey: CPointer<UInt64>, input: InputStream, output: OutputStream) {
    var keySize = getSize(pkey)
    var blockSize = Int64(keySize - PKCS1_PADDING_SIZE)
    if (blockSize < 0) {
        throw CryptoException("Encounter invalid block size.")
    }
    var readbuff = Array<Byte>(blockSize, repeat: 0)
    var writebuff = Array<Byte>(Int64(keySize), repeat: 0)
    var inBlock: CPointerHandle<Byte> = acquireArrayRawData(readbuff)
    var outBlock: CPointerHandle<Byte> = acquireArrayRawData(writebuff)
    try (outlenPtr = LibC.malloc<UIntNative>().asResource()) {
        if (outlenPtr.value.isNull()) {
            throw CryptoException("Encrypt init error, malloc failed.")
        }

        var ret = encryInit(ctx.ptr)
        if (ret <= 0) {
            throw CryptoException("Encrypt init error.")
        }
        ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PADDING)
        if (ret <= 0) {
            throw CryptoException("Set rsa padding type error.")
        }
        var inLen = UIntNative(input.read(readbuff))
        do {
            outlenPtr.value.write(UIntNative(keySize))
            ret = encrypt(ctx.ptr, outBlock.pointer, outlenPtr.value, inBlock.pointer, inLen)
            if (ret != 1 || outlenPtr.value.read() > UIntNative(keySize)) {
                throw CryptoException("Encrypt error.")
            }
            output.write(writebuff[..Int64(outlenPtr.value.read())])
            inLen = UIntNative(input.read(readbuff))
        } while (inLen > 0)
    } finally {
        releaseArrayRawData(inBlock)
        releaseArrayRawData(outBlock)
    }
}

unsafe func encryptOAEP(
    ctx: EVPKEYCTX,
    pkey: CPointer<UInt64>,
    input: InputStream,
    output: OutputStream,
    label: String,
    hash: Digest,
    mgfHash: Digest
) {
    var keySize = getSize(pkey)
    var blockSize = Int64(keySize) - 2 * hash.size - 2
    if (blockSize < 0) {
        throw CryptoException("RSA_key_size smaller that (2 * hash_size - 2).")
    }
    var readbuff = Array<Byte>(blockSize, repeat: 0)
    var writebuff = Array<Byte>(Int64(keySize), repeat: 0)
    var inBlock: CPointerHandle<Byte> = acquireArrayRawData(readbuff)
    var outBlock: CPointerHandle<Byte> = acquireArrayRawData(writebuff)
    try (outlenPtr = LibC.malloc<UIntNative>().asResource(), lablePtr = LibC.mallocCString(label).asResource()) {
        if (outlenPtr.value.isNull() || lablePtr.value.isNull()) {
            throw CryptoException("Encrypt init error, malloc failed.")
        }

        var ret = encryInit(ctx.ptr)
        if (ret <= 0) {
            throw CryptoException("Encrypt init error.")
        }
        ret = setRSAPadding(ctx.ptr, RSA_PKCS1_OAEP_PADDING)
        if (ret <= 0) {
            throw CryptoException("Set rsa padding type error.")
        }
        ret = keysOAEPSetting(ctx.ptr, lablePtr.value, getDigest(hash), getDigest(mgfHash))
        if (ret == -1) {
            throw CryptoException("Set rsa OAEP option error.")
        }
        var inLen = UIntNative(input.read(readbuff))
        do {
            outlenPtr.value.write(UIntNative(keySize))
            ret = encrypt(ctx.ptr, outBlock.pointer, outlenPtr.value, inBlock.pointer, inLen)
            if (ret != 1 || outlenPtr.value.read() > UIntNative(keySize)) {
                throw CryptoException("Encrypt error.")
            }
            output.write(writebuff[..Int64(outlenPtr.value.read())])
            inLen = UIntNative(input.read(readbuff))
        } while (inLen > 0)
    } finally {
        releaseArrayRawData(inBlock)
        releaseArrayRawData(outBlock)
    }
}

unsafe func decryptPKCS1(ctx: EVPKEYCTX, pkey: CPointer<UInt64>, input: InputStream, output: OutputStream) {
    var keySize = getSize(pkey)
    var blockSize = Int64(keySize)
    var readbuff = Array<Byte>(blockSize, repeat: 0)
    var writebuff = Array<Byte>(Int64(keySize), repeat: 0)
    var inBlock: CPointerHandle<Byte> = acquireArrayRawData(readbuff)
    var outBlock: CPointerHandle<Byte> = acquireArrayRawData(writebuff)
    try (outlenPtr = LibC.malloc<UIntNative>().asResource()) {
        if (outlenPtr.value.isNull()) {
            throw CryptoException("Decrypt init error, malloc failed.")
        }

        var ret = decryptInit(ctx.ptr)
        if (ret <= 0) {
            throw CryptoException("Decrypt init error.")
        }
        ret = setRSAPadding(ctx.ptr, RSA_PKCS1_PADDING)
        if (ret <= 0) {
            throw CryptoException("Set rsa padding error.")
        }

        var inLen = UIntNative(input.read(readbuff))
        while (inLen > 0) {
            outlenPtr.value.write(UIntNative(keySize))
            ret = decrypt(ctx.ptr, outBlock.pointer, outlenPtr.value, inBlock.pointer, inLen)
            if (ret != 1 || outlenPtr.value.read() > UIntNative(keySize)) {
                throw CryptoException("Decrypt error.")
            }
            output.write(writebuff[..Int64(outlenPtr.value.read())])
            inLen = UIntNative(input.read(readbuff))
        }
    } finally {
        releaseArrayRawData(inBlock)
        releaseArrayRawData(outBlock)
    }
}

unsafe func decryptOAEP(
    ctx: EVPKEYCTX,
    pkey: CPointer<UInt64>,
    input: InputStream,
    output: OutputStream,
    label: String,
    hash: Digest,
    mgfHash: Digest
) {
    var keySize = getSize(pkey)
    var blockSize = Int64(keySize)
    var readbuff = Array<Byte>(blockSize, repeat: 0)
    var writebuff = Array<Byte>(Int64(keySize), repeat: 0)
    var inBlock: CPointerHandle<Byte> = acquireArrayRawData(readbuff)
    var outBlock: CPointerHandle<Byte> = acquireArrayRawData(writebuff)
    try (outlenPtr = LibC.malloc<UIntNative>().asResource(), lablePtr = LibC.mallocCString(label).asResource()) {
        if (outlenPtr.value.isNull() || lablePtr.value.isNull()) {
            throw CryptoException("Decrypt init error, malloc failed.")
        }

        var ret = decryptInit(ctx.ptr)
        if (ret <= 0) {
            throw CryptoException("Decrypt init error.")
        }
        ret = setRSAPadding(ctx.ptr, RSA_PKCS1_OAEP_PADDING)
        if (ret <= 0) {
            throw CryptoException("Set rsa padding type error.")
        }
        ret = keysOAEPSetting(ctx.ptr, lablePtr.value, getDigest(hash), getDigest(mgfHash))
        if (ret == -1) {
            throw CryptoException("Set rsa OAEP option error.")
        }
        var inLen = UIntNative(input.read(readbuff))
        while (inLen > 0) {
            outlenPtr.value.write(UIntNative(keySize))
            ret = decrypt(ctx.ptr, outBlock.pointer, outlenPtr.value, inBlock.pointer, inLen)
            if (ret != 1 || outlenPtr.value.read() > UIntNative(keySize)) {
                throw CryptoException("Decrypt error.")
            }
            output.write(writebuff[..Int64(outlenPtr.value.read())])
            inLen = UIntNative(input.read(readbuff))
        }
    } finally {
        releaseArrayRawData(inBlock)
        releaseArrayRawData(outBlock)
    }
}

let _privateKeyRsaPhantom = Object()
let _rsaPrivateKey = AtomicReference<Object>(_privateKeyRsaPhantom)

func keepAlive(o: RSAPrivateKey) {
    _rsaPrivateKey.store(o)
    _rsaPrivateKey.store(_privateKeyRsaPhantom)
}

let _publicKeyRsaPhantom = Object()
let _rsaPublicKey = AtomicReference<Object>(_publicKeyRsaPhantom)

func keepAlive(o: RSAPublicKey) {
    _rsaPublicKey.store(o)
    _rsaPublicKey.store(_publicKeyRsaPhantom)
}