1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::Output::HTML::ArticleCompose::Sign; 10 11use parent 'Kernel::Output::HTML::Base'; 12 13use strict; 14use warnings; 15 16use Mail::Address; 17use Kernel::Language qw(Translatable); 18 19use Kernel::System::VariableCheck qw(:all); 20 21our @ObjectDependencies = ( 22 'Kernel::Config', 23 'Kernel::System::Crypt::PGP', 24 'Kernel::System::Crypt::SMIME', 25 'Kernel::Output::HTML::Layout', 26 'Kernel::System::Queue', 27); 28 29sub Option { 30 my ( $Self, %Param ) = @_; 31 32 # Get config object. 33 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 34 35 # Check if PGP and SMIME are disabled. 36 return if !$ConfigObject->Get('PGP') && !$ConfigObject->Get('SMIME'); 37 38 return ('SignKeyID'); 39} 40 41sub Run { 42 my ( $Self, %Param ) = @_; 43 44 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 45 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 46 47 # Check if PGP and SMIME are disabled. 48 return if !$ConfigObject->Get('PGP') && !$ConfigObject->Get('SMIME'); 49 50 my %KeyList = $Self->Data(%Param); 51 52 # Sender with unique key won't be displayed in the selection 53 my $UniqueSignKeyIDsToRemove = $Self->_GetUniqueSignKeyIDsToRemove(%Param); 54 my $InvalidMessage = ''; 55 my $Class = ''; 56 if ( IsArrayRefWithData($UniqueSignKeyIDsToRemove) ) { 57 UNIQUEKEY: 58 for my $UniqueSignKeyIDToRemove ( @{$UniqueSignKeyIDsToRemove} ) { 59 60 next UNIQUEKEY if !defined $KeyList{$UniqueSignKeyIDToRemove}; 61 62 if ( $KeyList{$UniqueSignKeyIDToRemove} =~ m/WARNING: EXPIRED KEY].*\] (.*)/ ) { 63 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 64 "Cannot use expired signing key: '%s'. ", $1 65 ); 66 $Self->{Error}->{InvalidKey} = 1; 67 $Class .= ' ServerError'; 68 } 69 elsif ( $KeyList{$UniqueSignKeyIDToRemove} =~ m/WARNING: REVOKED KEY].*\] (.*)/ ) { 70 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 71 "Cannot use revoked signing key: '%s'. ", $1 72 ); 73 $Self->{Error}->{InvalidKey} = 1; 74 $Class .= ' ServerError'; 75 } 76 77 delete $KeyList{$UniqueSignKeyIDToRemove}; 78 } 79 80 } 81 82 # Add signing options. 83 if ( 84 !defined $Param{SignKeyID} 85 || ( $Param{ExpandCustomerName} && $Param{ExpandCustomerName} == 3 ) 86 ) 87 { 88 89 # Get default signing key from the queue (if apply) or any other key from queue system 90 # address that fits. 91 if ( $Param{QueueID} ) { 92 93 $Param{SignKeyID} = $Self->_PickSignKeyID(%Param) || ''; 94 } 95 } 96 97 if ( 98 $Param{StoreNew} 99 && $Param{EmailSecurityOptions} 100 && $Param{EmailSecurityOptions} =~ m{Sign}msxi 101 ) 102 { 103 my $CheckSuccess = $Self->_CheckSender(%Param); 104 if ( !$CheckSuccess ) { 105 if ( IsArrayRefWithData( $Self->{MissingKeys} ) ) { 106 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 107 "There are no signing keys available for the addresses '%s'.", 108 join ', ', @{ $Self->{MissingKeys} } 109 ); 110 } 111 if ( IsArrayRefWithData( $Self->{MissingSelectedKey} ) ) { 112 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 113 "There are no selected signing keys for the addresses '%s'.", 114 join ', ', @{ $Self->{MissingSelectedKey} } 115 ); 116 } 117 $Self->{Error}->{SignMissingKey} = 1; 118 $Class .= ' ServerError'; 119 } 120 } 121 122 # Check if selected signing keys are expired. 123 if ( defined $Param{SignKeyID} && defined $KeyList{ $Param{SignKeyID} } && !$Self->{Error}->{InvalidKey} ) { 124 125 if ( $KeyList{ $Param{SignKeyID} } =~ m/WARNING: EXPIRED KEY].*] (.*)/ ) { 126 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 127 "Cannot use expired signing key: '%s'. ", 128 join ', ', $1 129 ); 130 $Self->{Error}->{InvalidKey} = 1; 131 $Class .= ' ServerError'; 132 } 133 elsif ( $KeyList{ $Param{SignKeyID} } =~ m/WARNING: REVOKED KEY].*\] (.*)/ ) { 134 $InvalidMessage .= $LayoutObject->{LanguageObject}->Translate( 135 "Cannot use revoked signing key: '%s'. ", $1 136 ); 137 $Self->{Error}->{InvalidKey} = 1; 138 $Class .= ' ServerError'; 139 } 140 } 141 142 my $List = $LayoutObject->BuildSelection( 143 Data => \%KeyList, 144 Name => 'SignKeyID', 145 SelectedID => $Param{SignKeyID}, 146 Class => "$Class Modernize", 147 PossibleNone => 1, 148 ); 149 $LayoutObject->Block( 150 Name => 'Option', 151 Data => { 152 Name => 'SignKeyID', 153 Key => Translatable('Sign'), 154 Value => $List, 155 Invalid => $InvalidMessage, 156 FieldExplanation => Translatable( 157 'Keys/certificates will only be shown for a sender with more than one key/certificate. The first found key/certificate will be pre-selected. Please make sure to select the correct one.' 158 ), 159 }, 160 ); 161 162 return; 163} 164 165sub Data { 166 my ( $Self, %Param ) = @_; 167 168 # Get config object. 169 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 170 171 # Check if PGP and SMIME are disabled 172 return if !$ConfigObject->Get('PGP') && !$ConfigObject->Get('SMIME'); 173 174 # generate key list 175 my %KeyList; 176 177 return %KeyList if !$Param{From}; 178 179 my @SearchAddress = Mail::Address->parse( $Param{From} ); 180 181 return %KeyList if !$Param{EmailSecurityOptions}; 182 183 # Get email security options. 184 my ( $Backend, $Sign, $Encrypt ) = split /::/, $Param{EmailSecurityOptions}; 185 186 return %KeyList if !$Backend; 187 return %KeyList if !$Sign; 188 return %KeyList if $Sign ne 'Sign'; 189 190 # check PGP backend 191 if ( $Backend eq 'PGP' ) { 192 193 my $PGPObject = $Kernel::OM->Get('Kernel::System::Crypt::PGP'); 194 195 return %KeyList if !$PGPObject; 196 197 # Get PGP method (Detached or In-line). 198 my $PGPMethod = $ConfigObject->Get('PGP::Method') || 'Detached'; 199 200 if ( 201 $PGPMethod eq 'Detached' 202 || ( $PGPMethod eq 'Inline' && !$Kernel::OM->Get('Kernel::Output::HTML::Layout')->{BrowserRichText} ) 203 ) 204 { 205 my @PrivateKeys = $PGPObject->PrivateKeySearch( 206 Search => $SearchAddress[0]->address(), 207 ); 208 for my $DataRef (@PrivateKeys) { 209 my $Expires = ''; 210 if ( $DataRef->{Expires} ) { 211 $Expires = "[$DataRef->{Expires}]"; 212 } 213 214 my $Status = '[' . $DataRef->{Status} . ']'; 215 if ( $DataRef->{Status} eq 'expired' ) { 216 $Status = '[WARNING: EXPIRED KEY]'; 217 } 218 elsif ( $DataRef->{Status} eq 'revoked' ) { 219 $Status = '[WARNING: REVOKED KEY]'; 220 } 221 222 $KeyList{"PGP::$DataRef->{Key}"} = "PGP: $Status $DataRef->{Key} $Expires $DataRef->{Identifier}"; 223 } 224 } 225 } 226 227 # Check SMIME backend. 228 elsif ( $Backend eq 'SMIME' ) { 229 230 my $SMIMEObject = $Kernel::OM->Get('Kernel::System::Crypt::SMIME'); 231 232 return %KeyList if !$SMIMEObject; 233 234 my @PrivateKeys = $SMIMEObject->PrivateSearch( 235 Search => $SearchAddress[0]->address(), 236 ); 237 for my $DataRef (@PrivateKeys) { 238 my $Expired = ''; 239 my $EndDate = ( defined $DataRef->{EndDate} ) ? "[$DataRef->{EndDate}]" : ''; 240 241 if ( defined $DataRef->{EndDate} && $SMIMEObject->KeyExpiredCheck( EndDate => $DataRef->{EndDate} ) ) { 242 $Expired = ' [WARNING: EXPIRED KEY]'; 243 } 244 245 $KeyList{"SMIME::$DataRef->{Filename}"} = "SMIME:$Expired $DataRef->{Filename} $EndDate $DataRef->{Email}"; 246 } 247 } 248 249 return %KeyList; 250} 251 252sub ArticleOption { 253 my ( $Self, %Param ) = @_; 254 255 if ( $Param{SignKeyID} ) { 256 257 my ( $Type, $Key ) = split /::/, $Param{SignKeyID}; 258 259 $Param{EmailSecurity}->{SignKey} = $Key; 260 261 return ( 262 EmailSecurity => $Param{EmailSecurity}, 263 ); 264 } 265 return; 266} 267 268sub GetParamAJAX { 269 my ( $Self, %Param ) = @_; 270 271 # Get config object. 272 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 273 274 # Check if PGP and SMIME are disabled 275 return if !$ConfigObject->Get('PGP') && !$ConfigObject->Get('SMIME'); 276 277 return if !$Param{QueueID} && !$Param{From}; 278 279 return ( 280 SignKeyID => $Self->_PickSignKeyID(%Param) || '', 281 ); 282} 283 284sub Error { 285 my ( $Self, %Param ) = @_; 286 287 if ( $Self->{Error} ) { 288 return %{ $Self->{Error} }; 289 } 290 return; 291} 292 293sub _CheckSender { 294 my ( $Self, %Param ) = @_; 295 296 # Get email security options. 297 my ( $Backend, $Sign, $Encrypt ) = split /::/, $Param{EmailSecurityOptions}; 298 299 # Stop checking if no backed was selected. 300 return 1 if !$Backend; 301 return 1 if $Sign ne 'Sign'; 302 303 # Get encrypt object. 304 my $EncryptObject = $Kernel::OM->Get("Kernel::System::Crypt::$Backend"); 305 306 # Return error if encrypt object could not be created. 307 return 0 if !$EncryptObject; 308 309 # Create a selected sign keys lookup table 310 my %SelectedSignKeyIDs = ( 311 $Param{SignKeyID} => 1, 312 ); 313 314 # Sender with unique key isn't part of the selection so add it manually 315 my $UniqueSignKeyIDsToRemove = $Self->_GetUniqueSignKeyIDsToRemove(%Param); 316 if ( IsArrayRefWithData($UniqueSignKeyIDsToRemove) ) { 317 for my $UniqueSignKeyIDToRemove ( @{$UniqueSignKeyIDsToRemove} ) { 318 $SelectedSignKeyIDs{$UniqueSignKeyIDToRemove} = 1; 319 } 320 } 321 322 my $MissingSelectedKeyFlag; 323 my $MissingKeysFlag; 324 325 my @SearchAddress = Mail::Address->parse( $Param{From} ); 326 327 ADDRESS: 328 for my $Address (@SearchAddress) { 329 330 my $EmailAddress = $Address->address(); 331 332 my @PrivateKeys; 333 if ( $Backend eq 'PGP' ) { 334 @PrivateKeys = $EncryptObject->PrivateKeySearch( 335 Search => $EmailAddress, 336 ); 337 } 338 else { 339 @PrivateKeys = $EncryptObject->PrivateSearch( 340 Search => $EmailAddress, 341 ); 342 } 343 344 # Remember addresses with no sign keys available 345 if ( !@PrivateKeys ) { 346 push @{ $Self->{MissingKeys} }, $EmailAddress; 347 $MissingKeysFlag = 1; 348 next ADDRESS; 349 } 350 351 $MissingSelectedKeyFlag = 1; 352 353 PRIVATEKEY: 354 for my $PrivateKey (@PrivateKeys) { 355 356 my $SignKeyID; 357 if ( $Backend eq 'PGP' ) { 358 $SignKeyID = "PGP::$PrivateKey->{Key}"; 359 } 360 else { 361 $SignKeyID = "SMIME::$PrivateKey->{Filename}"; 362 } 363 364 # If this key is selected everything is fine, remove missing key flag and check next 365 # address. 366 if ( $SelectedSignKeyIDs{$SignKeyID} ) { 367 $MissingSelectedKeyFlag = 0; 368 next ADDRESS; 369 } 370 } 371 372 push @{ $Self->{MissingSelectedKey} }, $EmailAddress; 373 } 374 375 # Return error if there was no sign key available for an email address. 376 return if $MissingKeysFlag; 377 378 # Return error if there was no selected key for an email address. 379 return if $MissingSelectedKeyFlag; 380 381 # Otherwise return success 382 return 1; 383} 384 385sub _PickSignKeyID { 386 my ( $Self, %Param ) = @_; 387 388 # Get the list of keys for the current backend. 389 my %KeyList = $Self->Data(%Param); 390 391 # Return nothing if there are no possible encrypt keys; 392 return if !%KeyList; 393 394 # Check if signing key is still valid for the selected backend. 395 if ( 396 $Param{SignKeyID} 397 && $KeyList{ $Param{SignKeyID} } && $KeyList{ $Param{SignKeyID} } !~ m/WARNING: EXPIRED KEY/ 398 ) 399 { 400 return $Param{SignKeyID}; 401 } 402 403 my $SignKeyID = ''; 404 if ( $Param{QueueID} ) { 405 406 # Get default signing key from queue data. 407 my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Param{QueueID} ); 408 $SignKeyID = $Queue{DefaultSignKey} || ''; 409 } 410 411 # Convert legacy stored default sign keys. 412 if ( $SignKeyID =~ m{ (?: Inline|Detached ) }msx ) { 413 my ( $Type, $SubType, $Key ) = split /::/, $SignKeyID; 414 $SignKeyID = "$Type::$Key"; 415 } 416 417 # if there is a preselected key from the queue, use it. 418 if ( $SignKeyID && $KeyList{$SignKeyID} && $KeyList{$SignKeyID} !~ m/WARNING: EXPIRED KEY/ ) { 419 return $SignKeyID; 420 } 421 422 # Get email security options. 423 return if !$Param{EmailSecurityOptions}; 424 my ( $Backend, $Sign, $Encrypt ) = split /::/, $Param{EmailSecurityOptions}; 425 426 # Return nothing if Backend is not present. 427 return if !$Backend; 428 429 # Get encrypt object. 430 my $EncryptObject = $Kernel::OM->Get("Kernel::System::Crypt::$Backend"); 431 432 # Return nothing if encrypt object was not created 433 return if !$EncryptObject; 434 435 my @SearchAddress = Mail::Address->parse( $Param{From} ); 436 437 # Search for privates keys for queue system address. 438 my @PrivateKeys; 439 if ( $Backend eq 'PGP' ) { 440 @PrivateKeys = $EncryptObject->PrivateKeySearch( 441 Search => $SearchAddress[0]->address(), 442 ); 443 444 @PrivateKeys = sort { $a->{Expires} cmp $b->{Expires} } grep { $_->{Status} eq 'good' } @PrivateKeys; 445 } 446 else { 447 @PrivateKeys = $EncryptObject->PrivateSearch( 448 Search => $SearchAddress[0]->address(), 449 Valid => 1, 450 ); 451 @PrivateKeys = sort { $a->{ShortEndDate} cmp $b->{ShortEndDate} } @PrivateKeys; 452 } 453 454 # If there are no private keys for this queue, return nothing. 455 return if !@PrivateKeys; 456 457 # Use the last key for the selected backend. 458 if ( $Backend eq 'PGP' ) { 459 $SignKeyID = "PGP::$PrivateKeys[-1]->{Key}"; 460 } 461 else { 462 $SignKeyID = "SMIME::$PrivateKeys[-1]->{Filename}"; 463 } 464 465 return $SignKeyID; 466 467} 468 469sub GetOptionsToRemoveAJAX { 470 my ( $Self, %Param ) = @_; 471 472 # Get config object. 473 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 474 475 # Check if PGP and SMIME are disabled 476 return if !$ConfigObject->Get('PGP') && !$ConfigObject->Get('SMIME'); 477 478 my $OptionsToRemove = $Self->_GetUniqueSignKeyIDsToRemove(%Param); 479 480 return if !IsArrayRefWithData($OptionsToRemove); 481 482 return @{$OptionsToRemove}; 483} 484 485sub _GetUniqueSignKeyIDsToRemove { 486 my ( $Self, %Param ) = @_; 487 488 # Get the list of keys for the current backend. 489 my %KeyList = $Self->Data(%Param); 490 491 # Return nothing if there are no possible sign keys; 492 return if !%KeyList; 493 494 # Return nothing if there are no security options. 495 return if !defined $Param{EmailSecurityOptions}; 496 497 # Get email security options. 498 my ( $Backend, $Sign, $Encrypt ) = split /::/, $Param{EmailSecurityOptions}; 499 500 return if !$Backend; 501 return if !$Sign || $Sign ne 'Sign'; 502 503 # Get encrypt object. 504 my $EncryptObject = $Kernel::OM->Get("Kernel::System::Crypt::$Backend"); 505 506 # Return nothing if encrypt object was not created 507 return if !$EncryptObject; 508 509 my %UniqueSignKeyIDsToRemove; 510 511 my @SearchAddress = Mail::Address->parse( $Param{From} ); 512 513 ADDRESS: 514 for my $Address (@SearchAddress) { 515 516 my @PrivateKeys; 517 if ( $Backend eq 'PGP' ) { 518 @PrivateKeys = $EncryptObject->PrivateKeySearch( 519 Search => $Address->address(), 520 ); 521 } 522 else { 523 @PrivateKeys = $EncryptObject->PrivateSearch( 524 Search => $Address->address(), 525 ); 526 } 527 528 # Only unique keys will be removed, so skip to next address if there 529 # is not exactly one key 530 next ADDRESS if @PrivateKeys != 1; 531 532 my $PrivateKey = shift @PrivateKeys; 533 534 my $SignKeyID; 535 if ( $Backend eq 'PGP' ) { 536 $SignKeyID = "PGP::$PrivateKey->{Key}"; 537 } 538 else { 539 $SignKeyID = "SMIME::$PrivateKey->{Filename}"; 540 } 541 542 $UniqueSignKeyIDsToRemove{$SignKeyID} = 1; 543 } 544 545 my @UniqueSignKeyIDsToRemove = sort keys %UniqueSignKeyIDsToRemove; 546 547 return \@UniqueSignKeyIDsToRemove; 548} 549 5501; 551