@@ -8,7 +8,7 @@ import { WarningIcon, Icon } from '../../icons';
88import { trackEvent } from '../../metrics' ;
99
1010import { uploadFile } from '../../util/ui' ;
11- import { asError } from '../../util/error' ;
11+ import { UnreachableCheck , asError , unreachableCheck } from '../../util/error' ;
1212
1313import { UpstreamProxyType , RulesStore } from '../../model/rules/rules-store' ;
1414import { ParsedCertificate , ValidationResult } from '../../model/crypto' ;
@@ -316,6 +316,9 @@ class ClientCertificateConfig extends React.Component<{ rulesStore: RulesStore }
316316 @observable
317317 clientCertState : undefined | 'encrypted' | 'processing' | 'error' | 'decrypted' ;
318318
319+ @observable
320+ clientCertError : string | undefined ;
321+
319322 @action . bound
320323 onClientCertSelected ( event : React . ChangeEvent < HTMLInputElement > ) {
321324 const input = event . target ;
@@ -326,6 +329,7 @@ class ClientCertificateConfig extends React.Component<{ rulesStore: RulesStore }
326329 fileReader . readAsArrayBuffer ( file ) ;
327330
328331 this . clientCertState = 'processing' ;
332+ this . clientCertError = undefined ;
329333
330334 const thisConfig = this ; // fileReader events set 'this'
331335 fileReader . addEventListener ( 'load' , flow ( function * ( ) {
@@ -338,29 +342,18 @@ class ClientCertificateConfig extends React.Component<{ rulesStore: RulesStore }
338342
339343 result = yield validatePKCS ( thisConfig . clientCertData . pfx , undefined ) ;
340344
341- if ( result === 'valid' ) {
342- thisConfig . clientCertState = 'decrypted' ;
343- thisConfig . clientCertData . passphrase = undefined ;
344- return ;
345- }
346-
347- if ( result === 'invalid-format' ) {
348- thisConfig . clientCertState = 'error' ;
349- return ;
350- }
345+ if ( result === 'invalid-passphrase' ) {
346+ // If it fails, try again with an empty key, since that is sometimes used for 'no passphrase'
347+ result = yield validatePKCS ( thisConfig . clientCertData . pfx , '' ) ;
351348
352- // If it fails, try again with an empty key, since that is sometimes used for 'no passphrase'
353- result = yield validatePKCS ( thisConfig . clientCertData . pfx , '' ) ;
349+ if ( result === 'valid' ) {
350+ thisConfig . clientCertData . passphrase = '' ;
351+ }
354352
355- if ( result === 'valid' ) {
356- thisConfig . clientCertState = 'decrypted' ;
357- thisConfig . clientCertData . passphrase = '' ;
358- return ;
353+ thisConfig . handleClientCertValidationResult ( result ) ;
354+ } else {
355+ thisConfig . handleClientCertValidationResult ( result ) ;
359356 }
360-
361- // If that still hasn't worked, it's encrypted. Mark is as such, and wait for the user
362- // to either cancel, or enter the correct passphrase.
363- thisConfig . clientCertState = 'encrypted' ;
364357 } ) ) ;
365358
366359 fileReader . addEventListener ( 'error' , ( ) => {
@@ -371,15 +364,32 @@ class ClientCertificateConfig extends React.Component<{ rulesStore: RulesStore }
371364 readonly decryptClientCertData = flow ( function * ( this : ClientCertificateConfig ) {
372365 const { pfx, passphrase } = this . clientCertData ! ;
373366
374- let result : ValidationResult ;
375-
376367 this . clientCertState = 'processing' ;
377- result = yield validatePKCS ( pfx , passphrase ) ;
378- this . clientCertState = result === 'valid'
379- ? 'decrypted'
380- : 'encrypted' ;
368+ this . clientCertError = undefined ;
369+
370+ const result = yield validatePKCS ( pfx , passphrase ) ;
371+ this . handleClientCertValidationResult ( result ) ;
381372 } ) ;
382373
374+ handleClientCertValidationResult ( result : ValidationResult ) {
375+ this . clientCertError = undefined ;
376+
377+ if ( result === 'valid' ) {
378+ this . clientCertState = 'decrypted' ;
379+ } else if ( result === 'invalid-passphrase' ) {
380+ this . clientCertState = 'encrypted' ;
381+ } else if ( result === 'invalid-format' ) {
382+ this . clientCertState = 'error' ;
383+ this . clientCertError = 'Parsing failed' ;
384+ } else if ( result === 'missing-key' ) {
385+ this . clientCertState = 'error' ;
386+ this . clientCertError = 'No private key found' ;
387+ } else if ( result === 'missing-cert' ) {
388+ this . clientCertState = 'error' ;
389+ this . clientCertError = 'No certificate found' ;
390+ } else unreachableCheck ( result ) ;
391+ }
392+
383393 @action . bound
384394 dropClientCertData ( ) {
385395 this . clientCertData = undefined ;
@@ -456,12 +466,14 @@ class ClientCertificateConfig extends React.Component<{ rulesStore: RulesStore }
456466 < Icon icon = { [ 'fas' , 'undo' ] } title = 'Deselect this certificate' />
457467 </ SettingsButton >
458468 </ DecryptionInput >
459- : < DecryptionInput >
460- < p > < WarningIcon /> Invalid certificate</ p >
461- < SettingsButton onClick = { this . dropClientCertData } >
462- < Icon icon = { [ 'fas' , 'undo' ] } title = 'Deselect this certificate' />
463- </ SettingsButton >
464- </ DecryptionInput >
469+ : this . clientCertState === 'error'
470+ ? < DecryptionInput >
471+ < p > < WarningIcon /> { this . clientCertError || 'Invalid certificate' } </ p >
472+ < SettingsButton onClick = { this . dropClientCertData } >
473+ < Icon icon = { [ 'fas' , 'undo' ] } title = 'Deselect this certificate' />
474+ </ SettingsButton >
475+ </ DecryptionInput >
476+ : unreachableCheck ( this . clientCertState )
465477 }
466478 < SettingsButton
467479 disabled = {
0 commit comments