From f67a7a2a3eefb0d2bfbe6d299883cdf65247f778 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Thu, 21 Feb 2019 06:10:21 +0200 Subject: [PATCH] Add minimal ASN.1 encoding/decoding primitives. Add ASN.1 DER serialization for EC NIST curves. --- libp2p/crypto/ecnist.nim | 331 ++++++++++------ libp2p/crypto/minasn1.nim | 773 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 988 insertions(+), 116 deletions(-) create mode 100644 libp2p/crypto/minasn1.nim diff --git a/libp2p/crypto/ecnist.nim b/libp2p/crypto/ecnist.nim index 3901eaca3..0f94117b5 100644 --- a/libp2p/crypto/ecnist.nim +++ b/libp2p/crypto/ecnist.nim @@ -8,6 +8,7 @@ ## those terms. import common import nimcrypto/utils +import minasn1 const PubKey256Length* = 65 @@ -120,24 +121,6 @@ proc getOffset(seckey: EcPrivateKey): int {.inline.} = else: result = cast[int](o) -proc copyKey(dest: var openarray[byte], seckey: EcPrivateKey): bool {.inline.} = - let length = seckey.key.xlen - if length > 0: - if len(dest) >= length: - let offset = getOffset(seckey) - if offset >= 0: - copyMem(addr dest[0], unsafeAddr seckey.buffer[offset], length - offset) - result = true - -proc copyKey(dest: var openarray[byte], pubkey: EcPublicKey): bool {.inline.} = - let length = pubkey.key.qlen - if length > 0: - if len(dest) >= length: - let offset = getOffset(pubkey) - if offset >= 0: - copyMem(addr dest[0], unsafeAddr pubkey.buffer[offset], length - offset) - result = true - template getSignatureLength*(curve: EcCurveKind): int = case curve of Secp256r1: @@ -313,57 +296,92 @@ proc `$`*(sig: EcSignature): string = else: result = toHex(sig.buffer) -proc toBytes*(seckey: EcPrivateKey, data: var openarray[byte]): bool = - ## Serialize EC private key ``seckey`` to raw binary form and store it to - ## ``data``. +proc toBytes*(seckey: EcPrivateKey, data: var openarray[byte]): int = + ## Serialize EC private key ``seckey`` to ASN.1 DER binary form and store it + ## to ``data``. ## - ## If ``seckey`` curve is ``Secp256r1`` length of ``data`` array must be at - ## least ``SecKey256Length``. - ## - ## If ``seckey`` curve is ``Secp384r1`` length of ``data`` array must be at - ## least ``SecKey384Length``. - ## - ## If ``seckey`` curve is ``Secp521r1`` length of ``data`` array must be at - ## least ``SecKey521Length``. - ## - ## Procedure returns ``true`` if serialization successfull, ``false`` - ## otherwise. + ## Procedure returns number of bytes (octets) needed to store EC private key, + ## or `0` if private key is not in supported curve. if seckey.key.curve in EcSupportedCurvesCint: - if copyKey(data, seckey): - result = true + var offset, length: int + var pubkey = seckey.getKey() + var b = Asn1Buffer.init() + var p = Asn1Composite.init(Asn1Tag.Sequence) + var c0 = Asn1Composite.init(0) + var c1 = Asn1Composite.init(1) + if seckey.key.curve == BR_EC_SECP256R1: + c0.write(Asn1Tag.Oid, Asn1OidSecp256r1) + elif seckey.key.curve == BR_EC_SECP384R1: + c0.write(Asn1Tag.Oid, Asn1OidSecp384r1) + elif seckey.key.curve == BR_EC_SECP521R1: + c0.write(Asn1Tag.Oid, Asn1OidSecp521r1) + c0.finish() + offset = pubkey.getOffset() + length = pubkey.key.qlen + c1.write(Asn1Tag.BitString, + pubkey.buffer.toOpenArray(offset, offset + length - 1)) + c1.finish() + offset = seckey.getOffset() + length = 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() + result = len(b) + if len(data) >= len(b): + copyMem(addr data[0], addr b.buffer[0], len(b)) -proc toBytes*(pubkey: EcPublicKey, data: var openarray[byte]): bool = - ## Serialize EC public key ``pubkey`` to raw binary form and store it to - ## ``data``. +proc toBytes*(pubkey: EcPublicKey, data: var openarray[byte]): int = + ## Serialize EC public key ``pubkey`` to ASN.1 DER binary form and store it + ## to ``data``. ## - ## If ``pubkey`` curve is ``Secp256r1`` length of ``data`` array must be at - ## least ``PubKey256Length``. - ## - ## If ``pubkey`` curve is ``Secp384r1`` length of ``data`` array must be at - ## least ``PubKey384Length``. - ## - ## If ``pubkey`` curve is ``Secp521r1`` length of ``data`` array must be at - ## least ``PubKey521Length``. - ## - ## Procedure returns ``true`` if serialization successfull, ``false`` - ## otherwise. + ## Procedure returns number of bytes (octets) needed to store EC public key, + ## or `0` if public key is not in supported curve. if pubkey.key.curve in EcSupportedCurvesCint: - if copyKey(data, pubkey): - result = true + 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 == BR_EC_SECP256R1: + c.write(Asn1Tag.Oid, Asn1OidSecp256r1) + elif pubkey.key.curve == BR_EC_SECP384R1: + c.write(Asn1Tag.Oid, Asn1OidSecp384r1) + elif pubkey.key.curve == BR_EC_SECP521R1: + c.write(Asn1Tag.Oid, Asn1OidSecp521r1) + c.finish() + p.write(c) + let offset = getOffset(pubkey) + let length = pubkey.key.qlen + p.write(Asn1Tag.BitString, + pubkey.buffer.toOpenArray(offset, offset + length - 1)) + p.finish() + b.write(p) + b.finish() + result = len(b) + if len(data) >= len(b): + copyMem(addr data[0], addr b.buffer[0], len(b)) proc getBytes*(seckey: EcPrivateKey): seq[byte] = - ## Serialize EC private key ``seckey`` to raw binary form and return it. + ## Serialize EC private key ``seckey`` to ASN.1 DER binary form and return it. if seckey.key.curve in EcSupportedCurvesCint: - result = newSeq[byte](seckey.key.xlen) - discard toBytes(seckey, result) + result = newSeq[byte]() + let length = seckey.toBytes(result) + result.setLen(length) + discard seckey.toBytes(result) else: raise newException(EcKeyIncorrectError, "Incorrect private key") proc getBytes*(pubkey: EcPublicKey): seq[byte] = - ## Serialize EC public key ``pubkey`` to raw binary form and return it. + ## Serialize EC public key ``pubkey`` to ASN.1 DER binary form and return it. if pubkey.key.curve in EcSupportedCurvesCint: - result = newSeq[byte](pubkey.key.qlen) - discard toBytes(pubkey, result) + result = newSeq[byte]() + let length = pubkey.toBytes(result) + result.setLen(length) + discard pubkey.toBytes(result) else: raise newException(EcKeyIncorrectError, "Incorrect public key") @@ -399,67 +417,127 @@ proc `==`*(sig1, sig2: EcSignature): bool = return false result = (sig1.buffer == sig2.buffer) -proc init*(key: var EcPrivateKey, data: openarray[byte]): bool = - ## Initialize EC `private key` or `scalar` ``key`` from raw binary +proc init*(key: var EcPrivateKey, data: openarray[byte]): Asn1Status = + ## Initialize EC `private key` or `scalar` ``key`` from ASN.1 DER binary ## representation ``data``. ## - ## Length of ``data`` array must be ``SecKey256Length``, ``SecKey384Length`` - ## or ``SecKey521Length``. - ## - ## Procedure returns ``true`` on success, ``false`` otherwise. + ## Procedure returns ``Asn1Status``. + var raw, oid, field: Asn1Field var curve: cint - if len(data) == SecKey256Length: - curve = cast[cint](Secp256r1) - result = true - elif len(data) == SecKey384Length: - curve = cast[cint](Secp384r1) - result = true - elif len(data) == SecKey521Length: - curve = cast[cint](Secp521r1) - result = true - if result: - result = false - if checkScalar(data, curve) == 1'u32: - let length = len(data) - key = new EcPrivateKey - key.buffer = newSeq[byte](length) - copyMem(addr key.buffer[0], unsafeAddr data[0], length) - key.key.x = cast[ptr cuchar](addr key.buffer[0]) - key.key.xlen = length - key.key.curve = curve - result = true -proc init*(pubkey: var EcPublicKey, data: openarray[byte]): bool = - ## Initialize EC public key ``pubkey`` from raw binary representation + var ab = Asn1Buffer.init(data) + + result = ab.read(field) + if result != Asn1Status.Success: + return + if field.kind != Asn1Tag.Sequence: + return Asn1Status.Incorrect + + var ib = field.getBuffer() + + result = ib.read(field) + if result != Asn1Status.Success: + return + if field.kind != Asn1Tag.Integer: + return Asn1Status.Incorrect + if field.vint != 1'u64: + return Asn1Status.Incorrect + + result = ib.read(raw) + if result != Asn1Status.Success: + return + if raw.kind != Asn1Tag.OctetString: + return Asn1Status.Incorrect + + result = ib.read(oid) + if result != Asn1Status.Success: + return + if oid.kind != Asn1Tag.Oid: + return Asn1Status.Incorrect + + if oid == Asn1OidSecp256r1: + curve = cast[cint](Secp256r1) + elif oid == Asn1OidSecp384r1: + curve = cast[cint](Secp384r1) + elif oid == Asn1OidSecp521r1: + curve = cast[cint](Secp521r1) + else: + return Asn1Status.Incorrect + + if checkScalar(raw.toOpenArray(), curve) == 1'u32: + key = new EcPrivateKey + key.buffer = newSeq[byte](raw.length) + copyMem(addr key.buffer[0], addr raw.buffer[raw.offset], raw.length) + key.key.x = cast[ptr cuchar](addr key.buffer[0]) + key.key.xlen = raw.length + key.key.curve = curve + result = Asn1Status.Success + else: + result = Asn1Status.Incorrect + +proc init*(pubkey: var EcPublicKey, data: openarray[byte]): Asn1Status = + ## Initialize EC public key ``pubkey`` from ASN.1 DER binary representation ## ``data``. ## - ## Length of ``data`` array must be ``PubKey256Length``, ``PubKey384Length`` - ## or ``PubKey521Length``. - ## - ## Procedure returns ``true`` on success, ``false`` otherwise. + ## Procedure returns ``Asn1Status``. + var raw, oid, field: Asn1Field var curve: cint - if len(data) > 0: - if data[0] == 0x04'u8: - if len(data) == PubKey256Length: - curve = cast[cint](Secp256r1) - result = true - elif len(data) == PubKey384Length: - curve = cast[cint](Secp384r1) - result = true - elif len(data) == PubKey521Length: - curve = cast[cint](Secp521r1) - result = true - if result: - result = false - if checkPublic(data, curve) != 0: - let length = len(data) - pubkey = new EcPublicKey - pubkey.buffer = newSeq[byte](length) - copyMem(addr pubkey.buffer[0], unsafeAddr data[0], length) - pubkey.key.q = cast[ptr cuchar](addr pubkey.buffer[0]) - pubkey.key.qlen = length - pubkey.key.curve = curve - result = true + + var ab = Asn1Buffer.init(data) + result = ab.read(field) + if result != Asn1Status.Success: + return + if field.kind != Asn1Tag.Sequence: + return Asn1Status.Incorrect + + var ib = field.getBuffer() + result = ib.read(field) + if result != Asn1Status.Success: + return + if field.kind != Asn1Tag.Sequence: + return Asn1Status.Incorrect + + var ob = field.getBuffer() + result = ob.read(oid) + if result != Asn1Status.Success: + return + if oid.kind != Asn1Tag.Oid: + return Asn1Status.Incorrect + + if oid != Asn1OidEcPublicKey: + return Asn1Status.Incorrect + + result = ob.read(oid) + if result != Asn1Status.Success: + return + if oid.kind != Asn1Tag.Oid: + return Asn1Status.Incorrect + + if oid == Asn1OidSecp256r1: + curve = cast[cint](Secp256r1) + elif oid == Asn1OidSecp384r1: + curve = cast[cint](Secp384r1) + elif oid == Asn1OidSecp521r1: + curve = cast[cint](Secp521r1) + else: + return Asn1Status.Incorrect + + result = ib.read(raw) + if result != Asn1Status.Success: + return + if raw.kind != Asn1Tag.BitString: + return Asn1Status.Incorrect + + if checkPublic(raw.toOpenArray(), curve) != 0: + pubkey = new EcPublicKey + pubkey.buffer = newSeq[byte](raw.length) + copyMem(addr pubkey.buffer[0], addr raw.buffer[raw.offset], raw.length) + pubkey.key.q = cast[ptr cuchar](addr pubkey.buffer[0]) + pubkey.key.qlen = raw.length + pubkey.key.curve = curve + result = Asn1Status.Success + else: + result = Asn1Status.Incorrect proc init*(sig: var EcSignature, data: openarray[byte]): bool = ## Initialize EC signature ``sig`` from raw binary representation ``data``. @@ -493,16 +571,20 @@ proc init*[T: EcPKI](sospk: var T, data: string): bool {.inline.} = result = sospk.init(fromHex(data)) proc init*(t: typedesc[EcPrivateKey], data: openarray[byte]): EcPrivateKey = - ## Initialize EC private key from raw binary representation ``data`` and + ## Initialize EC private key from ASN.1 DER binary representation ``data`` and ## return constructed object. - if not result.init(data): - raise newException(EcKeyIncorrectError, "Incorrect private key") + let res = result.init(data) + if res != Asn1Status.Success: + raise newException(EcKeyIncorrectError, + "Incorrect private key (" & $res & ")") proc init*(t: typedesc[EcPublicKey], data: openarray[byte]): EcPublicKey = - ## Initialize EC public key from raw binary representation ``data`` and + ## Initialize EC public key from ASN.1 DER binary representation ``data`` and ## return constructed object. - if not result.init(data): - raise newException(EcKeyIncorrectError, "Incorrect public key") + let res = result.init(data) + if res != Asn1Status.Success: + raise newException(EcKeyIncorrectError, + "Incorrect public key (" & $res & ")") proc init*(t: typedesc[EcSignature], data: openarray[byte]): EcSignature = ## Initialize EC signature from raw binary representation ``data`` and @@ -585,3 +667,20 @@ proc verify*[T: byte|char](sig: EcSignature, message: openarray[T], # Clear context with initial value kv.init(addr hc.vtable) result = (res == 1) + +when isMainModule: + var buffer = newSeq[byte]() + var kp = EcKeyPair.random(Secp256r1) + var length: int + + var serializedSK = kp.seckey.getBytes() + var serializedPK = kp.pubkey.getBytes() + echo toHex(serializedPK) + echo toHex(serializedSK) + + var kp2 = EcPrivateKey.init(serializedSK) + echo toHex(kp2.getBytes()) + + var pk2 = EcPublicKey.init(serializedPK) + echo repr pk2 + echo toHex(pk2.getBytes()) diff --git a/libp2p/crypto/minasn1.nim b/libp2p/crypto/minasn1.nim new file mode 100644 index 000000000..694644dcc --- /dev/null +++ b/libp2p/crypto/minasn1.nim @@ -0,0 +1,773 @@ +## Nim-Libp2p +## Copyright (c) 2018 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 minimal ASN.1 encoding/decoding primitives. +import endians +import nimcrypto/utils + +type + Asn1Status* {.pure.} = enum + Error, + Success, + Overflow, + Incomplete, + Indefinite, + Incorrect, + NoSupport, + Overrun + + Asn1Class* {.pure.} = enum + Universal = 0x00, + Application = 0x01 + ContextSpecific = 0x02 + Private = 0x03 + + Asn1Tag* {.pure.} = enum + ## Protobuf's field types enum + NoSupport, + Boolean, + Integer, + BitString, + OctetString, + Null, + Oid, + Sequence, + Context + + Asn1Buffer* = object of RootObj + ## ASN.1's message representation object + buffer*: seq[byte] + offset*: int + length*: int + + Asn1Field* = object + klass*: Asn1Class + index*: int + offset*: int + length*: int + buffer*: seq[byte] + case kind*: Asn1Tag + of Asn1Tag.Boolean: + vbool*: bool + of Asn1Tag.Integer: + vint*: uint64 + of Asn1Tag.BitString: + ubits*: int + else: + discard + + Asn1Composite* = object of Asn1Buffer + tag*: Asn1Tag + idx*: int + +const + Asn1OidSecp256r1* = [ + 0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x03'u8, 0x01'u8, 0x07'u8 + ] + ## Encoded OID for `secp256r1` curve (1.2.840.10045.3.1.7) + Asn1OidSecp384r1* = [ + 0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x22'u8 + ] + ## Encoded OID for `secp384r1` curve (1.3.132.0.34) + Asn1OidSecp521r1* = [ + 0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x23'u8 + ] + ## Encoded OID for `secp521r1` curve (1.3.132.0.35) + Asn1OidSecp256k1* = [ + 0x2B'u8, 0x81'u8, 0x04'u8, 0x00'u8, 0x0A'u8 + ] + ## Encoded OID for `secp256k1` curve (1.3.132.0.10) + Asn1OidEcPublicKey* = [ + 0x2A'u8, 0x86'u8, 0x48'u8, 0xCE'u8, 0x3D'u8, 0x02'u8, 0x01'u8 + ] + ## Encoded OID for Elliptic Curve Public Key (1.2.840.10045.2.1) + Asn1OidRsaEncryption* = [ + 0x2A'u8, 0x86'u8, 0x48'u8, 0x86'u8, 0xF7'u8, 0x0D'u8, 0x01'u8, + 0x01'u8, 0x01'u8 + ] + ## Encoded OID for RSA Encryption (1.2.840.113549.1.1.1) + Asn1True* = [0x01'u8, 0x01'u8, 0xFF'u8] + ## Encoded boolean ``TRUE``. + Asn1False* = [0x01'u8, 0x01'u8, 0x00'u8] + ## Encoded boolean ``FALSE``. + Asn1Null* = [0x05'u8, 0x00'u8] + ## Encoded ``NULL`` value. + +template toOpenArray*(ab: Asn1Buffer): untyped = + toOpenArray(ab.buffer, ab.offset, len(ab.buffer) - 1) + +template toOpenArray*(ac: Asn1Composite): untyped = + toOpenArray(ac.buffer, ac.offset, len(ac.buffer) - 1) + +template toOpenArray*(af: Asn1Field): untyped = + toOpenArray(af.buffer, af.offset, af.offset + af.length - 1) + +template isEmpty*(ab: Asn1Buffer): bool = + ab.offset >= len(ab.buffer) + +template isEnough*(ab: Asn1Buffer, length: int): bool = + len(ab.buffer) >= ab.offset + length + +proc len*[T: Asn1Buffer|Asn1Composite](abc: T): int {.inline.} = + len(abc.buffer) - abc.offset + +proc extend*[T: Asn1Buffer|Asn1Composite](abc: var T, length: int) {.inline.} = + ## Extend buffer or composite's internal buffer by ``length`` octets. + abc.buffer.setLen(len(abc.buffer) + length) + +proc code*(tag: Asn1Tag): byte {.inline.} = + ## Converts Nim ``tag`` enum to ASN.1 tag code. + case tag: + of Asn1Tag.NoSupport: + 0x00'u8 + of Asn1Tag.Boolean: + 0x01'u8 + of Asn1Tag.Integer: + 0x02'u8 + of Asn1Tag.BitString: + 0x03'u8 + of Asn1Tag.OctetString: + 0x04'u8 + of Asn1Tag.Null: + 0x05'u8 + of Asn1Tag.Oid: + 0x06'u8 + of Asn1Tag.Sequence: + 0x30'u8 + of Asn1Tag.Context: + 0xA0'u8 + +proc asn1EncodeLength*(dest: var openarray[byte], length: int64): int = + ## Encode ASN.1 DER length part of TLV triple and return number of bytes + ## (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``length`` value, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + if length < 0x80: + if len(dest) >= 1: + dest[0] = cast[byte](length) + result = 1 + else: + result = 0 + var z = length + while z != 0: + inc(result) + z = z shr 8 + if len(dest) >= result + 1: + dest[0] = cast[byte](0x80 + result) + var o = 1 + for j in countdown(result - 1, 0): + dest[o] = cast[byte](length shr (j shl 3)) + inc(o) + inc(result) + +proc asn1EncodeInteger*(dest: var openarray[byte], + value: openarray[byte]): int = + ## Encode big-endian binary representation of integer as ASN.1 DER `INTEGER` + ## and return number of bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var o = 0 + var lenlen = 0 + for i in 0.. 0: + if o == len(value): + dec(o) + if value[o] >= 0x80'u8: + lenlen = asn1EncodeLength(buffer, len(value) - o + 1) + result = 1 + lenlen + 1 + (len(value) - o) + else: + lenlen = asn1EncodeLength(buffer, len(value) - o) + result = 1 + lenlen + (len(value) - o) + else: + result = 2 + if len(dest) >= result: + var s = 1 + dest[0] = Asn1Tag.Integer.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + if value[o] >= 0x80'u8: + dest[1 + lenlen] = 0x00'u8 + s = 2 + if len(value) > 0: + copyMem(addr dest[s + lenlen], unsafeAddr value[o], len(value) - o) + +proc asn1EncodeInteger*[T: SomeUnsignedInt](dest: var openarray[byte], + value: T): int = + ## Encode Nim's unsigned integer as ASN.1 DER `INTEGER` and return number of + ## bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + when T is uint64: + var buffer: array[8, byte] + bigEndian64(addr buffer[0], cast[pointer](unsafeAddr value)) + result = asn1EncodeInteger(dest, buffer) + elif T is uint32: + var buffer: array[4, byte] + bigEndian32(addr buffer[0], cast[pointer](unsafeAddr value)) + result = asn1EncodeInteger(dest, buffer) + elif T is uint16: + var buffer: array[2, byte] + bigEndian16(addr buffer[0], cast[pointer](unsafeAddr value)) + result = asn1EncodeInteger(dest, buffer) + elif T is uint8: + var buffer: array[1, byte] + buffer[0] = value + result = asn1EncodeInteger(dest, buffer) + +proc asn1EncodeBoolean*(dest: var openarray[byte], value: bool): int = + ## Encode Nim's boolean as ASN.1 DER `BOOLEAN` and return number of bytes + ## (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + result = 3 + if len(dest) >= result: + dest[0] = Asn1Tag.Boolean.code() + dest[1] = 0x01'u8 + dest[2] = if value: 0xFF'u8 else: 0x00'u8 + +proc asn1EncodeNull*(dest: var openarray[byte]): int = + ## Encode ASN.1 DER `NULL` and return number of bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + result = 2 + if len(dest) >= result: + dest[0] = Asn1Tag.Null.code() + dest[1] = 0x00'u8 + +proc asn1EncodeOctetString*(dest: var openarray[byte], + value: openarray[byte]): int = + ## Encode array of bytes as ASN.1 DER `OCTET STRING` and return number of + ## bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var lenlen = asn1EncodeLength(buffer, len(value)) + result = 1 + lenlen + len(value) + if len(dest) >= result: + dest[0] = Asn1Tag.OctetString.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + if len(value) > 0: + copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value)) + +proc asn1EncodeBitString*(dest: var openarray[byte], + value: openarray[byte], bits = 0): int = + ## Encode array of bytes as ASN.1 DER `BIT STRING` and return number of bytes + ## (octets) used. + ## + ## ``bits`` number of used bits in ``value``. If ``bits == 0``, all the bits + ## from ``value`` are used, if ``bits != 0`` only number of ``bits`` will be + ## used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var lenlen = asn1EncodeLength(buffer, len(value) + 1) + var lbits = 0 + if bits != 0: + lbits = len(value) shl 3 - bits + result = 1 + lenlen + 1 + len(value) + if len(dest) >= result: + dest[0] = Asn1Tag.BitString.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + dest[1 + lenlen] = cast[byte](lbits) + if len(value) > 0: + copyMem(addr dest[2 + lenlen], unsafeAddr value[0], len(value)) + +proc asn1EncodeTag[T: SomeUnsignedInt](dest: var openarray[byte], + value: T): int = + var v = value + if value <= cast[T](0x7F): + if len(dest) >= 1: + dest[0] = cast[byte](value) + result = 1 + else: + var s = 0 + while v != 0: + v = v shr 7 + s += 7 + inc(result) + if len(dest) >= result: + var k = 0 + while s != 0: + s -= 7 + dest[k] = cast[byte](((value shr s) and cast[T](0x7F)) or cast[T](0x80)) + inc(k) + dest[k - 1] = dest[k - 1] and 0x7F'u8 + +proc asn1EncodeOid*(dest: var openarray[byte], value: openarray[int]): int = + ## Encode array of integers ``value`` as ASN.1 DER `OBJECT IDENTIFIER` and + ## return number of bytes (octets) used. + ## + ## OBJECT IDENTIFIER requirements for ``value`` elements: + ## * len(value) >= 2 + ## * value[0] >= 1 and value[0] < 2 + ## * value[1] >= 1 and value[1] < 39 + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + result = 1 + assert(len(value) >= 2) + assert(value[0] >= 1 and value[0] < 2) + assert(value[1] >= 1 and value[1] <= 39) + var oidlen = 1 + for i in 2..= result: + let last = len(dest) - 1 + var offset = 1 + dest[0] = Asn1Tag.Oid.code() + offset += asn1EncodeLength(dest.toOpenArray(offset, last), oidlen) + dest[offset] = cast[byte](value[0] * 40 + value[1]) + offset += 1 + for i in 2..= result: + dest[0] = Asn1Tag.Oid.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value)) + +proc asn1EncodeSequence*(dest: var openarray[byte], + value: openarray[byte]): int = + ## Encode ``value`` as ASN.1 DER `SEQUENCE` and return number of bytes + ## (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var lenlen = asn1EncodeLength(buffer, len(value)) + result = 1 + lenlen + len(value) + if len(dest) >= result: + dest[0] = Asn1Tag.Sequence.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value)) + +proc asn1EncodeComposite*(dest: var openarray[byte], + value: Asn1Composite): int = + ## Encode composite value and return number of bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var lenlen = asn1EncodeLength(buffer, len(value.buffer)) + result = 1 + lenlen + len(value.buffer) + if len(dest) >= result: + dest[0] = value.tag.code() + copyMem(addr dest[1], addr buffer[0], lenlen) + copyMem(addr dest[1 + lenlen], unsafeAddr value.buffer[0], + len(value.buffer)) + +proc asn1EncodeContextTag*(dest: var openarray[byte], value: openarray[byte], + tag: int): int = + ## Encode ASN.1 DER `CONTEXT SPECIFIC TAG` ``tag`` for value ``value`` and + ## return number of bytes (octets) used. + ## + ## If length of ``dest`` is less then number of required bytes to encode + ## ``value``, then result of encoding will not be stored in ``dest`` + ## but number of bytes (octets) required will be returned. + var buffer: array[16, byte] + var lenlen = asn1EncodeLength(buffer, len(value)) + result = 1 + lenlen + len(value) + if len(dest) >= result: + dest[0] = 0xA0'u8 or (cast[byte](tag) and 0x0F) + copyMem(addr dest[1], addr buffer[0], lenlen) + copyMem(addr dest[1 + lenlen], unsafeAddr value[0], len(value)) + +proc getLength(ab: var Asn1Buffer, length: var uint64): Asn1Status = + ## Decode length part of ASN.1 TLV triplet. + result = Asn1Status.Incomplete + if not ab.isEmpty(): + let b = ab.buffer[ab.offset] + if (b and 0x80'u8) == 0x00'u8: + length = cast[uint64](b) + ab.offset += 1 + result = Asn1Status.Success + return + if b == 0x80'u8: + length = 0'u64 + result = Asn1Status.Indefinite + return + if b == 0xFF'u8: + length = 0'u64 + result = Asn1Status.Incorrect + return + let octets = cast[uint64](b and 0x7F'u8) + if octets > 8'u64: + length = 0'u64 + result = Asn1Status.Overflow + return + length = 0'u64 + if ab.isEnough(int(octets)): + for i in 0..= 0 and c < 4: + klass = cast[Asn1Class](c) + else: + return Asn1Status.Incorrect + tag = int(b and 0x3F) + ab.offset += 1 + result = Asn1Status.Success + +proc read*(ab: var Asn1Buffer, field: var Asn1Field): Asn1Status = + ## Decode value part of ASN.1 TLV triplet. + var + tag, ttag, offset: int + length, tlength: uint64 + klass: Asn1Class + res: Asn1Status + inclass: bool + + inclass = false + while true: + offset = ab.offset + result = ab.getTag(tag, klass) + if result != Asn1Status.Success: + break + + if klass == Asn1Class.ContextSpecific: + if inclass: + result = Asn1Status.Incorrect + break + inclass = true + ttag = tag + result = ab.getLength(tlength) + if result != Asn1Status.Success: + break + + elif klass == Asn1Class.Universal: + result = ab.getLength(length) + if result != Asn1Status.Success: + break + + if inclass: + if length >= tlength: + result = Asn1Status.Incorrect + break + + if cast[byte](tag) == Asn1Tag.Boolean.code(): + # BOOLEAN + if length != 1: + result = Asn1Status.Incorrect + break + if not ab.isEnough(cast[int](length)): + result = Asn1Status.Incomplete + break + let b = ab.buffer[ab.offset] + if b != 0xFF'u8 and b != 0x00'u8: + result = Asn1Status.Incorrect + break + field = Asn1Field(kind: Asn1Tag.Boolean, klass: klass, + index: ttag, offset: cast[int](ab.offset), + length: 1) + shallowCopy(field.buffer, ab.buffer) + field.vbool = (b == 0xFF'u8) + ab.offset += 1 + result = Asn1Status.Success + break + elif cast[byte](tag) == Asn1Tag.Integer.code(): + # INTEGER + if not ab.isEnough(cast[int](length)): + result = Asn1Status.Incomplete + break + field = Asn1Field(kind: Asn1Tag.Integer, klass: klass, + index: ttag, offset: cast[int](ab.offset), + length: cast[int](length)) + shallowCopy(field.buffer, ab.buffer) + if length <= 8: + for i in 0.. 0: + if field.length == len(data): + result = equalMem(unsafeAddr field.buffer[field.offset], + unsafeAddr data[0], field.length) + +proc init*(t: typedesc[Asn1Buffer], data: openarray[byte]): Asn1Buffer = + ## Initialize ``Asn1Buffer`` from array of bytes ``data``. + result.buffer = @data + +proc init*(t: typedesc[Asn1Buffer], data: string): Asn1Buffer = + ## Initialize ``Asn1Buffer`` from hexadecimal string ``data``. + result.buffer = fromHex(data) + +proc init*(t: typedesc[Asn1Buffer]): Asn1Buffer = + ## Initialize empty ``Asn1Buffer``. + result.buffer = newSeq[byte]() + +proc init*(t: typedesc[Asn1Composite], tag: Asn1Tag): Asn1Composite = + ## Initialize ``Asn1Composite`` with tag ``tag``. + result.tag = tag + result.buffer = newSeq[byte]() + +proc init*(t: typedesc[Asn1Composite], idx: int): Asn1Composite = + ## Initialize ``Asn1Composite`` with tag context-specific id ``id``. + result.tag = Asn1Tag.Context + result.idx = idx + result.buffer = newSeq[byte]() + +proc `$`*(buffer: Asn1Buffer): string = + ## Return string representation of ``buffer``. + result = toHex(buffer.toOpenArray()) + +proc `$`*(field: Asn1Field): string = + ## Return string representation of ``field``. + result = "[" + result.add($field.kind) + result.add("]") + if field.kind == Asn1Tag.NoSupport: + result.add(" ") + result.add(toHex(field.toOpenArray())) + elif field.kind == Asn1Tag.Boolean: + result.add(" ") + result.add($field.vbool) + elif field.kind == Asn1Tag.Integer: + result.add(" ") + if field.length <= 8: + result.add($field.vint) + else: + result.add(toHex(field.toOpenArray())) + elif field.kind == Asn1Tag.BitString: + result.add(" ") + result.add("(") + result.add($field.ubits) + result.add(" bits) ") + result.add(toHex(field.toOpenArray())) + elif field.kind == Asn1Tag.OctetString: + result.add(" ") + result.add(toHex(field.toOpenArray())) + elif field.kind == Asn1Tag.Null: + result.add(" NULL") + elif field.kind == Asn1Tag.Oid: + result.add(" ") + result.add(toHex(field.toOpenArray())) + elif field.kind == Asn1Tag.Sequence: + result.add(" ") + result.add(toHex(field.toOpenArray())) + +proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag) = + ## Write empty value to buffer or composite with ``tag``. + ## + ## This procedure must be used to write `NULL`, `0` or empty `BIT STRING`, + ## `OCTET STRING` types. + assert(tag in {Asn1Tag.Null, Asn1Tag.Integer, Asn1Tag.BitString, + Asn1Tag.OctetString}) + var length: int + if tag == Asn1Tag.Null: + length = asn1EncodeNull(abc.toOpenArray()) + abc.extend(length) + discard asn1EncodeNull(abc.toOpenArray()) + elif tag == Asn1Tag.Integer: + length = asn1EncodeInteger(abc.toOpenArray(), 0'u64) + abc.extend(length) + discard asn1EncodeInteger(abc.toOpenArray(), 0'u64) + elif tag == Asn1Tag.BitString: + var tmp: array[1, byte] + length = asn1EncodeBitString(abc.toOpenArray(), tmp.toOpenArray(0, -1)) + abc.extend(length) + discard asn1EncodeBitString(abc.toOpenArray(), tmp.toOpenArray(0, -1)) + elif tag == Asn1Tag.OctetString: + var tmp: array[1, byte] + length = asn1EncodeOctetString(abc.toOpenArray(), tmp.toOpenArray(0, -1)) + abc.extend(length) + discard asn1EncodeOctetString(abc.toOpenArray(), tmp.toOpenArray(0, -1)) + abc.offset += length + +proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: uint64) = + ## Write uint64 ``value`` to buffer or composite as ASN.1 `INTEGER`. + let length = asn1EncodeInteger(abc.toOpenArray(), value) + abc.extend(length) + discard asn1EncodeInteger(abc.toOpenArray(), value) + abc.offset += length + +proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: bool) = + ## Write bool ``value`` to buffer or composite as ASN.1 `BOOLEAN`. + let length = asn1EncodeBoolean(abc.toOpenArray(), value) + abc.extend(length) + discard asn1EncodeBoolean(abc.toOpenArray(), value) + abc.offset += length + +proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, tag: Asn1Tag, + value: openarray[byte], bits = 0) = + ## Write array ``value`` using ``tag``. + ## + ## This procedure is used to write ASN.1 `INTEGER`, `OCTET STRING`, + ## `BIT STRING` or `OBJECT IDENTIFIER`. + ## + ## For `BIT STRING` you can use ``bits`` argument to specify number of used + ## bits. + assert(tag in {Asn1Tag.Integer, Asn1Tag.OctetString, Asn1Tag.BitString, + Asn1Tag.Oid}) + var length: int + if tag == Asn1Tag.Integer: + length = asn1EncodeInteger(abc.toOpenArray(), value) + abc.extend(length) + discard asn1EncodeInteger(abc.toOpenArray(), value) + elif tag == Asn1Tag.OctetString: + length = asn1EncodeOctetString(abc.toOpenArray(), value) + abc.extend(length) + discard asn1EncodeOctetString(abc.toOpenArray(), value) + elif tag == Asn1Tag.BitString: + length = asn1EncodeBitString(abc.toOpenArray(), value, bits) + abc.extend(length) + discard asn1EncodeBitString(abc.toOpenArray(), value, bits) + elif tag == Asn1Tag.Oid: + length = asn1EncodeOid(abc.toOpenArray(), value) + abc.extend(length) + discard asn1EncodeOid(abc.toOpenArray(), value) + abc.offset += length + +proc write*[T: Asn1Buffer|Asn1Composite](abc: var T, value: Asn1Composite) = + assert(len(value) > 0, "Composite value not finished") + var length: int + if value.tag == Asn1Tag.Sequence: + length = asn1EncodeSequence(abc.toOpenArray(), value.buffer) + abc.extend(length) + discard asn1EncodeSequence(abc.toOpenArray(), value.buffer) + elif value.tag == Asn1Tag.BitString: + length = asn1EncodeBitString(abc.toOpenArray(), value.buffer) + abc.extend(length) + discard asn1EncodeBitString(abc.toOpenArray(), value.buffer) + elif value.tag == Asn1Tag.Context: + length = asn1EncodeContextTag(abc.toOpenArray(), value.buffer, value.idx) + abc.extend(length) + discard asn1EncodeContextTag(abc.toOpenArray(), value.buffer, value.idx) + abc.offset += length + +proc finish*[T: Asn1Buffer|Asn1Composite](abc: var T) {.inline.} = + ## Finishes buffer or composite and prepares it for writing. + abc.offset = 0