From 536aae2c479f0f801fa614988cc5a665fe6f3cc7 Mon Sep 17 00:00:00 2001 From: LucasMF1 Date: Wed, 1 Oct 2025 10:56:07 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Dialogo=20de=20verifica=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20chaves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/keygen/keygen.component.html | 103 +++++++++++++----- .../app/components/keygen/keygen.component.ts | 101 +++++++++++------ 2 files changed, 144 insertions(+), 60 deletions(-) diff --git a/Site/ClientApp/src/app/components/keygen/keygen.component.html b/Site/ClientApp/src/app/components/keygen/keygen.component.html index 4466655..db67b40 100644 --- a/Site/ClientApp/src/app/components/keygen/keygen.component.html +++ b/Site/ClientApp/src/app/components/keygen/keygen.component.html @@ -36,40 +36,44 @@

Geração e download de chaves

open_in_new

+ + -

Baixar chave privada

- -
- - - Insira uma senha - - @if (password.invalid || passwordError()) { - {{passwordError()}} - } - - - - Confirme a senha - - @if (confirm.invalid || confirmError()) { - {{confirmError()}} - } - +
+

Baixar chave privada

+ +
+ + Insira uma senha + + Informe a senha + + Digite pelo menos {{minPasswordLength}} caracteres + + + + + Confirme a senha + + Confirme a senha + As senhas não coincidem + +
-
+
+ + -
- - - -
+
+
@@ -88,3 +92,44 @@

Confirmar descarte

+ + +
+

Verificar chave

