Thanh nguyen
03/08/2024, 4:20 AMStefan Oltmann
03/08/2024, 5:57 AMMatt Nelson
03/11/2024, 1:22 PMiOS
, use them on Jvm/Android.
Darwin generates PKCS#1 encoded RSA keys, but also accepts X509 pubkeys when instantiating the SecKeyRef
. If you take the public key generated by darwin target and try to load it for Jvm, it will fail. Must manipulate the DER encoding to transform from PKCS#1 to X509.Matt Nelson
03/11/2024, 1:26 PMinternal class RSAKey private constructor() {
@JvmInline
internal value class Private(private val key: ByteArray) {
internal fun <R: Any?> use(block: (key: ByteArray) -> R): R {
val copy = key.copyOf()
val result = try {
block(copy)
} finally {
copy.fill(0)
}
return result
}
@Throws(GeneralSecurityException::class)
internal fun <R: Any?> usePKCS8(block: (key: ByteArray) -> R): R {
val copy = if (PKCS1offset() != 0) {
// Assume it's already PKCS#8
key.copyOf()
} else {
val kLen = key.size.toDERLen()
// Need to pull that version byte from the PKCS#1 encoding
// and use it in the PKCS#8 encoding.
//
// PKCS#1 DER encoding looks like the following.
// SEQUENCE (tag: 48), key length bytes (2 to 5 bytes)
// INTEGER (tag: 02), length byte (1 byte), version byte
val v = key[1 + kLen.lastIndex + 1 + 1 + 1]
val header = byteArrayOf(
INTEGER, 1, v,
*PKCS8,
*kLen,
)
val derLen = (header.size + key.size).toDERLen()
byteArrayOf(SEQUENCE) + derLen + header + key
}
val result = try {
block(copy)
} finally {
copy.fill(0)
}
return result
}
@Throws(GeneralSecurityException::class)
internal fun <R: Any?> withPKCS1offset(block: (key: ByteArray, offset: Int) -> R): R {
val offset = PKCS1offset()
val copy = key.copyOf()
val result = try {
block(copy, offset)
} finally {
copy.fill(0)
}
return result
}
@Throws(GeneralSecurityException::class)
private fun PKCS1offset(): Int {
return try {
var index = 0
if (key[index++] != SEQUENCE) {
throw GeneralSecurityException("DER decoding failure. RSAPrivateKey is not DER encoded.")
}
// skip over DER length bytes
index += key[index].toDERLenCount()
// INTEGER, length (1 byte)
if (key[index++] != INTEGER && key[index++] != 1.toByte()) {
throw GeneralSecurityException("DER decoding failure. Could not determine version.")
}
// skip over version byte
index += 2
// Assume PKCS#1 (no offset)
if (key[index] == INTEGER) return 0
for (i in PKCS8.indices) {
if (PKCS8[i] == key[index + i]) continue
// not PKCS#8, return no offset
return 0
}
index += PKCS8.size
// skip over key length bytes
index += key[index].toDERLenCount()
// Index at which the PKCS#1 encoded RSA PrivateKey begins
index
} catch (e: IndexOutOfBoundsException) {
throw GeneralSecurityException("Failed to parse RSAPrivateKey", e)
}
}
private companion object {
private val PKCS8 = byteArrayOf(
SEQUENCE,
*OID_RSA,
NULL,
OCTET_STRING,
)
}
}
@JvmInline
internal value class Public(private val key: ByteArray) {
@Throws(GeneralSecurityException::class)
internal fun <R: Any?> useX509(block: (key: ByteArray) -> R): R {
val copy = try {
var index = 0
if (key[index++] != SEQUENCE) {
throw GeneralSecurityException("DER decoding failure. RSAPublicKey is not DER encoded.")
}
// skip over DER length bytes
index += key[index].toDERLenCount()
when (key[index]) {
// Assume encoded as PKCS#1
INTEGER -> {
// NULL + key.size
val kLen = (1 + key.size).toDERLen()
val header = byteArrayOf(
SEQUENCE,
*OID_RSA,
NULL,
BIT_STRING,
*kLen,
NULL,
)
val derLen = (header.size + key.size).toDERLen()
byteArrayOf(SEQUENCE) + derLen + header + key
}
// Assume encoded as X509
SEQUENCE -> {
key.copyOf()
}
else -> {
throw GeneralSecurityException("DER decoding failure. Failed to determine DER encoding format.")
}
}
} catch (e: IndexOutOfBoundsException) {
throw GeneralSecurityException("Failed to parse RSAPublicKey", e)
}
val result = try {
block(copy)
} finally {
copy.fill(0)
}
return result
}
}
private companion object {
private const val BIT_STRING: Byte = 3
private const val INTEGER: Byte = 2
private const val NULL: Byte = 0
private const val OCTET_STRING: Byte = 4
private const val OID: Byte = 6
private const val SEQUENCE: Byte = 48
private val OID_RSA = byteArrayOf(13, OID, 9, 42, -122, 72, -122, -9, 13, 1, 1, 1, 5)
@JvmStatic
private fun Int.toDERLen(): ByteArray {
var length = this
if (length <= Byte.MAX_VALUE) {
return ByteArray(1).apply { this[0] = length.toByte() }
}
val stack = ByteArray(5)
var pos = stack.size
do {
stack[--pos] = length.toByte()
length = length ushr 8
} while (length != 0)
val count = stack.size - pos
stack[--pos] = (0x80 or count).toByte()
if (pos == 0) return stack
return stack.copyOfRange(fromIndex = pos, toIndex = stack.size)
}
@Throws(GeneralSecurityException::class)
private fun Byte.toDERLenCount(): Int {
return if (this < 0) {
val c = 1 + (this xor Byte.MIN_VALUE)
// Maximum 5 bytes
if (c > 5) throw GeneralSecurityException("Invalid DER length")
c
} else {
1
}
}
}
}
Oleg Yukhnevich
03/11/2024, 1:28 PM