<commit_before>import { AsnConvert } from '@peculiar/asn1-schema';
import { Certificate } from '@peculiar/asn1-x509';
import {
  id_sha1WithRSAEncryption,
  id_sha256WithRSAEncryption,
  id_sha384WithRSAEncryption,
  id_sha512WithRSAEncryption,
} from '@peculiar/asn1-rsa';
import {
  id_ecdsaWithSHA1,
  id_ecdsaWithSHA256,
  id_ecdsaWithSHA384,
  id_ecdsaWithSHA512,
} from '@peculiar/asn1-ecc';
import { Convert } from 'pvtsutils';

import { HashedAlgorithm } from './types';
import { cryptoProvider } from './provider';
import { Name, INameJSON } from './name';
import { Extension } from './extension';
import { AsnData } from './asn_data';

export interface ISignature {
  algorithm: HashedAlgorithm;
  value: BufferSource;
}

export class X509Certificate extends AsnData<Certificate> {
  public readonly serialNumber: string;
  public readonly subject: INameJSON[];
  public readonly issuer: INameJSON[];
  public readonly notBefore: Date;
  public readonly notAfter: Date;
  public extensions: Extension[];
  public readonly version: number;
  public signature: ISignature;
  public publicKey: CryptoKey;

  public constructor(raw: BufferSource) {
    super(raw, Certificate);

    const { tbsCertificate } = this.asn;

    this.serialNumber = Convert.ToHex(tbsCertificate.serialNumber);
    this.subject = new Name(tbsCertificate.subject).toJSON();
    this.issuer = new Name(tbsCertificate.issuer).toJSON();
    this.version = tbsCertificate.version + 1;

    const notBefore = tbsCertificate.validity.notBefore.utcTime
      || tbsCertificate.validity.notBefore.generalTime;

    if (!notBefore) {
      throw new Error("Cannot get 'notBefore' value");
    }

    this.notBefore = notBefore;

    const notAfter = tbsCertificate.validity.notAfter.utcTime
      || tbsCertificate.validity.notAfter.generalTime;

    if (!notAfter) {
      throw new Error("Cannot get 'notAfter' value");
    }

    this.notAfter = notAfter;

    this.parseExtensions();
    this.parseSignature();
  }

  public parseExtensions() {
    const { tbsCertificate } = this.asn;

    if (tbsCertificate.extensions) {
      this.extensions = tbsCertificate.extensions
        .map(e => new Extension(AsnConvert.serialize(e)));
    }
  }

  public async parsePublicKey() {
    const signatureAlgorithm = this.getSignatureAlgorithm();
    const keyUsages: KeyUsage[] = ['verify'];
    const crypto = cryptoProvider.get();
    const spki = AsnConvert.serialize(this.asn.tbsCertificate.subjectPublicKeyInfo);

    this.publicKey = await crypto.subtle
      .importKey('spki', spki, signatureAlgorithm, true, keyUsages);
  }

  public parseSignature() {
    const { signatureValue } = this.asn;

    this.signature = {
      algorithm: this.getSignatureAlgorithm(),
      value: signatureValue,
    };
  }