+ +
+ + + Chave pública (PEM) + + + + + + + Chave privada (PEM criptografado) + + + + + + + senha + + + +
+ +
+ + +
+ +
+
diff --git a/Site/ClientApp/src/app/components/keygen/keygen.component.ts b/Site/ClientApp/src/app/components/keygen/keygen.component.ts index 1f4e34e..c792c71 100644 --- a/Site/ClientApp/src/app/components/keygen/keygen.component.ts +++ b/Site/ClientApp/src/app/components/keygen/keygen.component.ts @@ -1,9 +1,9 @@ -import { Component, TemplateRef } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit, TemplateRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import * as forge from 'node-forge'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { FormsModule, ReactiveFormsModule, FormControl, Validators, AbstractControl, ValidationErrors } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, FormControl, Validators, AbstractControl, ValidationErrors, FormBuilder, FormGroup } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { firstValueFrom } from 'rxjs'; @@ -27,48 +27,58 @@ import { MatDividerModule } from '@angular/material/divider'; templateUrl: './keygen.component.html', styleUrls: ['./keygen.component.scss'], }) -export class KeygenComponent { - constructor(private dialog: MatDialog) { } +export class KeygenComponent implements OnInit { + constructor( + private dialog: MatDialog, + private readonly fb: FormBuilder, + ) { } generating = false; + hasKeys = false; + verifying = false; publicKeyPem = ''; privateKeyEncryptedPem = ''; privateKeyObj: forge.pki.rsa.PrivateKey | null = null; thumbprint = ''; moniker = ''; - password = new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.minLength(4)] }); - confirm = new FormControl('', { - nonNullable: true, validators: [ - Validators.required, - (control: AbstractControl): ValidationErrors | null => { - if (this.password && control.value != this.password.value) { - return { passwordMismatch: true }; - } - return null; - } + form!: FormGroup; + minPasswordLength = 4; - ] }); + verifyForm!: FormGroup; - passwordError(): string { - if (this.password.hasError('required')) return 'Informe a senha'; - if (this.password.hasError('minlength')) return 'Digite pelo menos 4 caracteres'; - - return ''; + ngOnInit(): void { + this.initializeForm(); } - confirmError(): string { - if (this.confirm.hasError('required')) return 'Confirme a senha'; - if (this.password.value != this.confirm.value) return 'As senhas não coincidem'; - return ''; + private initializeForm(): void { + this.form = this.fb.group({ + password: ['', [Validators.required, Validators.minLength(this.minPasswordLength)]], + confirm: ['', [Validators.required]] + }, { validators: this.passwordsMatchValidator }); + + this.verifyForm = this.fb.group({ + publicKey: ['', [Validators.required]], + privateKey: ['', [Validators.required]], + password: ['', [Validators.required]] + }); } - get hasKeys(): boolean { - return !!(this.publicKeyPem && this.privateKeyObj); + private passwordsMatchValidator = (group: FormGroup) => { + const p = group.get('password')?.value; + const c = group.get('confirm')?.value; + if (p !== c) { + return { passwordMismatch: true }; + } + return null; + }; + + get passwordValue(): string { + return this.form.get('password')?.value; } get formInvalid(): boolean { - return !!this.passwordError() || !!this.confirmError(); + return this.form.invalid; } async generate(): Promise { @@ -94,6 +104,8 @@ export class KeygenComponent { this.moniker = digest.toHex().slice(0, 6); this.thumbprint = digest.toHex(); + this.hasKeys = true; + } catch (err) { console.error('Falha ao gerar chaves:', err); } finally { @@ -109,16 +121,16 @@ export class KeygenComponent { async downloadPrivate(passwordtpl: TemplateRef): Promise { if (!this.hasKeys || !this.privateKeyObj) return; + this.initializeForm(); + const ref = this.dialog.open(passwordtpl, { disableClose: true }); const password: string = await firstValueFrom(ref.afterClosed()); - const pwdValue = this.password.value; - this.password.reset(''); - this.confirm.reset(''); + this.initializeForm(); if (!password) return; - const pem = forge.pki.encryptRsaPrivateKey(this.privateKeyObj, pwdValue, { + const pem = forge.pki.encryptRsaPrivateKey(this.privateKeyObj, password, { algorithm: 'aes256', count: 200_000, prfAlgorithm: 'sha256', @@ -139,6 +151,7 @@ export class KeygenComponent { this.privateKeyObj = null; this.thumbprint = ''; this.moniker = ''; + this.hasKeys = false; } private download(content: string, filename: string, mime = 'application/octet-stream'): void { @@ -153,4 +166,30 @@ export class KeygenComponent { a.click(); setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 100); } + + openVerifyDialog(tpl: TemplateRef): void { + this.verifyForm.reset(); + this.verifying = false; + this.dialog.open(tpl, { disableClose: false }); + } + + async loadFile(event: Event, controlName: 'publicKey' | 'privateKey'): Promise { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (!file) return; + + try { + const text = await file.text(); + this.verifyForm.get(controlName)?.setValue(text); + this.verifyForm.get(controlName)?.markAsDirty(); + this.verifyForm.updateValueAndValidity(); + + } finally { + input.value = ''; + } + } + + async verifyKey(): Promise { + + } } From 29665fa548b089f9f56f7bfdbd3a50b950b14ab1 Mon Sep 17 00:00:00 2001 From: LucasMF1 Date: Fri, 3 Oct 2025 13:12:36 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Verifica=C3=A7=C3=A3o=20das=20chaves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/keygen/keygen.component.html | 19 ++++++- .../app/components/keygen/keygen.component.ts | 54 +++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Site/ClientApp/src/app/components/keygen/keygen.component.html b/Site/ClientApp/src/app/components/keygen/keygen.component.html index db67b40..d53bebf 100644 --- a/Site/ClientApp/src/app/components/keygen/keygen.component.html +++ b/Site/ClientApp/src/app/components/keygen/keygen.component.html @@ -94,7 +94,7 @@

Confirmar descarte

-
+

Verificar chave

@@ -122,6 +122,23 @@

Verificar chave

+
+ + +

+ Chaves válidas: a pública corresponde à privada. +

+

+ Senha incorreta ou chave privada inválida. +

+

+ Não corresponde: a pública não bate com a privada. +

+

+ Formato inválido: verifique os blocos PEM fornecidos. +

+
+
diff --git a/Site/ClientApp/src/app/components/keygen/keygen.component.ts b/Site/ClientApp/src/app/components/keygen/keygen.component.ts index c792c71..d7bed39 100644 --- a/Site/ClientApp/src/app/components/keygen/keygen.component.ts +++ b/Site/ClientApp/src/app/components/keygen/keygen.component.ts @@ -11,6 +11,8 @@ import { MatIconModule } from '@angular/material/icon'; import { delay } from '../../classes/utils'; import { MatDividerModule } from '@angular/material/divider'; +type VerifyState = 'idle' | 'ok' | 'badpass' | 'mismatch' | 'parse'; + @Component({ selector: 'app-keygen', standalone: true, @@ -35,7 +37,7 @@ export class KeygenComponent implements OnInit { generating = false; hasKeys = false; - verifying = false; + publicKeyPem = ''; privateKeyEncryptedPem = ''; privateKeyObj: forge.pki.rsa.PrivateKey | null = null; @@ -46,6 +48,8 @@ export class KeygenComponent implements OnInit { minPasswordLength = 4; verifyForm!: FormGroup; + verifyState: VerifyState = 'idle'; + verifying = false; ngOnInit(): void { this.initializeForm(); @@ -169,8 +173,9 @@ export class KeygenComponent implements OnInit { openVerifyDialog(tpl: TemplateRef): void { this.verifyForm.reset(); + this.verifyState = 'idle'; this.verifying = false; - this.dialog.open(tpl, { disableClose: false }); + this.dialog.open(tpl, { disableClose: false, width: '450px' }); } async loadFile(event: Event, controlName: 'publicKey' | 'privateKey'): Promise { @@ -190,6 +195,49 @@ export class KeygenComponent implements OnInit { } async verifyKey(): Promise { - + if (this.verifyForm.invalid || this.verifying) return; + + this.verifying = true; + this.verifyState = 'idle'; + + await delay(100); + + const publicPem = (this.verifyForm.get('publicKey')?.value).trim(); + const privatePem = (this.verifyForm.get('privateKey')?.value).trim(); + const password = this.verifyForm.get('password')?.value; + + try { + let providedPub: forge.pki.rsa.PublicKey; + try { + providedPub = forge.pki.publicKeyFromPem(publicPem) as forge.pki.rsa.PublicKey; + } catch { + this.verifyState = 'parse'; + return; + } + + let privateKey = forge.pki.decryptRsaPrivateKey(privatePem, password); + + if (!privateKey) { + this.verifyState = 'badpass'; + return; + } + + const pubFromPriv = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e); + const sameN = pubFromPriv.n.compareTo(providedPub.n) === 0; + const sameE = pubFromPriv.e.compareTo(providedPub.e) === 0; + + if (!(sameN && sameE)) { + this.verifyState = 'mismatch'; + return; + } else { + this.verifyState = 'ok'; + return + } + + } catch { + this.verifyState = 'parse'; + } finally { + this.verifying = false; + } } } From 3918283a450014d6fe9eae02b54b42a833d814d1 Mon Sep 17 00:00:00 2001 From: LucasMF1 Date: Mon, 6 Oct 2025 12:22:20 -0300 Subject: [PATCH 3/3] Pequeno ajuste --- Site/ClientApp/src/app/components/keygen/keygen.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Site/ClientApp/src/app/components/keygen/keygen.component.ts b/Site/ClientApp/src/app/components/keygen/keygen.component.ts index d7bed39..7e96869 100644 --- a/Site/ClientApp/src/app/components/keygen/keygen.component.ts +++ b/Site/ClientApp/src/app/components/keygen/keygen.component.ts @@ -1,9 +1,9 @@ -import { ChangeDetectorRef, Component, OnInit, TemplateRef } from '@angular/core'; +import { Component, OnInit, TemplateRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import * as forge from 'node-forge'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { FormsModule, ReactiveFormsModule, FormControl, Validators, AbstractControl, ValidationErrors, FormBuilder, FormGroup } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, Validators, FormBuilder, FormGroup } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { firstValueFrom } from 'rxjs';