@indrajala/asn1der
Version:
parse ASN.1 DER structures
228 lines (176 loc) • 7.25 kB
text/typescript
import { parseDER, parseOID } from "../asn1der";
import { asn1TagDescription } from "./descriptions";
import { Asn1Class, Asn1Tag } from "./enums";
import { IAsn1Identifier } from "./interfaces";
import { x509OIDs } from './oid';
// 'YYMMDD 000000 Z'
export const parseUtcString = (s: string): Date => {
const yyyy = 2000 + parseInt(s.substring(0, 2));
const mm = parseInt(s.substring(2, 4));
const dd = parseInt(s.substring(4, 6));
const hours = parseInt(s.substring(6, 8));
const mins = parseInt(s.substring(8, 10));
const seconds = parseInt(s.substring(10, 12));
// console.log(`${yyyy}/${mm}/${dd} ${hours}:${mins}:${seconds}`);
return new Date(Date.UTC(
yyyy,
mm - 1,
dd,
hours,
mins,
seconds
));
};
// YYYYMMDD000000Z
// YYYY MM DD 00 00 00 Z
// e.g.
// 19920521000000Z
// 19920622123421Z
// 19920722132100.3Z
//
export const parseGeneralizedTimeString = (s: string): Date => {
const yyyy = parseInt(s.substring(0, 4));
const mm = parseInt(s.substring(4, 6));
const dd = parseInt(s.substring(6, 8));
const hours = parseInt(s.substring(8, 10));
const mins = parseInt(s.substring(10, 12));
const seconds = parseInt(s.substring(12, 14));
const hasFractionalSeconds = s[14] == '.';
const ms = (hasFractionalSeconds)
? parseInt(s.substring(15, s.length - 1)) * 1000
: 0;
// console.log(`${yyyy}/${mm}/${dd} ${hours}:${mins}:${seconds}`);
return new Date(Date.UTC(
yyyy,
mm - 1,
dd,
hours,
mins,
seconds,
ms
));
};
export class Asn1Node {
public raw: Buffer;
public identifier: IAsn1Identifier;
public length: number;
public content: Buffer;
public children: Array<Asn1Node>;
constructor(
raw: Buffer,
identifier: IAsn1Identifier,
length: number,
content: Buffer,
children: Array<Asn1Node>
) {
this.raw = raw;
this.identifier = identifier;
this.length = length;
this.content = content;
this.children = children;
}
public toString = (tagNumberLookup: Map<number, string> = null): string => {
const tagNumberLookupIsRelevant = [
Asn1Class.Application,
Asn1Class.ContextSpecific,
Asn1Class.Private
].includes(this.identifier.class);
// console.log(`tag number: ${this.identifier.tagNumber}`);
// console.log(`lookup has: ${tagNumberLookup?.has(this.identifier.tagNumber)}`);
const tagDescription = (
(tagNumberLookup != null)
&& (tagNumberLookup.has(this.identifier.tagNumber))
&& tagNumberLookupIsRelevant
)
? tagNumberLookup.get(this.identifier.tagNumber) + ` [${this.identifier.tagNumber}]`
: (asn1TagDescription.has(this.identifier.tagNumber))
? asn1TagDescription.get(this.identifier.tagNumber)
: null;
const label = (tagDescription != null)
? tagDescription
: `${this.identifier.tagNumber}`;
const oidStr = (this.identifier.tagNumber == Asn1Tag.ObjectIdentifier)
? parseOID(this.content)
: 'badly formed OID';
const oidDescription = (x509OIDs.has(oidStr))
? x509OIDs.get(oidStr)
: 'unknown OID';
const contentHex = this.getContentAsHex();
const hexLimit = 20;
const cleanContentHex = (contentHex.length > hexLimit)
? contentHex.substring(0, hexLimit) + '...'
: contentHex;
const universalContentStr = (this.identifier.tagNumber == Asn1Tag.ObjectIdentifier)
? `${oidStr} (${oidDescription})`
: (this.identifier.tagNumber == Asn1Tag.UTF8String)
? this.getUTF8String()
: (this.identifier.tagNumber == Asn1Tag.PrintableString)
? this.getPrintableString()
: (this.identifier.tagNumber == Asn1Tag.UTCTime)
? this.getUTCTime().toString()
: (this.identifier.tagNumber == Asn1Tag.Integer)
? this.getInteger().toString()
: (this.identifier.tagNumber == Asn1Tag.IA5String)
? this.getIA5String()
: (this.identifier.tagNumber == Asn1Tag.Boolean)
? this.getBoolean()
: (this.identifier.tagNumber == Asn1Tag.GeneralizedTime)
? this.getGeneralizedTime().toString()
: cleanContentHex;
const contentStr = (this.identifier.class == Asn1Class.Universal)
? universalContentStr
: cleanContentHex;
return `${label} - ${contentStr}`;
}
public getUTF8String = (): string =>
this.content.toString('utf-8');
public getPrintableString = (): string =>
this.content.toString('ascii');
public getUTCTime = (): Date =>
parseUtcString(this.content.toString('ascii'));
public getGeneralizedTime = (): Date =>
parseGeneralizedTimeString(this.content.toString('ascii'));
public getIA5String = (): string =>
this.content.toString('ascii');
public getBoolean = (): boolean =>
!(this.content.readInt8(0) == 0);
public getInteger = (): number =>
parseInt(this.content.toString('hex'), 16);
public getSetElementsAsIntegers = (): Array<number> =>
this.children.map(child => child.getInteger())
public getSetElements = (): Array<Asn1Node> =>
this.children
public getNull = (): boolean =>
this.identifier.tagNumber == Asn1Tag.Null;
public getContentAsHex = (): string =>
this.content.toString('hex');
// TODO fix argument order
public summary = (indent: number, tagNumberLookup: Map<number, string>): Array<string> => {
const childrenReport = [];
this.children
.forEach(child => childrenReport.push(...child.summary(indent, tagNumberLookup)));
return [
this.toString(tagNumberLookup),
...childrenReport.map(line => ' '.repeat(indent) + line)
];
}
public getChildAtZIndex = (zindex: number): Asn1Node =>
this.children[zindex]
public getChildWithTagNumber = (tagNumber: number): Asn1Node =>
this.children.find(it => it.identifier.tagNumber == tagNumber);
public get = (path: string): Asn1Node => {
const steps = path.split('.');
const firstStep = steps[0];
const firstStepTarget = (firstStep.startsWith('#'))
? this.getChildWithTagNumber(parseInt(firstStep.substring(1)))
: this.getChildAtZIndex(parseInt(firstStep));
return (steps.length == 1)
? firstStepTarget
: (firstStepTarget)
? firstStepTarget.get(steps.slice(1).join('.'))
: null;
}
public reparse = (): void => {
this.children = parseDER(this.content);
}
}