343 lines
13 KiB
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()
|
|
|