Add separate pgp passphrase entry page.
This commit is contained in:
parent
2ef78f47a9
commit
d72629921a
@ -28,7 +28,7 @@ interface MutableKeyStore extends KeyStore {
|
||||
removePublicKeyForId(keyId: string): any;
|
||||
removePublicKey(publicKey: any): any;
|
||||
clearKeysInKeyring(): void;
|
||||
sign(plainText: string): Promise<any>;
|
||||
sign(plainText: string, passphrase: string): Promise<any>;
|
||||
}
|
||||
|
||||
class MutablePgpKeyStore implements MutableKeyStore{
|
||||
@ -144,11 +144,10 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
||||
keyring.clear();
|
||||
}
|
||||
|
||||
async sign(plainText): Promise<any> {
|
||||
async sign(plainText: string, passphrase: string): Promise<any> {
|
||||
const privateKey = this.getPrivateKey();
|
||||
if (!privateKey.isDecrypted()) {
|
||||
const password = window.prompt('password');
|
||||
await privateKey.decrypt(password);
|
||||
await privateKey.decrypt(passphrase);
|
||||
}
|
||||
const opts = {
|
||||
message: openpgp.message.fromText(plainText),
|
||||
|
@ -73,7 +73,7 @@ export class AuthService {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
getChallenge(): void {
|
||||
getChallenge(password: string): void {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
|
||||
@ -81,12 +81,21 @@ export class AuthService {
|
||||
if (xhr.status === 401) {
|
||||
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
|
||||
const o = hobaParseChallengeHeader(authHeader);
|
||||
await this.loginResponse(o);
|
||||
await this.loginResponse(o, password);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
passwordLogin(password: string): boolean {
|
||||
try {
|
||||
this.getChallenge(password);
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
login(): boolean {
|
||||
if (this.sessionToken !== undefined) {
|
||||
@ -96,32 +105,20 @@ export class AuthService {
|
||||
} catch (e) {
|
||||
this.loggingService.sendErrorLevelMessage('Login token failed', this, {error: e});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.getChallenge();
|
||||
} catch (e) {
|
||||
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async loginResponse(o): Promise<any> {
|
||||
async loginResponse(o: any, password: string): Promise<any> {
|
||||
try {
|
||||
const r = await signChallenge(o.challenge, o.realm, environment.cicMetaUrl, this.mutableKeyStore);
|
||||
const r = await signChallenge(o.challenge, o.realm, environment.cicMetaUrl, this.mutableKeyStore, password);
|
||||
this.sendResponse(r);
|
||||
} catch (error) {
|
||||
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'});
|
||||
}
|
||||
}
|
||||
|
||||
loginView(): void {
|
||||
document.getElementById('one').style.display = 'none';
|
||||
document.getElementById('two').style.display = 'block';
|
||||
this.setState('Click button to log in with PGP key ' + this.mutableKeyStore.getPrivateKeyId());
|
||||
}
|
||||
|
||||
async setKey(privateKeyArmored): Promise<boolean> {
|
||||
try {
|
||||
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
||||
@ -141,7 +138,6 @@ export class AuthService {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
this.loginView();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h1 class="text-white">CICADA</h1>
|
||||
</a>
|
||||
</mat-card-title>
|
||||
<div id="one" style="display: block" class="card-body p-4">
|
||||
<div id="one" style="display: block" class="card-body p-4 align-items-center">
|
||||
|
||||
<div class="text-center w-75 m-auto">
|
||||
<h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
|
||||
@ -19,13 +19,13 @@
|
||||
<mat-label>Private Key</mat-label>
|
||||
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
|
||||
[errorStateMatcher]="matcher"></textarea>
|
||||
<div *ngIf="submitted && keyFormStub.key.errors" class="invalid-feedback">
|
||||
<div *ngIf="keyFormSubmitted && keyFormStub.key.errors" class="invalid-feedback">
|
||||
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="loading">
|
||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="keyFormLoading">
|
||||
<span *ngIf="keyFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Add Key
|
||||
</button>
|
||||
|
||||
@ -33,6 +33,41 @@
|
||||
</div>
|
||||
<div id="two" style="display: none" class="card-body p-4 align-items-center">
|
||||
|
||||
<div class="text-center w-75 m-auto">
|
||||
<h4 id="passwordState" class="text-dark-50 text-center font-weight-bold"></h4>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="passwordForm" (ngSubmit)="onPasswordInput()">
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Password</mat-label>
|
||||
<input matInput type="password" formControlName="password" placeholder="Enter your private key password..."
|
||||
[errorStateMatcher]="matcher">
|
||||
<div *ngIf="passwordForm && passwordFormStub.password.errors" class="invalid-feedback">
|
||||
<mat-error *ngIf="passwordFormStub.password.errors.required">Private Key password is required.</mat-error>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button matRipple color="primary" type="submit" class="ml-3" [disabled]="passwordFormLoading">
|
||||
<span *ngIf="passwordFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Login
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 text-center">
|
||||
<p class="text-muted">Change private key?
|
||||
<a (click)="keyInput()" class="text-muted ml-1">
|
||||
<b>Enter private key</b>
|
||||
</a>
|
||||
</p>
|
||||
</div> <!-- end col-->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
<div id="three" style="display: none" class="card-body p-4 align-items-center">
|
||||
|
||||
<div class="text-center w-75 m-auto">
|
||||
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
|
||||
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
|
||||
@ -40,7 +75,11 @@
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 text-center">
|
||||
<p class="text-muted">Change private key? <a (click)="switchWindows()" class="text-muted ml-1"><b>Enter private key</b></a></p>
|
||||
<p class="text-muted">Change private key?
|
||||
<a (click)="keyInput()" class="text-muted ml-1">
|
||||
<b>Enter private key</b>
|
||||
</a>
|
||||
</p>
|
||||
</div> <!-- end col-->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
|
@ -12,8 +12,11 @@ import {Router} from '@angular/router';
|
||||
})
|
||||
export class AuthComponent implements OnInit {
|
||||
keyForm: FormGroup;
|
||||
submitted: boolean = false;
|
||||
loading: boolean = false;
|
||||
keyFormSubmitted: boolean = false;
|
||||
keyFormLoading: boolean = false;
|
||||
passwordForm: FormGroup;
|
||||
passwordFormSubmitted: boolean = false;
|
||||
passwordFormLoading: boolean = false;
|
||||
matcher = new CustomErrorStateMatcher();
|
||||
|
||||
constructor(
|
||||
@ -26,47 +29,94 @@ export class AuthComponent implements OnInit {
|
||||
this.keyForm = this.formBuilder.group({
|
||||
key: ['', Validators.required],
|
||||
});
|
||||
this.passwordForm = this.formBuilder.group({
|
||||
password: ['', Validators.required],
|
||||
});
|
||||
if (this.authService.privateKey !== undefined) {
|
||||
const setKey = await this.authService.setKey(this.authService.privateKey);
|
||||
if (setKey) {
|
||||
this.passwordInput();
|
||||
}
|
||||
if (setKey && this.authService.sessionToken !== undefined) {
|
||||
this.loginView();
|
||||
this.authService.setState('Click button to log in');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get keyFormStub(): any { return this.keyForm.controls; }
|
||||
get passwordFormStub(): any { return this.passwordForm.controls; }
|
||||
|
||||
async onSubmit(): Promise<void> {
|
||||
this.submitted = true;
|
||||
this.keyFormSubmitted = true;
|
||||
|
||||
if (this.keyForm.invalid) { return; }
|
||||
|
||||
this.loading = true;
|
||||
await this.authService.setKey(this.keyFormStub.key.value);
|
||||
this.loading = false;
|
||||
this.keyFormLoading = true;
|
||||
const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
|
||||
if (keySetup) {
|
||||
this.passwordInput();
|
||||
this.setPasswordState('Enter Password to log in with PGP key ' + this.authService.mutableKeyStore.getPrivateKeyId());
|
||||
}
|
||||
this.keyFormLoading = false;
|
||||
}
|
||||
|
||||
onPasswordInput(): void {
|
||||
this.passwordFormSubmitted = true;
|
||||
|
||||
if (this.passwordForm.invalid) { return; }
|
||||
|
||||
this.passwordFormLoading = true;
|
||||
const passwordLogin = this.authService.passwordLogin(this.passwordFormStub.password.value);
|
||||
if (passwordLogin) {
|
||||
this.loginView();
|
||||
}
|
||||
this.passwordFormLoading = false;
|
||||
}
|
||||
|
||||
login(): void {
|
||||
if (this.authService.sessionToken === undefined) {
|
||||
this.passwordInput();
|
||||
}
|
||||
const loginStatus = this.authService.login();
|
||||
if (loginStatus) {
|
||||
this.router.navigate(['/home']);
|
||||
}
|
||||
}
|
||||
|
||||
switchWindows(): void {
|
||||
keyInput(): void {
|
||||
this.authService.sessionToken = undefined;
|
||||
const divOne = document.getElementById('one');
|
||||
const divTwo = document.getElementById('two');
|
||||
this.toggleDisplay(divOne);
|
||||
this.toggleDisplay(divTwo);
|
||||
this.switchWindows(true, false, false);
|
||||
}
|
||||
|
||||
toggleDisplay(element: any): void {
|
||||
passwordInput(): void {
|
||||
// this.authService.sessionToken = undefined;
|
||||
this.switchWindows(false, true, false);
|
||||
}
|
||||
|
||||
loginView(): void {
|
||||
this.switchWindows(false, false, true);
|
||||
}
|
||||
|
||||
switchWindows(divOneStatus: boolean, divTwoStatus: boolean, divThreeStatus: boolean): void {
|
||||
const divOne = document.getElementById('one');
|
||||
const divTwo = document.getElementById('two');
|
||||
const divThree = document.getElementById('three');
|
||||
this.toggleDisplay(divOne, divOneStatus);
|
||||
this.toggleDisplay(divTwo, divTwoStatus);
|
||||
this.toggleDisplay(divThree, divThreeStatus);
|
||||
}
|
||||
|
||||
toggleDisplay(element: any, active: boolean): void {
|
||||
const style = window.getComputedStyle(element).display;
|
||||
if (style === 'block') {
|
||||
element.style.display = 'none';
|
||||
} else {
|
||||
if (active) {
|
||||
element.style.display = 'block';
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
setPasswordState(s): void {
|
||||
document.getElementById('passwordState').innerHTML = s;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import {hobaResult, hobaToSign} from "@src/assets/js/hoba.js";
|
||||
|
||||
const alg = '969';
|
||||
|
||||
export async function signChallenge(challenge, realm, origin, keyStore) {
|
||||
export async function signChallenge(challenge, realm, origin, keyStore, password) {
|
||||
const fingerprint = keyStore.getFingerprint();
|
||||
const nonce_array = new Uint8Array(32);
|
||||
crypto.getRandomValues(nonce_array);
|
||||
@ -14,7 +14,7 @@ export async function signChallenge(challenge, realm, origin, keyStore) {
|
||||
const a_challenge = btoa(challenge);
|
||||
const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg);
|
||||
|
||||
const signature = await keyStore.sign(message);
|
||||
const signature = await keyStore.sign(message, password);
|
||||
const a_signature = btoa(signature);
|
||||
|
||||
const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
|
||||
|
Loading…
Reference in New Issue
Block a user