Skip to content

Commit

Permalink
encoding agnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
hazae41 committed Feb 23, 2023
1 parent 9311f4e commit 77c21cd
Show file tree
Hide file tree
Showing 29 changed files with 865 additions and 604 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ npm i @hazae41/asn1
- 100% TypeScript and ESM
- No external dependency
- Zero-copy reading and writing
- Encoding agnostic (only DER is currently supported)
- Encoding agnostic (even though only DER is currently implemented)
- Almost all universal triplets
- Implicit and explicit tagged types

Expand Down
83 changes: 41 additions & 42 deletions src/mods/der.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Integer } from "mods/triplets/integer/integer.js";
import { Null } from "mods/triplets/null/null.js";
import { ObjectIdentifier } from "mods/triplets/object_identifier/object_identifier.js";
import { OctetString } from "mods/triplets/octet_string/octet_string.js";
import { OpaqueTriplet } from "mods/triplets/opaque/opaque.js";
import { Opaque } from "mods/triplets/opaque/opaque.js";
import { PrintableString } from "mods/triplets/printable_string/printable_string.js";
import { Sequence } from "mods/triplets/sequence/sequence.js";
import { Set } from "mods/triplets/set/set.js";
Expand All @@ -19,72 +19,71 @@ import { Type } from "mods/type/type.js";