  private getSignatureAlgorithm(): HashedAlgorithm {
    const { signatureAlgorithm } = this.asn;

    switch (signatureAlgorithm.algorithm) {
      case id_sha1WithRSAEncryption:
        return { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-1' } };
      case id_sha256WithRSAEncryption:
        return { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } };
      case id_sha384WithRSAEncryption:
        return { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-384' } };
      case id_sha512WithRSAEncryption:
        return { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512' } };
      case id_ecdsaWithSHA1:
        return { name: 'ECDSA', hash: { name: 'SHA-1' } };
      case id_ecdsaWithSHA256:
        return { name: 'ECDSA', hash: { name: 'SHA-256' } };
      case id_ecdsaWithSHA384:
        return { name: 'ECDSA', hash: { name: 'SHA-384' } };
      case id_ecdsaWithSHA512:
        return { name: 'ECDSA', hash: { name: 'SHA-512' } };
      default:
        return { name: signatureAlgorithm.algorithm, hash: { name: '' } };
    }
  }

  public async verify(date = new Date(), crypto = cryptoProvider.get()) {
    const tbs = AsnConvert.serialize(this.asn.tbsCertificate);
    const ok = await crypto.subtle
      .verify(this.signature.algorithm, this.publicKey, this.asn.signatureValue, tbs);
    const time = date.getTime();

    return ok && this.notBefore.getTime() < time && time < this.notAfter.getTime();
  }

  public async getThumbprint(
    algorithm: globalThis.AlgorithmIdentifier = 'SHA-1',
  ): Promise<ArrayBuffer> {
    const crypto = cryptoProvider.get();

    return crypto.subtle
      .digest(algorithm, this.rawData);
  }
}<commit_msg>feat(crypto): Added parsing public, added export method<commit_after>import { AsnConvert } from '@peculiar/asn1-schema';
import { ECParameters, id_ecPublicKey } from '@peculiar/asn1-ecc';
import { id_rsaEncryption, RSAPublicKey } from '@peculiar/asn1-rsa';
import { Certificate } from '@peculiar/asn1-x509';
import { Convert } from 'pvtsutils';

import * as dateFormatter from '../utils/dateFormatter';
import validator from '../utils/validator';

import { cryptoProvider } from './provider';
import { Name, INameJSON } from './name';
import { Extension } from './extension';
import { AsnData } from './asn_data';

export interface ISignature {
  algorithm: string;
  value: BufferSource;
}

export interface IPublicKey {
  algorithm: string;
  value: BufferSource;
  params?: ECParameters | RSAPublicKey;
}

export class X509Certificate extends AsnData<Certificate> {
  public readonly serialNumber: string;
  public readonly subject: INameJSON[];
  public readonly issuer: INameJSON[];
  public readonly notBefore: Date;
  public readonly notAfter: Date;
  public readonly validity: string;
  public extensions: Extension[];
  public readonly version: number;
  public thumprints: Record<string, ArrayBuffer> = {};

  private static base64Clear(base64: string) {
    return base64
      .replace(/.*base64,/, '')
      .replace(/-----.+-----/g, '')
      .replace(/[\s\r\n]/g, '');
  }

  private static rawClarify(raw: string): ArrayBuffer {
    const value = X509Certificate.base64Clear(raw);
    let certificateBuffer: ArrayBuffer;

    if (validator.isHex(value)) {
      certificateBuffer = Convert.FromHex(value);
    } else if (validator.isBase64(value) || validator.isPem(value)) {
      certificateBuffer = Convert.FromBase64(value);
    } else {
      certificateBuffer = Convert.FromBinary(raw);
    }

    return certificateBuffer;
  }

  public constructor(raw: string) {
    super(X509Certificate.rawClarify(raw), Certificate);

    const { tbsCertificate } = this.asn;

    this.serialNumber = Convert.ToHex(tbsCertificate.serialNumber);
    this.subject = new Name(tbsCertificate.subject).toJSON();
    this.issuer = new Name(tbsCertificate.issuer).toJSON();
    this.version = tbsCertificate.version + 1;

    const notBefore = tbsCertificate.validity.notBefore.utcTime
      || tbsCertificate.validity.notBefore.generalTime;

    if (!notBefore) {
      throw new Error("Cannot get 'notBefore' value");
    }

    this.notBefore = notBefore;

    const notAfter = tbsCertificate.validity.notAfter.utcTime
      || tbsCertificate.validity.notAfter.generalTime;

    if (!notAfter) {
      throw new Error("Cannot get 'notAfter' value");
    }

    this.notAfter = notAfter;
    this.validity = dateFormatter.diff(this.notBefore, this.notAfter);
  }

  public parseExtensions() {
    const { tbsCertificate } = this.asn;

    if (tbsCertificate.extensions) {
      this.extensions = tbsCertificate.extensions
        .map(e => new Extension(AsnConvert.serialize(e)));
    }
  }

  public get publicKey(): IPublicKey {
    const { subjectPublicKey, algorithm } = this.asn.tbsCertificate.subjectPublicKeyInfo;
    let params;

    if (algorithm.algorithm === id_ecPublicKey && algorithm.parameters) {
      params = AsnConvert.parse(algorithm.parameters, ECParameters);
    }

    if (algorithm.algorithm === id_rsaEncryption) {
      params = AsnConvert.parse(subjectPublicKey, RSAPublicKey);
    }

    const spki = AsnConvert.serialize(this.asn.tbsCertificate.subjectPublicKeyInfo);

    return {
      params,
      value: spki,
      algorithm: algorithm.algorithm,
    };
  }

  public get signature(): ISignature {
    const { signatureValue, signatureAlgorithm } = this.asn;

    return {
      value: signatureValue,
      algorithm: signatureAlgorithm.algorithm,
    };
  }

  public export(type: 'base64' | 'hex' | 'pem'): string {
    if (type === 'base64') {
      return Convert.ToBase64(this.raw);
    }

    if (type === 'hex') {
      return Convert.ToHex(this.raw);
    }

    if (type === 'pem') {
      return this.base64ToPem(Convert.ToBase64(this.raw));
    }
  }

  private base64ToPem(base64: string) {
    return `-----BEGIN CERTIFICATE-----\n${base64.replace(/(.{64})/g, '$1\n')}\n-----END CERTIFICATE-----`;
  }

  public async getThumbprint(
    algorithm: globalThis.AlgorithmIdentifier = 'SHA-1',
  ): Promise<void> {
    const crypto = cryptoProvider.get();
    const thumbprint = await crypto.subtle.digest(algorithm, this.raw);

    this.thumprints[algorithm as any] = thumbprint;
  }
}
{'fraction_non_alphanumeric': 0.12708472314876584, 'fraction_numerical': 0.015010006671114077, 'mean_word_length': 3.5567375886524824, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2204074', 'n_tokens_mistral': 2915, 'n_tokens_neox': 2779, 'n_words': 607}