# Nim-Libp2p
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
## This module implements constant-time ECDSA and ECDHE for NIST elliptic
## curves secp256r1, secp384r1 and secp521r1.
##
## This module uses unmodified parts of code from
## BearSSL library
## Copyright(C) 2018 Thomas Pornin .
{.push raises: [].}
import bearssl/[ec, rand, hash]
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
import nimcrypto/utils as ncrutils
import minasn1
export minasn1.Asn1Error
import stew/[results, ctops]
import ../utility
export results
const
PubKey256Length* = 65
PubKey384Length* = 97
PubKey521Length* = 133
SecKey256Length* = 32
SecKey384Length* = 48
SecKey521Length* = 66
Sig256Length* = 64
Sig384Length* = 96
Sig521Length* = 132
Secret256Length* = SecKey256Length
Secret384Length* = SecKey384Length
Secret521Length* = SecKey521Length
type
EcPrivateKey* = ref object
buffer*: array[EC_KBUF_PRIV_MAX_SIZE, byte]
key*: ec.EcPrivateKey
EcPublicKey* = ref object
buffer*: array[EC_KBUF_PUB_MAX_SIZE, byte]
key*: ec.EcPublicKey
EcKeyPair* = object
seckey*: EcPrivateKey
pubkey*: EcPublicKey
EcSignature* = ref object
buffer*: seq[byte]
EcCurveKind* = enum
Secp256r1 = EC_secp256r1
Secp384r1 = EC_secp384r1
Secp521r1 = EC_secp521r1
EcPKI* = EcPrivateKey | EcPublicKey | EcSignature
EcError* = enum
EcRngError
EcKeyGenError
EcPublicKeyError
EcKeyIncorrectError
EcSignatureError
EcResult*[T] = Result[T, EcError]
const EcSupportedCurvesCint* = @[cint(Secp256r1), cint(Secp384r1), cint(Secp521r1)]
proc `-`(x: uint32): uint32 {.inline.} =
result = (0xFFFF_FFFF'u32 - x) + 1'u32
proc GT(x, y: uint32): uint32 {.inline.} =
var z = cast[uint32](y - x)
result = (z xor ((x xor y) and (x xor z))) shr 31
proc CMP(x, y: uint32): int32 {.inline.} =
cast[int32](GT(x, y)) or -(cast[int32](GT(y, x)))
proc EQ0(x: int32): uint32 {.inline.} =
var q = cast[uint32](x)
result = not (q or -q) shr 31
proc NEQ(x, y: uint32): uint32 {.inline.} =
var q = cast[uint32](x xor y)
result = ((q or -q) shr 31)
proc LT0(x: int32): uint32 {.inline.} =
result = cast[uint32](x) shr 31
proc checkScalar(scalar: openArray[byte], curve: cint): uint32 =
## Return ``1`` if all of the following hold:
## - len(``scalar``) <= ``orderlen``
## - ``scalar`` != 0
## - ``scalar`` is lower than the curve ``order``.
##
## Otherwise, return ``0``.
var impl = ecGetDefault()
var orderlen: uint = 0
var order = cast[ptr UncheckedArray[byte]](impl.order(curve, orderlen))
var z = 0'u32
var c = 0'i32
for u in scalar:
z = z or u
if len(scalar) == int(orderlen):
for i in 0 ..< len(scalar):
c = c or (-(cast[int32](EQ0(c))) and CMP(scalar[i], order[i]))
else:
c = -1
result = NEQ(z, 0'u32) and LT0(c)
proc checkPublic(key: openArray[byte], curve: cint): uint32 =
## Return ``1`` if public key ``key`` is on curve.
var ckey = @key
var x = [byte 0x00, 0x01]
var impl = ecGetDefault()
var orderlen: uint = 0
discard impl.order(curve, orderlen)
result = impl.mul(unsafeAddr ckey[0], uint(len(ckey)), addr x[0], uint(len(x)), curve)
proc getOffset(pubkey: EcPublicKey): int {.inline.} =
let o = cast[uint](pubkey.key.q) - cast[uint](unsafeAddr pubkey.buffer[0])
if o + cast[uint](pubkey.key.qlen) > uint(len(pubkey.buffer)):
result = -1
else:
result = cast[int](o)
proc getOffset(seckey: EcPrivateKey): int {.inline.} =
let o = cast[uint](seckey.key.x) - cast[uint](unsafeAddr seckey.buffer[0])
if o + cast[uint](seckey.key.xlen) > uint(len(seckey.buffer)):
result = -1
else:
result = cast[int](o)
template getPublicKeyLength*(curve: EcCurveKind): int =
case curve
of Secp256r1: PubKey256Length
of Secp384r1: PubKey384Length
of Secp521r1: PubKey521Length
template getPrivateKeyLength*(curve: EcCurveKind): int =
case curve
of Secp256r1: SecKey256Length
of Secp384r1: SecKey384Length
of Secp521r1: SecKey521Length
proc copy*[T: EcPKI](dst: var T, src: T): bool =
## Copy EC `private key`, `public key` or `signature` ``src`` to ``dst``.
##
## Returns ``true`` on success, ``false`` otherwise.
if isNil(src):
result = false
else:
dst = new T
when T is EcPrivateKey:
let length = src.key.xlen
if length > 0 and len(src.buffer) > 0:
let offset = getOffset(src)
if offset >= 0:
dst.buffer = src.buffer
dst.key.curve = src.key.curve
dst.key.xlen = length
dst.key.x = addr dst.buffer[offset]
result = true
elif T is EcPublicKey:
let length = src.key.qlen
if length > 0 and len(src.buffer) > 0:
let offset = getOffset(src)
if offset >= 0:
dst.buffer = src.buffer
dst.key.curve = src.key.curve
dst.key.qlen = length
dst.key.q = addr dst.buffer[offset]
result = true
else:
let length = len(src.buffer)
if length > 0:
dst.buffer = src.buffer
result = true
proc copy*[T: EcPKI](src: T): T {.inline.} =
## Returns copy of EC `private key`, `public key` or `signature`
## object ``src``.
if not copy(result, src):
raise newException(EcKeyIncorrectError, "Incorrect key or signature")
proc clear*[T: EcPKI | EcKeyPair](pki: var T) =
## Wipe and clear EC `private key`, `public key` or `signature` object.
doAssert(not isNil(pki))
when T is EcPrivateKey:
burnMem(pki.buffer)
pki.buffer.setLen(0)
pki.key.x = nil
pki.key.xlen = 0
pki.key.curve = 0
elif T is EcPublicKey:
burnMem(pki.buffer)
pki.buffer.setLen(0)
pki.key.q = nil
pki.key.qlen = 0
pki.key.curve = 0
elif T is EcSignature:
burnMem(pki.buffer)
pki.buffer.setLen(0)
else:
burnMem(pki.seckey.buffer)
burnMem(pki.pubkey.buffer)
pki.seckey.buffer.setLen(0)
pki.pubkey.buffer.setLen(0)
pki.seckey.key.x = nil
pki.seckey.key.xlen = 0
pki.seckey.key.curve = 0
pki.pubkey.key.q = nil
pki.pubkey.key.qlen = 0
pki.pubkey.key.curve = 0
proc random*(
T: typedesc[EcPrivateKey], kind: EcCurveKind, rng: var HmacDrbgContext
): EcResult[EcPrivateKey] =
## Generate new random EC private key using BearSSL's HMAC-SHA256-DRBG
## algorithm.
##
## ``kind`` elliptic curve kind of your choice (secp256r1, secp384r1 or
## secp521r1).
var ecimp = ecGetDefault()
var res = new EcPrivateKey
if ecKeygen(
PrngClassPointerConst(addr rng.vtable),
ecimp,
addr res.key,
addr res.buffer[0],
safeConvert[cint](kind),
) == 0:
err(EcKeyGenError)
else:
ok(res)
proc getPublicKey*(seckey: EcPrivateKey): EcResult[EcPublicKey] =
## Calculate and return EC public key from private key ``seckey``.
if isNil(seckey):
return err(EcKeyIncorrectError)
var ecimp = ecGetDefault()
if seckey.key.curve in EcSupportedCurvesCint:
var res = new EcPublicKey
assert res.buffer.len > getPublicKeyLength(cast[EcCurveKind](seckey.key.curve))
if ecComputePub(ecimp, addr res.key, addr res.buffer[0], unsafeAddr seckey.key) == 0:
err(EcKeyIncorrectError)
else:
ok(res)
else:
err(EcKeyIncorrectError)
proc random*(
T: typedesc[EcKeyPair], kind: EcCurveKind, rng: var HmacDrbgContext
): EcResult[T] =
## Generate new random EC private and public keypair using BearSSL's
## HMAC-SHA256-DRBG algorithm.
##
## ``kind`` elliptic curve kind of your choice (secp256r1, secp384r1 or
## secp521r1).
let
seckey = ?EcPrivateKey.random(kind, rng)
pubkey = ?seckey.getPublicKey()
key = EcKeyPair(seckey: seckey, pubkey: pubkey)
ok(key)
proc `$`*(seckey: EcPrivateKey): string =
## Return string representation of EC private key.
if isNil(seckey) or seckey.key.curve == 0 or seckey.key.xlen == 0 or
len(seckey.buffer) == 0:
result = "Empty or uninitialized ECNIST key"
else:
if seckey.key.curve notin EcSupportedCurvesCint:
result = "Unknown key"
else:
let offset = seckey.getOffset()
if offset < 0:
result = "Corrupted key"
else:
let e = offset + cast[int](seckey.key.xlen) - 1
result = ncrutils.toHex(seckey.buffer.toOpenArray(offset, e))
proc `$`*(pubkey: EcPublicKey): string =
## Return string representation of EC public key.
if isNil(pubkey) or pubkey.key.curve == 0 or pubkey.key.qlen == 0 or
len(pubkey.buffer) == 0:
result = "Empty or uninitialized ECNIST key"
else:
if pubkey.key.curve notin EcSupportedCurvesCint:
result = "Unknown key"
else:
let offset = pubkey.getOffset()
if offset < 0:
result = "Corrupted key"
else:
let e = offset + cast[int](pubkey.key.qlen) - 1
result = ncrutils.toHex(pubkey.buffer.toOpenArray(offset, e))
proc `$`*(sig: EcSignature): string =
## Return hexadecimal string representation of EC signature.
if isNil(sig) or len(sig.buffer) == 0:
result = "Empty or uninitialized ECNIST signature"
else:
result = ncrutils.toHex(sig.buffer)
proc toRawBytes*(seckey: EcPrivateKey, data: var openArray[byte]): EcResult[int] =
## Serialize EC private key ``seckey`` to raw binary form and store it
## to ``data``.
##
## Returns number of bytes (octets) needed to store EC private key, or `0`
## if private key is not in supported curve.
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
let klen = getPrivateKeyLength(cast[EcCurveKind](seckey.key.curve))
if len(data) >= klen:
copyMem(addr data[0], unsafeAddr seckey.buffer[0], klen)
ok(klen)
else:
err(EcKeyIncorrectError)
proc toRawBytes*(pubkey: EcPublicKey, data: var openArray[byte]): EcResult[int] =
## Serialize EC public key ``pubkey`` to uncompressed form specified in
## section 4.3.6 of ANSI X9.62.
##
## Returns number of bytes (octets) needed to store EC public key, or `0`
## if public key is not in supported curve.
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
let klen = getPublicKeyLength(cast[EcCurveKind](pubkey.key.curve))
if len(data) >= klen:
copyMem(addr data[0], unsafeAddr pubkey.buffer[0], klen)
ok(klen)
else:
err(EcKeyIncorrectError)
proc toRawBytes*(sig: EcSignature, data: var openArray[byte]): int =
## Serialize EC signature ``sig`` to raw binary form and store it to ``data``.
##
## Returns number of bytes (octets) needed to store EC signature, or `0`
## if signature is not in supported curve.
doAssert(not isNil(sig))
result = len(sig.buffer)
if len(data) >= len(sig.buffer):
if len(sig.buffer) > 0:
copyMem(addr data[0], unsafeAddr sig.buffer[0], len(sig.buffer))
proc toBytes*(seckey: EcPrivateKey, data: var openArray[byte]): EcResult[int] =
## Serialize EC private key ``seckey`` to ASN.1 DER binary form and store it
## to ``data``.
##
## Procedure returns number of bytes (octets) needed to store EC private key,
## or `0` if private key is not in supported curve.
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
var offset, length: int
var pubkey = ?seckey.getPublicKey()
var b = Asn1Buffer.init()
var p = Asn1Composite.init(Asn1Tag.Sequence)
var c0 = Asn1Composite.init(0)
var c1 = Asn1Composite.init(1)
if seckey.key.curve == EC_secp256r1:
c0.write(Asn1Tag.Oid, Asn1OidSecp256r1)
elif seckey.key.curve == EC_secp384r1:
c0.write(Asn1Tag.Oid, Asn1OidSecp384r1)
elif seckey.key.curve == EC_secp521r1:
c0.write(Asn1Tag.Oid, Asn1OidSecp521r1)
c0.finish()
offset = pubkey.getOffset()
if offset < 0:
return err(EcKeyIncorrectError)
length = int(pubkey.key.qlen)
c1.write(Asn1Tag.BitString, pubkey.buffer.toOpenArray(offset, offset + length - 1))
c1.finish()
offset = seckey.getOffset()
if offset < 0:
return err(EcKeyIncorrectError)
length = int(seckey.key.xlen)
p.write(1'u64)
p.write(Asn1Tag.OctetString, seckey.buffer.toOpenArray(offset, offset + length - 1))
p.write(c0)
p.write(c1)
p.finish()
b.write(p)
b.finish()
var blen = len(b)
if len(data) >= blen:
copyMem(addr data[0], addr b.buffer[0], blen)
# ok anyway, since it might have been a query...
ok(blen)
else:
err(EcKeyIncorrectError)
proc toBytes*(pubkey: EcPublicKey, data: var openArray[byte]): EcResult[int] =
## Serialize EC public key ``pubkey`` to ASN.1 DER binary form and store it
## to ``data``.
##
## Procedure returns number of bytes (octets) needed to store EC public key,
## or `0` if public key is not in supported curve.
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
var b = Asn1Buffer.init()
var p = Asn1Composite.init(Asn1Tag.Sequence)
var c = Asn1Composite.init(Asn1Tag.Sequence)
c.write(Asn1Tag.Oid, Asn1OidEcPublicKey)
if pubkey.key.curve == EC_secp256r1:
c.write(Asn1Tag.Oid, Asn1OidSecp256r1)
elif pubkey.key.curve == EC_secp384r1:
c.write(Asn1Tag.Oid, Asn1OidSecp384r1)
elif pubkey.key.curve == EC_secp521r1:
c.write(Asn1Tag.Oid, Asn1OidSecp521r1)
c.finish()
p.write(c)
let offset = getOffset(pubkey)
if offset < 0:
return err(EcKeyIncorrectError)
let length = int(pubkey.key.qlen)
p.write(Asn1Tag.BitString, pubkey.buffer.toOpenArray(offset, offset + length - 1))
p.finish()
b.write(p)
b.finish()
var blen = len(b)
if len(data) >= blen:
copyMem(addr data[0], addr b.buffer[0], blen)
ok(blen)
else:
err(EcKeyIncorrectError)
proc toBytes*(sig: EcSignature, data: var openArray[byte]): EcResult[int] =
## Serialize EC signature ``sig`` to ASN.1 DER binary form and store it
## to ``data``.
##
## Procedure returns number of bytes (octets) needed to store EC signature,
## or `0` if signature is not in supported curve.
if isNil(sig):
return err(EcSignatureError)
let slen = len(sig.buffer)
if len(data) >= slen:
copyMem(addr data[0], unsafeAddr sig.buffer[0], slen)
ok(slen)
proc getBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
## Serialize EC private key ``seckey`` to ASN.1 DER binary form and return it.
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
let length = ?seckey.toBytes(res)
res.setLen(length)
discard ?seckey.toBytes(res)
ok(res)
else:
err(EcKeyIncorrectError)
proc getBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
## Serialize EC public key ``pubkey`` to ASN.1 DER binary form and return it.
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
let length = ?pubkey.toBytes(res)
res.setLen(length)
discard ?pubkey.toBytes(res)
ok(res)
else:
err(EcKeyIncorrectError)
proc getBytes*(sig: EcSignature): EcResult[seq[byte]] =
## Serialize EC signature ``sig`` to ASN.1 DER binary form and return it.
if isNil(sig):
return err(EcSignatureError)
var res = newSeq[byte]()
let length = ?sig.toBytes(res)
res.setLen(length)
discard ?sig.toBytes(res)
ok(res)
proc getRawBytes*(seckey: EcPrivateKey): EcResult[seq[byte]] =
## Serialize EC private key ``seckey`` to raw binary form and return it.
if isNil(seckey):
return err(EcKeyIncorrectError)
if seckey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
let length = ?seckey.toRawBytes(res)
res.setLen(length)
discard ?seckey.toRawBytes(res)
ok(res)
else:
err(EcKeyIncorrectError)
proc getRawBytes*(pubkey: EcPublicKey): EcResult[seq[byte]] =
## Serialize EC public key ``pubkey`` to raw binary form and return it.
if isNil(pubkey):
return err(EcKeyIncorrectError)
if pubkey.key.curve in EcSupportedCurvesCint:
var res = newSeq[byte]()
let length = ?pubkey.toRawBytes(res)
res.setLen(length)
discard ?pubkey.toRawBytes(res)
return ok(res)
else:
return err(EcKeyIncorrectError)
proc getRawBytes*(sig: EcSignature): EcResult[seq[byte]] =
## Serialize EC signature ``sig`` to raw binary form and return it.
if isNil(sig):
return err(EcSignatureError)
var res = newSeq[byte]()
let length = ?sig.toBytes(res)
res.setLen(length)
discard ?sig.toBytes(res)
ok(res)
proc `==`*(pubkey1, pubkey2: EcPublicKey): bool =
## Returns ``true`` if both keys ``pubkey1`` and ``pubkey2`` are equal.
if isNil(pubkey1) and isNil(pubkey2):
result = true
elif isNil(pubkey1) and (not isNil(pubkey2)):
result = false
elif isNil(pubkey2) and (not isNil(pubkey1)):
result = false
else:
if pubkey1.key.curve != pubkey2.key.curve:
return false
if pubkey1.key.qlen != pubkey2.key.qlen:
return false
let op1 = pubkey1.getOffset()
let op2 = pubkey2.getOffset()
if op1 == -1 or op2 == -1:
return false
return CT.isEqual(
pubkey1.buffer.toOpenArray(op1, pubkey1.key.qlen - 1),
pubkey2.buffer.toOpenArray(op2, pubkey2.key.qlen - 1),
)
proc `==`*(seckey1, seckey2: EcPrivateKey): bool =
## Returns ``true`` if both keys ``seckey1`` and ``seckey2`` are equal.
if isNil(seckey1) and isNil(seckey2):
result = true
elif isNil(seckey1) and (not isNil(seckey2)):
result = false
elif isNil(seckey2) and (not isNil(seckey1)):
result = false
else:
if seckey1.key.curve != seckey2.key.curve:
return false
if seckey1.key.xlen != seckey2.key.xlen:
return false
let op1 = seckey1.getOffset()
let op2 = seckey2.getOffset()
if op1 == -1 or op2 == -1:
return false
return CT.isEqual(
seckey1.buffer.toOpenArray(op1, seckey1.key.xlen - 1),
seckey2.buffer.toOpenArray(op2, seckey2.key.xlen - 1),
)
proc `==`*(a, b: EcSignature): bool =
## Return ``true`` if both signatures ``sig1`` and ``sig2`` are equal.
if isNil(a) and isNil(b):
true
elif isNil(a) and (not isNil(b)):
false
elif isNil(b) and (not isNil(a)):
false
else:
# We need to cover all the cases because Signature initialization procedure
# do not perform any checks.
if len(a.buffer) == 0 and len(b.buffer) == 0:
true
elif len(a.buffer) == 0 and len(b.buffer) != 0:
false
elif len(b.buffer) == 0 and len(a.buffer) != 0:
false
elif len(a.buffer) != len(b.buffer):
false
else:
CT.isEqual(a.buffer, b.buffer)
proc init*(key: var EcPrivateKey, data: openArray[byte]): Result[void, Asn1Error] =
## Initialize EC `private key` or `signature` ``key`` from ASN.1 DER binary
## representation ``data``.
##
## Procedure returns ``Result[void, Asn1Error]``.
var raw, oid, field: Asn1Field
var curve: cint
var ab = Asn1Buffer.init(data)
field = ?ab.read()
if field.kind != Asn1Tag.Sequence:
return err(Asn1Error.Incorrect)
var ib = field.getBuffer()
field = ?ib.read()
if field.kind != Asn1Tag.Integer:
return err(Asn1Error.Incorrect)
if field.vint != 1'u64:
return err(Asn1Error.Incorrect)
raw = ?ib.read()
if raw.kind != Asn1Tag.OctetString:
return err(Asn1Error.Incorrect)
oid = ?ib.read()
if oid.kind != Asn1Tag.Oid:
return err(Asn1Error.Incorrect)
if oid == Asn1OidSecp256r1:
curve = safeConvert[cint](Secp256r1)
elif oid == Asn1OidSecp384r1:
curve = safeConvert[cint](Secp384r1)
elif oid == Asn1OidSecp521r1:
curve = safeConvert[cint](Secp521r1)
else:
return err(Asn1Error.Incorrect)
if checkScalar(raw.toOpenArray(), curve) == 1'u32:
key = new EcPrivateKey
copyMem(addr key.buffer[0], addr raw.buffer[raw.offset], raw.length)
key.key.x = addr key.buffer[0]
key.key.xlen = uint(raw.length)
key.key.curve = curve
ok()
else:
err(Asn1Error.Incorrect)
proc init*(pubkey: var EcPublicKey, data: openArray[byte]): Result[void, Asn1Error] =
## Initialize EC public key ``pubkey`` from ASN.1 DER binary representation
## ``data``.
##
## Procedure returns ``Result[void, Asn1Error]``.
var raw, oid, field: Asn1Field
var curve: cint
var ab = Asn1Buffer.init(data)
field = ?ab.read()
if field.kind != Asn1Tag.Sequence:
return err(Asn1Error.Incorrect)
var ib = field.getBuffer()
field = ?ib.read()
if field.kind != Asn1Tag.Sequence:
return err(Asn1Error.Incorrect)
var ob = field.getBuffer()
oid = ?ob.read()
if oid.kind != Asn1Tag.Oid:
return err(Asn1Error.Incorrect)
if oid != Asn1OidEcPublicKey:
return err(Asn1Error.Incorrect)
oid = ?ob.read()
if oid.kind != Asn1Tag.Oid:
return err(Asn1Error.Incorrect)
if oid == Asn1OidSecp256r1:
curve = safeConvert[cint](Secp256r1)
elif oid == Asn1OidSecp384r1:
curve = safeConvert[cint](Secp384r1)
elif oid == Asn1OidSecp521r1:
curve = safeConvert[cint](Secp521r1)
else:
return err(Asn1Error.Incorrect)
raw = ?ib.read()
if raw.kind != Asn1Tag.BitString:
return err(Asn1Error.Incorrect)
if checkPublic(raw.toOpenArray(), curve) != 0:
pubkey = new EcPublicKey
copyMem(addr pubkey.buffer[0], addr raw.buffer[raw.offset], raw.length)
pubkey.key.q = addr pubkey.buffer[0]
pubkey.key.qlen = uint(raw.length)
pubkey.key.curve = curve
ok()
else:
err(Asn1Error.Incorrect)
proc init*(sig: var EcSignature, data: openArray[byte]): Result[void, Asn1Error] =
## Initialize EC signature ``sig`` from raw binary representation ``data``.
##
## Procedure returns ``Result[void, Asn1Error]``.
if len(data) > 0:
sig = new EcSignature
sig.buffer = @data
ok()
else:
err(Asn1Error.Incorrect)
proc init*[T: EcPKI](sospk: var T, data: string): Result[void, Asn1Error] {.inline.} =
## Initialize EC `private key`, `public key` or `signature` ``sospk`` from
## ASN.1 DER hexadecimal string representation ``data``.
##
## Procedure returns ``Asn1Status``.
sospk.init(ncrutils.fromHex(data))
proc init*(t: typedesc[EcPrivateKey], data: openArray[byte]): EcResult[EcPrivateKey] =
## Initialize EC private key from ASN.1 DER binary representation ``data`` and
## return constructed object.
var key: EcPrivateKey
let res = key.init(data)
if res.isErr:
err(EcKeyIncorrectError)
else:
ok(key)
proc init*(t: typedesc[EcPublicKey], data: openArray[byte]): EcResult[EcPublicKey] =
## Initialize EC public key from ASN.1 DER binary representation ``data`` and
## return constructed object.
var key: EcPublicKey
let res = key.init(data)
if res.isErr:
err(EcKeyIncorrectError)
else:
ok(key)
proc init*(t: typedesc[EcSignature], data: openArray[byte]): EcResult[EcSignature] =
## Initialize EC signature from raw binary representation ``data`` and
## return constructed object.
var sig: EcSignature
let res = sig.init(data)
if res.isErr:
err(EcSignatureError)
else:
ok(sig)
proc init*[T: EcPKI](t: typedesc[T], data: string): EcResult[T] =
## Initialize EC `private key`, `public key` or `signature` from hexadecimal
## string representation ``data`` and return constructed object.
t.init(ncrutils.fromHex(data))
proc initRaw*(key: var EcPrivateKey, data: openArray[byte]): bool =
## Initialize EC `private key` or `scalar` ``key`` from raw binary
## representation ``data``.
##
## Length of ``data`` array must be ``SecKey256Length``, ``SecKey384Length``
## or ``SecKey521Length``.
##
## Procedure returns ``true`` on success, ``false`` otherwise.
var curve: cint
if len(data) == SecKey256Length:
curve = safeConvert[cint](Secp256r1)
result = true
elif len(data) == SecKey384Length:
curve = safeConvert[cint](Secp384r1)
result = true
elif len(data) == SecKey521Length:
curve = safeConvert[cint](Secp521r1)
result = true
if result:
result = false
if checkScalar(data, curve) == 1'u32:
let length = len(data)
key = new EcPrivateKey
copyMem(addr key.buffer[0], unsafeAddr data[0], length)
key.key.x = addr key.buffer[0]
key.key.xlen = uint(length)
key.key.curve = curve
result = true
proc initRaw*(pubkey: var EcPublicKey, data: openArray[byte]): bool =
## Initialize EC public key ``pubkey`` from raw binary representation
## ``data``.
##
## Length of ``data`` array must be ``PubKey256Length``, ``PubKey384Length``
## or ``PubKey521Length``.
##
## Procedure returns ``true`` on success, ``false`` otherwise.
var curve: cint
if len(data) > 0:
if data[0] == 0x04'u8:
if len(data) == PubKey256Length:
curve = safeConvert[cint](Secp256r1)
result = true
elif len(data) == PubKey384Length:
curve = safeConvert[cint](Secp384r1)
result = true
elif len(data) == PubKey521Length:
curve = safeConvert[cint](Secp521r1)
result = true
if result:
result = false
if checkPublic(data, curve) != 0:
let length = len(data)
pubkey = new EcPublicKey
copyMem(addr pubkey.buffer[0], unsafeAddr data[0], length)
pubkey.key.q = addr pubkey.buffer[0]
pubkey.key.qlen = uint(length)
pubkey.key.curve = curve
result = true
proc initRaw*(sig: var EcSignature, data: openArray[byte]): bool =
## Initialize EC signature ``sig`` from raw binary representation ``data``.
##
## Length of ``data`` array must be ``Sig256Length``, ``Sig384Length``
## or ``Sig521Length``.
##
## Procedure returns ``true`` on success, ``false`` otherwise.
let length = len(data)
if (length == Sig256Length) or (length == Sig384Length) or (length == Sig521Length):
result = true
if result:
sig = new EcSignature
sig.buffer = @data
proc initRaw*[T: EcPKI](sospk: var T, data: string): bool {.inline.} =
## Initialize EC `private key`, `public key` or `signature` ``sospk`` from
## raw hexadecimal string representation ``data``.
##
## Procedure returns ``true`` on success, ``false`` otherwise.
result = sospk.initRaw(ncrutils.fromHex(data))
proc initRaw*(
t: typedesc[EcPrivateKey], data: openArray[byte]
): EcResult[EcPrivateKey] =
## Initialize EC private key from raw binary representation ``data`` and
## return constructed object.
var res: EcPrivateKey
if not res.initRaw(data):
err(EcKeyIncorrectError)
else:
ok(res)
proc initRaw*(t: typedesc[EcPublicKey], data: openArray[byte]): EcResult[EcPublicKey] =
## Initialize EC public key from raw binary representation ``data`` and
## return constructed object.
var res: EcPublicKey
if not res.initRaw(data):
err(EcKeyIncorrectError)
else:
ok(res)
proc initRaw*(t: typedesc[EcSignature], data: openArray[byte]): EcResult[EcSignature] =
## Initialize EC signature from raw binary representation ``data`` and
## return constructed object.
var res: EcSignature
if not res.initRaw(data):
err(EcSignatureError)
else:
ok(res)
proc initRaw*[T: EcPKI](t: typedesc[T], data: string): T {.inline.} =
## Initialize EC `private key`, `public key` or `signature` from raw
## hexadecimal string representation ``data`` and return constructed object.
result = t.initRaw(ncrutils.fromHex(data))
proc scalarMul*(pub: EcPublicKey, sec: EcPrivateKey): EcPublicKey =
## Return scalar multiplication of ``pub`` and ``sec``.
##
## Returns point in curve as ``pub * sec`` or ``nil`` otherwise.
doAssert((not isNil(pub)) and (not isNil(sec)))
var impl = ecGetDefault()
if sec.key.curve in EcSupportedCurvesCint:
if pub.key.curve == sec.key.curve:
var key = new EcPublicKey
if key.copy(pub):
let poffset = key.getOffset()
let soffset = sec.getOffset()
if poffset >= 0 and soffset >= 0:
let res = impl.mul(
addr key.buffer[poffset],
key.key.qlen,
unsafeAddr sec.buffer[soffset],
sec.key.xlen,
key.key.curve,
)
if res != 0:
result = key
proc toSecret*(
pubkey: EcPublicKey, seckey: EcPrivateKey, data: var openArray[byte]
): int =
## Calculate ECDHE shared secret using Go's elliptic/curve approach, using
## remote public key ``pubkey`` and local private key ``seckey`` and store
## shared secret to ``data``.
##
## Returns number of bytes (octets) needed to store shared secret, or ``0``
## on error.
##
## ``data`` array length must be at least 32 bytes for `secp256r1`, 48 bytes
## for `secp384r1` and 66 bytes for `secp521r1`.
doAssert((not isNil(pubkey)) and (not isNil(seckey)))
var mult = scalarMul(pubkey, seckey)
if not isNil(mult):
if seckey.key.curve == EC_secp256r1:
result = Secret256Length
elif seckey.key.curve == EC_secp384r1:
result = Secret384Length
elif seckey.key.curve == EC_secp521r1:
result = Secret521Length
if len(data) >= result:
var qplus1 = cast[pointer](cast[uint](mult.key.q) + 1'u)
copyMem(addr data[0], qplus1, result)
proc getSecret*(pubkey: EcPublicKey, seckey: EcPrivateKey): seq[byte] =
## Calculate ECDHE shared secret using Go's elliptic curve approach, using
## remote public key ``pubkey`` and local private key ``seckey`` and return
## shared secret.
##
## If error happens length of result array will be ``0``.
doAssert((not isNil(pubkey)) and (not isNil(seckey)))
var data: array[Secret521Length, byte]
let res = toSecret(pubkey, seckey, data)
if res > 0:
result = newSeq[byte](res)
copyMem(addr result[0], addr data[0], res)
proc sign*[T: byte | char](
seckey: EcPrivateKey, message: openArray[T]
): EcResult[EcSignature] {.gcsafe.} =
## Get ECDSA signature of data ``message`` using private key ``seckey``.
if isNil(seckey):
return err(EcKeyIncorrectError)
var hc: HashCompatContext
var hash: array[32, byte]
var impl = ecGetDefault()
if seckey.key.curve in EcSupportedCurvesCint:
var sig = new EcSignature
sig.buffer = newSeq[byte](256)
var kv = addr sha256Vtable
kv.init(addr hc.vtable)
if len(message) > 0:
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
else:
kv.update(addr hc.vtable, nil, 0)
kv.out(addr hc.vtable, addr hash[0])
let res =
ecdsaI31SignAsn1(impl, kv, addr hash[0], addr seckey.key, addr sig.buffer[0])
# Clear context with initial value
kv.init(addr hc.vtable)
if res != 0:
sig.buffer.setLen(res)
ok(sig)
else:
err(EcSignatureError)
else:
err(EcKeyIncorrectError)
proc verify*[T: byte | char](
sig: EcSignature, message: openArray[T], pubkey: EcPublicKey
): bool {.inline.} =
## Verify ECDSA signature ``sig`` using public key ``pubkey`` and data
## ``message``.
##
## Return ``true`` if message verification succeeded, ``false`` if
## verification failed.
doAssert((not isNil(sig)) and (not isNil(pubkey)))
var hc: HashCompatContext
var hash: array[32, byte]
var impl = ecGetDefault()
if pubkey.key.curve in EcSupportedCurvesCint:
var kv = addr sha256Vtable
kv.init(addr hc.vtable)
if len(message) > 0:
kv.update(addr hc.vtable, unsafeAddr message[0], uint(len(message)))
else:
kv.update(addr hc.vtable, nil, 0)
kv.out(addr hc.vtable, addr hash[0])
let res = ecdsaI31VrfyAsn1(
impl,
addr hash[0],
uint(len(hash)),
unsafeAddr pubkey.key,
addr sig.buffer[0],
uint(len(sig.buffer)),
)
# Clear context with initial value
kv.init(addr hc.vtable)
result = (res == 1)
type ECDHEScheme* = EcCurveKind
proc ephemeral*(scheme: ECDHEScheme, rng: var HmacDrbgContext): EcResult[EcKeyPair] =
## Generate ephemeral keys used to perform ECDHE.
var keypair: EcKeyPair
if scheme == Secp256r1:
keypair = ?EcKeyPair.random(Secp256r1, rng)
elif scheme == Secp384r1:
keypair = ?EcKeyPair.random(Secp384r1, rng)
elif scheme == Secp521r1:
keypair = ?EcKeyPair.random(Secp521r1, rng)
ok(keypair)
proc ephemeral*(scheme: string, rng: var HmacDrbgContext): EcResult[EcKeyPair] =
## Generate ephemeral keys used to perform ECDHE using string encoding.
##
## Currently supported encoding strings are P-256, P-384, P-521, if encoding
## string is not supported P-521 key will be generated.
if scheme == "P-256":
ephemeral(Secp256r1, rng)
elif scheme == "P-384":
ephemeral(Secp384r1, rng)
elif scheme == "P-521":
ephemeral(Secp521r1, rng)
else:
ephemeral(Secp521r1, rng)