nim-libp2p-experimental/libp2p/crypto/rsa.nim

343 lines
13 KiB
Nim

import strutils, hexdump, nimcrypto/utils
import common
const
DefaultPublicExponent = 3'u32
type
RsaPrivateKey* = ref object
buffer*: seq[byte]
key*: BrRsaPrivateKey
RsaPublicKey* = ref object
buffer*: seq[byte]
key*: BrRsaPublicKey
RsaKeyPair* = object
seckey*: RsaPrivateKey
pubkey*: RsaPublicKey
RsaModulus* = seq[byte]
RsaPublicExponent* = uint32
RsaPrivateExponent* = seq[byte]
RsaError = object of Exception
RsaRngError = object of RsaError
RsaGenError = object of RsaError
RsaIncorrectKeyError = object of RsaError
proc random*(t: typedesc[RsaKeyPair], bits: int): RsaKeyPair =
## Generate new random RSA key pair (private key and public key) using
## BearSSL's HMAC-SHA256-DRBG algorithm.
##
## ``bits`` number of bits in RSA key, must be in range [512, 4096].
var rng: BrHmacDrbgContext
var keygen: BrRsaKeygen
var seeder = brPrngSeederSystem(nil)
brHmacDrbgInit(addr rng, addr sha256Vtable, nil, 0)
if seeder(addr rng.vtable) == 0:
raise newException(ValueError, "Could not seed RNG")
keygen = brRsaKeygenGetDefault()
result.seckey = new RsaPrivateKey
result.pubkey = new RsaPublicKey
result.seckey.buffer = newSeq[byte](brRsaPrivateKeyBufferSize(bits))
result.pubkey.buffer = newSeq[byte](brRsaPublicKeyBufferSize(bits))
if keygen(addr rng.vtable,
addr result.seckey.key, addr result.seckey.buffer[0],
addr result.pubkey.key, addr result.pubkey.buffer[0],
cuint(bits), DefaultPublicExponent) == 0:
raise newException(RsaGenError, "Could not create keypair")
proc random*(t: typedesc[RsaPrivateKey], bits: int): RsaPrivateKey =
## Generate new random RSA private key using BearSSL's HMAC-SHA256-DRBG
## algorithm.
##
## ``bits`` number of bits in RSA key, must be in range [512, 4096].
var rng: BrHmacDrbgContext
var keygen: BrRsaKeygen
var seeder = brPrngSeederSystem(nil)
brHmacDrbgInit(addr rng, addr sha256Vtable, nil, 0)
if seeder(addr rng.vtable) == 0:
raise newException(RsaRngError, "Could not seed RNG")
keygen = brRsaKeygenGetDefault()
result = new RsaPrivateKey
result.buffer = newSeq[byte](brRsaPrivateKeyBufferSize(bits))
if keygen(addr rng.vtable,
addr result.seckey.key, addr result.seckey.buffer[0],
nil, nil,
cuint(bits), DefaultPublicExponent) == 0:
raise newException(RsaGenError, "Could not create private key")
proc modulus*(seckey: RsaPrivateKey): RsaModulus =
## Calculate and return RSA modulus.
let bytelen = (seckey.key.nBitlen + 7) shr 3
let compute = brRsaComputeModulusGetDefault()
result = newSeq[byte](bytelen)
let length = compute(addr result[0], unsafeAddr seckey.key)
assert(length == len(result), $length)
proc pubexp*(seckey: RsaPrivateKey): RsaPublicExponent =
## Calculate and return RSA public exponent.
let compute = brRsaComputePubexpGetDefault()
result = compute(unsafeAddr seckey.key)
proc privexp*(seckey: RsaPrivateKey,
pubexp: RsaPublicExponent): RsaPrivateExponent =
## Calculate and return RSA private exponent.
let bytelen = (seckey.key.nBitlen + 7) shr 3
let compute = brRsaComputePrivexpGetDefault()
result = newSeq[byte](bytelen)
let length = compute(addr result[0], unsafeAddr seckey.key, pubexp)
assert(length == len(result), $length)
proc getKey*(seckey: RsaPrivateKey): RsaPublicKey =
## Calculate and return RSA public key from RSA private key ``seckey``.
var ebuf: array[4, byte]
var modulus = seckey.modulus()
var pubexp = seckey.pubexp()
let modlen = len(modulus)
result = new RsaPublicKey
result.buffer = newSeq[byte](modlen + sizeof(ebuf))
result.key.n = cast[ptr cuchar](addr result.buffer[0])
result.key.nlen = modlen
result.key.e = cast[ptr cuchar](addr result.buffer[modlen])
result.key.elen = sizeof(ebuf)
ebuf[0] = cast[byte](pubexp shr 24)
ebuf[1] = cast[byte](pubexp shr 16)
ebuf[2] = cast[byte](pubexp shr 8)
ebuf[3] = cast[byte](pubexp)
copyMem(addr result.buffer[0], addr modulus[0], modlen)
copyMem(addr result.buffer[modlen], addr ebuf[0], sizeof(ebuf))
proc brEncodePublicRsaRawDer(pubkey: RsaPublicKey,
dest: var openarray[byte]): int =
# RSAPublicKey ::= SEQUENCE {
# modulus INTEGER, -- n
# publicExponent INTEGER, -- e
# }
var num: array[2, BrAsn1Uint]
num[0] = brAsn1UintPrepare(pubkey.key.n, pubkey.key.nlen)
num[1] = brAsn1UintPrepare(pubkey.key.e, pubkey.key.elen)
var slen = 0
for i in 0..<2:
var ilen = num[i].asn1len
slen += 1 + brAsn1EncodeLength(nil, ilen) + ilen
result = 1 + brAsn1EncodeLength(nil, slen) + slen
if len(dest) >= result:
var offset = 1
dest[0] = 0x30'u8
offset += brAsn1EncodeLength(addr dest[offset], slen)
for i in 0..<2:
offset += brAsn1EncodeUint(addr dest[offset], num[i])
proc brAsn1EncodeBitString(data: var openarray[byte], bytesize: int): int =
result = 1 + brAsn1EncodeLength(nil, bytesize + 1) + 1
if len(data) >= result:
var offset = 1
data[0] = 0x03'u8
offset += brAsn1EncodeLength(addr data[offset], bytesize + 1)
data[offset] = 0'u8
proc marshalBin*(pubkey: RsaPublicKey, kind: MarshalKind): seq[byte] =
## Serialize RSA public key ``pubkey`` to binary [RAWDER, PKCS8DER] format.
var tmp: array[1, byte]
if kind == RAW:
var res = brEncodePublicRsaRawDer(pubkey, tmp)
result = newSeq[byte](res)
res = brEncodePublicRsaRawDer(pubkey, result)
assert res == len(result)
elif kind == PKCS8:
var pkcs8head = [
0x30'u8, 0x0D'u8, 0x06'u8, 0x09'u8, 0x2A'u8, 0x86'u8, 0x48'u8, 0x86'u8,
0xF7'u8, 0x0D'u8, 0x01'u8, 0x01'u8, 0x01'u8, 0x05'u8, 0x00'u8
]
var lenraw = brEncodePublicRsaRawDer(pubkey, tmp)
echo "lenraw = ", lenraw, " hex = 0x", toHex(lenraw)
echo "RAWDER"
var lenseq = sizeof(pkcs8head) + brAsn1EncodeLength(nil, lenraw) + lenraw +
brAsn1EncodeBitString(tmp, lenraw)
echo "brAsn1EncodeBitString = ", brAsn1EncodeBitString(tmp, lenraw)
echo "lenseq = ", lenseq
result = newSeq[byte](1 + brAsn1EncodeLength(nil, lenseq) + lenseq)
result[0] = 0x30'u8
echo dumpHex(result)
var offset = 1
offset += brAsn1EncodeLength(addr result[offset], lenseq)
echo dumpHex(result)
copyMem(addr result[offset], addr pkcs8head[0], sizeof(pkcs8head))
echo dumpHex(result)
offset += sizeof(pkcs8head)
offset += brAsn1EncodeBitString(result.toOpenArray(offset, len(result) - 1),
lenraw)
echo "AFTER BITSTRING"
echo dumpHex(result)
echo dumpHex(result.toOpenArray(offset, len(result) - 1))
offset += brAsn1EncodeLength(addr result[offset], lenraw)
var res = brEncodePublicRsaRawDer(pubkey, result.toOpenArray(offset,
len(result) - 1))
echo "AFTER RAWDER"
echo dumpHex(result)
offset += res
proc marshalBin*(seckey: RsaPrivateKey, kind: MarshalKind): seq[byte] =
## Serialize RSA private key ``seckey`` to binary [RAWDER, PKCS8DER] format.
var ebuf: array[4, byte]
var pubkey: BrRsaPublicKey
var modulus = seckey.modulus()
var pubexp = seckey.pubexp()
var privexp = seckey.privexp(pubexp)
pubkey.n = cast[ptr cuchar](addr modulus[0])
pubkey.nlen = len(modulus)
ebuf[0] = cast[byte](pubexp shr 24)
ebuf[1] = cast[byte](pubexp shr 16)
ebuf[2] = cast[byte](pubexp shr 8)
ebuf[3] = cast[byte](pubexp)
pubkey.e = cast[ptr cuchar](addr ebuf[0])
pubkey.elen = sizeof(ebuf)
if kind == RAW:
let length = brEncodeRsaRawDer(nil, unsafeAddr seckey.key, addr pubkey,
addr privexp[0], len(privexp))
result = newSeq[byte](length)
let res = brEncodeRsaRawDer(addr result[0], unsafeAddr seckey.key,
addr pubkey, addr privexp[0],
len(privexp))
assert(res == length)
elif kind == PKCS8:
let length = brEncodeRsaPkcs8Der(nil, unsafeAddr seckey.key, addr pubkey,
addr privexp[0], len(privexp))
result = newSeq[byte](length)
let res = brEncodeRsaPkcs8Der(addr result[0], unsafeAddr seckey.key,
addr pubkey, addr privexp[0],
len(privexp))
assert(res == length)
proc marshalPem*[T: RsaPrivateKey | RsaPublicKey](key: T,
kind: MarshalKind): string =
## Serialize RSA private key ``seckey`` to PEM encoded format string.
var banner: string
let flags = cast[cuint](BR_PEM_CRLF or BR_PEM_LINE64)
if kind == RAW:
when T is RsaPrivateKey:
banner = "RSA PRIVATE KEY"
else:
banner = "RSA PUBLIC KEY"
elif kind == PKCS8:
when T is RsaPrivateKey:
banner = "PRIVATE KEY"
else:
banner = "PUBLIC KEY"
var buffer = marshalBin(key, kind)
if len(buffer) > 0:
let length = brPemEncode(nil, nil, len(buffer), banner, flags)
result = newString(length + 1)
let res = brPemEncode(cast[ptr byte](addr result[0]), addr buffer[0],
len(buffer), banner, flags)
result.setLen(res)
proc copyRsaPrivateKey(brkey: BrRsaPrivateKey,
buffer: ptr cuchar): RsaPrivateKey =
result.buffer = newSeq[byte](brRsaPrivateKeyBufferSize(int(brkey.nBitlen)))
let p = cast[uint](addr result.buffer[0])
let o = cast[uint](buffer)
let size = brkey.iqlen + cast[int](cast[uint](brkey.iq) - o)
if size > 0 and size <= len(result.buffer):
copyMem(addr result.buffer[0], buffer, size)
result.key.nBitlen = brkey.nBitlen
result.key.plen = brkey.plen
result.key.qlen = brkey.qlen
result.key.dplen = brkey.dplen
result.key.dqlen = brkey.dqlen
result.key.iqlen = brkey.iqlen
result.key.p = cast[ptr cuchar](p + cast[uint](brkey.p) - o)
result.key.q = cast[ptr cuchar](p + cast[uint](brkey.q) - o)
result.key.dp = cast[ptr cuchar](p + cast[uint](brkey.dp) - o)
result.key.dq = cast[ptr cuchar](p + cast[uint](brkey.dq) - o)
result.key.iq = cast[ptr cuchar](p + cast[uint](brkey.iq) - o)
proc tryUnmarshalBin*(key: var RsaPrivateKey,
data: openarray[byte]): X509Status =
## Unserialize RSA private key ``key`` from binary blob ``data``. Binary blob
## must be RAWDER or PKCS8DER format.
var ctx: BrSkeyDecoderContext
result = X509Status.INCORRECT_VALUE
if len(data) > 0:
brSkeyDecoderInit(addr ctx)
brSkeyDecoderPush(addr ctx, unsafeAddr data[0], len(data))
let err = brSkeyDecoderLastError(addr ctx)
if err == 0:
let kt = brSkeyDecoderKeyType(addr ctx)
if kt == BR_KEYTYPE_RSA:
key = copyRsaPrivateKey(ctx.pkey.rsa, addr ctx.keyData[0])
if key.key.nBitlen == ctx.pkey.rsa.nBitlen:
result = X509Status.OK
else:
result = X509Status.INCORRECT_VALUE
else:
result = X509Status.WRONG_KEY_TYPE
else:
result = cast[X509Status](err)
proc unmarshalBin*(rt: typedesc[RsaPrivateKey],
data: openarray[byte]): RsaPrivateKey {.inline.} =
let res = tryUnmarshalBin(result, data)
if res != X509Status.OK:
raise newException(ValueError, $res)
proc getPrivateKey*(key: var RsaPrivateKey, pemlist: PemList): X509Status =
## Get first
result = X509Status.MISSING_KEY
for item in pemlist:
if "RSA PRIVATE KEY" in item.name:
result = key.tryUnmarshalBin(item.data)
return
elif "PRIVATE KEY" in item.name:
result = key.tryUnmarshalBin(item.data)
return
proc cmp(a: ptr cuchar, alen: int, b: ptr cuchar, blen: int): bool =
var arr = cast[ptr UncheckedArray[byte]](a)
var brr = cast[ptr UncheckedArray[byte]](b)
if alen == blen:
var n = len(a)
var res, diff: int
while n > 0:
dec(n)
diff = int(arr[n]) - int(brr[n])
res = (res and -not(diff)) or diff
result = (res == 0)
proc `==`*(a, b: RsaPrivateKey): bool =
## Compare two RSA private keys for equality.
if a.key.nBitlen == b.key.nBitlen:
if cast[int](a.key.nBitlen) > 0:
let r1 = cmp(a.key.p, a.key.plen, b.key.p, b.key.plen)
let r2 = cmp(a.key.q, a.key.qlen, b.key.q, b.key.qlen)
let r3 = cmp(a.key.dp, a.key.dplen, b.key.dp, b.key.dplen)
let r4 = cmp(a.key.dq, a.key.dqlen, b.key.dq, b.key.dqlen)
let r5 = cmp(a.key.iq, a.key.iqlen, b.key.iq, b.key.iqlen)
result = r1 and r2 and r3 and r4 and r5
else:
result = true
proc `$`*(a: RsaPrivateKey): string =
## Return hexadecimal string representation of RSA private key.
result = "P: "
result.add("\n")
result.add("Q: ")
result.add("\n")
result.add("DP: ")
result.add("\n")
result.add("DQ: ")
result.add("\n")
result.add("IQ: ")
result.add("\n")
when isMainModule:
var pk = RsaKeyPair.random()