export namespace DER {

function resolve(opaque: OpaqueTriplet) {
return opaque.into(DER)
function resolve(opaque: Opaque): Triplet {
if (opaque.type.equals(Boolean.type))
return opaque.into(Boolean.DER)
if (opaque.type.equals(Integer.type))
return opaque.into(Integer.DER)
if (opaque.type.equals(BitString.type))
return opaque.into(BitString.DER)
if (opaque.type.equals(OctetString.type))
return opaque.into(OctetString.DER)
if (opaque.type.equals(Null.type))
return opaque.into(Null.DER)
if (opaque.type.equals(ObjectIdentifier.type))
return opaque.into(ObjectIdentifier.DER)
if (opaque.type.equals(UTF8String.type))
return opaque.into(UTF8String.DER)
if (opaque.type.equals(PrintableString.type))
return opaque.into(PrintableString.DER)
if (opaque.type.equals(Sequence.type))
return resolveSequence(opaque.into(Sequence.DER))
if (opaque.type.equals(Set.type))
return resolveSet(opaque.into(Set.DER))
if (opaque.type.equals(IA5String.type))
return opaque.into(IA5String.DER)
if (opaque.type.equals(UTCTime.type))
return opaque.into(UTCTime.DER)

if (opaque.type.clazz === Type.clazzes.UNIVERSAL)
return opaque.into(Unknown.DER)

if (opaque.type.wrap === Type.wraps.CONSTRUCTED)
return resolveConstructed(opaque.into(Constructed.DER))

return opaque.into(Unknown.DER)
}

function resolveSequence(sequence: Sequence<OpaqueTriplet>) {
function resolveSequence(sequence: Sequence<Opaque>) {
const { type, triplets } = sequence

return new Sequence(type, triplets.map(resolve))
}

function resolveSet(set: Set<OpaqueTriplet>) {
function resolveSet(set: Set<Opaque>) {
const { type, triplets } = set

return new Set(type, triplets.map(resolve))
}

function resolveConstructed(constructed: Constructed<OpaqueTriplet>) {
function resolveConstructed(constructed: Constructed<Opaque>) {
const { type, triplets } = constructed

return new Constructed(type, triplets.map(resolve))
}

export function read(cursor: Cursor): Triplet {
const start = cursor.offset
const type = Type.DER.read(cursor)
cursor.offset = start

if (type.equals(Boolean.type))
return Boolean.read(cursor)
if (type.equals(Integer.type))
return Integer.read(cursor)
if (type.equals(BitString.type))
return BitString.read(cursor)
if (type.equals(OctetString.type))
return OctetString.read(cursor)
if (type.equals(Null.type))
return Null.read(cursor)
if (type.equals(ObjectIdentifier.type))
return ObjectIdentifier.read(cursor)
if (type.equals(UTF8String.type))
return UTF8String.read(cursor)
if (type.equals(PrintableString.type))
return PrintableString.read(cursor)
if (type.equals(Sequence.type))
return resolveSequence(Sequence.read(cursor))
if (type.equals(Set.type))
return resolveSet(Set.read(cursor))
if (type.equals(IA5String.type))
return IA5String.read(cursor)
if (type.equals(UTCTime.type))
return UTCTime.read(cursor)

if (type.clazz === Type.clazzes.UNIVERSAL)
throw new Error(`Unknown UNIVERSAL type ${type.tag}`)

if (type.wrap === Type.wraps.CONSTRUCTED)
return resolveConstructed(Constructed.read(cursor))
return Unknown.read(cursor)
const opaque = Opaque.DER.read(cursor)

return resolve(opaque)
}

export function fromBytes(bytes: Uint8Array) {
return Readable.fromBytes(DER, bytes)
}

export function toBytes(triplet: Triplet) {
return Preparable.toBytes(triplet)
return Preparable.toBytes(triplet.DER)
}

export function tryRead(cursor: Cursor) {
Expand Down
2 changes: 1 addition & 1 deletion src/mods/triplets/bit_string/bit_string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function hexToCursor(hex: string) {

function checkReadWrite(hex: string) {
const input = hexToCursor(hex)
const triplet = BitString.read(input)
const triplet = BitString.DER.read(input)

const output = DER.toBytes(triplet)
return input.buffer.equals(output)
Expand Down
86 changes: 52 additions & 34 deletions src/mods/triplets/bit_string/bit_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class BitString {
Type.wraps.PRIMITIVE,
Type.tags.BIT_STRING)

readonly DER = new BitString.DER(this)

constructor(
readonly type: Type,
readonly padding: number,
Expand All @@ -22,54 +24,70 @@ export class BitString {
return new this(this.type, padding, bytes)
}

#data?: {
length: Length
get class() {
return this.#class
}

prepare() {
const length = new Length(1 + this.bytes.length).DER.prepare().parent
toString() {
const bignum = BigInt("0x" + Bytes.toHex(this.bytes))
const cursor = bignum.toString(2).padStart(this.bytes.length * 8, "0")

this.#data = { length }
return this
return `BITSTRING ${cursor.slice(0, cursor.length - this.padding)}`
}

size() {
if (!this.#data)
throw new Error(`Unprepared ${this.#class.name}`)
const { length } = this.#data
}

return Triplets.size(length)
}
export namespace BitString {

write(cursor: Cursor) {
if (!this.#data)
throw new Error(`Unprepared ${this.#class.name}`)
const { length } = this.#data
export class DER {
static parent = BitString

this.type.DER.write(cursor)
length.DER.write(cursor)
constructor(
readonly parent: BitString
) { }

cursor.writeUint8(this.padding)
cursor.write(this.bytes)
}
#data?: {
length: Length
}

static read(cursor: Cursor) {
const type = Type.DER.read(cursor)
const length = Length.DER.read(cursor)
prepare() {
const length = new Length(1 + this.parent.bytes.length).DER.prepare().parent

const subcursor = new Cursor(cursor.read(length.value))
this.#data = { length }
return this
}

const padding = subcursor.readUint8()
const buffer = subcursor.read(subcursor.remaining)
size() {
if (!this.#data)
throw new Error(`Unprepared ${this.parent.class.name}`)
const { length } = this.#data

return new this(type, padding, buffer)
}
return Triplets.size(length)
}

toString() {
const bignum = BigInt("0x" + Bytes.toHex(this.bytes))
const cursor = bignum.toString(2).padStart(this.bytes.length * 8, "0")
write(cursor: Cursor) {
if (!this.#data)
throw new Error(`Unprepared ${this.parent.class.name}`)
const { length } = this.#data

return `BITSTRING ${cursor.slice(0, cursor.length - this.padding)}`
}
this.parent.type.DER.write(cursor)
length.DER.write(cursor)

cursor.writeUint8(this.parent.padding)
cursor.write(this.parent.bytes)
}

static read(cursor: Cursor) {
const type = Type.DER.read(cursor)
const length = Length.DER.read(cursor)

const subcursor = new Cursor(cursor.read(length.value))

const padding = subcursor.readUint8()
const buffer = subcursor.read(subcursor.remaining)

return new this.parent(type, padding, buffer)
}

}
}
2 changes: 1 addition & 1 deletion src/mods/triplets/boolean/boolean.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function hexToCursor(hex: string) {

function checkReadWrite(hex: string) {
const input = hexToCursor(hex)
const triplet = Boolean.read(input)
const triplet = Boolean.DER.read(input)

const output = DER.toBytes(triplet)
return input.buffer.equals(output)
Expand Down
80 changes: 49 additions & 31 deletions src/mods/triplets/boolean/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export class Boolean {
Type.wraps.PRIMITIVE,
Type.tags.BOOLEAN)

readonly DER = new Boolean.DER(this)

constructor(
readonly type: Type,
readonly value: number
Expand All @@ -20,50 +22,66 @@ export class Boolean {
return new this(this.type, value)
}

#data?: {
length: Length
get class() {
return this.#class
}

prepare() {
const length = new Length(1).DER.prepare().parent

this.#data = { length }
return this
toString() {
return `BOOLEAN ${this.value !== 0}`
}

size() {
if (!this.#data)
throw new Error(`Unprepared ${this.#class.name}`)
const { length } = this.#data
}

return Triplets.size(length)
}
export namespace Boolean {

write(cursor: Cursor) {
if (!this.#data)
throw new Error(`Unprepared ${this.#class.name}`)
export class DER {
static parent = Boolean

const { length } = this.#data
constructor(
readonly parent: Boolean
) { }

this.type.DER.write(cursor)
length.DER.write(cursor)
#data?: {
length: Length
}

cursor.writeUint8(this.value)
}
prepare() {
const length = new Length(1).DER.prepare().parent

static read(cursor: Cursor) {
const type = Type.DER.read(cursor)
const length = Length.DER.read(cursor)
this.#data = { length }
return this
}

if (length.value !== 1)
throw new Error(`Invalid ${this.name} length`)
size() {
if (!this.#data)
throw new Error(`Unprepared ${this.parent.class.name}`)
const { length } = this.#data

const value = cursor.readUint8()
return Triplets.size(length)
}

return new this(type, value)
}
write(cursor: Cursor) {
if (!this.#data)
throw new Error(`Unprepared ${this.parent.class.name}`)

toString() {
return `BOOLEAN ${this.value !== 0}`
const { length } = this.#data

this.parent.type.DER.write(cursor)
length.DER.write(cursor)

cursor.writeUint8(this.parent.value)
}

static read(cursor: Cursor) {
const type = Type.DER.read(cursor)
const length = Length.DER.read(cursor)

if (length.value !== 1)
throw new Error(`Invalid ${this.name} length`)

const value = cursor.readUint8()

return new this.parent(type, value)
}
}
}
Loading

0 comments on commit 77c21cd

Please sign in to comment.