1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-19 02:42:05 +01:00

Merge pull request #404 from pgpainless/pgpainless2

PGPainless 2.0 - Kotlin Conversion
This commit is contained in:
Paul Schaub 2023-10-26 13:03:16 +02:00 committed by GitHub
commit 6fdf136024
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
373 changed files with 20253 additions and 26986 deletions

View file

@ -1,513 +1,51 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
# This .editorconfig section approximates ktfmt's formatting rules. You can include it in an
# existing .editorconfig file or use it standalone by copying it to <project root>/.editorconfig
# and making sure your editor is set to read settings from .editorconfig files.
#
# SPDX-License-Identifier: CC0-1.0
# It includes editor-specific config options for IntelliJ IDEA.
#
# If any option is wrong, PR are welcome
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
[{*.kt,*.kts}]
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = none
ij_wrap_on_typing = false
[*.java]
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_at_first_column = true
ij_java_builder_methods = none
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 10000
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = $*,|,java.**,javax.**,|,*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_at_first_column = true
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_names_count_to_use_import_on_demand = 1000
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_record_header = false
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[*.properties]
ij_properties_align_group_field_declarations = false
ij_properties_keep_blank_lines = false
ij_properties_key_value_delimiter = equals
ij_properties_spaces_around_key_value_delimiter = false
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = true
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
ij_xml_keep_whitespaces_inside_cdata = false
ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
ij_xml_use_custom_settings = false
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.gant,*.gradle,*.groovy,*.gy}]
ij_groovy_align_group_field_declarations = false
ij_groovy_align_multiline_array_initializer_expression = false
ij_groovy_align_multiline_assignment = false
ij_groovy_align_multiline_binary_operation = false
ij_groovy_align_multiline_chained_methods = false
ij_groovy_align_multiline_extends_list = false
ij_groovy_align_multiline_for = true
ij_groovy_align_multiline_list_or_map = true
ij_groovy_align_multiline_method_parentheses = false
ij_groovy_align_multiline_parameters = true
ij_groovy_align_multiline_parameters_in_calls = false
ij_groovy_align_multiline_resources = true
ij_groovy_align_multiline_ternary_operation = false
ij_groovy_align_multiline_throws_list = false
ij_groovy_align_named_args_in_map = true
ij_groovy_align_throws_keyword = false
ij_groovy_array_initializer_new_line_after_left_brace = false
ij_groovy_array_initializer_right_brace_on_new_line = false
ij_groovy_array_initializer_wrap = off
ij_groovy_assert_statement_wrap = off
ij_groovy_assignment_wrap = off
ij_groovy_binary_operation_wrap = off
ij_groovy_blank_lines_after_class_header = 0
ij_groovy_blank_lines_after_imports = 1
ij_groovy_blank_lines_after_package = 1
ij_groovy_blank_lines_around_class = 1
ij_groovy_blank_lines_around_field = 0
ij_groovy_blank_lines_around_field_in_interface = 0
ij_groovy_blank_lines_around_method = 1
ij_groovy_blank_lines_around_method_in_interface = 1
ij_groovy_blank_lines_before_imports = 1
ij_groovy_blank_lines_before_method_body = 0
ij_groovy_blank_lines_before_package = 0
ij_groovy_block_brace_style = end_of_line
ij_groovy_block_comment_at_first_column = true
ij_groovy_call_parameters_new_line_after_left_paren = false
ij_groovy_call_parameters_right_paren_on_new_line = false
ij_groovy_call_parameters_wrap = off
ij_groovy_catch_on_new_line = false
ij_groovy_class_annotation_wrap = split_into_lines
ij_groovy_class_brace_style = end_of_line
ij_groovy_class_count_to_use_import_on_demand = 5
ij_groovy_do_while_brace_force = never
ij_groovy_else_on_new_line = false
ij_groovy_enum_constants_wrap = off
ij_groovy_extends_keyword_wrap = off
ij_groovy_extends_list_wrap = off
ij_groovy_field_annotation_wrap = split_into_lines
ij_groovy_finally_on_new_line = false
ij_groovy_for_brace_force = never
ij_groovy_for_statement_new_line_after_left_paren = false
ij_groovy_for_statement_right_paren_on_new_line = false
ij_groovy_for_statement_wrap = off
ij_groovy_if_brace_force = never
ij_groovy_import_annotation_wrap = 2
ij_groovy_imports_layout = *,|,javax.**,java.**,|,$*
ij_groovy_indent_case_from_switch = true
ij_groovy_indent_label_blocks = true
ij_groovy_insert_inner_class_imports = false
ij_groovy_keep_blank_lines_before_right_brace = 2
ij_groovy_keep_blank_lines_in_code = 2
ij_groovy_keep_blank_lines_in_declarations = 2
ij_groovy_keep_control_statement_in_one_line = true
ij_groovy_keep_first_column_comment = true
ij_groovy_keep_indents_on_empty_lines = false
ij_groovy_keep_line_breaks = true
ij_groovy_keep_multiple_expressions_in_one_line = false
ij_groovy_keep_simple_blocks_in_one_line = false
ij_groovy_keep_simple_classes_in_one_line = true
ij_groovy_keep_simple_lambdas_in_one_line = true
ij_groovy_keep_simple_methods_in_one_line = true
ij_groovy_label_indent_absolute = false
ij_groovy_label_indent_size = 0
ij_groovy_lambda_brace_style = end_of_line
ij_groovy_layout_static_imports_separately = true
ij_groovy_line_comment_add_space = false
ij_groovy_line_comment_at_first_column = true
ij_groovy_method_annotation_wrap = split_into_lines
ij_groovy_method_brace_style = end_of_line
ij_groovy_method_call_chain_wrap = off
ij_groovy_method_parameters_new_line_after_left_paren = false
ij_groovy_method_parameters_right_paren_on_new_line = false
ij_groovy_method_parameters_wrap = off
ij_groovy_modifier_list_wrap = false
ij_groovy_names_count_to_use_import_on_demand = 3
ij_groovy_parameter_annotation_wrap = off
ij_groovy_parentheses_expression_new_line_after_left_paren = false
ij_groovy_parentheses_expression_right_paren_on_new_line = false
ij_groovy_prefer_parameters_wrap = false
ij_groovy_resource_list_new_line_after_left_paren = false
ij_groovy_resource_list_right_paren_on_new_line = false
ij_groovy_resource_list_wrap = off
ij_groovy_space_after_assert_separator = true
ij_groovy_space_after_colon = true
ij_groovy_space_after_comma = true
ij_groovy_space_after_comma_in_type_arguments = true
ij_groovy_space_after_for_semicolon = true
ij_groovy_space_after_quest = true
ij_groovy_space_after_type_cast = true
ij_groovy_space_before_annotation_parameter_list = false
ij_groovy_space_before_array_initializer_left_brace = false
ij_groovy_space_before_assert_separator = false
ij_groovy_space_before_catch_keyword = true
ij_groovy_space_before_catch_left_brace = true
ij_groovy_space_before_catch_parentheses = true
ij_groovy_space_before_class_left_brace = true
ij_groovy_space_before_closure_left_brace = true
ij_groovy_space_before_colon = true
ij_groovy_space_before_comma = false
ij_groovy_space_before_do_left_brace = true
ij_groovy_space_before_else_keyword = true
ij_groovy_space_before_else_left_brace = true
ij_groovy_space_before_finally_keyword = true
ij_groovy_space_before_finally_left_brace = true
ij_groovy_space_before_for_left_brace = true
ij_groovy_space_before_for_parentheses = true
ij_groovy_space_before_for_semicolon = false
ij_groovy_space_before_if_left_brace = true
ij_groovy_space_before_if_parentheses = true
ij_groovy_space_before_method_call_parentheses = false
ij_groovy_space_before_method_left_brace = true
ij_groovy_space_before_method_parentheses = false
ij_groovy_space_before_quest = true
ij_groovy_space_before_switch_left_brace = true
ij_groovy_space_before_switch_parentheses = true
ij_groovy_space_before_synchronized_left_brace = true
ij_groovy_space_before_synchronized_parentheses = true
ij_groovy_space_before_try_left_brace = true
ij_groovy_space_before_try_parentheses = true
ij_groovy_space_before_while_keyword = true
ij_groovy_space_before_while_left_brace = true
ij_groovy_space_before_while_parentheses = true
ij_groovy_space_in_named_argument = true
ij_groovy_space_in_named_argument_before_colon = false
ij_groovy_space_within_empty_array_initializer_braces = false
ij_groovy_space_within_empty_method_call_parentheses = false
ij_groovy_spaces_around_additive_operators = true
ij_groovy_spaces_around_assignment_operators = true
ij_groovy_spaces_around_bitwise_operators = true
ij_groovy_spaces_around_equality_operators = true
ij_groovy_spaces_around_lambda_arrow = true
ij_groovy_spaces_around_logical_operators = true
ij_groovy_spaces_around_multiplicative_operators = true
ij_groovy_spaces_around_regex_operators = true
ij_groovy_spaces_around_relational_operators = true
ij_groovy_spaces_around_shift_operators = true
ij_groovy_spaces_within_annotation_parentheses = false
ij_groovy_spaces_within_array_initializer_braces = false
ij_groovy_spaces_within_braces = true
ij_groovy_spaces_within_brackets = false
ij_groovy_spaces_within_cast_parentheses = false
ij_groovy_spaces_within_catch_parentheses = false
ij_groovy_spaces_within_for_parentheses = false
ij_groovy_spaces_within_gstring_injection_braces = false
ij_groovy_spaces_within_if_parentheses = false
ij_groovy_spaces_within_list_or_map = false
ij_groovy_spaces_within_method_call_parentheses = false
ij_groovy_spaces_within_method_parentheses = false
ij_groovy_spaces_within_parentheses = false
ij_groovy_spaces_within_switch_parentheses = false
ij_groovy_spaces_within_synchronized_parentheses = false
ij_groovy_spaces_within_try_parentheses = false
ij_groovy_spaces_within_tuple_expression = false
ij_groovy_spaces_within_while_parentheses = false
ij_groovy_special_else_if_treatment = true
ij_groovy_ternary_operation_wrap = off
ij_groovy_throws_keyword_wrap = off
ij_groovy_throws_list_wrap = off
ij_groovy_use_flying_geese_braces = false
ij_groovy_use_fq_class_names = false
ij_groovy_use_fq_class_names_in_javadoc = true
ij_groovy_use_relative_indents = false
ij_groovy_use_single_class_imports = true
ij_groovy_variable_annotation_wrap = off
ij_groovy_while_brace_force = never
ij_groovy_while_on_new_line = false
ij_groovy_wrap_long_lines = false
[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}]
insert_final_newline = true
max_line_length = 100
indent_size = 4
ij_continuation_indent_size = 4 # was 8
ij_java_names_count_to_use_import_on_demand = 9999
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = off
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = false
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = false
ij_kotlin_call_parameters_wrap = off
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = true
ij_kotlin_continuation_indent_for_expression_bodies = true
ij_kotlin_continuation_indent_in_argument_lists = true
ij_kotlin_continuation_indent_in_elvis = true
ij_kotlin_continuation_indent_in_if_conditions = true
ij_kotlin_continuation_indent_in_parameter_lists = true
ij_kotlin_continuation_indent_in_supertype_lists = true
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = false
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
@ -519,13 +57,13 @@ ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = off
ij_kotlin_method_parameters_new_line_after_left_paren = false
ij_kotlin_method_parameters_right_paren_on_new_line = false
ij_kotlin_method_parameters_wrap = off
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 9999
ij_kotlin_name_count_to_use_star_import_for_members = 9999
ij_java_names_count_to_use_import_on_demand = 9999
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
@ -552,72 +90,5 @@ ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 0
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
[{*.har,*.json}]
indent_size = 2
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = true
ij_json_space_before_comma = false
ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
ij_html_align_attributes = true
ij_html_align_text = false
ij_html_attribute_wrap = normal
ij_html_block_comment_at_first_column = true
ij_html_do_not_align_children_of_min_lines = 0
ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
ij_html_enforce_quotes = false
ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
ij_html_keep_blank_lines = 2
ij_html_keep_indents_on_empty_lines = false
ij_html_keep_line_breaks = true
ij_html_keep_line_breaks_in_text = true
ij_html_keep_whitespaces = false
ij_html_keep_whitespaces_inside = span,pre,textarea
ij_html_line_comment_at_first_column = true
ij_html_new_line_after_last_attribute = never
ij_html_new_line_before_first_attribute = never
ij_html_quote_style = double
ij_html_remove_new_line_before_tags = br
ij_html_space_after_tag_name = false
ij_html_space_around_equality_in_attribute = false
ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
ij_html_uniform_ident = false
[{*.markdown,*.md}]
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[{*.yaml,*.yml}]
indent_size = 2
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# Ignore initial spotlessApply using ktfmt
51e9bfc67f19e16a69790a8d92bd6b1c86a76a5f

View file

@ -9,6 +9,11 @@ Source: https://pgpainless.org
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
# GitBlameIgnore
Files: .git-blame-ignore-revs
Copyright: 2023 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
# Documentation
Files: docs/*
Copyright: 2022 Paul Schaub <info@pgpainless.org>
@ -23,6 +28,11 @@ Files: gradle*
Copyright: 2015 the original author or authors.
License: Apache-2.0
# Editorconfig
Files: .editorconfig
Copyright: Facebook
License: Apache-2.0
# PGPainless Logo
Files: assets/repository-open-graph.png
Copyright: 2021 Paul Schaub <info@pgpainless.org>

View file

@ -18,7 +18,8 @@ buildscript {
}
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.3'
id 'org.jetbrains.kotlin.jvm' version "1.8.10"
id 'com.diffplug.spotless' version '6.22.0' apply false
}
apply from: 'version.gradle'
@ -29,6 +30,8 @@ allprojects {
apply plugin: 'eclipse'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
apply plugin: 'kotlin'
apply plugin: 'com.diffplug.spotless'
compileJava {
options.release = 8
@ -41,23 +44,17 @@ allprojects {
onlyIf { !sourceSets.main.allSource.files.isEmpty() }
}
// For library modules, enable android api compatibility check
if (it.name != 'pgpainless-cli') {
// animalsniffer
apply plugin: 'ru.vyarus.animalsniffer'
dependencies {
signature "net.sf.androidscents.signature:android-api-level-${pgpainlessMinAndroidSdk}:2.3.3_r2@signature"
}
animalsniffer {
sourceSets = [sourceSets.main]
}
}
// checkstyle
checkstyle {
toolVersion = '10.12.1'
}
spotless {
kotlin {
ktfmt().dropboxStyle()
}
}
group 'org.pgpainless'
description = "Simple to use OpenPGP API for Java based on Bouncycastle"
version = shortVersion
@ -78,6 +75,13 @@ allprojects {
fileMode = 0644
}
// Compatibility of default implementations in kotlin interfaces with Java implementations.
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += ["-Xjvm-default=all-compatibility"]
}
}
project.ext {
rootConfigDir = new File(rootDir, 'config')
gitCommit = getGitCommit()

View file

@ -1,95 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.bouncycastle;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory;
import org.pgpainless.key.SubkeyIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Implementation of the {@link PublicKeyDataDecryptorFactory} which caches decrypted session keys.
* That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted.
*
* This implementation changes the behavior or {@link #recoverSessionData(int, byte[][])} to first return any
* cache hits.
* If no hit is found, the method call is delegated to the underlying {@link PublicKeyDataDecryptorFactory}.
* The result of that is then placed in the cache and returned.
*/
public class CachingBcPublicKeyDataDecryptorFactory
extends BcPublicKeyDataDecryptorFactory
implements CustomPublicKeyDataDecryptorFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(CachingBcPublicKeyDataDecryptorFactory.class);
private final Map<String, byte[]> cachedSessionKeys = new HashMap<>();
private final SubkeyIdentifier decryptionKey;
public CachingBcPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey, SubkeyIdentifier decryptionKey) {
super(privateKey);
this.decryptionKey = decryptionKey;
}
@Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
byte[] sessionKey = lookupSessionKeyData(secKeyData);
if (sessionKey == null) {
LOGGER.debug("Cache miss for encrypted session key " + Hex.toHexString(secKeyData[0]));
sessionKey = costlyRecoverSessionData(keyAlgorithm, secKeyData);
cacheSessionKeyData(secKeyData, sessionKey);
} else {
LOGGER.debug("Cache hit for encrypted session key " + Hex.toHexString(secKeyData[0]));
}
return sessionKey;
}
public byte[] costlyRecoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
return super.recoverSessionData(keyAlgorithm, secKeyData);
}
private byte[] lookupSessionKeyData(byte[][] secKeyData) {
String key = toKey(secKeyData);
byte[] sessionKey = cachedSessionKeys.get(key);
return copy(sessionKey);
}
private void cacheSessionKeyData(byte[][] secKeyData, byte[] sessionKey) {
String key = toKey(secKeyData);
cachedSessionKeys.put(key, copy(sessionKey));
}
private static String toKey(byte[][] secKeyData) {
byte[] sk = secKeyData[0];
String key = Base64.toBase64String(sk);
return key;
}
private static byte[] copy(byte[] bytes) {
if (bytes == null) {
return null;
}
byte[] copy = new byte[bytes.length];
System.arraycopy(bytes, 0, copy, 0, copy.length);
return copy;
}
public void clear() {
cachedSessionKeys.clear();
}
@Override
public SubkeyIdentifier getSubkeyIdentifier() {
return decryptionKey;
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes which could be upstreamed to BC at some point.
*/
package org.bouncycastle;

View file

@ -1,238 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import javax.annotation.Nonnull;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.decryption_verification.DecryptionBuilder;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.encryption_signing.EncryptionBuilder;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.key.certification.CertifyCertificate;
import org.pgpainless.key.generation.KeyRingBuilder;
import org.pgpainless.key.generation.KeyRingTemplates;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor;
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
import org.pgpainless.key.parsing.KeyRingReader;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.policy.Policy;
import org.pgpainless.util.ArmorUtils;
public final class PGPainless {
private PGPainless() {
}
/**
* Generate a fresh OpenPGP key ring from predefined templates.
* @return templates
*/
@Nonnull
public static KeyRingTemplates generateKeyRing() {
return new KeyRingTemplates();
}
/**
* Build a custom OpenPGP key ring.
*
* @return builder
*/
@Nonnull
public static KeyRingBuilder buildKeyRing() {
return new KeyRingBuilder();
}
/**
* Read an existing OpenPGP key ring.
* @return builder
*/
@Nonnull
public static KeyRingReader readKeyRing() {
return new KeyRingReader();
}
/**
* Extract a public key certificate from a secret key.
*
* @param secretKey secret key
* @return public key certificate
*/
@Nonnull
public static PGPPublicKeyRing extractCertificate(@Nonnull PGPSecretKeyRing secretKey) {
return KeyRingUtils.publicKeyRingFrom(secretKey);
}
/**
* Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together.
*
* @param originalCopy local, older copy of the cert
* @param updatedCopy updated, newer copy of the cert
* @return merged certificate
* @throws PGPException in case of an error
*/
@Nonnull
public static PGPPublicKeyRing mergeCertificate(
@Nonnull PGPPublicKeyRing originalCopy,
@Nonnull PGPPublicKeyRing updatedCopy)
throws PGPException {
return PGPPublicKeyRing.join(originalCopy, updatedCopy);
}
/**
* Wrap a key or certificate in ASCII armor.
*
* @param key key or certificate
* @return ascii armored string
*
* @throws IOException in case of an error in the {@link ArmoredOutputStream}
*/
@Nonnull
public static String asciiArmor(@Nonnull PGPKeyRing key)
throws IOException {
if (key instanceof PGPSecretKeyRing) {
return ArmorUtils.toAsciiArmoredString((PGPSecretKeyRing) key);
} else {
return ArmorUtils.toAsciiArmoredString((PGPPublicKeyRing) key);
}
}
/**
* Wrap the detached signature in ASCII armor.
*
* @param signature detached signature
* @return ascii armored string
*
* @throws IOException in case of an error in the {@link ArmoredOutputStream}
*/
@Nonnull
public static String asciiArmor(@Nonnull PGPSignature signature)
throws IOException {
return ArmorUtils.toAsciiArmoredString(signature);
}
/**
* Wrap a key of certificate in ASCII armor and write the result into the given {@link OutputStream}.
*
* @param key key or certificate
* @param outputStream output stream
*
* @throws IOException in case of an error ion the {@link ArmoredOutputStream}
*/
public static void asciiArmor(@Nonnull PGPKeyRing key, @Nonnull OutputStream outputStream)
throws IOException {
ArmoredOutputStream armorOut = ArmorUtils.toAsciiArmoredStream(key, outputStream);
key.encode(armorOut);
armorOut.close();
}
/**
* Create an {@link EncryptionStream}, which can be used to encrypt and/or sign data using OpenPGP.
*
* @return builder
*/
@Nonnull
public static EncryptionBuilder encryptAndOrSign() {
return new EncryptionBuilder();
}
/**
* Create a {@link DecryptionStream}, which can be used to decrypt and/or verify data using OpenPGP.
*
* @return builder
*/
@Nonnull
public static DecryptionBuilder decryptAndOrVerify() {
return new DecryptionBuilder();
}
/**
* Make changes to a secret key.
* This method can be used to change key expiration dates and passphrases, or add/revoke subkeys.
* <p>
* After making the desired changes in the builder, the modified key ring can be extracted using {@link SecretKeyRingEditorInterface#done()}.
*
* @param secretKeys secret key ring
* @return builder
*/
@Nonnull
public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys) {
return modifyKeyRing(secretKeys, new Date());
}
/**
* Make changes to a secret key at the given reference time.
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
* <p>
* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}.
*
* @param secretKeys secret key ring
* @param referenceTime reference time used as signature creation date
* @return builder
*/
@Nonnull
public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys,
@Nonnull Date referenceTime) {
return new SecretKeyRingEditor(secretKeys, referenceTime);
}
/**
* Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}.
* This method can be used to determine expiration dates, key flags and other information about a key.
* <p>
* To evaluate a key at a given date (e.g. to determine if the key was allowed to create a certain signature)
* use {@link #inspectKeyRing(PGPKeyRing, Date)} instead.
*
* @param keyRing key ring
* @return access object
*/
@Nonnull
public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing) {
return new KeyRingInfo(keyRing);
}
/**
* Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}.
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time.
*
* @param keyRing key ring
* @param referenceTime date of inspection
* @return access object
*/
@Nonnull
public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing, @Nonnull Date referenceTime) {
return new KeyRingInfo(keyRing, referenceTime);
}
/**
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
*
* @return policy
*/
@Nonnull
public static Policy getPolicy() {
return Policy.getInstance();
}
/**
* Create different kinds of signatures on other keys.
*
* @return builder
*/
@Nonnull
public static CertifyCertificate certify() {
return new CertifyCertificate();
}
}

View file

@ -1,100 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* List of AEAD algorithms defined in crypto-refresh-06.
*
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#name-aead-algorithms">
* Crypto-Refresh-06 §9.6 - AEAD Algorithms</a>
*/
public enum AEADAlgorithm {
EAX(1, 16, 16),
OCB(2, 15, 16),
GCM(3, 12, 16),
;
private final int algorithmId;
private final int ivLength;
private final int tagLength;
private static final Map<Integer, AEADAlgorithm> MAP = new HashMap<>();
static {
for (AEADAlgorithm h : AEADAlgorithm.values()) {
MAP.put(h.algorithmId, h);
}
}
AEADAlgorithm(int id, int ivLength, int tagLength) {
this.algorithmId = id;
this.ivLength = ivLength;
this.tagLength = tagLength;
}
/**
* Return the ID of the AEAD algorithm.
*
* @return algorithm ID
*/
public int getAlgorithmId() {
return algorithmId;
}
/**
* Return the length (in octets) of the IV.
*
* @return iv length
*/
public int getIvLength() {
return ivLength;
}
/**
* Return the length (in octets) of the authentication tag.
*
* @return tag length
*/
public int getTagLength() {
return tagLength;
}
/**
* Return the {@link AEADAlgorithm} value that corresponds to the provided algorithm id.
* If an invalid algorithm id was provided, null is returned.
*
* @param id numeric id
* @return enum value
*/
@Nullable
public static AEADAlgorithm fromId(int id) {
return MAP.get(id);
}
/**
* Return the {@link AEADAlgorithm} value that corresponds to the provided algorithm id.
* If an invalid algorithm id was provided, throw a {@link NoSuchElementException}.
*
* @param id algorithm id
* @return enum value
* @throws NoSuchElementException in case of an unknown algorithm id
*/
@Nonnull
public static AEADAlgorithm requireFromId(int id) {
AEADAlgorithm algorithm = fromId(id);
if (algorithm == null) {
throw new NoSuchElementException("No AEADAlgorithm found for id " + id);
}
return algorithm;
}
}

View file

@ -1,63 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* The {@link AlgorithmSuite} class is consulted when new OpenPGP keys are being generated to set
* preferred algorithms on the key.
*/
public class AlgorithmSuite {
private static final AlgorithmSuite defaultAlgorithmSuite = new AlgorithmSuite(
Arrays.asList(
SymmetricKeyAlgorithm.AES_256,
SymmetricKeyAlgorithm.AES_192,
SymmetricKeyAlgorithm.AES_128),
Arrays.asList(
HashAlgorithm.SHA512,
HashAlgorithm.SHA384,
HashAlgorithm.SHA256,
HashAlgorithm.SHA224),
Arrays.asList(
CompressionAlgorithm.ZLIB,
CompressionAlgorithm.BZIP2,
CompressionAlgorithm.ZIP,
CompressionAlgorithm.UNCOMPRESSED)
);
private final Set<SymmetricKeyAlgorithm> symmetricKeyAlgorithms;
private final Set<HashAlgorithm> hashAlgorithms;
private final Set<CompressionAlgorithm> compressionAlgorithms;
public AlgorithmSuite(List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms,
List<HashAlgorithm> hashAlgorithms,
List<CompressionAlgorithm> compressionAlgorithms) {
this.symmetricKeyAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(symmetricKeyAlgorithms));
this.hashAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(hashAlgorithms));
this.compressionAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(compressionAlgorithms));
}
public Set<SymmetricKeyAlgorithm> getSymmetricKeyAlgorithms() {
return new LinkedHashSet<>(symmetricKeyAlgorithms);
}
public Set<HashAlgorithm> getHashAlgorithms() {
return new LinkedHashSet<>(hashAlgorithms);
}
public Set<CompressionAlgorithm> getCompressionAlgorithms() {
return new LinkedHashSet<>(compressionAlgorithms);
}
public static AlgorithmSuite getDefaultAlgorithmSuite() {
return defaultAlgorithmSuite;
}
}

View file

@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import javax.annotation.Nonnull;
/**
* Subset of {@link SignatureType}, reduced to certification types.
*/
public enum CertificationType {
/**
* The issuer of this certification does not make any particular assertion as to how well the certifier has
* checked that the owner of the key is in fact the person described by the User ID.
*/
GENERIC(SignatureType.GENERIC_CERTIFICATION),
/**
* The issuer of this certification has not done any verification of the claim that the owner of this key is
* the User ID specified.
*/
NONE(SignatureType.NO_CERTIFICATION),
/**
* The issuer of this certification has done some casual verification of the claim of identity.
*/
CASUAL(SignatureType.CASUAL_CERTIFICATION),
/**
* The issuer of this certification has done some casual verification of the claim of identity.
*/
POSITIVE(SignatureType.POSITIVE_CERTIFICATION),
;
private final SignatureType signatureType;
CertificationType(@Nonnull SignatureType signatureType) {
this.signatureType = signatureType;
}
public @Nonnull SignatureType asSignatureType() {
return signatureType;
}
}

View file

@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Enumeration of possible compression algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-9.3">RFC4880: Compression Algorithm Tags</a>
*/
public enum CompressionAlgorithm {
UNCOMPRESSED (CompressionAlgorithmTags.UNCOMPRESSED),
ZIP (CompressionAlgorithmTags.ZIP),
ZLIB (CompressionAlgorithmTags.ZLIB),
BZIP2 (CompressionAlgorithmTags.BZIP2),
;
private static final Map<Integer, CompressionAlgorithm> MAP = new ConcurrentHashMap<>();
static {
for (CompressionAlgorithm c : CompressionAlgorithm.values()) {
MAP.put(c.algorithmId, c);
}
}
/**
* Return the {@link CompressionAlgorithm} value that corresponds to the provided numerical id.
* If an invalid id is provided, null is returned.
*
* @param id id
* @return compression algorithm
*/
@Nullable
public static CompressionAlgorithm fromId(int id) {
return MAP.get(id);
}
/**
* Return the {@link CompressionAlgorithm} value that corresponds to the provided numerical id.
* If an invalid id is provided, thrown an {@link NoSuchElementException}.
*
* @param id id
* @return compression algorithm
* @throws NoSuchElementException in case of an unmapped id
*/
@Nonnull
public static CompressionAlgorithm requireFromId(int id) {
CompressionAlgorithm algorithm = fromId(id);
if (algorithm == null) {
throw new NoSuchElementException("No CompressionAlgorithm found for id " + id);
}
return algorithm;
}
private final int algorithmId;
CompressionAlgorithm(int id) {
this.algorithmId = id;
}
/**
* Return the numerical algorithm tag corresponding to this compression algorithm.
* @return id
*/
public int getAlgorithmId() {
return algorithmId;
}
}

View file

@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
/**
* Subset of {@link SignatureType}, used for signatures over documents.
*/
public enum DocumentSignatureType {
/**
* Signature is calculated over the unchanged binary data.
*/
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
/**
* The signature is calculated over the text data with its line endings converted to
* <pre>
* {@code &lt;CR&gt;&lt;LF&gt;}
* </pre>.
*/
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
;
final SignatureType signatureType;
DocumentSignatureType(SignatureType signatureType) {
this.signatureType = signatureType;
}
public SignatureType getSignatureType() {
return signatureType;
}
}

View file

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
public enum EncryptionPurpose {
/**
* The stream will encrypt communication that goes over the wire.
* E.g. EMail, Chat...
*/
COMMUNICATIONS,
/**
* The stream will encrypt data at rest.
* E.g. Encrypted backup...
*/
STORAGE,
/**
* The stream will use keys with either flags to encrypt the data.
*/
ANY
}

View file

@ -1,152 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.bcpg.sig.Features;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An enumeration of features that may be set in the {@link Features} subpacket.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.24">RFC4880: Features</a>
*/
public enum Feature {
/**
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification
* Detection Code Packets.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.14">
* RFC-4880 §5.14: Modification Detection Code Packet</a>
*/
MODIFICATION_DETECTION(Features.FEATURE_MODIFICATION_DETECTION),
/**
* Support for Authenticated Encryption with Additional Data (AEAD).
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets.
*
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
* NOTE: This value is currently RESERVED.
*
* @see <a href="https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-">
* AEAD Encrypted Data Packet</a>
*/
GNUPG_AEAD_ENCRYPTED_DATA(Features.FEATURE_AEAD_ENCRYPTED_DATA),
/**
* If a key announces this feature, it is a version 5 public key.
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material.
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case
* of an unknown algorithm.
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys.
*
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
* NOTE: This value is currently RESERVED.
*
* @see <a href="https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats">
* Public-Key Packet Formats</a>
*/
GNUPG_VERSION_5_PUBLIC_KEY(Features.FEATURE_VERSION_5_PUBLIC_KEY),
/**
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
*
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd">
* crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format</a>
*/
MODIFICATION_DETECTION_2((byte) 0x08),
;
private static final Map<Byte, Feature> MAP = new ConcurrentHashMap<>();
static {
for (Feature f : Feature.values()) {
MAP.put(f.featureId, f);
}
}
/**
* Return the {@link Feature} encoded by the given id.
* If the id does not match any known features, return null.
*
* @param id feature id
* @return feature
*/
@Nullable
public static Feature fromId(byte id) {
return MAP.get(id);
}
/**
* Return the {@link Feature} encoded by the given id.
* If the id does not match any known features, throw an {@link NoSuchElementException}.
*
* @param id feature id
* @return feature
* @throws NoSuchElementException if an unmatched feature id is encountered
*/
@Nonnull
public static Feature requireFromId(byte id) {
Feature feature = fromId(id);
if (feature == null) {
throw new NoSuchElementException("Unknown feature id encountered: " + id);
}
return feature;
}
private final byte featureId;
Feature(byte featureId) {
this.featureId = featureId;
}
/**
* Return the id of the feature.
*
* @return feature id
*/
public byte getFeatureId() {
return featureId;
}
/**
* Convert a bitmask into a list of {@link KeyFlag KeyFlags}.
*
* @param bitmask bitmask
* @return list of key flags encoded by the bitmask
*/
@Nonnull
public static List<Feature> fromBitmask(int bitmask) {
List<Feature> features = new ArrayList<>();
for (Feature f : Feature.values()) {
if ((bitmask & f.featureId) != 0) {
features.add(f);
}
}
return features;
}
/**
* Encode a list of {@link KeyFlag KeyFlags} into a bitmask.
*
* @param features list of flags
* @return bitmask
*/
public static byte toBitmask(Feature... features) {
byte mask = 0;
for (Feature f : features) {
mask |= f.featureId;
}
return mask;
}
}

View file

@ -1,118 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An enumeration of different hashing algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-9.4">RFC4880: Hash Algorithms</a>
*/
public enum HashAlgorithm {
@Deprecated
MD5 (HashAlgorithmTags.MD5, "MD5"),
SHA1 (HashAlgorithmTags.SHA1, "SHA1"),
RIPEMD160 (HashAlgorithmTags.RIPEMD160, "RIPEMD160"),
SHA256 (HashAlgorithmTags.SHA256, "SHA256"),
SHA384 (HashAlgorithmTags.SHA384, "SHA384"),
SHA512 (HashAlgorithmTags.SHA512, "SHA512"),
SHA224 (HashAlgorithmTags.SHA224, "SHA224"),
SHA3_256 (12, "SHA3-256"),
SHA3_512 (14, "SHA3-512"),
;
private static final Map<Integer, HashAlgorithm> ID_MAP = new HashMap<>();
private static final Map<String, HashAlgorithm> NAME_MAP = new HashMap<>();
static {
for (HashAlgorithm h : HashAlgorithm.values()) {
ID_MAP.put(h.algorithmId, h);
NAME_MAP.put(h.name, h);
}
}
/**
* Return the {@link HashAlgorithm} value that corresponds to the provided algorithm id.
* If an invalid algorithm id was provided, null is returned.
*
* @param id numeric id
* @return enum value
*/
@Nullable
public static HashAlgorithm fromId(int id) {
return ID_MAP.get(id);
}
/**
* Return the {@link HashAlgorithm} value that corresponds to the provided algorithm id.
* If an invalid algorithm id was provided, throw a {@link NoSuchElementException}.
*
* @param id algorithm id
* @return enum value
* @throws NoSuchElementException in case of an unknown algorithm id
*/
@Nonnull
public static HashAlgorithm requireFromId(int id) {
HashAlgorithm algorithm = fromId(id);
if (algorithm == null) {
throw new NoSuchElementException("No HashAlgorithm found for id " + id);
}
return algorithm;
}
/**
* Return the {@link HashAlgorithm} value that corresponds to the provided name.
* If an invalid algorithm name was provided, null is returned.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-9.4">RFC4880: §9.4 Hash Algorithms</a>
* for a list of algorithms and names.
*
* @param name text name
* @return enum value
*/
@Nullable
public static HashAlgorithm fromName(String name) {
String algorithmName = name.toUpperCase();
HashAlgorithm algorithm = NAME_MAP.get(algorithmName);
if (algorithm == null) {
algorithm = NAME_MAP.get(algorithmName.replace("-", ""));
}
return algorithm;
}
private final int algorithmId;
private final String name;
HashAlgorithm(int id, String name) {
this.algorithmId = id;
this.name = name;
}
/**
* Return the numeric algorithm id of the hash algorithm.
*
* @return numeric id
*/
public int getAlgorithmId() {
return algorithmId;
}
/**
* Return the text name of the hash algorithm.
*
* @return text name
*/
public String getAlgorithmName() {
return name;
}
}

View file

@ -1,121 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.ArrayList;
import java.util.List;
import org.bouncycastle.bcpg.sig.KeyFlags;
/**
* Enumeration of different key flags.
* Key flags denote different capabilities of a key pair.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.21">RFC4880: Key Flags</a>
*/
public enum KeyFlag {
/**
* This key may be used to certify third-party keys.
*/
CERTIFY_OTHER (KeyFlags.CERTIFY_OTHER),
/**
* This key may be used to sign data.
*/
SIGN_DATA (KeyFlags.SIGN_DATA),
/**
* This key may be used to encrypt communications.
*/
ENCRYPT_COMMS (KeyFlags.ENCRYPT_COMMS),
/**
* This key may be used to encrypt storage.
*/
ENCRYPT_STORAGE(KeyFlags.ENCRYPT_STORAGE),
/**
* The private component of this key may have been split by a secret-sharing mechanism.
*/
SPLIT (KeyFlags.SPLIT),
/**
* This key may be used for authentication.
*/
AUTHENTICATION (KeyFlags.AUTHENTICATION),
/**
* The private component of this key may be in the possession of more than one person.
*/
SHARED (KeyFlags.SHARED),
;
private final int flag;
KeyFlag(int flag) {
this.flag = flag;
}
/**
* Return the numeric id of the {@link KeyFlag}.
*
* @return numeric id
*/
public int getFlag() {
return flag;
}
/**
* Convert a bitmask into a list of {@link KeyFlag KeyFlags}.
*
* @param bitmask bitmask
* @return list of key flags encoded by the bitmask
*/
public static List<KeyFlag> fromBitmask(int bitmask) {
List<KeyFlag> flags = new ArrayList<>();
for (KeyFlag f : KeyFlag.values()) {
if ((bitmask & f.flag) != 0) {
flags.add(f);
}
}
return flags;
}
/**
* Encode a list of {@link KeyFlag KeyFlags} into a bitmask.
*
* @param flags list of flags
* @return bitmask
*/
public static int toBitmask(KeyFlag... flags) {
int mask = 0;
for (KeyFlag f : flags) {
mask |= f.getFlag();
}
return mask;
}
/**
* Return true if the provided bitmask has the bit for the provided flag set.
* Return false if the mask does not contain the flag.
*
* @param mask bitmask
* @param flag flag to be tested for
* @return true if flag is set, false otherwise
*/
public static boolean hasKeyFlag(int mask, KeyFlag flag) {
return (mask & flag.getFlag()) == flag.getFlag();
}
public static boolean containsAny(int mask, KeyFlag... flags) {
for (KeyFlag flag : flags) {
if (hasKeyFlag(mask, flag)) {
return true;
}
}
return false;
}
}

View file

@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import org.bouncycastle.bcpg.PacketTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
public enum OpenPgpPacket {
PKESK(PacketTags.PUBLIC_KEY_ENC_SESSION),
SIG(PacketTags.SIGNATURE),
SKESK(PacketTags.SYMMETRIC_KEY_ENC_SESSION),
OPS(PacketTags.ONE_PASS_SIGNATURE),
SK(PacketTags.SECRET_KEY),
PK(PacketTags.PUBLIC_KEY),
SSK(PacketTags.SECRET_SUBKEY),
COMP(PacketTags.COMPRESSED_DATA),
SED(PacketTags.SYMMETRIC_KEY_ENC),
MARKER(PacketTags.MARKER),
LIT(PacketTags.LITERAL_DATA),
TRUST(PacketTags.TRUST),
UID(PacketTags.USER_ID),
PSK(PacketTags.PUBLIC_SUBKEY),
UATTR(PacketTags.USER_ATTRIBUTE),
SEIPD(PacketTags.SYM_ENC_INTEGRITY_PRO),
MDC(PacketTags.MOD_DETECTION_CODE),
EXP_1(PacketTags.EXPERIMENTAL_1),
EXP_2(PacketTags.EXPERIMENTAL_2),
EXP_3(PacketTags.EXPERIMENTAL_3),
EXP_4(PacketTags.EXPERIMENTAL_4),
;
static final Map<Integer, OpenPgpPacket> MAP = new HashMap<>();
static {
for (OpenPgpPacket p : OpenPgpPacket.values()) {
MAP.put(p.getTag(), p);
}
}
final int tag;
@Nullable
public static OpenPgpPacket fromTag(int tag) {
return MAP.get(tag);
}
@Nonnull
public static OpenPgpPacket requireFromTag(int tag) {
OpenPgpPacket p = fromTag(tag);
if (p == null) {
throw new NoSuchElementException("No OpenPGP packet known for tag " + tag);
}
return p;
}
OpenPgpPacket(int tag) {
this.tag = tag;
}
int getTag() {
return tag;
}
}

View file

@ -1,163 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Enumeration of public key algorithms as defined in RFC4880.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-9.1">RFC4880: Public-Key Algorithms</a>
*/
public enum PublicKeyAlgorithm {
/**
* RSA capable of encryption and signatures.
*/
RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL, true, true),
/**
* RSA with usage encryption.
*
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
*/
@Deprecated
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true),
/**
* RSA with usage of creating signatures.
*
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
*/
@Deprecated
RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN, true, false),
/**
* ElGamal with usage encryption.
*/
ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, false, true),
/**
* Digital Signature Algorithm.
*/
DSA (PublicKeyAlgorithmTags.DSA, true, false),
/**
* EC is deprecated.
* @deprecated use {@link #ECDH} instead.
*/
@Deprecated
EC (PublicKeyAlgorithmTags.EC, false, true),
/**
* Elliptic Curve Diffie-Hellman.
*/
ECDH (PublicKeyAlgorithmTags.ECDH, false, true),
/**
* Elliptic Curve Digital Signature Algorithm.
*/
ECDSA (PublicKeyAlgorithmTags.ECDSA, true, false),
/**
* ElGamal General.
*
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a>
*/
@Deprecated
ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL, true, true),
/**
* Diffie-Hellman key exchange algorithm.
*/
DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN, false, true),
/**
* Digital Signature Algorithm based on twisted Edwards Curves.
*/
EDDSA (PublicKeyAlgorithmTags.EDDSA, true, false),
;
private static final Map<Integer, PublicKeyAlgorithm> MAP = new ConcurrentHashMap<>();
static {
for (PublicKeyAlgorithm p : PublicKeyAlgorithm.values()) {
MAP.put(p.algorithmId, p);
}
}
/**
* Return the {@link PublicKeyAlgorithm} that corresponds to the provided algorithm id.
* If an invalid id is provided, null is returned.
*
* @param id numeric algorithm id
* @return algorithm or null
*/
@Nullable
public static PublicKeyAlgorithm fromId(int id) {
return MAP.get(id);
}
/**
* Return the {@link PublicKeyAlgorithm} that corresponds to the provided algorithm id.
* If an invalid id is provided, throw a {@link NoSuchElementException}.
*
* @param id numeric algorithm id
* @return algorithm
* @throws NoSuchElementException in case of an unmatched algorithm id
*/
@Nonnull
public static PublicKeyAlgorithm requireFromId(int id) {
PublicKeyAlgorithm algorithm = fromId(id);
if (algorithm == null) {
throw new NoSuchElementException("No PublicKeyAlgorithm found for id " + id);
}
return algorithm;
}
private final int algorithmId;
private final boolean signingCapable;
private final boolean encryptionCapable;
PublicKeyAlgorithm(int algorithmId, boolean signingCapable, boolean encryptionCapable) {
this.algorithmId = algorithmId;
this.signingCapable = signingCapable;
this.encryptionCapable = encryptionCapable;
}
/**
* Return the numeric identifier of the public key algorithm.
*
* @return id
*/
public int getAlgorithmId() {
return algorithmId;
}
/**
* Return true if this public key algorithm is able to create signatures.
*
* @return true if the algorithm can sign
*/
public boolean isSigningCapable() {
return signingCapable;
}
/**
* Return true if this public key algorithm can be used as an encryption algorithm.
*
* @return true if the algorithm can encrypt
*/
public boolean isEncryptionCapable() {
return encryptionCapable;
}
}

View file

@ -1,131 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import org.pgpainless.util.DateUtil;
import javax.annotation.Nonnull;
import java.util.Date;
import java.util.NoSuchElementException;
public final class RevocationState implements Comparable<RevocationState> {
private final RevocationStateType type;
private final Date date;
private RevocationState(RevocationStateType type) {
this(type, null);
}
private RevocationState(RevocationStateType type, Date date) {
this.type = type;
if (type == RevocationStateType.softRevoked && date == null) {
throw new NullPointerException("If type is 'softRevoked' then date cannot be null.");
}
this.date = date;
}
public static RevocationState notRevoked() {
return new RevocationState(RevocationStateType.notRevoked);
}
public static RevocationState softRevoked(@Nonnull Date date) {
return new RevocationState(RevocationStateType.softRevoked, date);
}
public static RevocationState hardRevoked() {
return new RevocationState(RevocationStateType.hardRevoked);
}
public RevocationStateType getType() {
return type;
}
public @Nonnull Date getDate() {
if (!isSoftRevocation()) {
throw new NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.");
}
return date;
}
public boolean isHardRevocation() {
return getType() == RevocationStateType.hardRevoked;
}
public boolean isSoftRevocation() {
return getType() == RevocationStateType.softRevoked;
}
public boolean isNotRevoked() {
return getType() == RevocationStateType.notRevoked;
}
@Override
public String toString() {
String out = getType().toString();
if (isSoftRevocation()) {
out = out + " (" + DateUtil.formatUTCDate(date) + ")";
}
return out;
}
@Override
public int compareTo(@Nonnull RevocationState o) {
switch (getType()) {
case notRevoked:
if (o.isNotRevoked()) {
return 0;
} else {
return -1;
}
case softRevoked:
if (o.isNotRevoked()) {
return 1;
} else if (o.isSoftRevocation()) {
// Compare soft dates in reverse
return o.getDate().compareTo(getDate());
} else {
return -1;
}
case hardRevoked:
if (o.isHardRevocation()) {
return 0;
} else {
return 1;
}
default:
throw new AssertionError("Unknown type: " + type);
}
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (isSoftRevocation() ? getDate().hashCode() : 0);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof RevocationState)) {
return false;
}
RevocationState other = (RevocationState) obj;
if (getType() != other.getType()) {
return false;
}
if (isSoftRevocation()) {
return DateUtil.toSecondsPrecision(getDate()).getTime() == DateUtil.toSecondsPrecision(other.getDate()).getTime();
}
return true;
}
}

View file

@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
public enum RevocationStateType {
/**
* Certificate is not revoked.
*/
notRevoked,
/**
* Certificate is revoked with a soft revocation.
*/
softRevoked,
/**
* Certificate is revoked with a hard revocation.
*/
hardRevoked
}

View file

@ -1,462 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ATTESTED_CERTIFICATIONS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.CREATION_TIME;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EMBEDDED_SIGNATURE;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPIRE_TIME;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPORTABLE;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.FEATURES;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.INTENDED_RECIPIENT_FINGERPRINT;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_FINGERPRINT;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_KEY_ID;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_EXPIRE_TIME;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_FLAGS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_SERVER_PREFS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.NOTATION_DATA;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PLACEHOLDER;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.POLICY_URL;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_COMP_ALGS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_HASH_ALGS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_KEY_SERV;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_SYM_ALGS;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PRIMARY_USER_ID;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REG_EXP;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCABLE;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_REASON;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNATURE_TARGET;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNER_USER_ID;
import static org.bouncycastle.bcpg.SignatureSubpacketTags.TRUST_SIG;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
/**
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.1">RFC4880: Signature Subpacket Specification</a>
*/
public enum SignatureSubpacket {
/**
* The time the signature was made.
* MUST be present in the hashed area of the signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.4">Signature Creation Time</a>
*/
signatureCreationTime(CREATION_TIME),
/**
* The validity period of the signature. This is the number of seconds
* after the signature creation time that the signature expires. If
* this is not present or has a value of zero, it never expires.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.10">Signature Expiration Time</a>
*/
signatureExpirationTime(EXPIRE_TIME),
/**
* Denotes whether the signature is exportable for other users.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.11">Exportable Certification</a>
*/
exportableCertification(EXPORTABLE),
/**
* Signer asserts that the key is not only valid but also trustworthy at
* the specified level. Level 0 has the same meaning as an ordinary
* validity signature. Level 1 means that the signed key is asserted to
* be a valid, trusted introducer, with the 2nd octet of the body
* specifying the degree of trust. Level 2 means that the signed key is
* asserted to be trusted to issue level 1 trust signatures, i.e., that
* it is a "meta introducer". Generally, a level n trust signature
* asserts that a key is trusted to issue level n-1 trust signatures.
* The trust amount is in a range from 0-255, interpreted such that
* values less than 120 indicate partial trust and values of 120 or
* greater indicate complete trust. Implementations SHOULD emit values
* of 60 for partial trust and 120 for complete trust.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.13">Trust Signature</a>
*/
trustSignature(TRUST_SIG),
/**
* Used in conjunction with trust Signature packets (of level greater 0) to
* limit the scope of trust that is extended. Only signatures by the
* target key on User IDs that match the regular expression in the body
* of this packet have trust extended by the trust Signature subpacket.
* The regular expression uses the same syntax as the Henry Spencer's
* "almost public domain" regular expression [REGEX] package. A
* description of the syntax is found in Section 8 below.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.14">Regular Expression</a>
*/
regularExpression(REG_EXP),
/**
* Signature's revocability status. The packet body contains a Boolean
* flag indicating whether the signature is revocable. Signatures that
* are not revocable have any later revocation signatures ignored. They
* represent a commitment by the signer that he cannot revoke his
* signature for the life of his key. If this packet is not present,
* the signature is revocable.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.12">Revocable</a>
*/
revocable(REVOCABLE),
/**
* The validity period of the key. This is the number of seconds after
* the key creation time that the key expires. If this is not present
* or has a value of zero, the key never expires. This is found only on
* a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.6">Key Expiration Time</a>
*/
keyExpirationTime(KEY_EXPIRE_TIME),
/**
* Placeholder for backwards compatibility.
*/
placeholder(PLACEHOLDER),
/**
* Symmetric algorithm numbers that indicate which algorithms the keyholder
* prefers to use. The subpackets body is an ordered list of
* octets with the most preferred listed first. It is assumed that only
* algorithms listed are supported by the recipient's software.
* This is only found on a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.7">Preferred Symmetric Algorithms</a>
*/
preferredSymmetricAlgorithms(PREFERRED_SYM_ALGS),
/**
* Authorizes the specified key to issue revocation signatures for this
* key. Class octet must have bit 0x80 set. If the bit 0x40 is set,
* then this means that the revocation information is sensitive. Other
* bits are for future expansion to other kinds of authorizations. This
* is found on a self-signature.
*
* If the "sensitive" flag is set, the keyholder feels this subpacket
* contains private trust information that describes a real-world
* sensitive relationship. If this flag is set, implementations SHOULD
* NOT export this signature to other users except in cases where the
* data needs to be available: when the signature is being sent to the
* designated revoker, or when it is accompanied by a revocation
* signature from that revoker. Note that it may be appropriate to
* isolate this subpacket within a separate signature so that it is not
* combined with other subpackets that need to be exported.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.15">Revocation Key</a>
*/
revocationKey(REVOCATION_KEY),
/**
* The OpenPGP Key ID of the key issuing the signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.5">Issuer Key ID</a>
*/
issuerKeyId(ISSUER_KEY_ID),
/**
* This subpacket describes a "notation" on the signature that the
* issuer wishes to make. The notation has a name and a value, each of
* which are strings of octets. There may be more than one notation in
* a signature. Notations can be used for any extension the issuer of
* the signature cares to make. The "flags" field holds four octets of
* flags.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.16">Notation Data</a>
*/
notationData(NOTATION_DATA),
/**
* Message digest algorithm numbers that indicate which algorithms the
* keyholder prefers to receive. Like the preferred symmetric
* algorithms, the list is ordered.
* This is only found on a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.8">Preferred Hash Algorithms</a>
*/
preferredHashAlgorithms(PREFERRED_HASH_ALGS),
/**
* Compression algorithm numbers that indicate which algorithms the
* keyholder prefers to use. Like the preferred symmetric algorithms, the
* list is ordered. If this subpacket is not included, ZIP is preferred.
* A zero denotes that uncompressed data is preferred; the keyholder's
* software might have no compression software in that implementation.
* This is only found on a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.9">Preferred Compressio Algorithms</a>
*/
preferredCompressionAlgorithms(PREFERRED_COMP_ALGS),
/**
* This is a list of one-bit flags that indicate preferences that the
* keyholder has about how the key is handled on a key server. All
* undefined flags MUST be zero.
* This is found only on a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.17">Key Server Preferences</a>
*/
keyServerPreferences(KEY_SERVER_PREFS),
/**
* This is a URI of a key server that the keyholder prefers be used for
* updates. Note that keys with multiple User IDs can have a preferred
* key server for each User ID. Note also that since this is a URI, the
* key server can actually be a copy of the key retrieved by ftp, http,
* finger, etc.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.18">Preferred Key Server</a>
*/
preferredKeyServers(PREFERRED_KEY_SERV),
/**
* This is a flag in a User ID's self-signature that states whether this
* User ID is the main User ID for this key. It is reasonable for an
* implementation to resolve ambiguities in preferences, etc. by
* referring to the primary User ID. If this flag is absent, its value
* is zero. If more than one User ID in a key is marked as primary, the
* implementation may resolve the ambiguity in any way it sees fit, but
* it is RECOMMENDED that priority be given to the User ID with the most
* recent self-signature.
*
* When appearing on a self-signature on a User ID packet, this
* subpacket applies only to User ID packets. When appearing on a
* self-signature on a User Attribute packet, this subpacket applies
* only to User Attribute packets. That is to say, there are two
* different and independent "primaries" -- one for User IDs, and one
* for User Attributes.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.19">Primary User-ID</a>
*/
primaryUserId(PRIMARY_USER_ID),
/**
* This subpacket contains a URI of a document that describes the policy
* under which the signature was issued.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.20">Policy URL</a>
*/
policyUrl(POLICY_URL),
/**
* This subpacket contains a list of binary flags that hold information
* about a key. It is a string of octets, and an implementation MUST
* NOT assume a fixed size. This is so it can grow over time. If a
* list is shorter than an implementation expects, the unstated flags
* are considered to be zero.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.21">Key Flags</a>
*/
keyFlags(KEY_FLAGS),
/**
* This subpacket allows a keyholder to state which User ID is
* responsible for the signing. Many keyholders use a single key for
* different purposes, such as business communications as well as
* personal communications. This subpacket allows such a keyholder to
* state which of their roles is making a signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.22">Signer's User ID</a>
*/
signerUserId(SIGNER_USER_ID),
/**
* This subpacket is used only in key revocation and certification
* revocation signatures. It describes the reason why the key or
* certificate was revoked.
*
* The first octet contains a machine-readable code that denotes the
* reason for the revocation:
*
* 0 - No reason specified (key revocations or cert revocations)
* 1 - Key is superseded (key revocations)
* 2 - Key material has been compromised (key revocations)
* 3 - Key is retired and no longer used (key revocations)
* 32 - User ID information is no longer valid (cert revocations)
* 100-110 - Private Use
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.23">Reason for Revocation</a>
*/
revocationReason(REVOCATION_REASON),
/**
* The Features subpacket denotes which advanced OpenPGP features a
* user's implementation supports. This is so that as features are
* added to OpenPGP that cannot be backwards-compatible, a user can
* state that they can use that feature. The flags are single bits that
* indicate that a given feature is supported.
*
* This subpacket is similar to a preferences subpacket, and only
* appears in a self-signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.24">Features</a>
*/
features(FEATURES),
/**
* This subpacket identifies a specific target signature to which a
* signature refers. For revocation signatures, this subpacket
* provides explicit designation of which signature is being revoked.
* For a third-party or timestamp signature, this designates what
* signature is signed. All arguments are an identifier of that target
* signature.
*
* The N octets of hash data MUST be the size of the hash of the
* signature. For example, a target signature with a SHA-1 hash MUST
* have 20 octets of hash data.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.25">Signature Target</a>
*/
signatureTarget(SIGNATURE_TARGET),
/**
* This subpacket contains a complete Signature packet body as
* specified in Section 5.2 above. It is useful when one signature
* needs to refer to, or be incorporated in, another signature.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.2.3.26">Embedded Signature</a>
*/
embeddedSignature(EMBEDDED_SIGNATURE),
/**
* The OpenPGP Key fingerprint of the key issuing the signature. This
* subpacket SHOULD be included in all signatures. If the version of
* the issuing key is 4 and an Issuer subpacket is also included in the
* signature, the key ID of the Issuer subpacket MUST match the low 64
* bits of the fingerprint.
*
* Note that the length N of the fingerprint for a version 4 key is 20
* octets; for a version 5 key N is 32.
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28">Issuer Fingerprint</a>
*/
issuerFingerprint(ISSUER_FINGERPRINT),
/**
* AEAD algorithm numbers that indicate which AEAD algorithms the
* keyholder prefers to use. The subpackets body is an ordered list of
* octets with the most preferred listed first. It is assumed that only
* algorithms listed are supported by the recipient's software.
* This is only found on a self-signature.
* Note that support for the AEAD Encrypted Data packet in the general
* is indicated by a Feature Flag.
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8">Preferred AEAD Algorithms</a>
*/
preferredAEADAlgorithms(PREFERRED_AEAD_ALGORITHMS),
/**
* The OpenPGP Key fingerprint of the intended recipient primary key.
* If one or more subpackets of this type are included in a signature,
* it SHOULD be considered valid only in an encrypted context, where the
* key it was encrypted to is one of the indicated primary keys, or one
* of their subkeys. This can be used to prevent forwarding a signature
* outside its intended, encrypted context.
*
* Note that the length N of the fingerprint for a version 4 key is 20
* octets; for a version 5 key N is 32.
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29">Intended Recipient Fingerprint</a>
*/
intendedRecipientFingerprint(INTENDED_RECIPIENT_FINGERPRINT),
/**
* This subpacket MUST only appear as a hashed subpacket of an
* Attestation Key Signature. It has no meaning in any other signature
* type. It is used by the primary key to attest to a set of third-
* party certifications over the associated User ID or User Attribute.
* This enables the holder of an OpenPGP primary key to mark specific
* third-party certifications as re-distributable with the rest of the
* Transferable Public Key (see the "No-modify" flag in "Key Server
* Preferences", above). Implementations MUST include exactly one
* Attested Certification subpacket in any generated Attestation Key
* Signature.
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30">Attested Certification</a>
*/
attestedCertification(ATTESTED_CERTIFICATIONS)
;
private static final Map<Integer, SignatureSubpacket> MAP = new ConcurrentHashMap<>();
static {
for (SignatureSubpacket p : values()) {
MAP.put(p.code, p);
}
}
private final int code;
SignatureSubpacket(int code) {
this.code = code;
}
/**
* Return the numerical identifier of the {@link SignatureSubpacket}.
* @return id
*/
public int getCode() {
return code;
}
/**
* Return the {@link SignatureSubpacket} that corresponds to the provided id.
* If an unmatched code is presented, return null.
*
* @param code id
* @return signature subpacket
*/
@Nullable
public static SignatureSubpacket fromCode(int code) {
return MAP.get(code);
}
/**
* Return the {@link SignatureSubpacket} that corresponds to the provided code.
*
* @param code code
* @return signature subpacket
* @throws NoSuchElementException in case of an unmatched subpacket tag
*/
@Nonnull
public static SignatureSubpacket requireFromCode(int code) {
SignatureSubpacket tag = fromCode(code);
if (tag == null) {
throw new NoSuchElementException("No SignatureSubpacket tag found with code " + code);
}
return tag;
}
/**
* Convert an array of signature subpacket tags into a list of {@link SignatureSubpacket SignatureSubpackets}.
*
* @param codes array of codes
* @return list of subpackets
*/
public static List<SignatureSubpacket> fromCodes(int[] codes) {
List<SignatureSubpacket> tags = new ArrayList<>();
for (int code : codes) {
try {
tags.add(requireFromCode(code));
} catch (NoSuchElementException e) {
// skip
}
}
return tags;
}
}

View file

@ -1,225 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import org.bouncycastle.openpgp.PGPSignature;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1
* See {@link org.bouncycastle.openpgp.PGPSignature} for comparison.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.11">rfc4880 §5.2.1. Signature Types</a>
*/
public enum SignatureType {
/**
* Signature of a binary document.
* This means the signer owns it, created it, or certifies that it
* has not been modified.
*/
BINARY_DOCUMENT(PGPSignature.BINARY_DOCUMENT),
/**
* Signature of a canonical text document.
* This means the signer owns it, created it, or certifies that it
* has not been modified. The signature is calculated over the text
* data with its line endings converted to {@code <CR><LF>}.
*/
CANONICAL_TEXT_DOCUMENT(PGPSignature.CANONICAL_TEXT_DOCUMENT),
/**
* Standalone signature.
* This signature is a signature of only its own subpacket contents.
* It is calculated identically to a signature over a zero-length
* binary document. Note that it doesn't make sense to have a V3
* standalone signature.
*/
STANDALONE(PGPSignature.STAND_ALONE),
/**
* Generic certification of a User ID and Public-Key packet.
* The issuer of this certification does not make any particular
* assertion as to how well the certifier has checked that the owner
* of the key is in fact the person described by the User ID.
*/
GENERIC_CERTIFICATION(PGPSignature.DEFAULT_CERTIFICATION),
/**
* Persona certification of a User ID and Public-Key packet.
* The issuer of this certification has not done any verification of
* the claim that the owner of this key is the User ID specified.
*/
NO_CERTIFICATION(PGPSignature.NO_CERTIFICATION),
/**
* Casual certification of a User ID and Public-Key packet.
* The issuer of this certification has done some casual
* verification of the claim of identity.
*/
CASUAL_CERTIFICATION(PGPSignature.CASUAL_CERTIFICATION),
/**
* Positive certification of a User ID and Public-Key packet.
* The issuer of this certification has done substantial
* verification of the claim of identity.
*/
POSITIVE_CERTIFICATION(PGPSignature.POSITIVE_CERTIFICATION),
/**
* Subkey Binding Signature.
* This signature is a statement by the top-level signing key that
* indicates that it owns the subkey. This signature is calculated
* directly on the primary key and subkey, and not on any User ID or
* other packets. A signature that binds a signing subkey MUST have
* an Embedded Signature subpacket in this binding signature that
* contains a {@link #PRIMARYKEY_BINDING} signature made by the
* signing subkey on the primary key and subkey.
*/
SUBKEY_BINDING(PGPSignature.SUBKEY_BINDING),
/**
* Primary Key Binding Signature
* This signature is a statement by a signing subkey, indicating
* that it is owned by the primary key and subkey. This signature
* is calculated the same way as a {@link #SUBKEY_BINDING} signature:
* directly on the primary key and subkey, and not on any User ID or
* other packets.
*/
PRIMARYKEY_BINDING(PGPSignature.PRIMARYKEY_BINDING),
/**
* Signature directly on a key
* This signature is calculated directly on a key. It binds the
* information in the Signature subpackets to the key, and is
* appropriate to be used for subpackets that provide information
* about the key, such as the Revocation Key subpacket. It is also
* appropriate for statements that non-self certifiers want to make
* about the key itself, rather than the binding between a key and a
* name.
*/
DIRECT_KEY(PGPSignature.DIRECT_KEY),
/**
* Key revocation signature
* The signature is calculated directly on the key being revoked. A
* revoked key is not to be used. Only revocation signatures by the
* key being revoked, or by an authorized revocation key, should be
* considered valid revocation signatures.
*/
KEY_REVOCATION(PGPSignature.KEY_REVOCATION),
/**
* Subkey revocation signature
* The signature is calculated directly on the subkey being revoked.
* A revoked subkey is not to be used. Only revocation signatures
* by the top-level signature key that is bound to this subkey, or
* by an authorized revocation key, should be considered valid
* revocation signatures.
*/
SUBKEY_REVOCATION(PGPSignature.SUBKEY_REVOCATION),
/**
* Certification revocation signature
* This signature revokes an earlier User ID certification signature
* (signature class 0x10 through 0x13) or signature {@link #DIRECT_KEY}.
* It should be issued by the same key that issued the
* revoked signature or an authorized revocation key. The signature
* is computed over the same data as the certificate that it
* revokes, and should have a later creation date than that
* certificate.
*/
CERTIFICATION_REVOCATION(PGPSignature.CERTIFICATION_REVOCATION),
/**
* Timestamp signature.
* This signature is only meaningful for the timestamp contained in
* it.
*/
TIMESTAMP(PGPSignature.TIMESTAMP),
/**
* Third-Party Confirmation signature.
* This signature is a signature over some other OpenPGP Signature
* packet(s). It is analogous to a notary seal on the signed data.
* A third-party signature SHOULD include Signature Target
* subpacket(s) to give easy identification. Note that we really do
* mean SHOULD. There are plausible uses for this (such as a blind
* party that only sees the signature, not the key or source
* document) that cannot include a target subpacket.
*/
THIRD_PARTY_CONFIRMATION(0x50)
;
private static final Map<Integer, SignatureType> map = new ConcurrentHashMap<>();
static {
for (SignatureType sigType : SignatureType.values()) {
map.put(sigType.getCode(), sigType);
}
}
/**
* Convert a numerical id into a {@link SignatureType}.
*
* @param code numeric id
* @return signature type enum
* @throws IllegalArgumentException in case of an unmatched signature type code
*/
@Nonnull
public static SignatureType valueOf(int code) {
SignatureType type = map.get(code);
if (type != null) {
return type;
}
throw new IllegalArgumentException("Signature type 0x" + Integer.toHexString(code) + " appears to be invalid.");
}
private final int code;
SignatureType(int code) {
this.code = code;
}
/**
* Return the numeric id of the signature type enum.
*
* @return numeric id
*/
public int getCode() {
return code;
}
public static boolean isRevocationSignature(int signatureType) {
return isRevocationSignature(SignatureType.valueOf(signatureType));
}
public static boolean isRevocationSignature(SignatureType signatureType) {
switch (signatureType) {
case BINARY_DOCUMENT:
case CANONICAL_TEXT_DOCUMENT:
case STANDALONE:
case GENERIC_CERTIFICATION:
case NO_CERTIFICATION:
case CASUAL_CERTIFICATION:
case POSITIVE_CERTIFICATION:
case SUBKEY_BINDING:
case PRIMARYKEY_BINDING:
case DIRECT_KEY:
case TIMESTAMP:
case THIRD_PARTY_CONFIRMATION:
return false;
case KEY_REVOCATION:
case SUBKEY_REVOCATION:
case CERTIFICATION_REVOCATION:
return true;
default:
throw new IllegalArgumentException("Unknown signature type: " + signatureType);
}
}
}

View file

@ -1,101 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.openpgp.PGPLiteralData;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Enumeration of possible encoding formats of the content of the literal data packet.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-5.9">RFC4880: Literal Data Packet</a>
*/
public enum StreamEncoding {
/**
* The Literal packet contains binary data.
*/
BINARY(PGPLiteralData.BINARY),
/**
* The Literal packet contains text data, and thus may need line ends converted to local form, or other
* text-mode changes.
*/
TEXT(PGPLiteralData.TEXT),
/**
* Indication that the implementation believes that the literal data contains UTF-8 text.
*/
UTF8(PGPLiteralData.UTF8),
/**
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions.
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one).
* Both of these local modes are deprecated.
*/
@Deprecated
LOCAL('l'),
;
private final char code;
private static final Map<Character, StreamEncoding> MAP = new ConcurrentHashMap<>();
static {
for (StreamEncoding f : StreamEncoding.values()) {
MAP.put(f.code, f);
}
// RFC 1991 [RFC1991] incorrectly stated local mode flag as '1', see doc of LOCAL.
MAP.put('1', LOCAL);
}
StreamEncoding(char code) {
this.code = code;
}
/**
* Return the code identifier of the encoding.
*
* @return identifier
*/
public char getCode() {
return code;
}
/**
* Return the {@link StreamEncoding} corresponding to the provided code identifier.
* If no matching encoding is found, return null.
*
* @param code identifier
* @return encoding enum
*/
@Nullable
public static StreamEncoding fromCode(int code) {
return MAP.get((char) code);
}
/**
* Return the {@link StreamEncoding} corresponding to the provided code identifier.
* If no matching encoding is found, throw a {@link NoSuchElementException}.
*
* @param code identifier
* @return encoding enum
*
* @throws NoSuchElementException in case of an unmatched identifier
*/
@Nonnull
public static StreamEncoding requireFromCode(int code) {
StreamEncoding encoding = fromCode(code);
if (encoding == null) {
throw new NoSuchElementException("No StreamEncoding found for code " + code);
}
return encoding;
}
}

View file

@ -1,150 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Enumeration of possible symmetric encryption algorithms.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-9.2">RFC4880: Symmetric-Key Algorithms</a>
*/
public enum SymmetricKeyAlgorithm {
/**
* Plaintext or unencrypted data.
*/
NULL (SymmetricKeyAlgorithmTags.NULL),
/**
* IDEA is deprecated.
* @deprecated use a different algorithm.
*/
@Deprecated
IDEA (SymmetricKeyAlgorithmTags.IDEA),
/**
* TripleDES (DES-EDE - 168 bit key derived from 192).
*/
TRIPLE_DES (SymmetricKeyAlgorithmTags.TRIPLE_DES),
/**
* CAST5 (128-bit key, as per RFC2144).
*/
CAST5 (SymmetricKeyAlgorithmTags.CAST5),
/**
* Blowfish (128-bit key, 16 rounds).
*/
BLOWFISH (SymmetricKeyAlgorithmTags.BLOWFISH),
/**
* Reserved in RFC4880.
* SAFER-SK128 (13 rounds)
*/
SAFER (SymmetricKeyAlgorithmTags.SAFER),
/**
* Reserved in RFC4880.
* Reserved for DES/SK
*/
DES (SymmetricKeyAlgorithmTags.DES),
/**
* AES with 128-bit key.
*/
AES_128 (SymmetricKeyAlgorithmTags.AES_128),
/**
* AES with 192-bit key.
*/
AES_192 (SymmetricKeyAlgorithmTags.AES_192),
/**
* AES with 256-bit key.
*/
AES_256 (SymmetricKeyAlgorithmTags.AES_256),
/**
* Twofish with 256-bit key.
*/
TWOFISH (SymmetricKeyAlgorithmTags.TWOFISH),
/**
* Reserved for Camellia with 128-bit key.
*/
CAMELLIA_128 (SymmetricKeyAlgorithmTags.CAMELLIA_128),
/**
* Reserved for Camellia with 192-bit key.
*/
CAMELLIA_192 (SymmetricKeyAlgorithmTags.CAMELLIA_192),
/**
* Reserved for Camellia with 256-bit key.
*/
CAMELLIA_256 (SymmetricKeyAlgorithmTags.CAMELLIA_256),
;
private static final Map<Integer, SymmetricKeyAlgorithm> MAP = new ConcurrentHashMap<>();
static {
for (SymmetricKeyAlgorithm s : SymmetricKeyAlgorithm.values()) {
MAP.put(s.algorithmId, s);
}
}
/**
* Return the {@link SymmetricKeyAlgorithm} enum that corresponds to the provided numeric id.
* If an invalid id is provided, null is returned.
*
* @param id numeric algorithm id
* @return symmetric key algorithm enum
*/
@Nullable
public static SymmetricKeyAlgorithm fromId(int id) {
return MAP.get(id);
}
/**
* Return the {@link SymmetricKeyAlgorithm} enum that corresponds to the provided numeric id.
* If an invalid id is provided, throw a {@link NoSuchElementException}.
*
* @param id numeric algorithm id
* @return symmetric key algorithm enum
*
* @throws NoSuchElementException if an unmatched id is provided
*/
@Nonnull
public static SymmetricKeyAlgorithm requireFromId(int id) {
SymmetricKeyAlgorithm algorithm = fromId(id);
if (algorithm == null) {
throw new NoSuchElementException("No SymmetricKeyAlgorithm found for id " + id);
}
return algorithm;
}
private final int algorithmId;
SymmetricKeyAlgorithm(int algorithmId) {
this.algorithmId = algorithmId;
}
/**
* Return the numeric algorithm id of the enum.
*
* @return numeric id
*/
public int getAlgorithmId() {
return algorithmId;
}
}

View file

@ -1,188 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
/**
* Facade class for {@link org.bouncycastle.bcpg.sig.TrustSignature}.
* A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act
* as a trusted introducer.
*/
public class Trustworthiness {
private final int amount;
private final int depth;
public static final int THRESHOLD_FULLY_CONVINCED = 120; // greater or equal is fully trusted
public static final int MARGINALLY_CONVINCED = 60; // default value for marginally convinced
public static final int NOT_TRUSTED = 0; // 0 is not trusted
public Trustworthiness(int amount, int depth) {
this.amount = capAmount(amount);
this.depth = capDepth(depth);
}
/**
* Get the trust amount.
* This value means how confident the issuer of the signature is in validity of the binding.
*
* @return trust amount
*/
public int getAmount() {
return amount;
}
/**
* Get the depth of the trust signature.
* This value controls, whether the certificate can act as a trusted introducer.
*
* @return depth
*/
public int getDepth() {
return depth;
}
/**
* Returns true, if the trust amount is equal to 0.
* This means the key is not trusted.
*
* Otherwise return false
* @return true if untrusted
*/
public boolean isNotTrusted() {
return getAmount() == NOT_TRUSTED;
}
/**
* Return true if the certificate is at least marginally trusted.
* That is the case, if the trust amount is greater than 0.
*
* @return true if the cert is at least marginally trusted
*/
public boolean isMarginallyTrusted() {
return getAmount() > NOT_TRUSTED;
}
/**
* Return true if the certificate is fully trusted. That is the case if the trust amount is
* greater than or equal to 120.
*
* @return true if the cert is fully trusted
*/
public boolean isFullyTrusted() {
return getAmount() >= THRESHOLD_FULLY_CONVINCED;
}
/**
* Return true, if the cert is an introducer. That is the case if the depth is greater 0.
*
* @return true if introducer
*/
public boolean isIntroducer() {
return getDepth() >= 1;
}
/**
* Return true, if the certified cert can introduce certificates with trust depth of <pre>otherDepth</pre>.
*
* @param otherDepth other certifications trust depth
* @return true if the cert can introduce the other
*/
public boolean canIntroduce(int otherDepth) {
return getDepth() > otherDepth;
}
/**
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre> trust depth.
*
* @param other other certificates trust depth
* @return true if the cert can introduce the other
*/
public boolean canIntroduce(Trustworthiness other) {
return canIntroduce(other.getDepth());
}
/**
* This means that we are fully convinced of the trustworthiness of the key.
*
* @return builder
*/
public static Builder fullyTrusted() {
return new Builder(THRESHOLD_FULLY_CONVINCED);
}
/**
* This means that we are marginally (partially) convinced of the trustworthiness of the key.
*
* @return builder
*/
public static Builder marginallyTrusted() {
return new Builder(MARGINALLY_CONVINCED);
}
/**
* This means that we do not trust the key.
* Can be used to overwrite previous trust.
*
* @return builder
*/
public static Builder untrusted() {
return new Builder(NOT_TRUSTED);
}
public static final class Builder {
private final int amount;
private Builder(int amount) {
this.amount = amount;
}
/**
* The key is a trusted introducer (depth 1).
* Certifications made by this key are considered trustworthy.
*
* @return trust
*/
public Trustworthiness introducer() {
return new Trustworthiness(amount, 1);
}
/**
* The key is a meta introducer (depth 2).
* This key can introduce trusted introducers of depth 1.
*
* @return trust
*/
public Trustworthiness metaIntroducer() {
return new Trustworthiness(amount, 2);
}
/**
* The key is a meta introducer of depth <pre>n</pre>.
* This key can introduce meta introducers of depth <pre>n - 1</pre>.
*
* @param n depth
* @return trust
*/
public Trustworthiness metaIntroducerOfDepth(int n) {
return new Trustworthiness(amount, n);
}
}
private static int capAmount(int amount) {
if (amount < 0 || amount > 255) {
throw new IllegalArgumentException("Trust amount MUST be a value between 0 and 255");
}
return amount;
}
private static int capDepth(int depth) {
if (depth < 0 || depth > 255) {
throw new IllegalArgumentException("Trust depth MUST be a value between 0 and 255");
}
return depth;
}
}

View file

@ -1,70 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm.negotiation;
import java.util.Set;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.policy.Policy;
/**
* Interface for a class that negotiates {@link HashAlgorithm HashAlgorithms}.
*
* You can provide your own implementation using custom logic by implementing the
* {@link #negotiateHashAlgorithm(Set)} method.
*/
public interface HashAlgorithmNegotiator {
/**
* Pick one {@link HashAlgorithm} from the ordered set of acceptable algorithms.
*
* @param orderedHashAlgorithmPreferencesSet hash algorithm preferences
* @return picked algorithms
*/
HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> orderedHashAlgorithmPreferencesSet);
/**
* Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} used for non-revocation signatures
* based on the given {@link Policy}.
*
* @param policy algorithm policy
* @return negotiator
*/
static HashAlgorithmNegotiator negotiateSignatureHashAlgorithm(Policy policy) {
return negotiateByPolicy(policy.getSignatureHashAlgorithmPolicy());
}
/**
* Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} used for revocation signatures
* based on the given {@link Policy}.
*
* @param policy algorithm policy
* @return negotiator
*/
static HashAlgorithmNegotiator negotiateRevocationSignatureAlgorithm(Policy policy) {
return negotiateByPolicy(policy.getRevocationSignatureHashAlgorithmPolicy());
}
/**
* Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} based on the given
* {@link Policy.HashAlgorithmPolicy}.
*
* @param hashAlgorithmPolicy algorithm policy for hash algorithms
* @return negotiator
*/
static HashAlgorithmNegotiator negotiateByPolicy(Policy.HashAlgorithmPolicy hashAlgorithmPolicy) {
return new HashAlgorithmNegotiator() {
@Override
public HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> orderedPreferencesSet) {
for (HashAlgorithm preference : orderedPreferencesSet) {
if (hashAlgorithmPolicy.isAcceptable(preference)) {
return preference;
}
}
return hashAlgorithmPolicy.defaultHashAlgorithm();
}
};
}
}

View file

@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm.negotiation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.policy.Policy;
/**
* Interface for symmetric key algorithm negotiation.
*/
public interface SymmetricKeyAlgorithmNegotiator {
/**
* Negotiate a symmetric encryption algorithm.
* If the override is non-null, it will be returned instead of performing an actual negotiation.
* Otherwise, the list of ordered sets containing the preferences of different recipient keys will be
* used to determine a suitable symmetric encryption algorithm.
*
* @param policy algorithm policy
* @param override algorithm override (if not null, return this)
* @param keyPreferences list of preferences per key
* @return negotiated algorithm
*/
SymmetricKeyAlgorithm negotiate(
Policy.SymmetricKeyAlgorithmPolicy policy,
SymmetricKeyAlgorithm override,
List<Set<SymmetricKeyAlgorithm>> keyPreferences);
/**
* Return an instance that negotiates a {@link SymmetricKeyAlgorithm} by selecting the most popular acceptable
* algorithm from the list of preferences.
*
* This negotiator has the best chances to select an algorithm which is understood by all recipients.
*
* @return negotiator that selects by popularity
*/
static SymmetricKeyAlgorithmNegotiator byPopularity() {
return new SymmetricKeyAlgorithmNegotiator() {
@Override
public SymmetricKeyAlgorithm negotiate(
Policy.SymmetricKeyAlgorithmPolicy policy,
SymmetricKeyAlgorithm override,
List<Set<SymmetricKeyAlgorithm>> preferences) {
if (override == SymmetricKeyAlgorithm.NULL) {
throw new IllegalArgumentException("Algorithm override cannot be NULL (plaintext).");
}
if (override != null) {
return override;
}
// Count score (occurrences) of each algorithm
Map<SymmetricKeyAlgorithm, Integer> supportWeight = new LinkedHashMap<>();
for (Set<SymmetricKeyAlgorithm> keyPreferences : preferences) {
for (SymmetricKeyAlgorithm preferred : keyPreferences) {
if (supportWeight.containsKey(preferred)) {
supportWeight.put(preferred, supportWeight.get(preferred) + 1);
} else {
supportWeight.put(preferred, 1);
}
}
}
// Pivot the score map
Map<Integer, List<SymmetricKeyAlgorithm>> byScore = new HashMap<>();
for (SymmetricKeyAlgorithm algorithm : supportWeight.keySet()) {
int score = supportWeight.get(algorithm);
List<SymmetricKeyAlgorithm> withSameScore = byScore.get(score);
if (withSameScore == null) {
withSameScore = new ArrayList<>();
byScore.put(score, withSameScore);
}
withSameScore.add(algorithm);
}
List<Integer> scores = new ArrayList<>(byScore.keySet());
// Sort map and iterate from highest to lowest score
Collections.sort(scores);
for (int i = scores.size() - 1; i >= 0; i--) {
int score = scores.get(i);
List<SymmetricKeyAlgorithm> withSameScore = byScore.get(score);
// Select best algorithm
SymmetricKeyAlgorithm best = policy.selectBest(withSameScore);
if (best != null) {
return best;
}
}
// If no algorithm is acceptable, choose fallback
return policy.getDefaultSymmetricKeyAlgorithm();
}
};
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes related to algorithm negotiation.
*/
package org.pgpainless.algorithm.negotiation;

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Enums which map to OpenPGP's algorithm IDs.
*/
package org.pgpainless.algorithm;

View file

@ -1,139 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.authentication;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
public class CertificateAuthenticity {
private final String userId;
private final PGPPublicKeyRing certificate;
private final Map<CertificationChain, Integer> certificationChains = new HashMap<>();
private final int targetAmount;
public CertificateAuthenticity(@Nonnull PGPPublicKeyRing certificate,
@Nonnull String userId,
@Nonnull Map<CertificationChain, Integer> certificationChains,
int targetAmount) {
this.userId = userId;
this.certificate = certificate;
this.certificationChains.putAll(certificationChains);
this.targetAmount = targetAmount;
}
@Nonnull
public String getUserId() {
return userId;
}
@Nonnull
public PGPPublicKeyRing getCertificate() {
return certificate;
}
public int getTotalTrustAmount() {
int total = 0;
for (int v : certificationChains.values()) {
total += v;
}
return total;
}
/**
* Return the degree of authentication of the binding in percent.
* 100% means full authentication.
* Values smaller than 100% mean partial authentication.
*
* @return authenticity in percent
*/
public int getAuthenticityPercentage() {
return targetAmount * 100 / getTotalTrustAmount();
}
/**
* Return true, if the binding is authenticated to a sufficient degree.
*
* @return true if total gathered evidence outweighs the target trust amount.
*/
public boolean isAuthenticated() {
return targetAmount <= getTotalTrustAmount();
}
/**
* Return a map of {@link CertificationChain CertificationChains} and their respective effective trust amount.
* The effective trust amount of a path might be smaller than its actual trust amount, for example if nodes of a
* path are used multiple times.
*
* @return map of certification chains and their effective trust amounts
*/
@Nonnull
public Map<CertificationChain, Integer> getCertificationChains() {
return Collections.unmodifiableMap(certificationChains);
}
public static class CertificationChain {
private final int trustAmount;
private final List<ChainLink> chainLinks = new ArrayList<>();
/**
* A chain of certifications.
*
* @param trustAmount actual trust amount of the chain
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
*/
public CertificationChain(int trustAmount, @Nonnull List<ChainLink> chainLinks) {
this.trustAmount = trustAmount;
this.chainLinks.addAll(chainLinks);
}
/**
* Actual trust amount of the certification chain.
* @return trust amount
*/
public int getTrustAmount() {
return trustAmount;
}
/**
* Return all links in the chain, starting at the trust-root and ending at the target.
* @return chain links
*/
@Nonnull
public List<ChainLink> getChainLinks() {
return Collections.unmodifiableList(chainLinks);
}
}
/**
* A chain link contains a node in the trust chain.
*/
public static class ChainLink {
private final PGPPublicKeyRing certificate;
/**
* Create a chain link.
* @param certificate node in the trust chain
*/
public ChainLink(@Nonnull PGPPublicKeyRing certificate) {
this.certificate = certificate;
}
/**
* Return the certificate that belongs to the node.
* @return certificate
*/
@Nonnull
public PGPPublicKeyRing getCertificate() {
return certificate;
}
}
}

View file

@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.authentication;
import org.pgpainless.key.OpenPgpFingerprint;
import javax.annotation.Nonnull;
import java.util.Date;
import java.util.List;
/**
* Interface for a CA that can authenticate trust-worthy certificates.
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
*
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
*/
public interface CertificateAuthority {
/**
* Determine the authenticity of the binding between the given fingerprint and the userId.
* In other words, determine, how much evidence can be gathered, that the certificate with the given
* fingerprint really belongs to the user with the given userId.
*
* @param fingerprint fingerprint of the certificate
* @param userId userId
* @param email if true, the userId will be treated as an email address and all user-IDs containing
* the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @return information about the authenticity of the binding
*/
CertificateAuthenticity authenticateBinding(@Nonnull OpenPgpFingerprint fingerprint,
@Nonnull String userId,
boolean email,
@Nonnull Date referenceTime,
int targetAmount);
/**
* Lookup certificates, which carry a trustworthy binding to the given userId.
*
* @param userId userId
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing
* the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @return list of identified bindings
*/
List<CertificateAuthenticity> lookupByUserId(@Nonnull String userId,
boolean email,
@Nonnull Date referenceTime,
int targetAmount);
/**
* Identify trustworthy bindings for a certificate.
* The result is a list of authenticatable userIds on the certificate.
*
* @param fingerprint fingerprint of the certificate
* @param referenceTime reference time for trust calculations
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @return list of identified bindings
*/
List<CertificateAuthenticity> identifyByFingerprint(@Nonnull OpenPgpFingerprint fingerprint,
@Nonnull Date referenceTime,
int targetAmount);
}

View file

@ -1,508 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.SessionKey;
/**
* Options for decryption and signature verification.
*/
public class ConsumerOptions {
private boolean ignoreMDCErrors = false;
private boolean forceNonOpenPgpData = false;
private Date verifyNotBefore = null;
private Date verifyNotAfter = new Date();
private final CertificateSource certificates = new CertificateSource();
private final Set<PGPSignature> detachedSignatures = new HashSet<>();
private MissingPublicKeyCallback missingCertificateCallback = null;
// Session key for decryption without passphrase/key
private SessionKey sessionKey = null;
private final Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> customPublicKeyDataDecryptorFactories =
new HashMap<>();
private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE;
private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy();
public static ConsumerOptions get() {
return new ConsumerOptions();
}
/**
* Consider signatures on the message made before the given timestamp invalid.
* Null means no limitation.
*
* @param timestamp timestamp
* @return options
*/
public ConsumerOptions verifyNotBefore(Date timestamp) {
this.verifyNotBefore = timestamp;
return this;
}
/**
* Return the earliest creation date on which signatures on the message are considered valid.
* Signatures made earlier than this date are considered invalid.
*
* @return earliest allowed signature creation date or null
*/
public @Nullable Date getVerifyNotBefore() {
return verifyNotBefore;
}
/**
* Consider signatures on the message made after the given timestamp invalid.
* Null means no limitation.
*
* @param timestamp timestamp
* @return options
*/
public ConsumerOptions verifyNotAfter(Date timestamp) {
this.verifyNotAfter = timestamp;
return this;
}
/**
* Return the latest possible creation date on which signatures made on the message are considered valid.
* Signatures made later than this date are considered invalid.
*
* @return Latest possible creation date or null.
*/
public Date getVerifyNotAfter() {
return verifyNotAfter;
}
/**
* Add a certificate (public key ring) for signature verification.
*
* @param verificationCert certificate for signature verification
* @return options
*/
public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) {
this.certificates.addCertificate(verificationCert);
return this;
}
/**
* Add a set of certificates (public key rings) for signature verification.
*
* @param verificationCerts certificates for signature verification
* @return options
*/
public ConsumerOptions addVerificationCerts(PGPPublicKeyRingCollection verificationCerts) {
for (PGPPublicKeyRing certificate : verificationCerts) {
addVerificationCert(certificate);
}
return this;
}
/**
* Add some detached signatures from the given {@link InputStream} for verification.
*
* @param signatureInputStream input stream of detached signatures
* @return options
*
* @throws IOException in case of an IO error
* @throws PGPException in case of an OpenPGP error
*/
public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream)
throws IOException, PGPException {
List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream);
return addVerificationOfDetachedSignatures(signatures);
}
/**
* Add some detached signatures for verification.
*
* @param detachedSignatures detached signatures
* @return options
*/
public ConsumerOptions addVerificationOfDetachedSignatures(List<PGPSignature> detachedSignatures) {
for (PGPSignature signature : detachedSignatures) {
addVerificationOfDetachedSignature(signature);
}
return this;
}
/**
* Add a detached signature for the signature verification process.
*
* @param detachedSignature detached signature
* @return options
*/
public ConsumerOptions addVerificationOfDetachedSignature(PGPSignature detachedSignature) {
detachedSignatures.add(detachedSignature);
return this;
}
/**
* Set a callback that's used when a certificate (public key) is missing for signature verification.
*
* @param callback callback
* @return options
*/
public ConsumerOptions setMissingCertificateCallback(MissingPublicKeyCallback callback) {
this.missingCertificateCallback = callback;
return this;
}
/**
* Attempt decryption using a session key.
*
* Note: PGPainless does not yet support decryption with session keys.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a>
*
* @param sessionKey session key
* @return options
*/
public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) {
this.sessionKey = sessionKey;
return this;
}
/**
* Return the session key.
*
* @return session key or null
*/
public @Nullable SessionKey getSessionKey() {
return sessionKey;
}
/**
* Add a key for message decryption.
* The key is expected to be unencrypted.
*
* @param key unencrypted key
* @return options
*/
public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key) {
return addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys());
}
/**
* Add a key for message decryption. If the key is encrypted, the {@link SecretKeyRingProtector}
* is used to decrypt it when needed.
*
* @param key key
* @param keyRingProtector protector for the secret key
* @return options
*/
public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key,
@Nonnull SecretKeyRingProtector keyRingProtector) {
decryptionKeys.put(key, keyRingProtector);
return this;
}
/**
* Add the keys in the provided key collection for message decryption.
*
* @param keys key collection
* @param keyRingProtector protector for encrypted secret keys
* @return options
*/
public ConsumerOptions addDecryptionKeys(@Nonnull PGPSecretKeyRingCollection keys,
@Nonnull SecretKeyRingProtector keyRingProtector) {
for (PGPSecretKeyRing key : keys) {
addDecryptionKey(key, keyRingProtector);
}
return this;
}
/**
* Add a passphrase for message decryption.
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.7">Symmetrically Encrypted Data Packet</a>
*
* @param passphrase passphrase
* @return options
*/
public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) {
decryptionPassphrases.add(passphrase);
return this;
}
/**
* Add a custom {@link PublicKeyDataDecryptorFactory} which enable decryption of messages, e.g. using
* hardware-backed secret keys.
* (See e.g. {@link org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory}).
*
* @param factory decryptor factory
* @return options
*/
public ConsumerOptions addCustomDecryptorFactory(@Nonnull CustomPublicKeyDataDecryptorFactory factory) {
this.customPublicKeyDataDecryptorFactories.put(factory.getSubkeyIdentifier(), factory);
return this;
}
/**
* Return the custom {@link PublicKeyDataDecryptorFactory PublicKeyDataDecryptorFactories} that were
* set by the user.
* These factories can be used to decrypt session keys using a custom logic.
*
* @return custom decryptor factories
*/
Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> getCustomDecryptorFactories() {
return new HashMap<>(customPublicKeyDataDecryptorFactories);
}
/**
* Return the set of available decryption keys.
*
* @return decryption keys
*/
public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
return Collections.unmodifiableSet(decryptionKeys.keySet());
}
/**
* Return the set of available message decryption passphrases.
*
* @return decryption passphrases
*/
public @Nonnull Set<Passphrase> getDecryptionPassphrases() {
return Collections.unmodifiableSet(decryptionPassphrases);
}
/**
* Return the explicitly set verification certificates.
*
* @deprecated use {@link #getCertificateSource()} instead.
* @return verification certs
*/
@Deprecated
public @Nonnull Set<PGPPublicKeyRing> getCertificates() {
return certificates.getExplicitCertificates();
}
/**
* Return an object holding available certificates for signature verification.
*
* @return certificate source
*/
public @Nonnull CertificateSource getCertificateSource() {
return certificates;
}
/**
* Return the callback that gets called when a certificate for signature verification is missing.
* This method might return <pre>null</pre> if the users hasn't set a callback.
*
* @return missing public key callback
*/
public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() {
return missingCertificateCallback;
}
/**
* Return the {@link SecretKeyRingProtector} for the given {@link PGPSecretKeyRing}.
*
* @param decryptionKeyRing secret key
* @return protector for that particular secret key
*/
public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
return decryptionKeys.get(decryptionKeyRing);
}
/**
* Return the set of detached signatures the user provided.
*
* @return detached signatures
*/
public @Nonnull Set<PGPSignature> getDetachedSignatures() {
return Collections.unmodifiableSet(detachedSignatures);
}
/**
* By default, PGPainless will require encrypted messages to make use of SEIP data packets.
* Those are Symmetrically Encrypted Integrity Protected Data packets.
* Symmetrically Encrypted Data Packets without integrity protection are rejected by default.
* Furthermore, PGPainless will throw an exception if verification of the MDC error detection
* code of the SEIP packet fails.
*
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an
* attack or data corruption.
*
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data
* without integrity protection.
* If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will
* <ul>
* <li>not throw exceptions for SEIP packets with tampered ciphertext</li>
* <li>not throw exceptions for SEIP packets with tampered MDC</li>
* <li>not throw exceptions for MDCs with bad CTB</li>
* <li>not throw exceptions for MDCs with bad length</li>
* </ul>
*
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.13">
* Sym. Encrypted Integrity Protected Data Packet</a>
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
* @return options
*/
@Deprecated
public ConsumerOptions setIgnoreMDCErrors(boolean ignoreMDCErrors) {
this.ignoreMDCErrors = ignoreMDCErrors;
return this;
}
/**
* Return true, if PGPainless is ignoring MDC errors.
*
* @return ignore mdc errors
*/
boolean isIgnoreMDCErrors() {
return ignoreMDCErrors;
}
/**
* Force PGPainless to handle the data provided by the {@link InputStream} as non-OpenPGP data.
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data.
*
* @return options
*/
public ConsumerOptions forceNonOpenPgpData() {
this.forceNonOpenPgpData = true;
return this;
}
/**
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
*
* @return true if non-OpenPGP data is forced
*/
boolean isForceNonOpenPgpData() {
return forceNonOpenPgpData;
}
/**
* Specify the {@link MissingKeyPassphraseStrategy}.
* This strategy defines, how missing passphrases for unlocking secret keys are handled.
* In interactive mode ({@link MissingKeyPassphraseStrategy#INTERACTIVE}) PGPainless will try to obtain missing
* passphrases for secret keys via the {@link SecretKeyRingProtector SecretKeyRingProtectors}
* {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider} callback.
*
* In non-interactice mode ({@link MissingKeyPassphraseStrategy#THROW_EXCEPTION}, PGPainless will instead
* throw a {@link org.pgpainless.exception.MissingPassphraseException} containing the ids of all keys for which
* there are missing passphrases.
*
* @param strategy strategy
* @return options
*/
public ConsumerOptions setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy strategy) {
this.missingKeyPassphraseStrategy = strategy;
return this;
}
/**
* Return the currently configured {@link MissingKeyPassphraseStrategy}.
*
* @return missing key passphrase strategy
*/
MissingKeyPassphraseStrategy getMissingKeyPassphraseStrategy() {
return missingKeyPassphraseStrategy;
}
/**
* Set a custom multi-pass strategy for processing cleartext-signed messages.
* Uses {@link InMemoryMultiPassStrategy} by default.
*
* @param multiPassStrategy multi-pass caching strategy
* @return builder
*/
public ConsumerOptions setMultiPassStrategy(@Nonnull MultiPassStrategy multiPassStrategy) {
this.multiPassStrategy = multiPassStrategy;
return this;
}
/**
* Return the currently configured {@link MultiPassStrategy}.
* Defaults to {@link InMemoryMultiPassStrategy}.
*
* @return multi-pass strategy
*/
public MultiPassStrategy getMultiPassStrategy() {
return multiPassStrategy;
}
/**
* Source for OpenPGP certificates.
* When verifying signatures on a message, this object holds available signer certificates.
*/
public static class CertificateSource {
private Set<PGPPublicKeyRing> explicitCertificates = new HashSet<>();
/**
* Add a certificate as verification cert explicitly.
*
* @param certificate certificate
*/
public void addCertificate(PGPPublicKeyRing certificate) {
this.explicitCertificates.add(certificate);
}
/**
* Return the set of explicitly set verification certificates.
* @return explicitly set verification certs
*/
public Set<PGPPublicKeyRing> getExplicitCertificates() {
return Collections.unmodifiableSet(explicitCertificates);
}
/**
* Return a certificate which contains a subkey with the given keyId.
* This method first checks all explicitly set verification certs and if no cert is found it consults
* the certificate stores.
*
* @param keyId key id
* @return certificate
*/
public PGPPublicKeyRing getCertificate(long keyId) {
for (PGPPublicKeyRing cert : explicitCertificates) {
if (cert.getPublicKey(keyId) != null) {
return cert;
}
}
return null;
}
}
}

View file

@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.pgpainless.key.SubkeyIdentifier;
/**
* Custom {@link PublicKeyDataDecryptorFactory} which can enable customized implementations of message decryption
* using public keys.
* This class can for example be used to implement message encryption using hardware tokens like smartcards or
* TPMs.
* @see ConsumerOptions#addCustomDecryptorFactory(CustomPublicKeyDataDecryptorFactory)
*/
public interface CustomPublicKeyDataDecryptorFactory extends PublicKeyDataDecryptorFactory {
/**
* Return the {@link SubkeyIdentifier} for which this particular {@link CustomPublicKeyDataDecryptorFactory}
* is intended.
*
* @return subkey identifier
*/
SubkeyIdentifier getSubkeyIdentifier();
}

View file

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
/**
* Builder class that takes an {@link InputStream} of ciphertext (or plaintext signed data)
* and combines it with a configured {@link ConsumerOptions} object to form a {@link DecryptionStream} which
* can be used to decrypt an OpenPGP message or verify signatures.
*/
public class DecryptionBuilder implements DecryptionBuilderInterface {
@Override
public DecryptWith onInputStream(@Nonnull InputStream inputStream) {
return new DecryptWithImpl(inputStream);
}
static class DecryptWithImpl implements DecryptWith {
private final InputStream inputStream;
DecryptWithImpl(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException {
if (consumerOptions == null) {
throw new IllegalArgumentException("Consumer options cannot be null.");
}
return OpenPgpMessageInputStream.create(inputStream, consumerOptions);
}
}
}

View file

@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
public interface DecryptionBuilderInterface {
/**
* Create a {@link DecryptionStream} on an {@link InputStream} which contains the encrypted and/or signed data.
*
* @param inputStream encrypted and/or signed data.
* @return api handle
*/
DecryptWith onInputStream(@Nonnull InputStream inputStream);
interface DecryptWith {
/**
* Add options for decryption / signature verification, such as keys, passphrases etc.
*
* @param consumerOptions consumer options
* @return decryption stream
* @throws PGPException in case of an OpenPGP related error
* @throws IOException in case of an IO error
*/
DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException;
}
}

View file

@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.InputStream;
/**
* Abstract definition of an {@link InputStream} which can be used to decrypt / verify OpenPGP messages.
*/
public abstract class DecryptionStream extends InputStream {
/**
* Return {@link MessageMetadata metadata} about the decrypted / verified message.
* The {@link DecryptionStream} MUST be closed via {@link #close()} before the metadata object can be accessed.
*
* @return message metadata
*/
public abstract MessageMetadata getMetadata();
/**
* Return a {@link OpenPgpMetadata} object containing information about the decrypted / verified message.
* The {@link DecryptionStream} MUST be closed via {@link #close()} before the metadata object can be accessed.
*
* @return message metadata
* @deprecated use {@link #getMetadata()} instead.
*/
@Deprecated
public OpenPgpMetadata getResult() {
return getMetadata().toLegacyMetadata();
}
}

View file

@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.bouncycastle.bcpg.AEADEncDataPacket;
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.pgpainless.key.SubkeyIdentifier;
/**
* Enable integration of hardware-backed OpenPGP keys.
*/
public class HardwareSecurity {
public interface DecryptionCallback {
/**
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with
* hardware security modules such as smartcards or TPMs.
*
* If decryption fails for some reason, a subclass of the {@link HardwareSecurityException} is thrown.
*
* @param keyId id of the key
* @param keyAlgorithm algorithm
* @param sessionKeyData encrypted session key
*
* @return decrypted session key
* @throws HardwareSecurityException exception
*/
byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData)
throws HardwareSecurityException;
}
/**
* Implementation of {@link PublicKeyDataDecryptorFactory} which delegates decryption of encrypted session keys
* to a {@link DecryptionCallback}.
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs.
*/
public static class HardwareDataDecryptorFactory implements CustomPublicKeyDataDecryptorFactory {
private final DecryptionCallback callback;
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
private final PublicKeyDataDecryptorFactory factory =
new BcPublicKeyDataDecryptorFactory(null);
private final SubkeyIdentifier subkey;
/**
* Create a new {@link HardwareDataDecryptorFactory}.
*
* @param subkeyIdentifier identifier of the decryption subkey
* @param callback decryption callback
*/
public HardwareDataDecryptorFactory(SubkeyIdentifier subkeyIdentifier, DecryptionCallback callback) {
this.callback = callback;
this.subkey = subkeyIdentifier;
}
@Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
throws PGPException {
try {
// delegate decryption to the callback
return callback.decryptSessionKey(subkey.getSubkeyId(), keyAlgorithm, secKeyData[0]);
} catch (HardwareSecurityException e) {
throw new PGPException("Hardware-backed decryption failed.", e);
}
}
@Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException {
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
}
@Override
public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException {
return factory.createDataDecryptor(aeadEncDataPacket, sessionKey);
}
@Override
public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
throws PGPException {
return factory.createDataDecryptor(seipd, sessionKey);
}
@Override
public SubkeyIdentifier getSubkeyIdentifier() {
return subkey;
}
}
public static class HardwareSecurityException
extends Exception {
}
}

View file

@ -1,60 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.pgpainless.exception.ModificationDetectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IntegrityProtectedInputStream extends InputStream {
private static final Logger LOGGER = LoggerFactory.getLogger(IntegrityProtectedInputStream.class);
private final InputStream inputStream;
private final PGPEncryptedData encryptedData;
private final ConsumerOptions options;
private boolean closed = false;
public IntegrityProtectedInputStream(InputStream inputStream, PGPEncryptedData encryptedData, ConsumerOptions options) {
this.inputStream = inputStream;
this.encryptedData = encryptedData;
this.options = options;
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(@Nonnull byte[] b, int offset, int length) throws IOException {
return inputStream.read(b, offset, length);
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
if (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
try {
if (!encryptedData.verify()) {
throw new ModificationDetectionException();
}
LOGGER.debug("Integrity Protection check passed");
} catch (PGPException e) {
throw new IOException("Data appears to not be integrity protected.", e);
}
}
}
}

View file

@ -1,146 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPUtil;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.util.ArmorUtils;
/**
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected.
*/
public final class MessageInspector {
public static class EncryptionInfo {
private final List<Long> keyIds = new ArrayList<>();
private boolean isPassphraseEncrypted = false;
private boolean isSignedOnly = false;
/**
* Return a list of recipient key ids for whom the message is encrypted.
* @return recipient key ids
*/
public List<Long> getKeyIds() {
return Collections.unmodifiableList(keyIds);
}
public boolean isPassphraseEncrypted() {
return isPassphraseEncrypted;
}
/**
* Return true, if the message is encrypted.
*
* @return true if encrypted
*/
public boolean isEncrypted() {
return isPassphraseEncrypted || !keyIds.isEmpty();
}
/**
* Return true, if the message is not encrypted, but signed using {@link org.bouncycastle.openpgp.PGPOnePassSignature OnePassSignatures}.
*
* @return true if message is signed only
*/
public boolean isSignedOnly() {
return isSignedOnly;
}
}
private MessageInspector() {
}
/**
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
*
* @param message OpenPGP message
* @return encryption info
*
* @throws PGPException in case the message is broken
* @throws IOException in case of an IO error
*/
public static EncryptionInfo determineEncryptionInfoForMessage(String message) throws PGPException, IOException {
@SuppressWarnings("CharsetObjectCanBeUsed")
Charset charset = Charset.forName("UTF-8");
return determineEncryptionInfoForMessage(new ByteArrayInputStream(message.getBytes(charset)));
}
/**
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
* Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves.
*
* @param dataIn openpgp message
* @return encryption information
*
* @throws IOException in case of an IO error
* @throws PGPException if the message is broken
*/
public static EncryptionInfo determineEncryptionInfoForMessage(InputStream dataIn) throws IOException, PGPException {
InputStream decoded = ArmorUtils.getDecoderStream(dataIn);
EncryptionInfo info = new EncryptionInfo();
processMessage(decoded, info);
return info;
}
private static void processMessage(InputStream dataIn, EncryptionInfo info) throws PGPException, IOException {
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(dataIn);
Object next;
while ((next = objectFactory.nextObject()) != null) {
if (next instanceof PGPOnePassSignatureList) {
PGPOnePassSignatureList signatures = (PGPOnePassSignatureList) next;
if (!signatures.isEmpty()) {
info.isSignedOnly = true;
return;
}
}
if (next instanceof PGPEncryptedDataList) {
PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next;
for (PGPEncryptedData encryptedData : encryptedDataList) {
if (encryptedData instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData pubKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
info.keyIds.add(pubKeyEncryptedData.getKeyID());
} else if (encryptedData instanceof PGPPBEEncryptedData) {
info.isPassphraseEncrypted = true;
}
}
// Data is encrypted, we cannot go deeper
return;
}
if (next instanceof PGPCompressedData) {
PGPCompressedData compressed = (PGPCompressedData) next;
InputStream decompressed = compressed.getDataStream();
InputStream decoded = PGPUtil.getDecoderStream(decompressed);
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoded);
}
if (next instanceof PGPLiteralData) {
return;
}
}
}
}

View file

@ -1,858 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.authentication.CertificateAuthenticity;
import org.pgpainless.authentication.CertificateAuthority;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.SessionKey;
/**
* View for extracting metadata about a {@link Message}.
*/
public class MessageMetadata {
protected Message message;
public MessageMetadata(@Nonnull Message message) {
this.message = message;
}
/**
* Convert this {@link MessageMetadata} object into a legacy {@link OpenPgpMetadata} object.
* This method is intended to be used for a transition period between the 1.3 / 1.4+ branches.
* TODO: Remove in 1.6.X
*
* @return converted {@link OpenPgpMetadata} object
*/
public @Nonnull OpenPgpMetadata toLegacyMetadata() {
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm());
resultBuilder.setModificationDate(getModificationDate());
resultBuilder.setFileName(getFilename());
resultBuilder.setFileEncoding(getLiteralDataEncoding());
resultBuilder.setSessionKey(getSessionKey());
resultBuilder.setDecryptionKey(getDecryptionKey());
for (SignatureVerification accepted : getVerifiedDetachedSignatures()) {
resultBuilder.addVerifiedDetachedSignature(accepted);
}
for (SignatureVerification.Failure rejected : getRejectedDetachedSignatures()) {
resultBuilder.addInvalidDetachedSignature(rejected.getSignatureVerification(), rejected.getValidationException());
}
for (SignatureVerification accepted : getVerifiedInlineSignatures()) {
resultBuilder.addVerifiedInbandSignature(accepted);
}
for (SignatureVerification.Failure rejected : getRejectedInlineSignatures()) {
resultBuilder.addInvalidInbandSignature(rejected.getSignatureVerification(), rejected.getValidationException());
}
if (message.isCleartextSigned()) {
resultBuilder.setCleartextSigned();
}
return resultBuilder.build();
}
public boolean isUsingCleartextSignatureFramework() {
return message.isCleartextSigned();
}
public boolean isEncrypted() {
SymmetricKeyAlgorithm algorithm = getEncryptionAlgorithm();
return algorithm != null && algorithm != SymmetricKeyAlgorithm.NULL;
}
public boolean isEncryptedFor(@Nonnull PGPKeyRing keys) {
Iterator<EncryptedData> encryptionLayers = getEncryptionLayers();
while (encryptionLayers.hasNext()) {
EncryptedData encryptedData = encryptionLayers.next();
for (long recipient : encryptedData.getRecipients()) {
PGPPublicKey key = keys.getPublicKey(recipient);
if (key != null) {
return true;
}
}
}
return false;
}
/**
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId.
*
* @param userId userId
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
* @param certificateAuthority certificate authority
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
*/
public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority) {
return isAuthenticatablySignedBy(userId, email, certificateAuthority, 120);
}
/**
* Return true, if the message was verifiably signed by a certificate for which we can authenticate a binding to the given userId.
*
* @param userId userId
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
* @param certificateAuthority certificate authority
* @param targetAmount target trust amount
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
*/
public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority, int targetAmount) {
for (SignatureVerification verification : getVerifiedSignatures()) {
CertificateAuthenticity authenticity = certificateAuthority.authenticateBinding(
verification.getSigningKey().getFingerprint(), userId, email,
verification.getSignature().getCreationTime(), targetAmount);
if (authenticity.isAuthenticated()) {
return true;
}
}
return false;
}
/**
* Return a list containing all recipient keyIDs.
*
* @return list of recipients
*/
public List<Long> getRecipientKeyIds() {
List<Long> keyIds = new ArrayList<>();
Iterator<EncryptedData> encLayers = getEncryptionLayers();
while (encLayers.hasNext()) {
EncryptedData layer = encLayers.next();
keyIds.addAll(layer.getRecipients());
}
return keyIds;
}
public @Nonnull Iterator<EncryptedData> getEncryptionLayers() {
return new LayerIterator<EncryptedData>(message) {
@Override
public boolean matches(Packet layer) {
return layer instanceof EncryptedData;
}
@Override
public EncryptedData getProperty(Layer last) {
return (EncryptedData) last;
}
};
}
/**
* Return the {@link SymmetricKeyAlgorithm} of the outermost encrypted data packet, or null if message is
* unencrypted.
*
* @return encryption algorithm
*/
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
return firstOrNull(getEncryptionAlgorithms());
}
/**
* Return an {@link Iterator} of {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} encountered in the message.
* The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item
* that of the next nested encrypted data packet and so on.
* The iterator might also be empty, in case of an unencrypted message.
*
* @return iterator of symmetric encryption algorithms
*/
public @Nonnull Iterator<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
return map(getEncryptionLayers(), encryptedData -> encryptedData.algorithm);
}
public @Nonnull Iterator<CompressedData> getCompressionLayers() {
return new LayerIterator<CompressedData>(message) {
@Override
boolean matches(Packet layer) {
return layer instanceof CompressedData;
}
@Override
CompressedData getProperty(Layer last) {
return (CompressedData) last;
}
};
}
/**
* Return the {@link CompressionAlgorithm} of the outermost compressed data packet, or null, if the message
* does not contain any compressed data packets.
*
* @return compression algorithm
*/
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
return firstOrNull(getCompressionAlgorithms());
}
/**
* Return an {@link Iterator} of {@link CompressionAlgorithm CompressionAlgorithms} encountered in the message.
* The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next
* item that of the next nested compressed data packet and so on.
* The iterator might also be empty, in case of a message without any compressed data packets.
*
* @return iterator of compression algorithms
*/
public @Nonnull Iterator<CompressionAlgorithm> getCompressionAlgorithms() {
return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm);
}
/**
* Return the {@link SessionKey} of the outermost encrypted data packet.
* If the message was unencrypted, this method returns <pre>null</pre>.
*
* @return session key of the message
*/
public @Nullable SessionKey getSessionKey() {
return firstOrNull(getSessionKeys());
}
/**
* Return an {@link Iterator} of {@link SessionKey SessionKeys} for all encrypted data packets in the message.
* The first item returned by the iterator is the session key of the outermost encrypted data packet,
* the next item that of the next nested encrypted data packet and so on.
* The iterator might also be empty, in case of an unencrypted message.
*
* @return iterator of session keys
*/
public @Nonnull Iterator<SessionKey> getSessionKeys() {
return map(getEncryptionLayers(), encryptedData -> encryptedData.sessionKey);
}
public boolean isVerifiedSignedBy(@Nonnull PGPKeyRing keys) {
return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys);
}
public List<SignatureVerification> getVerifiedSignatures() {
List<SignatureVerification> allVerifiedSignatures = getVerifiedInlineSignatures();
allVerifiedSignatures.addAll(getVerifiedDetachedSignatures());
return allVerifiedSignatures;
}
public boolean isVerifiedDetachedSignedBy(@Nonnull PGPKeyRing keys) {
return containsSignatureBy(getVerifiedDetachedSignatures(), keys);
}
/**
* Return a list of all verified detached signatures.
* This list contains all acceptable, correct detached signatures.
*
* @return verified detached signatures
*/
public @Nonnull List<SignatureVerification> getVerifiedDetachedSignatures() {
return message.getVerifiedDetachedSignatures();
}
/**
* Return a list of all rejected detached signatures.
*
* @return rejected detached signatures
*/
public @Nonnull List<SignatureVerification.Failure> getRejectedDetachedSignatures() {
return message.getRejectedDetachedSignatures();
}
public boolean isVerifiedInlineSignedBy(@Nonnull PGPKeyRing keys) {
return containsSignatureBy(getVerifiedInlineSignatures(), keys);
}
/**
* Return a list of all verified inline-signatures.
* This list contains all acceptable, correct signatures that were part of the message itself.
*
* @return verified inline signatures
*/
public @Nonnull List<SignatureVerification> getVerifiedInlineSignatures() {
List<SignatureVerification> verifications = new ArrayList<>();
Iterator<List<SignatureVerification>> verificationsByLayer = getVerifiedInlineSignaturesByLayer();
while (verificationsByLayer.hasNext()) {
verifications.addAll(verificationsByLayer.next());
}
return verifications;
}
/**
* Return an {@link Iterator} of {@link List Lists} of verified inline-signatures of the message.
* Since signatures might occur in different layers within a message, this method can be used to gain more detailed
* insights into what signatures were encountered at what layers of the message structure.
* Each item of the {@link Iterator} represents a layer of the message and contains only signatures from
* this layer.
* An empty list means no (or no acceptable) signatures were encountered in that layer.
*
* @return iterator of lists of signatures by-layer.
*/
public @Nonnull Iterator<List<SignatureVerification>> getVerifiedInlineSignaturesByLayer() {
return new LayerIterator<List<SignatureVerification>>(message) {
@Override
boolean matches(Packet layer) {
return layer instanceof Layer;
}
@Override
List<SignatureVerification> getProperty(Layer last) {
List<SignatureVerification> list = new ArrayList<>();
list.addAll(last.getVerifiedOnePassSignatures());
list.addAll(last.getVerifiedPrependedSignatures());
return list;
}
};
}
/**
* Return a list of all rejected inline-signatures of the message.
*
* @return list of rejected inline-signatures
*/
public @Nonnull List<SignatureVerification.Failure> getRejectedInlineSignatures() {
List<SignatureVerification.Failure> rejected = new ArrayList<>();
Iterator<List<SignatureVerification.Failure>> rejectedByLayer = getRejectedInlineSignaturesByLayer();
while (rejectedByLayer.hasNext()) {
rejected.addAll(rejectedByLayer.next());
}
return rejected;
}
/**
* Similar to {@link #getVerifiedInlineSignaturesByLayer()}, this method returns all rejected inline-signatures
* of the message, but organized by layer.
*
* @return rejected inline-signatures by-layer
*/
public @Nonnull Iterator<List<SignatureVerification.Failure>> getRejectedInlineSignaturesByLayer() {
return new LayerIterator<List<SignatureVerification.Failure>>(message) {
@Override
boolean matches(Packet layer) {
return layer instanceof Layer;
}
@Override
List<SignatureVerification.Failure> getProperty(Layer last) {
List<SignatureVerification.Failure> list = new ArrayList<>();
list.addAll(last.getRejectedOnePassSignatures());
list.addAll(last.getRejectedPrependedSignatures());
return list;
}
};
}
private static boolean containsSignatureBy(@Nonnull List<SignatureVerification> verifications,
@Nonnull PGPKeyRing keys) {
for (SignatureVerification verification : verifications) {
SubkeyIdentifier issuer = verification.getSigningKey();
if (issuer == null) {
// No issuer, shouldn't happen, but better be safe and skip...
continue;
}
if (keys.getPublicKey().getKeyID() != issuer.getPrimaryKeyId()) {
// Wrong cert
continue;
}
if (keys.getPublicKey(issuer.getSubkeyId()) != null) {
// Matching cert and signing key
return true;
}
}
return false;
}
/**
* Return the value of the literal data packet's filename field.
* This value can be used to store a decrypted file under its original filename,
* but since this field is not necessarily part of the signed data of a message, usage of this field is
* discouraged.
*
* @return filename
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
public @Nullable String getFilename() {
LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getFileName();
}
/**
* Returns true, if the filename of the literal data packet indicates that the data is intended for your eyes only.
*
* @return isForYourEyesOnly
*/
public boolean isForYourEyesOnly() {
return PGPLiteralData.CONSOLE.equals(getFilename());
}
/**
* Return the value of the literal data packets modification date field.
* This value can be used to restore the modification date of a decrypted file,
* but since this field is not necessarily part of the signed data, its use is discouraged.
*
* @return modification date
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
public @Nullable Date getModificationDate() {
LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getModificationDate();
}
/**
* Return the value of the format field of the literal data packet.
* This value indicates what format (text, binary data, ...) the data has.
* Since this field is not necessarily part of the signed data of a message, its usage is discouraged.
*
* @return format
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
public @Nullable StreamEncoding getLiteralDataEncoding() {
LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getFormat();
}
/**
* Find the {@link LiteralData} layer of an OpenPGP message.
* Usually, every message has a literal data packet, but for malformed messages this method might still
* return <pre>null</pre>.
*
* @return literal data
*/
private @Nullable LiteralData findLiteralData() {
Nested nested = message.getChild();
if (nested == null) {
return null;
}
while (nested != null && nested.hasNestedChild()) {
Layer layer = (Layer) nested;
nested = layer.getChild();
}
return (LiteralData) nested;
}
/**
* Return the {@link SubkeyIdentifier} of the decryption key that was used to decrypt the outermost encryption
* layer.
* If the message was unencrypted, this might return <pre>null</pre>.
*
* @return decryption key
*/
public SubkeyIdentifier getDecryptionKey() {
return firstOrNull(map(getEncryptionLayers(), encryptedData -> encryptedData.decryptionKey));
}
public boolean isVerifiedSigned() {
return !getVerifiedSignatures().isEmpty();
}
public interface Packet {
}
public abstract static class Layer implements Packet {
public static final int MAX_LAYER_DEPTH = 16;
protected final int depth;
protected final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
protected final List<SignatureVerification.Failure> rejectedDetachedSignatures = new ArrayList<>();
protected final List<SignatureVerification> verifiedOnePassSignatures = new ArrayList<>();
protected final List<SignatureVerification.Failure> rejectedOnePassSignatures = new ArrayList<>();
protected final List<SignatureVerification> verifiedPrependedSignatures = new ArrayList<>();
protected final List<SignatureVerification.Failure> rejectedPrependedSignatures = new ArrayList<>();
protected Nested child;
public Layer(int depth) {
this.depth = depth;
if (depth > MAX_LAYER_DEPTH) {
throw new MalformedOpenPgpMessageException("Maximum packet nesting depth (" + MAX_LAYER_DEPTH + ") exceeded.");
}
}
/**
* Return the nested child element of this layer.
* Might return <pre>null</pre>, if this layer does not have a child element
* (e.g. if this is a {@link LiteralData} packet).
*
* @return child element
*/
public @Nullable Nested getChild() {
return child;
}
/**
* Set the nested child element for this layer.
*
* @param child child element
*/
void setChild(Nested child) {
this.child = child;
}
/**
* Return a list of all verified detached signatures of this layer.
*
* @return all verified detached signatures of this layer
*/
public List<SignatureVerification> getVerifiedDetachedSignatures() {
return new ArrayList<>(verifiedDetachedSignatures);
}
/**
* Return a list of all rejected detached signatures of this layer.
*
* @return all rejected detached signatures of this layer
*/
public List<SignatureVerification.Failure> getRejectedDetachedSignatures() {
return new ArrayList<>(rejectedDetachedSignatures);
}
/**
* Add a verified detached signature for this layer.
*
* @param signatureVerification verified detached signature
*/
void addVerifiedDetachedSignature(SignatureVerification signatureVerification) {
verifiedDetachedSignatures.add(signatureVerification);
}
/**
* Add a rejected detached signature for this layer.
*
* @param failure rejected detached signature
*/
void addRejectedDetachedSignature(SignatureVerification.Failure failure) {
rejectedDetachedSignatures.add(failure);
}
/**
* Return a list of all verified one-pass-signatures of this layer.
*
* @return all verified one-pass-signatures of this layer
*/
public List<SignatureVerification> getVerifiedOnePassSignatures() {
return new ArrayList<>(verifiedOnePassSignatures);
}
/**
* Return a list of all rejected one-pass-signatures of this layer.
*
* @return all rejected one-pass-signatures of this layer
*/
public List<SignatureVerification.Failure> getRejectedOnePassSignatures() {
return new ArrayList<>(rejectedOnePassSignatures);
}
/**
* Add a verified one-pass-signature for this layer.
*
* @param verifiedOnePassSignature verified one-pass-signature for this layer
*/
void addVerifiedOnePassSignature(SignatureVerification verifiedOnePassSignature) {
this.verifiedOnePassSignatures.add(verifiedOnePassSignature);
}
/**
* Add a rejected one-pass-signature for this layer.
*
* @param rejected rejected one-pass-signature for this layer
*/
void addRejectedOnePassSignature(SignatureVerification.Failure rejected) {
this.rejectedOnePassSignatures.add(rejected);
}
/**
* Return a list of all verified prepended signatures of this layer.
*
* @return all verified prepended signatures of this layer
*/
public List<SignatureVerification> getVerifiedPrependedSignatures() {
return new ArrayList<>(verifiedPrependedSignatures);
}
/**
* Return a list of all rejected prepended signatures of this layer.
*
* @return all rejected prepended signatures of this layer
*/
public List<SignatureVerification.Failure> getRejectedPrependedSignatures() {
return new ArrayList<>(rejectedPrependedSignatures);
}
/**
* Add a verified prepended signature for this layer.
*
* @param verified verified prepended signature
*/
void addVerifiedPrependedSignature(SignatureVerification verified) {
this.verifiedPrependedSignatures.add(verified);
}
/**
* Add a rejected prepended signature for this layer.
*
* @param rejected rejected prepended signature
*/
void addRejectedPrependedSignature(SignatureVerification.Failure rejected) {
this.rejectedPrependedSignatures.add(rejected);
}
}
public interface Nested extends Packet {
boolean hasNestedChild();
}
public static class Message extends Layer {
protected boolean cleartextSigned;
public Message() {
super(0);
}
/**
* Returns true, is the message is a signed message using the cleartext signature framework.
*
* @return <pre>true</pre> if message is cleartext-signed, <pre>false</pre> otherwise
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a>
*/
public boolean isCleartextSigned() {
return cleartextSigned;
}
}
public static class LiteralData implements Nested {
protected String fileName;
protected Date modificationDate;
protected StreamEncoding format;
public LiteralData() {
this("", new Date(0L), StreamEncoding.BINARY);
}
public LiteralData(@Nonnull String fileName,
@Nonnull Date modificationDate,
@Nonnull StreamEncoding format) {
this.fileName = fileName;
this.modificationDate = modificationDate;
this.format = format;
}
/**
* Return the value of the filename field.
* An empty String <pre>""</pre> indicates no filename.
*
* @return filename
*/
public @Nonnull String getFileName() {
return fileName;
}
/**
* Return the value of the modification date field.
* A special date <pre>{@code new Date(0L)}</pre> indicates no modification date.
*
* @return modification date
*/
public @Nonnull Date getModificationDate() {
return modificationDate;
}
/**
* Return the value of the format field.
*
* @return format
*/
public @Nonnull StreamEncoding getFormat() {
return format;
}
@Override
public boolean hasNestedChild() {
// A literal data packet MUST NOT have a child element, as its content is the plaintext
return false;
}
}
public static class CompressedData extends Layer implements Nested {
protected final CompressionAlgorithm algorithm;
public CompressedData(@Nonnull CompressionAlgorithm zip, int depth) {
super(depth);
this.algorithm = zip;
}
/**
* Return the {@link CompressionAlgorithm} used to compress the packet.
* @return compression algorithm
*/
public @Nonnull CompressionAlgorithm getAlgorithm() {
return algorithm;
}
@Override
public boolean hasNestedChild() {
// A compressed data packet MUST have a child element
return true;
}
}
public static class EncryptedData extends Layer implements Nested {
protected final SymmetricKeyAlgorithm algorithm;
protected SubkeyIdentifier decryptionKey;
protected SessionKey sessionKey;
protected List<Long> recipients;
public EncryptedData(@Nonnull SymmetricKeyAlgorithm algorithm, int depth) {
super(depth);
this.algorithm = algorithm;
}
/**
* Return the {@link SymmetricKeyAlgorithm} used to encrypt the packet.
* @return symmetric encryption algorithm
*/
public @Nonnull SymmetricKeyAlgorithm getAlgorithm() {
return algorithm;
}
/**
* Return the {@link SessionKey} used to decrypt the packet.
*
* @return session key
*/
public @Nonnull SessionKey getSessionKey() {
return sessionKey;
}
/**
* Return a list of all recipient key ids to which the packet was encrypted for.
*
* @return recipients
*/
public @Nonnull List<Long> getRecipients() {
if (recipients == null) {
return new ArrayList<>();
}
return new ArrayList<>(recipients);
}
@Override
public boolean hasNestedChild() {
// An encrypted data packet MUST have a child element
return true;
}
}
private abstract static class LayerIterator<O> implements Iterator<O> {
private Nested current;
Layer last = null;
Message parent;
LayerIterator(@Nonnull Message message) {
super();
this.parent = message;
this.current = message.getChild();
if (matches(current)) {
last = (Layer) current;
}
}
@Override
public boolean hasNext() {
if (parent != null && matches(parent)) {
return true;
}
if (last == null) {
findNext();
}
return last != null;
}
@Override
public O next() {
if (parent != null && matches(parent)) {
O property = getProperty(parent);
parent = null;
return property;
}
if (last == null) {
findNext();
}
if (last != null) {
O property = getProperty(last);
last = null;
return property;
}
throw new NoSuchElementException();
}
private void findNext() {
while (current != null && current instanceof Layer) {
current = ((Layer) current).getChild();
if (matches(current)) {
last = (Layer) current;
break;
}
}
}
abstract boolean matches(Packet layer);
abstract O getProperty(Layer last);
}
private static <A,B> Iterator<B> map(Iterator<A> from, Function<A, B> mapping) {
return new Iterator<B>() {
@Override
public boolean hasNext() {
return from.hasNext();
}
@Override
public B next() {
return mapping.apply(from.next());
}
};
}
public interface Function<A, B> {
B apply(A item);
}
private static @Nullable <A> A firstOrNull(Iterator<A> iterator) {
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
private static @Nonnull <A> A firstOr(Iterator<A> iterator, A item) {
if (iterator.hasNext()) {
return iterator.next();
}
return item;
}
}

View file

@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
/**
* Strategy defining how missing secret key passphrases are handled.
*/
public enum MissingKeyPassphraseStrategy {
/**
* Try to interactively obtain key passphrases one-by-one via callbacks,
* eg {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider}.
*/
INTERACTIVE,
/**
* Do not try to obtain passphrases interactively and instead throw a
* {@link org.pgpainless.exception.MissingPassphraseException} listing all keys with missing passphrases.
*/
THROW_EXCEPTION
}

View file

@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
public interface MissingPublicKeyCallback {
/**
* This method gets called if we encounter a signature made by a key which was not provided for signature verification.
* If you cannot provide the requested key, it is safe to return null here.
* PGPainless will then continue verification with the next signature.
*
* Note: The key-id might belong to a subkey, so be aware that when looking up the {@link PGPPublicKeyRing},
* you may not only search for the key-id on the key rings primary key!
*
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures
* only contain the key id.
*
* @param keyId ID of the missing signing (sub)key
*
* @return keyring containing the key or null
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
*/
@Nullable PGPPublicKeyRing onMissingPublicKeyEncountered(@Nonnull Long keyId);
}

View file

@ -1,380 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.SessionKey;
/**
* Legacy class containing metadata about an OpenPGP message.
* It is advised to use {@link MessageMetadata} instead.
*
* TODO: Remove in 1.6.X
*/
public class OpenPgpMetadata {
private final Set<Long> recipientKeyIds;
private final SubkeyIdentifier decryptionKey;
private final List<SignatureVerification> verifiedInbandSignatures;
private final List<SignatureVerification.Failure> invalidInbandSignatures;
private final List<SignatureVerification> verifiedDetachedSignatures;
private final List<SignatureVerification.Failure> invalidDetachedSignatures;
private final SessionKey sessionKey;
private final CompressionAlgorithm compressionAlgorithm;
private final String fileName;
private final Date modificationDate;
private final StreamEncoding fileEncoding;
private final boolean cleartextSigned;
public OpenPgpMetadata(Set<Long> recipientKeyIds,
SubkeyIdentifier decryptionKey,
SessionKey sessionKey,
CompressionAlgorithm algorithm,
List<SignatureVerification> verifiedInbandSignatures,
List<SignatureVerification.Failure> invalidInbandSignatures,
List<SignatureVerification> verifiedDetachedSignatures,
List<SignatureVerification.Failure> invalidDetachedSignatures,
String fileName,
Date modificationDate,
StreamEncoding fileEncoding,
boolean cleartextSigned) {
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
this.decryptionKey = decryptionKey;
this.sessionKey = sessionKey;
this.compressionAlgorithm = algorithm;
this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures);
this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures);
this.verifiedDetachedSignatures = Collections.unmodifiableList(verifiedDetachedSignatures);
this.invalidDetachedSignatures = Collections.unmodifiableList(invalidDetachedSignatures);
this.fileName = fileName;
this.modificationDate = modificationDate;
this.fileEncoding = fileEncoding;
this.cleartextSigned = cleartextSigned;
}
/**
* Return a set of key-ids the messages was encrypted for.
*
* @return recipient ids
*/
public @Nonnull Set<Long> getRecipientKeyIds() {
return recipientKeyIds;
}
/**
* Return true, if the message was encrypted.
*
* @return true if encrypted, false otherwise
*/
public boolean isEncrypted() {
return sessionKey != null && sessionKey.getAlgorithm() != SymmetricKeyAlgorithm.NULL;
}
/**
* Return the {@link SubkeyIdentifier} of the key that was used to decrypt the message.
* This can be null if the message was decrypted using a {@link org.pgpainless.util.Passphrase}, or if it was not
* encrypted at all (e.g. signed only).
*
* @return subkey identifier of decryption key
*/
public @Nullable SubkeyIdentifier getDecryptionKey() {
return decryptionKey;
}
/**
* Return the algorithm that was used to symmetrically encrypt the message.
*
* @return encryption algorithm
*/
public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
return sessionKey == null ? null : sessionKey.getAlgorithm();
}
public @Nullable SessionKey getSessionKey() {
return sessionKey;
}
/**
* Return the {@link CompressionAlgorithm} that was used to compress the message.
*
* @return compression algorithm
*/
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
return compressionAlgorithm;
}
/**
* Return a set of all signatures on the message.
* Note: This method returns just the signatures. There is no guarantee that the signatures are verified or even correct.
*
* Use {@link #getVerifiedSignatures()} instead to get all verified signatures.
* @return unverified and verified signatures
*/
public @Nonnull Set<PGPSignature> getSignatures() {
Set<PGPSignature> signatures = new HashSet<>();
for (SignatureVerification v : getVerifiedDetachedSignatures()) {
signatures.add(v.getSignature());
}
for (SignatureVerification v : getVerifiedInbandSignatures()) {
signatures.add(v.getSignature());
}
for (SignatureVerification.Failure f : getInvalidDetachedSignatures()) {
signatures.add(f.getSignatureVerification().getSignature());
}
for (SignatureVerification.Failure f : getInvalidInbandSignatures()) {
signatures.add(f.getSignatureVerification().getSignature());
}
return signatures;
}
/**
* Return true if the message contained at least one signature.
*
* Note: This method does not reflect, whether the signature on the message is correct.
* Use {@link #isVerified()} instead to determine, if the message carries a verifiable signature.
*
* @return true if message contains at least one unverified or verified signature, false otherwise.
*/
public boolean isSigned() {
return !getSignatures().isEmpty();
}
/**
* Return a map of all verified signatures on the message.
* The map contains verified signatures as value, with the {@link SubkeyIdentifier} of the key that was used to verify
* the signature as the maps keys.
*
* @return verified detached and one-pass signatures
*/
public Map<SubkeyIdentifier, PGPSignature> getVerifiedSignatures() {
Map<SubkeyIdentifier, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>();
for (SignatureVerification detachedSignature : getVerifiedDetachedSignatures()) {
verifiedSignatures.put(detachedSignature.getSigningKey(), detachedSignature.getSignature());
}
for (SignatureVerification inbandSignatures : verifiedInbandSignatures) {
verifiedSignatures.put(inbandSignatures.getSigningKey(), inbandSignatures.getSignature());
}
return verifiedSignatures;
}
public List<SignatureVerification> getVerifiedInbandSignatures() {
return verifiedInbandSignatures;
}
public List<SignatureVerification> getVerifiedDetachedSignatures() {
return verifiedDetachedSignatures;
}
public List<SignatureVerification.Failure> getInvalidInbandSignatures() {
return invalidInbandSignatures;
}
public List<SignatureVerification.Failure> getInvalidDetachedSignatures() {
return invalidDetachedSignatures;
}
/**
* Return true, if the message is signed and at least one signature on the message was verified successfully.
*
* @return true if message is verified, false otherwise
*/
public boolean isVerified() {
return !getVerifiedSignatures().isEmpty();
}
/**
* Return true, if the message contains at least one verified signature made by a key in the
* given certificate.
*
* @param certificate certificate
* @return true if message was signed by the certificate (and the signature is valid), false otherwise
*/
public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing certificate) {
for (PGPPublicKey key : certificate) {
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(key);
if (containsVerifiedSignatureFrom(fingerprint)) {
return true;
}
}
return false;
}
/**
* Return true, if the message contains at least one valid signature made by the key with the given
* fingerprint, false otherwise.
*
* The fingerprint might be of the signing subkey, or the primary key of the signing certificate.
*
* @param fingerprint fingerprint of primary key or signing subkey
* @return true if validly signed, false otherwise
*/
public boolean containsVerifiedSignatureFrom(OpenPgpFingerprint fingerprint) {
for (SubkeyIdentifier verifiedSigningKey : getVerifiedSignatures().keySet()) {
if (verifiedSigningKey.getPrimaryKeyFingerprint().equals(fingerprint) ||
verifiedSigningKey.getSubkeyFingerprint().equals(fingerprint)) {
return true;
}
}
return false;
}
/**
* Return the name of the encrypted / signed file.
*
* @return file name
*/
public String getFileName() {
return fileName;
}
/**
* Return true, if the encrypted data is intended for your eyes only.
*
* @return true if for-your-eyes-only
*/
public boolean isForYourEyesOnly() {
return PGPLiteralData.CONSOLE.equals(getFileName());
}
/**
* Return the modification date of the encrypted / signed file.
*
* @return modification date
*/
public Date getModificationDate() {
return modificationDate;
}
/**
* Return the encoding format of the encrypted / signed file.
*
* @return encoding
*/
public StreamEncoding getFileEncoding() {
return fileEncoding;
}
/**
* Return true if the message was signed using the cleartext signature framework.
*
* @return true if cleartext signed.
*/
public boolean isCleartextSigned() {
return cleartextSigned;
}
public static Builder getBuilder() {
return new Builder();
}
public static class Builder {
private final Set<Long> recipientFingerprints = new HashSet<>();
private SessionKey sessionKey;
private SubkeyIdentifier decryptionKey;
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
private String fileName;
private StreamEncoding fileEncoding;
private Date modificationDate;
private boolean cleartextSigned = false;
private final List<SignatureVerification> verifiedInbandSignatures = new ArrayList<>();
private final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
private final List<SignatureVerification.Failure> invalidInbandSignatures = new ArrayList<>();
private final List<SignatureVerification.Failure> invalidDetachedSignatures = new ArrayList<>();
public Builder addRecipientKeyId(Long keyId) {
this.recipientFingerprints.add(keyId);
return this;
}
public Builder setDecryptionKey(SubkeyIdentifier decryptionKey) {
this.decryptionKey = decryptionKey;
return this;
}
public Builder setSessionKey(SessionKey sessionKey) {
this.sessionKey = sessionKey;
return this;
}
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
this.compressionAlgorithm = algorithm;
return this;
}
public Builder setFileName(@Nullable String fileName) {
this.fileName = fileName;
return this;
}
public Builder setModificationDate(Date modificationDate) {
this.modificationDate = modificationDate;
return this;
}
public Builder setFileEncoding(StreamEncoding encoding) {
this.fileEncoding = encoding;
return this;
}
public Builder addVerifiedInbandSignature(SignatureVerification signatureVerification) {
this.verifiedInbandSignatures.add(signatureVerification);
return this;
}
public Builder addVerifiedDetachedSignature(SignatureVerification signatureVerification) {
this.verifiedDetachedSignatures.add(signatureVerification);
return this;
}
public Builder addInvalidInbandSignature(SignatureVerification signatureVerification, SignatureValidationException e) {
this.invalidInbandSignatures.add(new SignatureVerification.Failure(signatureVerification, e));
return this;
}
public Builder addInvalidDetachedSignature(SignatureVerification signatureVerification, SignatureValidationException e) {
this.invalidDetachedSignatures.add(new SignatureVerification.Failure(signatureVerification, e));
return this;
}
public Builder setCleartextSigned() {
this.cleartextSigned = true;
return this;
}
public OpenPgpMetadata build() {
return new OpenPgpMetadata(
recipientFingerprints, decryptionKey,
sessionKey, compressionAlgorithm,
verifiedInbandSignatures, invalidInbandSignatures,
verifiedDetachedSignatures, invalidDetachedSignatures,
fileName, modificationDate, fileEncoding, cleartextSigned);
}
}
}

View file

@ -1,106 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.util.encoders.Hex;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.key.SubkeyIdentifier;
/**
* Tuple of a signature and an identifier of its corresponding verification key.
* Semantic meaning of the signature verification (success, failure) is merely given by context.
* E.g. {@link OpenPgpMetadata#getVerifiedInbandSignatures()} contains verified verifications,
* while the class {@link Failure} contains failed verifications.
*/
public class SignatureVerification {
private final PGPSignature signature;
private final SubkeyIdentifier signingKey;
/**
* Construct a verification tuple.
*
* @param signature PGPSignature object
* @param signingKey identifier of the signing key
*/
public SignatureVerification(PGPSignature signature, @Nullable SubkeyIdentifier signingKey) {
this.signature = signature;
this.signingKey = signingKey;
}
/**
* Return the {@link PGPSignature}.
*
* @return signature
*/
public PGPSignature getSignature() {
return signature;
}
/**
* Return a {@link SubkeyIdentifier} of the (sub-) key that is used for signature verification.
* Note, that this method might return null, e.g. in case of a {@link Failure} due to missing verification key.
*
* @return verification key identifier
*/
@Nullable
public SubkeyIdentifier getSigningKey() {
return signingKey;
}
@Override
public String toString() {
return "Signature: " + (signature != null ? Hex.toHexString(signature.getDigestPrefix()) : "null")
+ "; Key: " + (signingKey != null ? signingKey.toString() : "null") + ";";
}
/**
* Tuple object of a {@link SignatureVerification} and the corresponding {@link SignatureValidationException}
* that caused the verification to fail.
*/
public static class Failure {
private final SignatureVerification signatureVerification;
private final SignatureValidationException validationException;
/**
* Construct a signature verification failure object.
*
* @param verification verification
* @param validationException exception that caused the verification to fail
*/
public Failure(SignatureVerification verification, SignatureValidationException validationException) {
this.signatureVerification = verification;
this.validationException = validationException;
}
/**
* Return the verification (tuple of {@link PGPSignature} and corresponding {@link SubkeyIdentifier})
* of the signing/verification key.
*
* @return verification
*/
public SignatureVerification getSignatureVerification() {
return signatureVerification;
}
/**
* Return the {@link SignatureValidationException} that caused the verification to fail.
*
* @return exception
*/
public SignatureValidationException getValidationException() {
return validationException;
}
@Override
public String toString() {
return signatureVerification.toString() + " Failure: " + getValidationException().getMessage();
}
}
}

View file

@ -1,159 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.MarkerPacket;
import org.bouncycastle.bcpg.Packet;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.OpenPgpPacket;
import javax.annotation.Nonnull;
/**
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data.
* Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since
* {@link BCPGInputStream#readPacket()} inconsistently calls a mix of {@link BCPGInputStream#read()} and
* {@link InputStream#read()} of the underlying stream. This would cause the second length byte to get swallowed up.
*
* Therefore, this class delegates the teeing to an {@link DelayedTeeInputStream} which wraps the underlying
* stream. Since calling {@link BCPGInputStream#nextPacketTag()} reads up to and including the next packets tag,
* we need to delay teeing out that byte to signature verifiers.
* Hence, the reading methods of the {@link TeeBCPGInputStream} handle pushing this byte to the output stream using
* {@link DelayedTeeInputStream#squeeze()}.
*/
public class TeeBCPGInputStream {
protected final DelayedTeeInputStream delayedTee;
// InputStream of OpenPGP packets of the current layer
protected final BCPGInputStream packetInputStream;
public TeeBCPGInputStream(BCPGInputStream inputStream, OutputStream outputStream) {
this.delayedTee = new DelayedTeeInputStream(inputStream, outputStream);
this.packetInputStream = BCPGInputStream.wrap(delayedTee);
}
public OpenPgpPacket nextPacketTag() throws IOException {
int tag = packetInputStream.nextPacketTag();
if (tag == -1) {
return null;
}
return OpenPgpPacket.requireFromTag(tag);
}
public Packet readPacket() throws IOException {
return packetInputStream.readPacket();
}
public PGPCompressedData readCompressedData() throws IOException {
delayedTee.squeeze();
PGPCompressedData compressedData = new PGPCompressedData(packetInputStream);
return compressedData;
}
public PGPLiteralData readLiteralData() throws IOException {
delayedTee.squeeze();
return new PGPLiteralData(packetInputStream);
}
public PGPEncryptedDataList readEncryptedDataList() throws IOException {
delayedTee.squeeze();
return new PGPEncryptedDataList(packetInputStream);
}
public PGPOnePassSignature readOnePassSignature() throws PGPException, IOException {
PGPOnePassSignature onePassSignature = new PGPOnePassSignature(packetInputStream);
delayedTee.squeeze();
return onePassSignature;
}
public PGPSignature readSignature() throws PGPException, IOException {
PGPSignature signature = new PGPSignature(packetInputStream);
delayedTee.squeeze();
return signature;
}
public MarkerPacket readMarker() throws IOException {
MarkerPacket markerPacket = (MarkerPacket) readPacket();
delayedTee.squeeze();
return markerPacket;
}
public void close() throws IOException {
this.packetInputStream.close();
}
public static class DelayedTeeInputStream extends InputStream {
private int last = -1;
private final InputStream inputStream;
private final OutputStream outputStream;
public DelayedTeeInputStream(InputStream inputStream, OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
}
@Override
public int read() throws IOException {
if (last != -1) {
outputStream.write(last);
}
try {
last = inputStream.read();
return last;
} catch (IOException e) {
if (e.getMessage().contains("crc check failed in armored message")) {
throw e;
}
return -1;
}
}
@Override
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
if (last != -1) {
outputStream.write(last);
}
int r = inputStream.read(b, off, len);
if (r > 0) {
outputStream.write(b, off, r - 1);
last = b[off + r - 1];
} else {
last = -1;
}
return r;
}
/**
* Squeeze the last byte out and update the output stream.
*
* @throws IOException in case of an IO error
*/
public void squeeze() throws IOException {
if (last != -1) {
outputStream.write(last);
}
last = -1;
}
@Override
public void close() throws IOException {
inputStream.close();
outputStream.close();
}
}
}

View file

@ -1,169 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.cleartext_signatures;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.util.Strings;
import org.pgpainless.exception.WrongConsumingMethodException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.util.ArmoredInputStreamFactory;
/**
* Utility class to deal with cleartext-signed messages.
* Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}.
*/
public final class ClearsignedMessageUtil {
private ClearsignedMessageUtil() {
}
/**
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
* messageOutputStream.
*
* @param clearsignedInputStream input stream containing a clearsigned message
* @param messageOutputStream output stream to which the dearmored message shall be written
* @return signatures
*
* @throws IOException if the message is not clearsigned or some other IO error happens
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
*/
public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream,
OutputStream messageOutputStream)
throws IOException, WrongConsumingMethodException {
ArmoredInputStream in;
if (clearsignedInputStream instanceof ArmoredInputStream) {
in = (ArmoredInputStream) clearsignedInputStream;
} else {
in = ArmoredInputStreamFactory.get(clearsignedInputStream);
}
if (!in.isClearText()) {
throw new WrongConsumingMethodException("Message is not using the Cleartext Signature Framework.");
}
OutputStream out = new BufferedOutputStream(messageOutputStream);
try {
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, in);
byte[] lineSep = getLineSeparator();
if (lookAhead != -1 && in.isClearText()) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
while (lookAhead != -1 && in.isClearText()) {
lookAhead = readInputLine(lineOut, lookAhead, in);
line = lineOut.toByteArray();
out.write(lineSep);
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
}
} else {
if (lookAhead != -1) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
}
}
} finally {
out.close();
}
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(in);
PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject();
return signatures;
}
public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException {
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0) {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
return lookAhead;
}
public static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
throws IOException {
bOut.reset();
int ch = lookAhead;
do {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
while ((ch = fIn.read()) >= 0);
if (ch < 0) {
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException {
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n') {
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
private static byte[] getLineSeparator() {
String nl = Strings.lineSeparator();
byte[] nlBytes = new byte[nl.length()];
for (int i = 0; i != nlBytes.length; i++) {
nlBytes[i] = (byte) nl.charAt(i);
}
return nlBytes;
}
private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isLineEnding(byte b) {
return b == '\r' || b == '\n';
}
private static boolean isWhiteSpace(byte b) {
return isLineEnding(b) || b == '\t' || b == ' ';
}
}

View file

@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.cleartext_signatures;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* Implementation of the {@link MultiPassStrategy}.
* This class keeps the read data in memory by caching the data inside a {@link ByteArrayOutputStream}.
*
* Note, that this class is suitable and efficient for processing small amounts of data.
* For larger data like encrypted files, use of the {@link WriteToFileMultiPassStrategy} is recommended to
* prevent {@link OutOfMemoryError OutOfMemoryErrors} and other issues.
*/
public class InMemoryMultiPassStrategy implements MultiPassStrategy {
private final ByteArrayOutputStream cache = new ByteArrayOutputStream();
@Override
public ByteArrayOutputStream getMessageOutputStream() {
return cache;
}
@Override
public ByteArrayInputStream getMessageInputStream() {
return new ByteArrayInputStream(getBytes());
}
public byte[] getBytes() {
return getMessageOutputStream().toByteArray();
}
}

View file

@ -1,70 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.cleartext_signatures;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures,
* a strategy for how to cache the read data is required.
* Otherwise, large data kept in memory could cause {@link OutOfMemoryError OutOfMemoryErrors} or other issues.
*
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes
* to do verification.
*
* This interface can be used to write the signed data stream out via {@link #getMessageOutputStream()} and later
* get access to the data again via {@link #getMessageInputStream()}.
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away.
*/
public interface MultiPassStrategy {
/**
* Provide an {@link OutputStream} into which the signed data can be read into.
*
* @return output stream
* @throws IOException io error
*/
OutputStream getMessageOutputStream() throws IOException;
/**
* Provide an {@link InputStream} which contains the data that was previously written away in
* {@link #getMessageOutputStream()}.
*
* As there may be multiple signatures that need to be processed, each call of this method MUST return
* a new {@link InputStream}.
*
* @return input stream
* @throws IOException io error
*/
InputStream getMessageInputStream() throws IOException;
/**
* Write the message content out to a file and re-read it to verify signatures.
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory.
* After the message has been processed completely, the messages content are available at the provided file.
*
* @param file target file
* @return strategy
*/
static MultiPassStrategy writeMessageToFile(File file) {
return new WriteToFileMultiPassStrategy(file);
}
/**
* Read the message content into memory.
* This strategy is best suited for small messages which fit into memory.
* After the message has been processed completely, the message content can be accessed by calling
* {@link ByteArrayOutputStream#toByteArray()} on {@link #getMessageOutputStream()}.
*
* @return strategy
*/
static InMemoryMultiPassStrategy keepMessageInMemory() {
return new InMemoryMultiPassStrategy();
}
}

View file

@ -1,54 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.cleartext_signatures;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Implementation of the {@link MultiPassStrategy}.
* When processing signed data the first time, the data is being written out into a file.
* For the second pass, that file is being read again.
*
* This strategy is recommended when larger amounts of data need to be processed.
* For smaller files, {@link InMemoryMultiPassStrategy} yields higher efficiency.
*/
public class WriteToFileMultiPassStrategy implements MultiPassStrategy {
private final File file;
/**
* Create a {@link MultiPassStrategy} which writes data to a file.
* Note that {@link #getMessageOutputStream()} will create the file if necessary.
*
* @param file file to write the data to and read from
*/
public WriteToFileMultiPassStrategy(File file) {
this.file = file;
}
@Override
public OutputStream getMessageOutputStream() throws IOException {
if (!file.exists()) {
boolean created = file.createNewFile();
if (!created) {
throw new IOException("New file '" + file.getAbsolutePath() + "' was not created.");
}
}
return new FileOutputStream(file);
}
@Override
public InputStream getMessageInputStream() throws IOException {
if (!file.exists()) {
throw new IOException("File '" + file.getAbsolutePath() + "' does no longer exist.");
}
return new FileInputStream(file);
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes related to cleartext signature verification.
*/
package org.pgpainless.decryption_verification.cleartext_signatures;

View file

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPSignatureList;
public enum InputSymbol {
/**
* A {@link PGPLiteralData} packet.
*/
LiteralData,
/**
* A {@link PGPSignatureList} object.
*/
Signature,
/**
* A {@link PGPOnePassSignatureList} object.
*/
OnePassSignature,
/**
* A {@link PGPCompressedData} packet.
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
* its nested packet sequence.
*/
CompressedData,
/**
* A {@link PGPEncryptedDataList} object.
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
* (possibly Integrity Protected) Data packet.
*/
EncryptedData,
/**
* Marks the end of a (sub-) sequence.
* This input is given if the end of an OpenPGP message is reached.
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
* (e.g. the end of a Compressed Data packet).
*/
EndOfSequence
}

View file

@ -1,142 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This class describes the syntax for OpenPGP messages as specified by rfc4880.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">
* rfc4880 - §11.3. OpenPGP Messages</a>
* @see <a href="https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/">
* Blog post about theoretic background and translation of grammar to PDA syntax</a>
* @see <a href="https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/">
* Blog post about practically implementing the PDA for packet syntax validation</a>
*/
public class OpenPgpMessageSyntax implements Syntax {
@Override
public @Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
switch (from) {
case OpenPgpMessage:
return fromOpenPgpMessage(input, stackItem);
case LiteralMessage:
return fromLiteralMessage(input, stackItem);
case CompressedMessage:
return fromCompressedMessage(input, stackItem);
case EncryptedMessage:
return fromEncryptedMessage(input, stackItem);
case Valid:
return fromValid(input, stackItem);
}
throw new MalformedOpenPgpMessageException(from, input, stackItem);
}
@Nonnull
Transition fromOpenPgpMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
if (stackItem != StackSymbol.msg) {
throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem);
}
switch (input) {
case LiteralData:
return new Transition(State.LiteralMessage);
case Signature:
return new Transition(State.OpenPgpMessage, StackSymbol.msg);
case OnePassSignature:
return new Transition(State.OpenPgpMessage, StackSymbol.ops, StackSymbol.msg);
case CompressedData:
return new Transition(State.CompressedMessage);
case EncryptedData:
return new Transition(State.EncryptedMessage);
case EndOfSequence:
default:
throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem);
}
}
@Nonnull
Transition fromLiteralMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
switch (input) {
case Signature:
if (stackItem == StackSymbol.ops) {
return new Transition(State.LiteralMessage);
}
break;
case EndOfSequence:
if (stackItem == StackSymbol.terminus) {
return new Transition(State.Valid);
}
break;
}
throw new MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem);
}
@Nonnull
Transition fromCompressedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
switch (input) {
case Signature:
if (stackItem == StackSymbol.ops) {
return new Transition(State.CompressedMessage);
}
break;
case EndOfSequence:
if (stackItem == StackSymbol.terminus) {
return new Transition(State.Valid);
}
break;
}
throw new MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem);
}
@Nonnull
Transition fromEncryptedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
switch (input) {
case Signature:
if (stackItem == StackSymbol.ops) {
return new Transition(State.EncryptedMessage);
}
break;
case EndOfSequence:
if (stackItem == StackSymbol.terminus) {
return new Transition(State.Valid);
}
break;
}
throw new MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem);
}
@Nonnull
Transition fromValid(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException {
if (input == InputSymbol.EndOfSequence) {
// allow subsequent read() calls.
return new Transition(State.Valid);
}
// There is no applicable transition rule out of Valid
throw new MalformedOpenPgpMessageException(State.Valid, input, stackItem);
}
}

View file

@ -1,156 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.msg;
import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.terminus;
/**
* Pushdown Automaton for validating context-free languages.
* In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">OpenPGP Message Syntax</a>
*/
public class PDA {
private static final Logger LOGGER = LoggerFactory.getLogger(PDA.class);
// right now we implement what rfc4880 specifies.
// TODO: Consider implementing what we proposed here:
// https://mailarchive.ietf.org/arch/msg/openpgp/uepOF6XpSegMO4c59tt9e5H1i4g/
private final Syntax syntax;
private final Stack<StackSymbol> stack = new Stack<>();
private final List<InputSymbol> inputs = new ArrayList<>(); // Track inputs for debugging / error reporting
private State state;
/**
* Default constructor which initializes the PDA to work with the {@link OpenPgpMessageSyntax}.
*/
public PDA() {
this(new OpenPgpMessageSyntax(), State.OpenPgpMessage, terminus, msg);
}
/**
* Construct a PDA with a custom {@link Syntax}, initial {@link State} and initial {@link StackSymbol StackSymbols}.
*
* @param syntax syntax
* @param initialState initial state
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance)
*/
public PDA(@Nonnull Syntax syntax, @Nonnull State initialState, @Nonnull StackSymbol... initialStack) {
this.syntax = syntax;
this.state = initialState;
for (StackSymbol symbol : initialStack) {
pushStack(symbol);
}
}
/**
* Process the next {@link InputSymbol}.
* This will either leave the PDA in the next state, or throw a {@link MalformedOpenPgpMessageException} if the
* input symbol is rejected.
*
* @param input input symbol
* @throws MalformedOpenPgpMessageException if the input symbol is rejected
*/
public void next(@Nonnull InputSymbol input)
throws MalformedOpenPgpMessageException {
StackSymbol stackSymbol = popStack();
try {
Transition transition = syntax.transition(state, input, stackSymbol);
state = transition.getNewState();
for (StackSymbol item : transition.getPushedItems()) {
pushStack(item);
}
inputs.add(input);
} catch (MalformedOpenPgpMessageException e) {
MalformedOpenPgpMessageException wrapped = new MalformedOpenPgpMessageException(
"Malformed message: After reading packet sequence " + Arrays.toString(inputs.toArray()) +
", token '" + input + "' is not allowed." +
"\nNo transition from state '" + state + "' with stack " + Arrays.toString(stack.toArray()) +
(stackSymbol != null ? "||'" + stackSymbol + "'." : "."), e);
LOGGER.debug("Invalid input '" + input + "'", wrapped);
throw wrapped;
}
}
/**
* Return the current state of the PDA.
*
* @return state
*/
public @Nonnull State getState() {
return state;
}
/**
* Peek at the stack, returning the topmost stack item without changing the stack.
*
* @return topmost stack item, or null if stack is empty
*/
public @Nullable StackSymbol peekStack() {
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}
/**
* Return true, if the PDA is in a valid state (the OpenPGP message is valid).
*
* @return true if valid, false otherwise
*/
public boolean isValid() {
return getState() == State.Valid && stack.isEmpty();
}
/**
* Throw a {@link MalformedOpenPgpMessageException} if the pda is not in a valid state right now.
*
* @throws MalformedOpenPgpMessageException if the pda is not in an acceptable state
*/
public void assertValid() throws MalformedOpenPgpMessageException {
if (!isValid()) {
throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString());
}
}
/**
* Pop an item from the stack.
*
* @return stack item
*/
private StackSymbol popStack() {
if (stack.isEmpty()) {
return null;
}
return stack.pop();
}
/**
* Push an item onto the stack.
*
* @param item item
*/
private void pushStack(StackSymbol item) {
stack.push(item);
}
@Override
public String toString() {
return "State: " + state + " Stack: " + stack;
}
}

View file

@ -1,20 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
public enum StackSymbol {
/**
* OpenPGP Message.
*/
msg,
/**
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
*/
ops,
/**
* Special symbol representing the end of the message.
*/
terminus
}

View file

@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
/**
* Set of states of the automaton.
*/
public enum State {
OpenPgpMessage,
LiteralMessage,
CompressedMessage,
EncryptedMessage,
Valid
}

View file

@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This interface can be used to define a custom syntax for the {@link PDA}.
*/
public interface Syntax {
/**
* Describe a transition rule from {@link State} <pre>from</pre> for {@link InputSymbol} <pre>input</pre>
* with {@link StackSymbol} <pre>stackItem</pre> from the top of the {@link PDA PDAs} stack.
* The resulting {@link Transition} contains the new {@link State}, as well as a list of
* {@link StackSymbol StackSymbols} that get pushed onto the stack by the transition rule.
* If there is no applicable rule, a {@link MalformedOpenPgpMessageException} is thrown, since in this case
* the {@link InputSymbol} must be considered illegal.
*
* @param from current state of the PDA
* @param input input symbol
* @param stackItem item that got popped from the top of the stack
* @return applicable transition rule containing the new state and pushed stack symbols
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal)
*/
@Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
throws MalformedOpenPgpMessageException;
}

View file

@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification.syntax_check;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Result of applying a transition rule.
* Transition rules can be described by implementing the {@link Syntax} interface.
*/
public class Transition {
private final List<StackSymbol> pushedItems = new ArrayList<>();
private final State newState;
public Transition(@Nonnull State newState, @Nonnull StackSymbol... pushedItems) {
this.newState = newState;
this.pushedItems.addAll(Arrays.asList(pushedItems));
}
/**
* Return the new {@link State} that is reached by applying the transition.
*
* @return new state
*/
@Nonnull
public State getNewState() {
return newState;
}
/**
* Return a list of {@link StackSymbol StackSymbols} that are pushed onto the stack
* by applying the transition.
* The list contains items in the order in which they are pushed onto the stack.
* The list may be empty.
*
* @return list of items to be pushed onto the stack
*/
@Nonnull
public List<StackSymbol> getPushedItems() {
return new ArrayList<>(pushedItems);
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Pushdown Automaton to verify validity of packet sequences according to the OpenPGP Message format.
*/
package org.pgpainless.decryption_verification.syntax_check;

View file

@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.security.MessageDigest;
import java.util.List;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import javax.annotation.Nonnull;
public class BcHashContextSigner {
public static PGPSignature signHashContext(@Nonnull MessageDigest hashContext,
@Nonnull SignatureType signatureType,
@Nonnull PGPSecretKeyRing secretKeys,
@Nonnull SecretKeyRingProtector protector)
throws PGPException {
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
List<PGPPublicKey> signingSubkeyCandidates = info.getSigningSubkeys();
PGPSecretKey signingKey = null;
for (PGPPublicKey signingKeyCandidate : signingSubkeyCandidates) {
signingKey = secretKeys.getSecretKey(signingKeyCandidate.getKeyID());
if (signingKey != null) {
break;
}
}
if (signingKey == null) {
throw new PGPException("Key does not contain suitable signing subkey.");
}
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(signingKey, protector);
return signHashContext(hashContext, signatureType, privateKey);
}
/**
* Create an OpenPGP Signature over the given {@link MessageDigest} hash context.
*
* @param hashContext hash context
* @param privateKey signing-capable key
* @return signature
* @throws PGPException in case of an OpenPGP error
*/
static PGPSignature signHashContext(MessageDigest hashContext, SignatureType signatureType, PGPPrivateKey privateKey)
throws PGPException {
PGPSignatureGenerator sigGen = new PGPSignatureGenerator(
new BcPGPHashContextContentSignerBuilder(hashContext)
);
sigGen.init(signatureType.getCode(), privateKey);
return sigGen.generate();
}
}

View file

@ -1,174 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.io.OutputStream;
import java.security.MessageDigest;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.DSADigestSigner;
import org.bouncycastle.crypto.signers.DSASigner;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.crypto.signers.Ed448Signer;
import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.operator.PGPContentSigner;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
import org.bouncycastle.util.Arrays;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
/**
* Implementation of {@link PGPContentSignerBuilder} using the BC API, which can be used to sign hash contexts.
* This can come in handy to sign data, which was already processed to calculate the hash context, without the
* need to process it again to calculate the OpenPGP signature.
*/
class BcPGPHashContextContentSignerBuilder extends PGPHashContextContentSignerBuilder {
private final BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
private final MessageDigest messageDigest;
private final HashAlgorithm hashAlgorithm;
BcPGPHashContextContentSignerBuilder(MessageDigest messageDigest) {
this.messageDigest = messageDigest;
this.hashAlgorithm = requireFromName(messageDigest.getAlgorithm());
}
private static HashAlgorithm requireFromName(String digestName) {
HashAlgorithm hashAlgorithm = HashAlgorithm.fromName(digestName);
if (hashAlgorithm == null) {
throw new IllegalArgumentException("Cannot recognize OpenPGP Hash Algorithm: " + digestName);
}
return hashAlgorithm;
}
@Override
public PGPContentSigner build(int signatureType, PGPPrivateKey privateKey) throws PGPException {
PublicKeyAlgorithm keyAlgorithm = PublicKeyAlgorithm.requireFromId(privateKey.getPublicKeyPacket().getAlgorithm());
AsymmetricKeyParameter privKeyParam = keyConverter.getPrivateKey(privateKey);
final Signer signer = createSigner(keyAlgorithm, messageDigest, privKeyParam);
signer.init(true, privKeyParam);
return new PGPContentSigner() {
public int getType() {
return signatureType;
}
public int getHashAlgorithm() {
return hashAlgorithm.getAlgorithmId();
}
public int getKeyAlgorithm() {
return keyAlgorithm.getAlgorithmId();
}
public long getKeyID() {
return privateKey.getKeyID();
}
public OutputStream getOutputStream() {
return new PGPHashContextContentSignerBuilder.SignerOutputStream(signer);
}
public byte[] getSignature() {
try {
return signer.generateSignature();
} catch (CryptoException e) {
throw new IllegalStateException("unable to create signature");
}
}
public byte[] getDigest() {
return messageDigest.digest();
}
};
}
static Signer createSigner(
PublicKeyAlgorithm keyAlgorithm,
MessageDigest messageDigest,
CipherParameters keyParam)
throws PGPException {
ExistingMessageDigest staticDigest = new ExistingMessageDigest(messageDigest);
switch (keyAlgorithm.getAlgorithmId()) {
case PublicKeyAlgorithmTags.RSA_GENERAL:
case PublicKeyAlgorithmTags.RSA_SIGN:
return new RSADigestSigner(staticDigest);
case PublicKeyAlgorithmTags.DSA:
return new DSADigestSigner(new DSASigner(), staticDigest);
case PublicKeyAlgorithmTags.ECDSA:
return new DSADigestSigner(new ECDSASigner(), staticDigest);
case PublicKeyAlgorithmTags.EDDSA:
if (keyParam instanceof Ed25519PrivateKeyParameters || keyParam instanceof Ed25519PublicKeyParameters) {
return new EdDsaSigner(new Ed25519Signer(), staticDigest);
}
return new EdDsaSigner(new Ed448Signer(new byte[0]), staticDigest);
default:
throw new PGPException("cannot recognise keyAlgorithm: " + keyAlgorithm);
}
}
// Copied from BCs BcImplProvider - required since BCs class is package visible only :/
private static class EdDsaSigner
implements Signer {
private final Signer signer;
private final Digest digest;
private final byte[] digBuf;
EdDsaSigner(Signer signer, Digest digest) {
this.signer = signer;
this.digest = digest;
this.digBuf = new byte[digest.getDigestSize()];
}
public void init(boolean forSigning, CipherParameters param) {
this.signer.init(forSigning, param);
this.digest.reset();
}
public void update(byte b) {
this.digest.update(b);
}
public void update(byte[] in, int off, int len) {
this.digest.update(in, off, len);
}
public byte[] generateSignature()
throws CryptoException, DataLengthException {
digest.doFinal(digBuf, 0);
signer.update(digBuf, 0, digBuf.length);
return signer.generateSignature();
}
public boolean verifySignature(byte[] signature) {
digest.doFinal(digBuf, 0);
signer.update(digBuf, 0, digBuf.length);
return signer.verifySignature(signature);
}
public void reset() {
Arrays.clear(digBuf);
signer.reset();
digest.reset();
}
}
}

View file

@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: 2021 David Hook <dgh@cryptoworkshop.com>
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import org.pgpainless.algorithm.StreamEncoding;
import java.io.IOException;
import java.io.OutputStream;
/**
* {@link OutputStream} which applies CR-LF encoding of its input data, based on the desired {@link StreamEncoding}.
* This implementation originates from the Bouncy Castle library.
*/
public class CRLFGeneratorStream extends OutputStream {
protected final OutputStream crlfOut;
private final boolean isBinary;
private int lastB = 0;
public CRLFGeneratorStream(OutputStream crlfOut, StreamEncoding encoding) {
this.crlfOut = crlfOut;
this.isBinary = encoding == StreamEncoding.BINARY;
}
public void write(int b) throws IOException {
if (!isBinary) {
if (b == '\n' && lastB != '\r') { // Unix
crlfOut.write('\r');
} else if (lastB == '\r') { // MAC
if (b != '\n') {
crlfOut.write('\n');
}
}
lastB = b;
}
crlfOut.write(b);
}
public void close() throws IOException {
if (!isBinary && lastB == '\r') { // MAC
crlfOut.write('\n');
}
crlfOut.close();
}
@Override
public void flush() throws IOException {
super.flush();
crlfOut.flush();
}
}

View file

@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator;
import org.pgpainless.key.SubkeyIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EncryptionBuilder implements EncryptionBuilderInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionBuilder.class);
private OutputStream outputStream;
@Override
public WithOptions onOutputStream(@Nonnull OutputStream outputStream) {
this.outputStream = outputStream;
return new WithOptionsImpl();
}
class WithOptionsImpl implements WithOptions {
@Override
public EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException {
if (options == null) {
throw new NullPointerException("ProducerOptions cannot be null.");
}
return new EncryptionStream(outputStream, options);
}
}
/**
* Negotiate the {@link SymmetricKeyAlgorithm} used for message encryption.
*
* @param encryptionOptions encryption options
* @return negotiated symmetric key algorithm
*/
public static SymmetricKeyAlgorithm negotiateSymmetricEncryptionAlgorithm(EncryptionOptions encryptionOptions) {
List<Set<SymmetricKeyAlgorithm>> preferences = new ArrayList<>();
for (SubkeyIdentifier key : encryptionOptions.getKeyViews().keySet()) {
preferences.add(encryptionOptions.getKeyViews().get(key).getPreferredSymmetricKeyAlgorithms());
}
SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithmNegotiator
.byPopularity()
.negotiate(
PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy(),
encryptionOptions.getEncryptionAlgorithmOverride(),
preferences);
LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm);
return algorithm;
}
public static CompressionAlgorithm negotiateCompressionAlgorithm(ProducerOptions producerOptions) {
CompressionAlgorithm compressionAlgorithmOverride = producerOptions.getCompressionAlgorithmOverride();
if (compressionAlgorithmOverride != null) {
return compressionAlgorithmOverride;
}
// TODO: Negotiation
return PGPainless.getPolicy().getCompressionAlgorithmPolicy().defaultCompressionAlgorithm();
}
}

View file

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
public interface EncryptionBuilderInterface {
/**
* Create a {@link EncryptionStream} on an {@link OutputStream} that contains the plain data that
* shall be encrypted and or signed.
*
* @param outputStream output stream of the plain data.
* @return api handle
*/
WithOptions onOutputStream(@Nonnull OutputStream outputStream);
interface WithOptions {
/**
* Create an {@link EncryptionStream} with the given options (recipients, signers, algorithms...).
*
* @param options options
* @return encryption stream
*
* @throws PGPException if something goes wrong during encryption stream preparation
* @throws IOException if something goes wrong during encryption stream preparation (writing headers)
*/
EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException;
}
}

View file

@ -1,473 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.authentication.CertificateAuthenticity;
import org.pgpainless.authentication.CertificateAuthority;
import org.pgpainless.exception.KeyException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyAccessor;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.util.Passphrase;
/**
* Options for the encryption process.
* This class can be used to set encryption parameters, like encryption keys and passphrases, algorithms etc.
* <p>
* A typical use might look like follows:
* <pre>
* {@code
* EncryptionOptions opt = new EncryptionOptions();
* opt.addRecipient(aliceKey, "Alice <alice@wonderland.lit>");
* opt.addPassphrase(Passphrase.fromPassword("AdditionalDecryptionPassphrase123"));
* }
* </pre>
*<p>
* To use a custom symmetric encryption algorithm, use {@link #overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}.
* This will cause PGPainless to use the provided algorithm for message encryption, instead of negotiating an algorithm
* by inspecting the provided recipient keys.
* <p>
* By default, PGPainless will encrypt to all suitable, encryption capable subkeys on each recipient's certificate.
* This behavior can be changed per recipient, e.g. by calling
* <pre>
* {@code
* opt.addRecipient(aliceKey, EncryptionOptions.encryptToFirstSubkey());
* }
* </pre>
* when adding the recipient key.
*/
public class EncryptionOptions {
private final EncryptionPurpose purpose;
private final Set<PGPKeyEncryptionMethodGenerator> encryptionMethods = new LinkedHashSet<>();
private final Set<SubkeyIdentifier> encryptionKeys = new LinkedHashSet<>();
private final Map<SubkeyIdentifier, KeyRingInfo> keyRingInfo = new HashMap<>();
private final Map<SubkeyIdentifier, KeyAccessor> keyViews = new HashMap<>();
private final EncryptionKeySelector encryptionKeySelector = encryptToAllCapableSubkeys();
private boolean allowEncryptionWithMissingKeyFlags = false;
private Date evaluationDate = new Date();
private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null;
/**
* Encrypt to keys both carrying the key flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}
* or {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*/
public EncryptionOptions() {
this(EncryptionPurpose.ANY);
}
public EncryptionOptions(@Nonnull EncryptionPurpose purpose) {
this.purpose = purpose;
}
/**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
* which carry either the {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS} or
* {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE} flag.
* <p>
* Use this if you are not sure.
*
* @return encryption options
*/
public static EncryptionOptions get() {
return new EncryptionOptions();
}
/**
* Override the evaluation date for recipient keys with the given date.
*
* @param evaluationDate new evaluation date
* @return this
*/
public EncryptionOptions setEvaluationDate(@Nonnull Date evaluationDate) {
this.evaluationDate = evaluationDate;
return this;
}
/**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
* which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
*
* @return encryption options
*/
public static EncryptionOptions encryptCommunications() {
return new EncryptionOptions(EncryptionPurpose.COMMUNICATIONS);
}
/**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
* which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*
* @return encryption options
*/
public static EncryptionOptions encryptDataAtRest() {
return new EncryptionOptions(EncryptionPurpose.STORAGE);
}
/**
* Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for
* identifiable bindings.
* Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients.
* @param userId userId
* @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address
* @param authority certificate authority
* @return encryption options
*/
public EncryptionOptions addAuthenticatableRecipients(String userId, boolean email, CertificateAuthority authority) {
return addAuthenticatableRecipients(userId, email, authority, 120);
}
/**
* Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for
* identifiable bindings.
* Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients.
* @param userId userId
* @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address
* @param authority certificate authority
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @return encryption options
*/
public EncryptionOptions addAuthenticatableRecipients(String userId, boolean email, CertificateAuthority authority, int targetAmount) {
List<CertificateAuthenticity> identifiedCertificates = authority.lookupByUserId(userId, email, evaluationDate, targetAmount);
boolean foundAcceptable = false;
for (CertificateAuthenticity candidate : identifiedCertificates) {
if (candidate.isAuthenticated()) {
addRecipient(candidate.getCertificate());
foundAcceptable = true;
}
}
if (!foundAcceptable) {
throw new IllegalArgumentException("Could not identify any trust-worthy certificates for '" + userId + "' and target trust amount " + targetAmount);
}
return this;
}
/**
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
*
* @param keys keys
* @return this
*/
public EncryptionOptions addRecipients(@Nonnull Iterable<PGPPublicKeyRing> keys) {
if (!keys.iterator().hasNext()) {
throw new IllegalArgumentException("Set of recipient keys cannot be empty.");
}
for (PGPPublicKeyRing key : keys) {
addRecipient(key);
}
return this;
}
/**
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
* Per key ring, the selector is applied to select one or more encryption subkeys.
*
* @param keys keys
* @param selector encryption key selector
* @return this
*/
public EncryptionOptions addRecipients(@Nonnull Iterable<PGPPublicKeyRing> keys, @Nonnull EncryptionKeySelector selector) {
if (!keys.iterator().hasNext()) {
throw new IllegalArgumentException("Set of recipient keys cannot be empty.");
}
for (PGPPublicKeyRing key : keys) {
addRecipient(key, selector);
}
return this;
}
/**
* Add a recipient by providing a key and recipient user-id.
* The user-id is used to determine the recipients preferences (algorithms etc.).
*
* @param key key ring
* @param userId user id
* @return this
*/
public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull CharSequence userId) {
return addRecipient(key, userId, encryptionKeySelector);
}
/**
* Add a recipient by providing a key and recipient user-id, as well as a strategy for selecting one or multiple
* encryption capable subkeys from the key.
*
* @param key key
* @param userId user-id
* @param encryptionKeySelectionStrategy strategy to select one or more encryption subkeys to encrypt to
* @return this
*/
public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key,
@Nonnull CharSequence userId,
@Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) {
KeyRingInfo info = new KeyRingInfo(key, evaluationDate);
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId.toString(), purpose));
if (encryptionSubkeys.isEmpty()) {
throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key));
}
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
keyRingInfo.put(keyId, info);
keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId.toString()));
addRecipientKey(key, encryptionSubkey, false);
}
return this;
}
/**
* Add a recipient by providing a key.
*
* @param key key ring
* @return this
*/
public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key) {
return addRecipient(key, encryptionKeySelector);
}
/**
* Add a recipient by providing a key and an encryption key selection strategy.
*
* @param key key ring
* @param encryptionKeySelectionStrategy strategy used to select one or multiple encryption subkeys.
* @return this
*/
public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key,
@Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) {
return addAsRecipient(key, encryptionKeySelectionStrategy, false);
}
/**
* Add a certificate as hidden recipient.
* The recipients key-id will be obfuscated by setting a wildcard key ID.
*
* @param key recipient key
* @return this
*/
public EncryptionOptions addHiddenRecipient(@Nonnull PGPPublicKeyRing key) {
return addHiddenRecipient(key, encryptionKeySelector);
}
/**
* Add a certificate as hidden recipient, using the provided {@link EncryptionKeySelector} to select recipient subkeys.
* The recipients key-ids will be obfuscated by setting a wildcard key ID instead.
*
* @param key recipient key
* @param encryptionKeySelectionStrategy strategy to select recipient (sub) keys.
* @return this
*/
public EncryptionOptions addHiddenRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy) {
return addAsRecipient(key, encryptionKeySelectionStrategy, true);
}
private EncryptionOptions addAsRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy, boolean wildcardKeyId) {
KeyRingInfo info = new KeyRingInfo(key, evaluationDate);
Date primaryKeyExpiration;
try {
primaryKeyExpiration = info.getPrimaryKeyExpirationDate();
} catch (NoSuchElementException e) {
throw new KeyException.UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key));
}
if (primaryKeyExpiration != null && primaryKeyExpiration.before(evaluationDate)) {
throw new KeyException.ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration);
}
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose));
// There are some legacy keys around without key flags.
// If we allow encryption for those keys, we add valid keys without any key flags, if they are
// capable of encryption by means of their algorithm
if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) {
List<PGPPublicKey> validSubkeys = info.getValidSubkeys();
for (PGPPublicKey validSubkey : validSubkeys) {
if (!validSubkey.isEncryptionKey()) {
continue;
}
// only add encryption keys with no key flags.
if (info.getKeyFlagsOf(validSubkey.getKeyID()).isEmpty()) {
encryptionSubkeys.add(validSubkey);
}
}
}
if (encryptionSubkeys.isEmpty()) {
throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key));
}
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
keyRingInfo.put(keyId, info);
keyViews.put(keyId, new KeyAccessor.ViaKeyId(info, keyId));
addRecipientKey(key, encryptionSubkey, wildcardKeyId);
}
return this;
}
private void addRecipientKey(@Nonnull PGPPublicKeyRing keyRing,
@Nonnull PGPPublicKey key,
boolean wildcardKeyId) {
encryptionKeys.add(new SubkeyIdentifier(keyRing, key.getKeyID()));
PublicKeyKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
encryptionMethod.setUseWildcardKeyID(wildcardKeyId);
addEncryptionMethod(encryptionMethod);
}
/**
* Add a symmetric passphrase which the message will be encrypted to.
*
* @param passphrase passphrase
* @return this
*/
public EncryptionOptions addPassphrase(@Nonnull Passphrase passphrase) {
if (passphrase.isEmpty()) {
throw new IllegalArgumentException("Passphrase must not be empty.");
}
PBEKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
return addEncryptionMethod(encryptionMethod);
}
/**
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase)
* or {@link PGPKeyEncryptionMethodGenerator} (public key).
*
* This method is intended for advanced users to allow encryption for specific subkeys.
* This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless.
*
* @param encryptionMethod encryption method
* @return this
*/
public EncryptionOptions addEncryptionMethod(@Nonnull PGPKeyEncryptionMethodGenerator encryptionMethod) {
encryptionMethods.add(encryptionMethod);
return this;
}
Set<PGPKeyEncryptionMethodGenerator> getEncryptionMethods() {
return new HashSet<>(encryptionMethods);
}
Map<SubkeyIdentifier, KeyRingInfo> getKeyRingInfo() {
return new HashMap<>(keyRingInfo);
}
Set<SubkeyIdentifier> getEncryptionKeyIdentifiers() {
return new HashSet<>(encryptionKeys);
}
Map<SubkeyIdentifier, KeyAccessor> getKeyViews() {
return new HashMap<>(keyViews);
}
SymmetricKeyAlgorithm getEncryptionAlgorithmOverride() {
return encryptionAlgorithmOverride;
}
/**
* Override the used symmetric encryption algorithm.
* The symmetric encryption algorithm is used to encrypt the message itself,
* while the used symmetric key will be encrypted to all recipients using public key
* cryptography.
*
* If the algorithm is not overridden, a suitable algorithm will be negotiated.
*
* @param encryptionAlgorithm encryption algorithm override
* @return this
*/
public EncryptionOptions overrideEncryptionAlgorithm(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) {
if (encryptionAlgorithm == SymmetricKeyAlgorithm.NULL) {
throw new IllegalArgumentException("Plaintext encryption can only be used to denote unencrypted secret keys.");
}
this.encryptionAlgorithmOverride = encryptionAlgorithm;
return this;
}
/**
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption
* for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket.
* This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm
* type to convey the subkeys use.
*
* @return this
*/
public EncryptionOptions setAllowEncryptionWithMissingKeyFlags() {
this.allowEncryptionWithMissingKeyFlags = true;
return this;
}
/**
* Return <pre>true</pre> iff the user specified at least one encryption method,
* <pre>false</pre> otherwise.
*
* @return encryption methods is not empty
*/
public boolean hasEncryptionMethod() {
return !encryptionMethods.isEmpty();
}
public interface EncryptionKeySelector {
List<PGPPublicKey> selectEncryptionSubkeys(@Nonnull List<PGPPublicKey> encryptionCapableKeys);
}
/**
* Only encrypt to the first valid encryption capable subkey we stumble upon.
*
* @return encryption key selector
*/
public static EncryptionKeySelector encryptToFirstSubkey() {
return new EncryptionKeySelector() {
@Override
public List<PGPPublicKey> selectEncryptionSubkeys(@Nonnull List<PGPPublicKey> encryptionCapableKeys) {
return encryptionCapableKeys.isEmpty() ? Collections.emptyList() : Collections.singletonList(encryptionCapableKeys.get(0));
}
};
}
/**
* Encrypt to any valid, encryption capable subkey on the key ring.
*
* @return encryption key selector
*/
public static EncryptionKeySelector encryptToAllCapableSubkeys() {
return new EncryptionKeySelector() {
@Override
public List<PGPPublicKey> selectEncryptionSubkeys(@Nonnull List<PGPPublicKey> encryptionCapableKeys) {
return encryptionCapableKeys;
}
};
}
// TODO: Create encryptToBestSubkey() method
}

View file

@ -1,209 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.MultiMap;
public final class EncryptionResult {
private final SymmetricKeyAlgorithm encryptionAlgorithm;
private final CompressionAlgorithm compressionAlgorithm;
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures;
private final Set<SubkeyIdentifier> recipients;
private final String fileName;
private final Date modificationDate;
private final StreamEncoding fileEncoding;
private EncryptionResult(SymmetricKeyAlgorithm encryptionAlgorithm,
CompressionAlgorithm compressionAlgorithm,
MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures,
Set<SubkeyIdentifier> recipients,
String fileName,
Date modificationDate,
StreamEncoding encoding) {
this.encryptionAlgorithm = encryptionAlgorithm;
this.compressionAlgorithm = compressionAlgorithm;
this.detachedSignatures = detachedSignatures;
this.recipients = Collections.unmodifiableSet(recipients);
this.fileName = fileName;
this.modificationDate = modificationDate;
this.fileEncoding = encoding;
}
/**
* Return the symmetric encryption algorithm used to encrypt the message.
*
* @return symmetric encryption algorithm
* */
public SymmetricKeyAlgorithm getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
/**
* Return the compression algorithm that was used to compress the message before encryption/signing.
*
* @return compression algorithm
*/
public CompressionAlgorithm getCompressionAlgorithm() {
return compressionAlgorithm;
}
/**
* Return a {@link MultiMap} of key identifiers and detached signatures that were generated for the message.
* Each key of the map represents a signing key, which has one or more detached signatures associated with it.
*
* @return detached signatures
*/
public MultiMap<SubkeyIdentifier, PGPSignature> getDetachedSignatures() {
return detachedSignatures;
}
/**
* Return the set of recipient encryption keys.
*
* @return recipients
*/
public Set<SubkeyIdentifier> getRecipients() {
return recipients;
}
/**
* Return the file name of the encrypted/signed data.
*
* @return filename
*/
public String getFileName() {
return fileName;
}
/**
* Return the modification date of the encrypted/signed file.
*
* @return modification date
*/
public Date getModificationDate() {
return modificationDate;
}
/**
* Return the encoding format of the encrypted/signed data.
*
* @return encoding format
*/
public StreamEncoding getFileEncoding() {
return fileEncoding;
}
/**
* Return true, if the message is marked as for-your-eyes-only.
* This is typically done by setting the filename "_CONSOLE".
*
* @return is message for your eyes only?
*/
public boolean isForYourEyesOnly() {
return PGPLiteralData.CONSOLE.equals(getFileName());
}
/**
* Returns true, if the message was encrypted for at least one subkey of the given certificate.
*
* @param certificate certificate
* @return true if encrypted for 1+ subkeys, false otherwise.
*/
public boolean isEncryptedFor(PGPPublicKeyRing certificate) {
for (SubkeyIdentifier recipient : recipients) {
if (certificate.getPublicKey().getKeyID() != recipient.getPrimaryKeyId()) {
continue;
}
if (certificate.getPublicKey(recipient.getSubkeyId()) != null) {
return true;
}
}
return false;
}
/**
* Create a builder for the encryption result class.
*
* @return builder
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private SymmetricKeyAlgorithm encryptionAlgorithm;
private CompressionAlgorithm compressionAlgorithm;
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures = new MultiMap<>();
private final Set<SubkeyIdentifier> recipients = new HashSet<>();
private String fileName = "";
private Date modificationDate = new Date(0L); // NOW
private StreamEncoding encoding = StreamEncoding.BINARY;
public Builder setEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
return this;
}
public Builder setCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
this.compressionAlgorithm = compressionAlgorithm;
return this;
}
public Builder addRecipient(SubkeyIdentifier recipient) {
this.recipients.add(recipient);
return this;
}
public Builder addDetachedSignature(SubkeyIdentifier signingSubkeyIdentifier, PGPSignature detachedSignature) {
this.detachedSignatures.put(signingSubkeyIdentifier, detachedSignature);
return this;
}
public Builder setFileName(@Nonnull String fileName) {
this.fileName = fileName;
return this;
}
public Builder setModificationDate(@Nonnull Date modificationDate) {
this.modificationDate = modificationDate;
return this;
}
public Builder setFileEncoding(StreamEncoding fileEncoding) {
this.encoding = fileEncoding;
return this;
}
public EncryptionResult build() {
if (encryptionAlgorithm == null) {
throw new IllegalStateException("Encryption algorithm not set.");
}
if (compressionAlgorithm == null) {
throw new IllegalStateException("Compression algorithm not set.");
}
return new EncryptionResult(encryptionAlgorithm, compressionAlgorithm, detachedSignatures, recipients,
fileName, modificationDate, encoding);
}
}
}

View file

@ -1,313 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.ArmoredOutputStreamFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* OutputStream that produces an OpenPGP message. The message can be encrypted, signed, or both,
* depending on its configuration.
*
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
*/
public final class EncryptionStream extends OutputStream {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionStream.class);
private final ProducerOptions options;
private final EncryptionResult.Builder resultBuilder = EncryptionResult.builder();
private boolean closed = false;
// 1 << 8 causes wrong partial body length encoding
// 1 << 9 fixes this.
// see https://github.com/pgpainless/pgpainless/issues/160
private static final int BUFFER_SIZE = 1 << 9;
OutputStream outermostStream;
OutputStream signatureLayerStream;
private ArmoredOutputStream armorOutputStream = null;
private OutputStream publicKeyEncryptedStream = null;
private PGPCompressedDataGenerator compressedDataGenerator;
private BCPGOutputStream basicCompressionStream;
private PGPLiteralDataGenerator literalDataGenerator;
private OutputStream literalDataStream;
EncryptionStream(@Nonnull OutputStream targetOutputStream,
@Nonnull ProducerOptions options)
throws IOException, PGPException {
this.options = options;
outermostStream = targetOutputStream;
prepareArmor();
prepareEncryption();
prepareCompression();
prepareOnePassSignatures();
prepareLiteralDataProcessing();
prepareSigningStream();
prepareInputEncoding();
}
private void prepareArmor() {
if (!options.isAsciiArmor()) {
LOGGER.debug("Output will be unarmored");
return;
}
// ArmoredOutputStream better be buffered
outermostStream = new BufferedOutputStream(outermostStream);
LOGGER.debug("Wrap encryption output in ASCII armor");
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options);
outermostStream = armorOutputStream;
}
private void prepareEncryption() throws IOException, PGPException {
EncryptionOptions encryptionOptions = options.getEncryptionOptions();
if (encryptionOptions == null || encryptionOptions.getEncryptionMethods().isEmpty()) {
// No encryption options/methods -> no encryption
resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL);
return;
}
SymmetricKeyAlgorithm encryptionAlgorithm = EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(encryptionOptions);
resultBuilder.setEncryptionAlgorithm(encryptionAlgorithm);
LOGGER.debug("Encrypt message using {}", encryptionAlgorithm);
PGPDataEncryptorBuilder dataEncryptorBuilder =
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm);
dataEncryptorBuilder.setWithIntegrityPacket(true);
PGPEncryptedDataGenerator encryptedDataGenerator =
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) {
encryptedDataGenerator.addMethod(encryptionMethod);
}
for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) {
resultBuilder.addRecipient(recipientSubkeyIdentifier);
}
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
outermostStream = publicKeyEncryptedStream;
}
private void prepareCompression() throws IOException {
CompressionAlgorithm compressionAlgorithm = EncryptionBuilder.negotiateCompressionAlgorithm(options);
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
compressedDataGenerator = new PGPCompressedDataGenerator(
compressionAlgorithm.getAlgorithmId());
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
return;
}
LOGGER.debug("Compress using {}", compressionAlgorithm);
basicCompressionStream = new BCPGOutputStream(compressedDataGenerator.open(outermostStream));
outermostStream = basicCompressionStream;
}
private void prepareOnePassSignatures() throws IOException, PGPException {
signatureLayerStream = outermostStream;
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
// No singing options/methods -> no signing
return;
}
int sigIndex = 0;
for (SubkeyIdentifier identifier : signingOptions.getSigningMethods().keySet()) {
sigIndex++;
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(identifier);
if (!signingMethod.isDetached()) {
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
// The last sig is not nested, all others are
boolean nested = sigIndex != signingOptions.getSigningMethods().size();
signatureGenerator.generateOnePassVersion(nested).encode(outermostStream);
}
}
}
private void prepareLiteralDataProcessing() throws IOException {
if (options.isCleartextSigned()) {
int[] algorithmIds = collectHashAlgorithmsForCleartextSigning();
armorOutputStream.beginClearText(algorithmIds);
return;
}
literalDataGenerator = new PGPLiteralDataGenerator();
literalDataStream = literalDataGenerator.open(outermostStream, options.getEncoding().getCode(),
options.getFileName(), options.getModificationDate(), new byte[BUFFER_SIZE]);
outermostStream = literalDataStream;
resultBuilder.setFileName(options.getFileName())
.setModificationDate(options.getModificationDate())
.setFileEncoding(options.getEncoding());
}
public void prepareSigningStream() {
outermostStream = new SignatureGenerationStream(outermostStream, options.getSigningOptions());
}
public void prepareInputEncoding() {
// By buffering here, we drastically improve performance
// Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to
// "convert" to write(buf) calls again
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outermostStream);
CRLFGeneratorStream crlfGeneratorStream = new CRLFGeneratorStream(bufferedOutputStream,
options.isApplyCRLFEncoding() ? StreamEncoding.UTF8 : StreamEncoding.BINARY);
outermostStream = crlfGeneratorStream;
}
private int[] collectHashAlgorithmsForCleartextSigning() {
SigningOptions signOpts = options.getSigningOptions();
Set<HashAlgorithm> hashAlgorithms = new HashSet<>();
if (signOpts != null) {
for (SigningOptions.SigningMethod method : signOpts.getSigningMethods().values()) {
hashAlgorithms.add(method.getHashAlgorithm());
}
}
int[] algorithmIds = new int[hashAlgorithms.size()];
Iterator<HashAlgorithm> iterator = hashAlgorithms.iterator();
for (int i = 0; i < algorithmIds.length; i++) {
algorithmIds[i] = iterator.next().getAlgorithmId();
}
return algorithmIds;
}
@Override
public void write(int data) throws IOException {
outermostStream.write(data);
}
@Override
public void write(@Nonnull byte[] buffer) throws IOException {
write(buffer, 0, buffer.length);
}
@Override
public void write(@Nonnull byte[] buffer, int off, int len) throws IOException {
outermostStream.write(buffer, 0, len);
}
@Override
public void flush() throws IOException {
outermostStream.flush();
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
outermostStream.close();
// Literal Data
if (literalDataStream != null) {
literalDataStream.flush();
literalDataStream.close();
}
if (literalDataGenerator != null) {
literalDataGenerator.close();
}
if (options.isCleartextSigned()) {
// Add linebreak between body and signatures
// TODO: We should only add this line if required.
// I.e. if the message already ends with \n, don't add another linebreak.
armorOutputStream.write('\r');
armorOutputStream.write('\n');
armorOutputStream.endClearText();
}
try {
writeSignatures();
} catch (PGPException e) {
throw new IOException("Exception while writing signatures.", e);
}
// Compressed Data
compressedDataGenerator.close();
// Public Key Encryption
if (publicKeyEncryptedStream != null) {
publicKeyEncryptedStream.flush();
publicKeyEncryptedStream.close();
}
// Armor
if (armorOutputStream != null) {
armorOutputStream.flush();
armorOutputStream.close();
}
closed = true;
}
private void writeSignatures() throws PGPException, IOException {
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
// One-Pass-Signatures are bracketed. That means we have to append the signatures in reverse order
// compared to the one-pass-signature packets.
List<SubkeyIdentifier> signingKeys = new ArrayList<>(signingOptions.getSigningMethods().keySet());
for (int i = signingKeys.size() - 1; i >= 0; i--) {
SubkeyIdentifier signingKey = signingKeys.get(i);
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
PGPSignature signature = signatureGenerator.generate();
if (signingMethod.isDetached()) {
resultBuilder.addDetachedSignature(signingKey, signature);
}
if (!signingMethod.isDetached() || options.isCleartextSigned()) {
signature.encode(signatureLayerStream);
}
}
}
public EncryptionResult getResult() {
if (!closed) {
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
}
return resultBuilder.build();
}
public boolean isClosed() {
return closed;
}
}

View file

@ -1,83 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.io.OutputStream;
import java.security.MessageDigest;
import javax.annotation.Nonnull;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
abstract class PGPHashContextContentSignerBuilder implements PGPContentSignerBuilder {
// Copied from BC, required since BCs class is package visible only
static class SignerOutputStream
extends OutputStream {
private Signer sig;
SignerOutputStream(Signer sig) {
this.sig = sig;
}
public void write(@Nonnull byte[] bytes, int off, int len) {
sig.update(bytes, off, len);
}
public void write(@Nonnull byte[] bytes) {
sig.update(bytes, 0, bytes.length);
}
public void write(int b) {
sig.update((byte) b);
}
}
static class ExistingMessageDigest implements Digest {
private final MessageDigest digest;
ExistingMessageDigest(MessageDigest messageDigest) {
this.digest = messageDigest;
}
@Override
public void update(byte in) {
digest.update(in);
}
@Override
public void update(byte[] in, int inOff, int len) {
digest.update(in, inOff, len);
}
@Override
public int doFinal(byte[] out, int outOff) {
byte[] hash = digest.digest();
System.arraycopy(hash, 0, out, outOff, hash.length);
return getDigestSize();
}
@Override
public void reset() {
// Nope!
// We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset
// the messageDigest, losing its state. This would shatter our intention.
}
@Override
public String getAlgorithmName() {
return digest.getAlgorithm();
}
@Override
public int getDigestSize() {
return digest.getDigestLength();
}
}
}

View file

@ -1,355 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.util.Date;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
public final class ProducerOptions {
private final EncryptionOptions encryptionOptions;
private final SigningOptions signingOptions;
private String fileName = "";
private Date modificationDate = PGPLiteralData.NOW;
private StreamEncoding encodingField = StreamEncoding.BINARY;
private boolean applyCRLFEncoding = false;
private boolean cleartextSigned = false;
private boolean hideArmorHeaders = false;
private CompressionAlgorithm compressionAlgorithmOverride = PGPainless.getPolicy().getCompressionAlgorithmPolicy()
.defaultCompressionAlgorithm();
private boolean asciiArmor = true;
private String comment = null;
private String version = null;
private ProducerOptions(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
this.encryptionOptions = encryptionOptions;
this.signingOptions = signingOptions;
}
/**
* Sign and encrypt some data.
*
* @param encryptionOptions encryption options
* @param signingOptions signing options
* @return builder
*/
public static ProducerOptions signAndEncrypt(EncryptionOptions encryptionOptions,
SigningOptions signingOptions) {
throwIfNull(encryptionOptions);
throwIfNull(signingOptions);
return new ProducerOptions(encryptionOptions, signingOptions);
}
/**
* Sign some data without encryption.
*
* @param signingOptions signing options
* @return builder
*/
public static ProducerOptions sign(SigningOptions signingOptions) {
throwIfNull(signingOptions);
return new ProducerOptions(null, signingOptions);
}
/**
* Encrypt some data without signing.
*
* @param encryptionOptions encryption options
* @return builder
*/
public static ProducerOptions encrypt(EncryptionOptions encryptionOptions) {
throwIfNull(encryptionOptions);
return new ProducerOptions(encryptionOptions, null);
}
/**
* Only wrap the data in an OpenPGP packet.
* No encryption or signing will be applied.
*
* @return builder
*/
public static ProducerOptions noEncryptionNoSigning() {
return new ProducerOptions(null, null);
}
private static void throwIfNull(EncryptionOptions encryptionOptions) {
if (encryptionOptions == null) {
throw new NullPointerException("EncryptionOptions cannot be null.");
}
}
private static void throwIfNull(SigningOptions signingOptions) {
if (signingOptions == null) {
throw new NullPointerException("SigningOptions cannot be null.");
}
}
/**
* Specify, whether the result of the encryption/signing operation shall be ascii armored.
* The default value is true.
*
* @param asciiArmor ascii armor
* @return builder
*/
public ProducerOptions setAsciiArmor(boolean asciiArmor) {
if (cleartextSigned && !asciiArmor) {
throw new IllegalArgumentException("Cleartext signing is enabled. Cannot disable ASCII armoring.");
}
this.asciiArmor = asciiArmor;
return this;
}
/**
* Return true if the output of the encryption/signing operation shall be ascii armored.
*
* @return ascii armored
*/
public boolean isAsciiArmor() {
return asciiArmor;
}
/**
* Set the comment header in ASCII armored output.
* The default value is null, which means no comment header is added.
* Multiline comments are possible using '\\n'.
* <br>
* Note: If a default header comment is set using {@link org.pgpainless.util.ArmoredOutputStreamFactory#setComment(String)},
* then both comments will be written to the produced ASCII armor.
*
* @param comment comment header text
* @return builder
*/
public ProducerOptions setComment(String comment) {
this.comment = comment;
return this;
}
/**
* Set the version header in ASCII armored output.
* The default value is null, which means no version header is added.
* <br>
* Note: If the value is non-null, then this method overrides the default version header set using
* {@link org.pgpainless.util.ArmoredOutputStreamFactory#setVersionInfo(String)}.
*
* @param version version header, or null for no version info.
* @return builder
*/
public ProducerOptions setVersion(String version) {
this.version = version;
return this;
}
/**
* Return comment set for header in ascii armored output.
*
* @return comment
*/
public String getComment() {
return comment;
}
/**
* Return the version info header in ascii armored output.
*
* @return version info
*/
public String getVersion() {
return version;
}
/**
* Return whether a comment was set (!= null).
*
* @return true if commend is set
*/
public boolean hasComment() {
return comment != null;
}
/**
* Return whether a version header was set (!= null).
*
* @return true if version header is set
*/
public boolean hasVersion() {
return version != null;
}
public ProducerOptions setCleartextSigned() {
if (signingOptions == null) {
throw new IllegalArgumentException("Signing Options cannot be null if cleartext signing is enabled.");
}
if (encryptionOptions != null) {
throw new IllegalArgumentException("Cannot encode encrypted message as Cleartext Signed.");
}
for (SigningOptions.SigningMethod method : signingOptions.getSigningMethods().values()) {
if (!method.isDetached()) {
throw new IllegalArgumentException("For cleartext signed message, all signatures must be added as detached signatures.");
}
}
cleartextSigned = true;
asciiArmor = true;
compressionAlgorithmOverride = CompressionAlgorithm.UNCOMPRESSED;
return this;
}
public boolean isCleartextSigned() {
return cleartextSigned;
}
/**
* Set the name of the encrypted file.
* Note: This option cannot be used simultaneously with {@link #setForYourEyesOnly()}.
*
* @param fileName name of the encrypted file
* @return this
*/
public ProducerOptions setFileName(@Nonnull String fileName) {
this.fileName = fileName;
return this;
}
/**
* Return the encrypted files name.
*
* @return file name
*/
public String getFileName() {
return fileName;
}
/**
* Mark the encrypted message as for-your-eyes-only by setting a special file name.
* Note: Therefore this method cannot be used simultaneously with {@link #setFileName(String)}.
*
* @return this
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in
* newly generated literal data packets
*/
@Deprecated
public ProducerOptions setForYourEyesOnly() {
this.fileName = PGPLiteralData.CONSOLE;
return this;
}
/**
* Set the modification date of the encrypted file.
*
* @param modificationDate Modification date of the encrypted file.
* @return this
*/
public ProducerOptions setModificationDate(@Nonnull Date modificationDate) {
this.modificationDate = modificationDate;
return this;
}
/**
* Return the modification date of the encrypted file.
*
* @return modification date
*/
public Date getModificationDate() {
return modificationDate;
}
/**
* Set format metadata field of the literal data packet.
* Defaults to {@link StreamEncoding#BINARY}.
* <br>
* This does not change the encoding of the wrapped data itself.
* To apply CR/LF encoding to your input data before processing, use {@link #applyCRLFEncoding()} instead.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*
* @param encoding encoding
* @return this
*
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged.
*/
@Deprecated
public ProducerOptions setEncoding(@Nonnull StreamEncoding encoding) {
this.encodingField = encoding;
return this;
}
public StreamEncoding getEncoding() {
return encodingField;
}
/**
* Apply special encoding of line endings to the input data.
* By default, this is disabled, which means that the data is not altered.
* <br>
* Enabling it will change the line endings to CR/LF.
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in
* the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
*
* @return this
*/
public ProducerOptions applyCRLFEncoding() {
this.applyCRLFEncoding = true;
return this;
}
/**
* Return the input encoding that will be applied before signing / encryption.
*
* @return input encoding
*/
public boolean isApplyCRLFEncoding() {
return applyCRLFEncoding;
}
/**
* Override which compression algorithm shall be used.
*
* @param compressionAlgorithm compression algorithm override
* @return builder
*/
public ProducerOptions overrideCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
if (compressionAlgorithm == null) {
throw new NullPointerException("Compression algorithm cannot be null.");
}
this.compressionAlgorithmOverride = compressionAlgorithm;
return this;
}
public CompressionAlgorithm getCompressionAlgorithmOverride() {
return compressionAlgorithmOverride;
}
public @Nullable EncryptionOptions getEncryptionOptions() {
return encryptionOptions;
}
public @Nullable SigningOptions getSigningOptions() {
return signingOptions;
}
public boolean isHideArmorHeaders() {
return hideArmorHeaders;
}
/**
* If set to <pre>true</pre>, armor headers like version or comments will be omitted from armored output.
* By default, armor headers are not hidden.
* Note: If comments are added via {@link #setComment(String)}, those are not omitted, even if
* {@link #hideArmorHeaders} is set to <pre>true</pre>.
*
* @param hideArmorHeaders true or false
* @return this
*/
public ProducerOptions setHideArmorHeaders(boolean hideArmorHeaders) {
this.hideArmorHeaders = hideArmorHeaders;
return this;
}
}

View file

@ -1,65 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.pgpainless.key.SubkeyIdentifier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
/**
* OutputStream which has the task of updating signature generators for written data.
*/
class SignatureGenerationStream extends OutputStream {
private final OutputStream wrapped;
private final SigningOptions options;
SignatureGenerationStream(@Nonnull OutputStream wrapped, @Nullable SigningOptions signingOptions) {
this.wrapped = wrapped;
this.options = signingOptions;
}
@Override
public void write(int b) throws IOException {
wrapped.write(b);
if (options == null || options.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : options.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = options.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
byte asByte = (byte) (b & 0xff);
signatureGenerator.update(asByte);
}
}
@Override
public void write(@Nonnull byte[] buffer) throws IOException {
write(buffer, 0, buffer.length);
}
@Override
public void write(@Nonnull byte[] buffer, int off, int len) throws IOException {
wrapped.write(buffer, 0, len);
if (options == null || options.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : options.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = options.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
signatureGenerator.update(buffer, 0, len);
}
}
@Override
public void close() throws IOException {
wrapped.close();
}
}

View file

@ -1,634 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
import org.pgpainless.exception.KeyException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
public final class SigningOptions {
/**
* A method of signing.
*/
public static final class SigningMethod {
private final PGPSignatureGenerator signatureGenerator;
private final boolean detached;
private final HashAlgorithm hashAlgorithm;
private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator,
boolean detached,
@Nonnull HashAlgorithm hashAlgorithm) {
this.signatureGenerator = signatureGenerator;
this.detached = detached;
this.hashAlgorithm = hashAlgorithm;
}
/**
* Inline-signature method.
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
*
* @param signatureGenerator signature generator
* @param hashAlgorithm hash algorithm used to generate the signature
* @return inline signing method
*/
public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
@Nonnull HashAlgorithm hashAlgorithm) {
return new SigningMethod(signatureGenerator, false, hashAlgorithm);
}
/**
* Detached signing method.
* The resulting signature will not be added to the message, and instead can be distributed separately
* to the signed message.
*
* @param signatureGenerator signature generator
* @param hashAlgorithm hash algorithm used to generate the signature
* @return detached signing method
*/
public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
@Nonnull HashAlgorithm hashAlgorithm) {
return new SigningMethod(signatureGenerator, true, hashAlgorithm);
}
public boolean isDetached() {
return detached;
}
public PGPSignatureGenerator getSignatureGenerator() {
return signatureGenerator;
}
public HashAlgorithm getHashAlgorithm() {
return hashAlgorithm;
}
}
private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
private HashAlgorithm hashAlgorithmOverride;
private Date evaluationDate = new Date();
@Nonnull
public static SigningOptions get() {
return new SigningOptions();
}
/**
* Override the evaluation date for signing keys with the given date.
*
* @param evaluationDate new evaluation date
* @return this
*/
public SigningOptions setEvaluationDate(@Nonnull Date evaluationDate) {
this.evaluationDate = evaluationDate;
return this;
}
/**
* Sign the message using an inline signature made by the provided signing key.
*
* @param signingKeyProtector protector to unlock the signing key
* @param signingKey key ring containing the signing key
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
*/
@Nonnull
public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector,
@Nonnull PGPSecretKeyRing signingKey)
throws PGPException {
return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
}
/**
* Add inline signatures with all secret key rings in the provided secret key ring collection.
*
* @param secrectKeyDecryptor decryptor to unlock the signing secret keys
* @param signingKeys collection of signing keys
* @param signatureType type of signature (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with any of the keys
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
*/
@Nonnull
public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor,
@Nonnull Iterable<PGPSecretKeyRing> signingKeys,
@Nonnull DocumentSignatureType signatureType)
throws KeyException, PGPException {
for (PGPSecretKeyRing signingKey : signingKeys) {
addInlineSignature(secrectKeyDecryptor, signingKey, signatureType);
}
return this;
}
/**
* Add an inline-signature.
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
* of one-pass-signature packets.
*
* @param secretKeyDecryptor decryptor to unlock the signing secret key
* @param secretKey signing key
* @param signatureType type of signature (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/
@Nonnull
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nonnull DocumentSignatureType signatureType)
throws KeyException, PGPException {
return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
}
/**
* Add an inline-signature.
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
* of one-pass-signature packets.
* <p>
* This method uses the passed in user-id to select user-specific hash algorithms.
*
* @param secretKeyDecryptor decryptor to unlock the signing secret key
* @param secretKey signing key
* @param userId user-id of the signer
* @param signatureType signature type (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/
@Nonnull
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nullable CharSequence userId,
@Nonnull DocumentSignatureType signatureType)
throws KeyException, PGPException {
return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
}
/**
* Add an inline-signature.
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
* of one-pass-signature packets.
* <p>
* This method uses the passed in user-id to select user-specific hash algorithms.
*
* @param secretKeyDecryptor decryptor to unlock the signing secret key
* @param secretKey signing key
* @param userId user-id of the signer
* @param signatureType signature type (binary, canonical text)
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
* @return this
*
* @throws KeyException if the key is invalid
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/
@Nonnull
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nullable CharSequence userId,
@Nonnull DocumentSignatureType signatureType,
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback)
throws KeyException, PGPException {
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw new KeyException.UnboundUserIdException(
OpenPgpFingerprint.of(secretKey),
userId.toString(),
keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId)
);
}
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
}
for (PGPPublicKey signingPubKey : signingPubKeys) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
if (signingSecKey == null) {
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
}
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
: keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
}
return this;
}
/**
* Create a binary inline signature using the signing key with the given keyId.
*
* @param secretKeyDecryptor decryptor to unlock the secret key
* @param secretKey secret key ring
* @param keyId keyId of the signing (sub-)key
* @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
*/
@Nonnull
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
long keyId) throws PGPException {
return addInlineSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
}
/**
* Create an inline signature using the signing key with the given keyId.
*
* @param secretKeyDecryptor decryptor to unlock the secret key
* @param secretKey secret key ring
* @param keyId keyId of the signing (sub-)key
* @param signatureType signature type
* @param subpacketsCallback callback to modify the signatures subpackets
* @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
*/
@Nonnull
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
long keyId,
@Nonnull DocumentSignatureType signatureType,
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
}
for (PGPPublicKey signingPubKey : signingPubKeys) {
if (signingPubKey.getKeyID() == keyId) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
if (signingSecKey == null) {
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
}
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
return this;
}
}
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
}
/**
* Add detached signatures with all key rings from the provided secret key ring collection.
*
* @param secretKeyDecryptor decryptor to unlock the secret signing keys
* @param signingKeys collection of signing key rings
* @param signatureType type of the signature (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with any of the keys
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
*/
@Nonnull
public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull Iterable<PGPSecretKeyRing> signingKeys,
@Nonnull DocumentSignatureType signatureType)
throws PGPException {
for (PGPSecretKeyRing signingKey : signingKeys) {
addDetachedSignature(secretKeyDecryptor, signingKey, signatureType);
}
return this;
}
/**
* Create a detached signature.
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
*
* @param secretKeyDecryptor decryptor to unlock the secret signing key
* @param signingKey signing key
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing signingKey)
throws PGPException {
return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
}
/**
* Create a detached signature.
* Detached signatures are not being added into the PGP message itself.
* Instead, they can be distributed separately to the message.
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
*
* @param secretKeyDecryptor decryptor to unlock the secret signing key
* @param secretKey signing key
* @param signatureType type of data that is signed (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nonnull DocumentSignatureType signatureType)
throws PGPException {
return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
}
/**
* Create a detached signature.
* Detached signatures are not being added into the PGP message itself.
* Instead, they can be distributed separately to the message.
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
* <p>
* This method uses the passed in user-id to select user-specific hash algorithms.
*
* @param secretKeyDecryptor decryptor to unlock the secret signing key
* @param secretKey signing key
* @param userId user-id
* @param signatureType type of data that is signed (binary, canonical text)
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nullable CharSequence userId,
@Nonnull DocumentSignatureType signatureType)
throws PGPException {
return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
}
/**
* Create a detached signature.
* Detached signatures are not being added into the PGP message itself.
* Instead, they can be distributed separately to the message.
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
* <p>
* This method uses the passed in user-id to select user-specific hash algorithms.
*
* @param secretKeyDecryptor decryptor to unlock the secret signing key
* @param secretKey signing key
* @param userId user-id
* @param signatureType type of data that is signed (binary, canonical text)
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
* @return this
*
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
@Nullable CharSequence userId,
@Nonnull DocumentSignatureType signatureType,
@Nullable BaseSignatureSubpackets.Callback subpacketCallback)
throws PGPException {
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw new KeyException.UnboundUserIdException(
OpenPgpFingerprint.of(secretKey),
userId.toString(),
keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId)
);
}
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
}
for (PGPPublicKey signingPubKey : signingPubKeys) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
if (signingSecKey == null) {
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
}
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
: keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true);
}
return this;
}
/**
* Create a detached binary signature using the signing key with the given keyId.
*
* @param secretKeyDecryptor decryptor to unlock the secret key
* @param secretKey secret key ring
* @param keyId keyId of the signing (sub-)key
* @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
long keyId) throws PGPException {
return addDetachedSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
}
/**
* Create a detached signature using the signing key with the given keyId.
*
* @param secretKeyDecryptor decryptor to unlock the secret key
* @param secretKey secret key ring
* @param keyId keyId of the signing (sub-)key
* @param signatureType signature type
* @param subpacketsCallback callback to modify the signatures subpackets
* @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
*/
@Nonnull
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing secretKey,
long keyId,
@Nonnull DocumentSignatureType signatureType,
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
}
for (PGPPublicKey signingPubKey : signingPubKeys) {
if (signingPubKey.getKeyID() == keyId) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
if (signingSecKey == null) {
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
}
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, true);
return this;
}
}
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
}
private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey,
@Nonnull PGPPrivateKey signingSubkey,
@Nullable BaseSignatureSubpackets.Callback subpacketCallback,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull DocumentSignatureType signatureType,
boolean detached)
throws PGPException {
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID());
PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID());
PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm());
int bitStrength = signingSecretKey.getPublicKey().getBitStrength();
if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
throw new KeyException.UnacceptableSigningKeyException(
new KeyException.PublicKeyAlgorithmPolicyException(
OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength));
}
PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);
// Subpackets
SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey());
SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets();
if (subpacketCallback != null) {
subpacketCallback.modifyHashedSubpackets(hashedSubpackets);
subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets);
}
generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets));
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets));
SigningMethod signingMethod = detached ?
SigningMethod.detachedSignature(generator, hashAlgorithm) :
SigningMethod.inlineSignature(generator, hashAlgorithm);
signingMethods.put(signingKeyIdentifier, signingMethod);
}
/**
* Negotiate, which hash algorithm to use.
* <p>
* This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}.
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
* Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is
* used as a fallback.
*
* @param preferences preferences
* @param policy policy
* @return selected hash algorithm
*/
@Nonnull
private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set<HashAlgorithm> preferences,
@Nonnull Policy policy) {
if (hashAlgorithmOverride != null) {
return hashAlgorithmOverride;
}
return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy)
.negotiateHashAlgorithm(preferences);
}
@Nonnull
private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull DocumentSignatureType signatureType)
throws PGPException {
int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm();
PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance()
.getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId());
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey);
return signatureGenerator;
}
/**
* Return a map of key-ids and signing methods.
* For internal use.
*
* @return signing methods
*/
@Nonnull
Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
return Collections.unmodifiableMap(signingMethods);
}
/**
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
* If no override has been set, an accetable algorithm will be negotiated instead.
* <p>
* Note: To override the hash algorithm for signing, call this method *before* calling
* {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or
* {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}.
*
* @param hashAlgorithmOverride override hash algorithm
* @return this
*/
@Nonnull
public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) {
this.hashAlgorithmOverride = hashAlgorithmOverride;
return this;
}
/**
* Return the hash algorithm override (or null if no override is set).
*
* @return hash algorithm override
*/
@Nullable
public HashAlgorithm getHashAlgorithmOverride() {
return hashAlgorithmOverride;
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes used to encrypt or sign data using OpenPGP.
*/
package org.pgpainless.encryption_signing;

View file

@ -1,154 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.implementation;
import java.io.InputStream;
import java.security.KeyPair;
import java.util.Date;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.Passphrase;
public class BcImplementationFactory extends ImplementationFactory {
@Override
public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm,
PGPDigestCalculator digestCalculator,
Passphrase passphrase) {
return new BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator)
.build(passphrase.getChars());
}
@Override
public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) {
return new BcPBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider())
.build(passphrase.getChars());
}
@Override
public BcPGPDigestCalculatorProvider getPGPDigestCalculatorProvider() {
return new BcPGPDigestCalculatorProvider();
}
@Override
public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() {
return new BcPGPContentVerifierBuilderProvider();
}
@Override
public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) {
return new BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm);
}
@Override
public KeyFingerPrintCalculator getKeyFingerprintCalculator() {
return new BcKeyFingerprintCalculator();
}
@Override
public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) {
return new BcPBEDataDecryptorFactory(passphrase.getChars(), getPGPDigestCalculatorProvider());
}
@Override
public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) {
return new BcPublicKeyDataDecryptorFactory(privateKey);
}
@Override
public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
return new BcSessionKeyDataDecryptorFactory(sessionKey);
}
@Override
public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) {
return new BcPublicKeyKeyEncryptionMethodGenerator(key);
}
@Override
public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) {
return new BcPBEKeyEncryptionMethodGenerator(passphrase.getChars());
}
@Override
public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) {
return new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm);
}
@Override
public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate)
throws PGPException {
return new BcPGPKeyPair(algorithm.getAlgorithmId(), jceToBcKeyPair(algorithm, keyPair, creationDate), creationDate);
}
@Override
public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException {
return new BcPBESecretKeyEncryptorBuilder(
encryptionAlgorithm.getAlgorithmId(),
getPGPDigestCalculator(hashAlgorithm),
s2kCount)
.build(passphrase.getChars());
}
@Override
public PGPObjectFactory getPGPObjectFactory(byte[] bytes) {
return new BcPGPObjectFactory(bytes);
}
@Override
public PGPObjectFactory getPGPObjectFactory(InputStream inputStream) {
return new BcPGPObjectFactory(inputStream);
}
private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm,
KeyPair keyPair,
Date creationDate) throws PGPException {
BcPGPKeyConverter converter = new BcPGPKeyConverter();
PGPKeyPair pair = new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate);
AsymmetricKeyParameter publicKey = converter.getPublicKey(pair.getPublicKey());
AsymmetricKeyParameter privateKey = converter.getPrivateKey(pair.getPrivateKey());
return new AsymmetricCipherKeyPair(publicKey, privateKey);
}
}

View file

@ -1,119 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.implementation;
import java.io.InputStream;
import java.security.KeyPair;
import java.util.Date;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.SessionKey;
public abstract class ImplementationFactory {
private static ImplementationFactory FACTORY_IMPLEMENTATION;
public static void setFactoryImplementation(ImplementationFactory implementation) {
FACTORY_IMPLEMENTATION = implementation;
}
public static ImplementationFactory getInstance() {
if (FACTORY_IMPLEMENTATION == null) {
FACTORY_IMPLEMENTATION = new BcImplementationFactory();
}
return FACTORY_IMPLEMENTATION;
}
public abstract PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm,
PGPDigestCalculator digestCalculator,
Passphrase passphrase);
public abstract PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException;
public PGPDigestCalculator getV4FingerprintCalculator() throws PGPException {
return getPGPDigestCalculator(HashAlgorithm.SHA1);
}
public PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException {
return getPGPDigestCalculator(algorithm.getAlgorithmId());
}
public PGPDigestCalculator getPGPDigestCalculator(int algorithm) throws PGPException {
return getPGPDigestCalculatorProvider().get(algorithm);
}
public abstract PGPDigestCalculatorProvider getPGPDigestCalculatorProvider() throws PGPException;
public abstract PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider();
public PGPContentSignerBuilder getPGPContentSignerBuilder(PublicKeyAlgorithm keyAlgorithm, HashAlgorithm hashAlgorithm) {
return getPGPContentSignerBuilder(keyAlgorithm.getAlgorithmId(), hashAlgorithm.getAlgorithmId());
}
public abstract PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm);
public abstract KeyFingerPrintCalculator getKeyFingerprintCalculator();
public abstract PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) throws PGPException;
public abstract PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey);
public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(SessionKey sessionKey) {
PGPSessionKey pgpSessionKey = new PGPSessionKey(
sessionKey.getAlgorithm().getAlgorithmId(),
sessionKey.getKey()
);
return getSessionKeyDataDecryptorFactory(pgpSessionKey);
}
public abstract SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey);
public abstract PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key);
public abstract PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase);
public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
return getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId());
}
public abstract PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm);
public abstract PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException;
public abstract PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm,
HashAlgorithm hashAlgorithm, int s2kCount,
Passphrase passphrase) throws PGPException;
public abstract PGPObjectFactory getPGPObjectFactory(InputStream inputStream);
public abstract PGPObjectFactory getPGPObjectFactory(byte[] bytes);
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View file

@ -1,141 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.implementation;
import java.io.InputStream;
import java.security.KeyPair;
import java.util.Date;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.provider.ProviderFactory;
import org.pgpainless.util.Passphrase;
public class JceImplementationFactory extends ImplementationFactory {
public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, PGPDigestCalculator digestCalculator, Passphrase passphrase) {
return new JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator)
.setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars());
}
public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException {
return new JcePBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider())
.setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars());
}
public PGPDigestCalculatorProvider getPGPDigestCalculatorProvider()
throws PGPException {
return new JcaPGPDigestCalculatorProviderBuilder()
.setProvider(ProviderFactory.getProvider())
.build();
}
public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() {
return new JcaPGPContentVerifierBuilderProvider()
.setProvider(ProviderFactory.getProvider());
}
public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) {
return new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
.setProvider(ProviderFactory.getProvider());
}
public KeyFingerPrintCalculator getKeyFingerprintCalculator() {
return new JcaKeyFingerprintCalculator()
.setProvider(ProviderFactory.getProvider());
}
public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase)
throws PGPException {
return new JcePBEDataDecryptorFactoryBuilder(getPGPDigestCalculatorProvider())
.setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars());
}
public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) {
return new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(ProviderFactory.getProvider())
.build(privateKey);
}
@Override
public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
return new JceSessionKeyDataDecryptorFactoryBuilder()
.build(sessionKey);
}
public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) {
return new JcePublicKeyKeyEncryptionMethodGenerator(key)
.setProvider(ProviderFactory.getProvider());
}
public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) {
return new JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
.setProvider(ProviderFactory.getProvider());
}
public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) {
return new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm)
.setProvider(ProviderFactory.getProvider());
}
public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException {
return new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate);
}
public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException {
return new JcePBESecretKeyEncryptorBuilder(
encryptionAlgorithm.getAlgorithmId(),
getPGPDigestCalculator(hashAlgorithm),
s2kCount)
.setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars());
}
@Override
public PGPObjectFactory getPGPObjectFactory(InputStream inputStream) {
return new PGPObjectFactory(inputStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
}
@Override
public PGPObjectFactory getPGPObjectFactory(byte[] bytes) {
return new PGPObjectFactory(bytes, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Implementation factory classes to be able to switch out the underlying crypto engine implementation.
*/
package org.pgpainless.implementation;

View file

@ -1,189 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import java.nio.charset.Charset;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
/**
* Abstract super class of different version OpenPGP fingerprints.
*
*/
public abstract class OpenPgpFingerprint implements CharSequence, Comparable<OpenPgpFingerprint> {
@SuppressWarnings("CharsetObjectCanBeUsed")
protected static final Charset utf8 = Charset.forName("UTF-8");
protected final String fingerprint;
/**
* Return the fingerprint of the given key.
* This method automatically matches key versions to fingerprint implementations.
*
* @param key key
* @return fingerprint
*/
public static OpenPgpFingerprint of(PGPSecretKey key) {
return of(key.getPublicKey());
}
/**
* Return the fingerprint of the given key.
* This method automatically matches key versions to fingerprint implementations.
*
* @param key key
* @return fingerprint
*/
public static OpenPgpFingerprint of(PGPPublicKey key) {
if (key.getVersion() == 4) {
return new OpenPgpV4Fingerprint(key);
}
if (key.getVersion() == 5) {
return new OpenPgpV5Fingerprint(key);
}
if (key.getVersion() == 6) {
return new OpenPgpV6Fingerprint(key);
}
throw new IllegalArgumentException("OpenPGP keys of version " + key.getVersion() + " are not supported.");
}
/**
* Return the fingerprint of the primary key of the given key ring.
* This method automatically matches key versions to fingerprint implementations.
*
* @param ring key ring
* @return fingerprint
*/
public static OpenPgpFingerprint of(PGPKeyRing ring) {
return of(ring.getPublicKey());
}
/**
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
* to know the version of the key beforehand.
*
* @param fingerprint fingerprint
* @return parsed fingerprint
* @deprecated Use the constructor methods of the versioned fingerprint subclasses instead.
*/
@Deprecated
public static OpenPgpFingerprint parse(String fingerprint) {
String fp = fingerprint.replace(" ", "").trim().toUpperCase();
if (fp.matches("^[0-9A-F]{40}$")) {
return new OpenPgpV4Fingerprint(fp);
}
if (fp.matches("^[0-9A-F]{64}$")) {
// Might be v5 or v6 :/
return new _64DigitFingerprint(fp);
}
throw new IllegalArgumentException("Fingerprint does not appear to match any known fingerprint patterns.");
}
/**
* Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object.
*
* @param binaryFingerprint binary representation of the fingerprint
* @return parsed fingerprint
* @deprecated use the parse() methods of the versioned fingerprint subclasses instead.
*/
@Deprecated
public static OpenPgpFingerprint parseFromBinary(byte[] binaryFingerprint) {
String hex = Hex.toHexString(binaryFingerprint).toUpperCase();
return parse(hex);
}
public OpenPgpFingerprint(String fingerprint) {
String fp = fingerprint.replace(" ", "").trim().toUpperCase();
if (!isValid(fp)) {
throw new IllegalArgumentException(
String.format("Fingerprint '%s' does not appear to be a valid OpenPGP V%d fingerprint.", fingerprint, getVersion())
);
}
this.fingerprint = fp;
}
public OpenPgpFingerprint(@Nonnull byte[] bytes) {
this(new String(bytes, utf8));
}
public OpenPgpFingerprint(PGPPublicKey key) {
this(Hex.encode(key.getFingerprint()));
if (key.getVersion() != getVersion()) {
throw new IllegalArgumentException(String.format("Key is not a v%d OpenPgp key.", getVersion()));
}
}
public OpenPgpFingerprint(@Nonnull PGPPublicKeyRing ring) {
this(ring.getPublicKey());
}
public OpenPgpFingerprint(@Nonnull PGPSecretKeyRing ring) {
this(ring.getPublicKey());
}
public OpenPgpFingerprint(@Nonnull PGPKeyRing ring) {
this(ring.getPublicKey());
}
/**
* Return the version of the fingerprint.
*
* @return version
*/
public abstract int getVersion();
/**
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
* @param fp fingerprint to check.
* @return true if fingerprint is valid.
*/
protected abstract boolean isValid(@Nonnull String fp);
/**
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
* This method can be implemented for V4 and V5 fingerprints.
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
* @return key id
*/
public abstract long getKeyId();
@Override
public int length() {
return fingerprint.length();
}
@Override
public char charAt(int i) {
return fingerprint.charAt(i);
}
@Override
public CharSequence subSequence(int i, int i1) {
return fingerprint.subSequence(i, i1);
}
@Override
@Nonnull
public String toString() {
return fingerprint;
}
/**
* Return a pretty printed representation of the fingerprint.
*
* @return pretty printed fingerprint
*/
public abstract String prettyPrint();
}

View file

@ -1,149 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
/**
* This class represents a hex encoded, uppercase OpenPGP v4 fingerprint.
*/
public class OpenPgpV4Fingerprint extends OpenPgpFingerprint {
public static final String SCHEME = "openpgp4fpr";
/**
* Create an {@link OpenPgpV4Fingerprint}.
*
* @param fingerprint uppercase hexadecimal fingerprint of length 40
*/
public OpenPgpV4Fingerprint(@Nonnull String fingerprint) {
super(fingerprint);
}
public OpenPgpV4Fingerprint(@Nonnull byte[] bytes) {
super(Hex.encode(bytes));
}
public OpenPgpV4Fingerprint(@Nonnull PGPPublicKey key) {
super(key);
}
public OpenPgpV4Fingerprint(@Nonnull PGPSecretKey key) {
this(key.getPublicKey());
}
public OpenPgpV4Fingerprint(@Nonnull PGPPublicKeyRing ring) {
super(ring);
}
public OpenPgpV4Fingerprint(@Nonnull PGPSecretKeyRing ring) {
super(ring);
}
public OpenPgpV4Fingerprint(@Nonnull PGPKeyRing ring) {
super(ring);
}
@Override
public int getVersion() {
return 4;
}
@Override
protected boolean isValid(@Nonnull String fp) {
return fp.matches("^[0-9A-F]{40}$");
}
@Override
public long getKeyId() {
byte[] bytes = Hex.decode(toString().getBytes(utf8));
ByteBuffer buf = ByteBuffer.wrap(bytes);
// The key id is the right-most 8 bytes (conveniently a long)
// We have to cast here in order to be compatible with java 8
// https://github.com/eclipse/jetty.project/issues/3244
((Buffer) buf).position(12); // 20 - 8 bytes = offset 12
return buf.getLong();
}
@Override
public String prettyPrint() {
String fp = toString();
StringBuilder pretty = new StringBuilder();
for (int i = 0; i < 5; i++) {
pretty.append(fp, i * 4, (i + 1) * 4).append(' ');
}
pretty.append(' ');
for (int i = 5; i < 9; i++) {
pretty.append(fp, i * 4, (i + 1) * 4).append(' ');
}
pretty.append(fp, 36, 40);
return pretty.toString();
}
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (!(other instanceof CharSequence)) {
return false;
}
return this.toString().equals(other.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Return the fingerprint as an openpgp4fpr {@link URI}.
* An example would be 'openpgp4fpr:7F9116FEA90A5983936C7CFAA027DB2F3E1E118A'.
*
* @return openpgp4fpr fingerprint uri
* @see <a href="https://metacode.biz/openpgp/openpgp4fpr">openpgp4fpr URI scheme</a>
*/
public URI toUri() {
try {
return new URI(OpenPgpV4Fingerprint.SCHEME, toString(), null);
} catch (URISyntaxException e) {
throw new AssertionError(e);
}
}
/**
* Convert an openpgp4fpr URI to an {@link OpenPgpV4Fingerprint}.
*
* @param uri {@link URI} with scheme 'openpgp4fpr'
* @return fingerprint parsed from the uri
* @see <a href="https://metacode.biz/openpgp/openpgp4fpr">openpgp4fpr URI scheme</a>
*/
public static OpenPgpV4Fingerprint fromUri(URI uri) {
if (!SCHEME.equals(uri.getScheme())) {
throw new IllegalArgumentException("URI scheme MUST equal '" + SCHEME + "'");
}
return new OpenPgpV4Fingerprint(uri.getSchemeSpecificPart());
}
@Override
public int compareTo(@Nonnull OpenPgpFingerprint openPgpFingerprint) {
return toString().compareTo(openPgpFingerprint.toString());
}
}

View file

@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
/**
* This class represents a hex encoded, upper case OpenPGP v5 fingerprint.
*/
public class OpenPgpV5Fingerprint extends _64DigitFingerprint {
/**
* Create an {@link OpenPgpV5Fingerprint}.
*
* @param fingerprint uppercase hexadecimal fingerprint of length 64
*/
public OpenPgpV5Fingerprint(@Nonnull String fingerprint) {
super(fingerprint);
}
public OpenPgpV5Fingerprint(@Nonnull byte[] bytes) {
super(bytes);
}
public OpenPgpV5Fingerprint(@Nonnull PGPPublicKey key) {
super(key);
}
public OpenPgpV5Fingerprint(@Nonnull PGPSecretKey key) {
this(key.getPublicKey());
}
public OpenPgpV5Fingerprint(@Nonnull PGPPublicKeyRing ring) {
super(ring);
}
public OpenPgpV5Fingerprint(@Nonnull PGPSecretKeyRing ring) {
super(ring);
}
public OpenPgpV5Fingerprint(@Nonnull PGPKeyRing ring) {
super(ring);
}
@Override
public int getVersion() {
return 5;
}
}

View file

@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
/**
* This class represents a hex encoded, upper case OpenPGP v6 fingerprint.
*/
public class OpenPgpV6Fingerprint extends _64DigitFingerprint {
/**
* Create an {@link OpenPgpV6Fingerprint}.
*
* @param fingerprint uppercase hexadecimal fingerprint of length 64
*/
public OpenPgpV6Fingerprint(@Nonnull String fingerprint) {
super(fingerprint);
}
public OpenPgpV6Fingerprint(@Nonnull byte[] bytes) {
super(bytes);
}
public OpenPgpV6Fingerprint(@Nonnull PGPPublicKey key) {
super(key);
}
public OpenPgpV6Fingerprint(@Nonnull PGPSecretKey key) {
this(key.getPublicKey());
}
public OpenPgpV6Fingerprint(@Nonnull PGPPublicKeyRing ring) {
super(ring);
}
public OpenPgpV6Fingerprint(@Nonnull PGPSecretKeyRing ring) {
super(ring);
}
public OpenPgpV6Fingerprint(@Nonnull PGPKeyRing ring) {
super(ring);
}
@Override
public int getVersion() {
return 6;
}
}

View file

@ -1,146 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
/**
* Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring,
* as well as the subkeys fingerprint.
*/
public class SubkeyIdentifier {
private final OpenPgpFingerprint primaryKeyFingerprint;
private final OpenPgpFingerprint subkeyFingerprint;
/**
* Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing}.
* The identifier will point to the primary key of the provided ring.
*
* @param keyRing key ring
*/
public SubkeyIdentifier(PGPKeyRing keyRing) {
this(keyRing, keyRing.getPublicKey().getKeyID());
}
/**
* Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing} and the subkeys key id.
* {@link #getPrimaryKeyFingerprint()} will return the {@link OpenPgpFingerprint} of the keyrings primary key,
* while {@link #getSubkeyFingerprint()} will return the subkeys fingerprint.
*
* @param keyRing keyring the subkey belongs to
* @param keyId keyid of the subkey
*/
public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, long keyId) {
PGPPublicKey subkey = keyRing.getPublicKey(keyId);
if (subkey == null) {
throw new NoSuchElementException("Key ring does not contain subkey with id " + Long.toHexString(keyId));
}
this.primaryKeyFingerprint = OpenPgpFingerprint.of(keyRing);
this.subkeyFingerprint = OpenPgpFingerprint.of(subkey);
}
public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, @Nonnull OpenPgpFingerprint subkeyFingerprint) {
this(OpenPgpFingerprint.of(keyRing), subkeyFingerprint);
}
/**
* Create a {@link SubkeyIdentifier} that identifies the primary key with the given fingerprint.
* This means, both {@link #getPrimaryKeyFingerprint()} and {@link #getSubkeyFingerprint()} return the same.
*
* @param primaryKeyFingerprint fingerprint of the identified key
*/
public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint) {
this(primaryKeyFingerprint, primaryKeyFingerprint);
}
/**
* Create a {@link SubkeyIdentifier} which points to the subkey with the given subkeyFingerprint,
* which has a primary key with the given primaryKeyFingerprint.
*
* @param primaryKeyFingerprint fingerprint of the primary key
* @param subkeyFingerprint fingerprint of the subkey
*/
public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint, @Nonnull OpenPgpFingerprint subkeyFingerprint) {
this.primaryKeyFingerprint = primaryKeyFingerprint;
this.subkeyFingerprint = subkeyFingerprint;
}
public @Nonnull OpenPgpFingerprint getFingerprint() {
return getSubkeyFingerprint();
}
public long getKeyId() {
return getSubkeyId();
}
/**
* Return the {@link OpenPgpFingerprint} of the primary key of the identified key.
* This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key.
*
* @return primary key fingerprint
*/
public @Nonnull OpenPgpFingerprint getPrimaryKeyFingerprint() {
return primaryKeyFingerprint;
}
/**
* Return the key id of the primary key of the identified key.
* This might be the same as {@link #getSubkeyId()} if the identified subkey is the primary key.
*
* @return primary key id
*/
public long getPrimaryKeyId() {
return getPrimaryKeyFingerprint().getKeyId();
}
/**
* Return the {@link OpenPgpFingerprint} of the identified subkey.
*
* @return subkey fingerprint
*/
public @Nonnull OpenPgpFingerprint getSubkeyFingerprint() {
return subkeyFingerprint;
}
/**
* Return the key id of the identified subkey.
*
* @return subkey id
*/
public long getSubkeyId() {
return getSubkeyFingerprint().getKeyId();
}
@Override
public int hashCode() {
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof SubkeyIdentifier)) {
return false;
}
SubkeyIdentifier other = (SubkeyIdentifier) obj;
return getPrimaryKeyFingerprint().equals(other.getPrimaryKeyFingerprint())
&& getSubkeyFingerprint().equals(other.getSubkeyFingerprint());
}
@Override
public String toString() {
return getSubkeyFingerprint() + " " + getPrimaryKeyFingerprint();
}
}

View file

@ -1,119 +0,0 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
/**
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
* key version.
*/
public class _64DigitFingerprint extends OpenPgpFingerprint {
/**
* Create an {@link _64DigitFingerprint}.
*
* @param fingerprint uppercase hexadecimal fingerprint of length 64
*/
protected _64DigitFingerprint(@Nonnull String fingerprint) {
super(fingerprint);
}
protected _64DigitFingerprint(@Nonnull byte[] bytes) {
super(Hex.encode(bytes));
}
protected _64DigitFingerprint(@Nonnull PGPPublicKey key) {
super(key);
}
protected _64DigitFingerprint(@Nonnull PGPSecretKey key) {
this(key.getPublicKey());
}
protected _64DigitFingerprint(@Nonnull PGPPublicKeyRing ring) {
super(ring);
}
protected _64DigitFingerprint(@Nonnull PGPSecretKeyRing ring) {
super(ring);
}
protected _64DigitFingerprint(@Nonnull PGPKeyRing ring) {
super(ring);
}
@Override
public int getVersion() {
return -1; // might be v5 or v6
}
@Override
protected boolean isValid(@Nonnull String fp) {
return fp.matches("^[0-9A-F]{64}$");
}
@Override
public long getKeyId() {
byte[] bytes = Hex.decode(toString().getBytes(utf8));
ByteBuffer buf = ByteBuffer.wrap(bytes);
// The key id is the left-most 8 bytes (conveniently a long).
// We have to cast here in order to be compatible with java 8
// https://github.com/eclipse/jetty.project/issues/3244
((Buffer) buf).position(0);
return buf.getLong();
}
@Override
public String prettyPrint() {
String fp = toString();
StringBuilder pretty = new StringBuilder();
for (int i = 0; i < 4; i++) {
pretty.append(fp, i * 8, (i + 1) * 8).append(' ');
}
pretty.append(' ');
for (int i = 4; i < 7; i++) {
pretty.append(fp, i * 8, (i + 1) * 8).append(' ');
}
pretty.append(fp, 56, 64);
return pretty.toString();
}
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (!(other instanceof CharSequence)) {
return false;
}
return this.toString().equals(other.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public int compareTo(OpenPgpFingerprint openPgpFingerprint) {
return toString().compareTo(openPgpFingerprint.toString());
}
}

View file

@ -1,298 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.certification;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CertificationType;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.Trustworthiness;
import org.pgpainless.exception.KeyException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder;
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder;
import org.pgpainless.signature.subpackets.CertificationSubpackets;
import org.pgpainless.util.DateUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date;
/**
* API for creating certifications and delegations (Signatures) on keys.
* This API can be used to sign another persons OpenPGP key.
*
* A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs
* to the owner of the certificate.
* A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer.
*/
public class CertifyCertificate {
/**
* Create a certification over a User-Id.
* By default, this method will use {@link CertificationType#GENERIC} to create the signature.
* If you need to create another type of certification, use
* {@link #userIdOnCertificate(String, PGPPublicKeyRing, CertificationType)} instead.
*
* @param userId user-id to certify
* @param certificate certificate
* @return API
*/
public CertificationOnUserId userIdOnCertificate(@Nonnull String userId,
@Nonnull PGPPublicKeyRing certificate) {
return userIdOnCertificate(userId, certificate, CertificationType.GENERIC);
}
/**
* Create a certification of the given {@link CertificationType} over a User-Id.
*
* @param userid user-id to certify
* @param certificate certificate
* @param certificationType type of signature
* @return API
*/
public CertificationOnUserId userIdOnCertificate(@Nonnull String userid,
@Nonnull PGPPublicKeyRing certificate,
@Nonnull CertificationType certificationType) {
return new CertificationOnUserId(userid, certificate, certificationType);
}
/**
* Create a delegation (direct key signature) over a certificate.
* This can be used to mark a certificate as a trusted introducer
* (see {@link #certificate(PGPPublicKeyRing, Trustworthiness)}).
*
* @param certificate certificate
* @return API
*/
public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate) {
return certificate(certificate, null);
}
/**
* Create a delegation (direct key signature) containing a {@link org.bouncycastle.bcpg.sig.TrustSignature} packet
* over a certificate.
* This can be used to mark a certificate as a trusted introducer.
*
* @param certificate certificate
* @param trustworthiness trustworthiness of the certificate
* @return API
*/
public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate,
@Nullable Trustworthiness trustworthiness) {
return new DelegationOnCertificate(certificate, trustworthiness);
}
public static class CertificationOnUserId {
private final PGPPublicKeyRing certificate;
private final String userId;
private final CertificationType certificationType;
CertificationOnUserId(@Nonnull String userId,
@Nonnull PGPPublicKeyRing certificate,
@Nonnull CertificationType certificationType) {
this.userId = userId;
this.certificate = certificate;
this.certificationType = certificationType;
}
/**
* Create the certification using the given key.
*
* @param certificationKey key used to create the certification
* @param protector protector to unlock the certification key
* @return API
* @throws PGPException in case of an OpenPGP related error
*/
public CertificationOnUserIdWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey,
@Nonnull SecretKeyRingProtector protector)
throws PGPException {
PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey);
ThirdPartyCertificationSignatureBuilder sigBuilder = new ThirdPartyCertificationSignatureBuilder(
certificationType.asSignatureType(), secretKey, protector);
return new CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder);
}
}
public static class CertificationOnUserIdWithSubpackets {
private final PGPPublicKeyRing certificate;
private final String userId;
private final ThirdPartyCertificationSignatureBuilder sigBuilder;
CertificationOnUserIdWithSubpackets(@Nonnull PGPPublicKeyRing certificate,
@Nonnull String userId,
@Nonnull ThirdPartyCertificationSignatureBuilder sigBuilder) {
this.certificate = certificate;
this.userId = userId;
this.sigBuilder = sigBuilder;
}
/**
* Apply the given signature subpackets and build the certification.
*
* @param subpacketCallback callback to modify the signatures subpackets
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketCallback)
throws PGPException {
sigBuilder.applyCallback(subpacketCallback);
return build();
}
/**
* Build the certification signature.
*
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
public CertificationResult build() throws PGPException {
PGPSignature signature = sigBuilder.build(certificate, userId);
PGPPublicKeyRing certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature);
return new CertificationResult(certifiedCertificate, signature);
}
}
public static class DelegationOnCertificate {
private final PGPPublicKeyRing certificate;
private final Trustworthiness trustworthiness;
DelegationOnCertificate(@Nonnull PGPPublicKeyRing certificate,
@Nullable Trustworthiness trustworthiness) {
this.certificate = certificate;
this.trustworthiness = trustworthiness;
}
/**
* Build the delegation using the given certification key.
*
* @param certificationKey key to create the certification with
* @param protector protector to unlock the certification key
* @return API
* @throws PGPException in case of an OpenPGP related error
*/
public DelegationOnCertificateWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey,
@Nonnull SecretKeyRingProtector protector)
throws PGPException {
PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey);
ThirdPartyDirectKeySignatureBuilder sigBuilder = new ThirdPartyDirectKeySignatureBuilder(secretKey, protector);
if (trustworthiness != null) {
sigBuilder.getHashedSubpackets().setTrust(true, trustworthiness.getDepth(), trustworthiness.getAmount());
}
return new DelegationOnCertificateWithSubpackets(certificate, sigBuilder);
}
}
public static class DelegationOnCertificateWithSubpackets {
private final PGPPublicKeyRing certificate;
private final ThirdPartyDirectKeySignatureBuilder sigBuilder;
DelegationOnCertificateWithSubpackets(@Nonnull PGPPublicKeyRing certificate,
@Nonnull ThirdPartyDirectKeySignatureBuilder sigBuilder) {
this.certificate = certificate;
this.sigBuilder = sigBuilder;
}
/**
* Apply the given signature subpackets and build the delegation signature.
*
* @param subpacketsCallback callback to modify the signatures subpackets
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketsCallback)
throws PGPException {
sigBuilder.applyCallback(subpacketsCallback);
return build();
}
/**
* Build the delegation signature.
*
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
public CertificationResult build() throws PGPException {
PGPPublicKey delegatedKey = certificate.getPublicKey();
PGPSignature delegation = sigBuilder.build(delegatedKey);
PGPPublicKeyRing delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation);
return new CertificationResult(delegatedCertificate, delegation);
}
}
public static class CertificationResult {
private final PGPPublicKeyRing certificate;
private final PGPSignature certification;
CertificationResult(@Nonnull PGPPublicKeyRing certificate, @Nonnull PGPSignature certification) {
this.certificate = certificate;
this.certification = certification;
}
/**
* Return the signature.
*
* @return signature
*/
@Nonnull
public PGPSignature getCertification() {
return certification;
}
/**
* Return the certificate, which now contains the signature.
*
* @return certificate + signature
*/
@Nonnull
public PGPPublicKeyRing getCertifiedCertificate() {
return certificate;
}
}
private static PGPSecretKey getCertifyingSecretKey(PGPSecretKeyRing certificationKey) {
Date now = DateUtil.now();
KeyRingInfo info = PGPainless.inspectKeyRing(certificationKey, now);
// We only support certification-capable primary keys
OpenPgpFingerprint fingerprint = info.getFingerprint();
PGPPublicKey certificationPubKey = info.getPublicKey(fingerprint);
assert (certificationPubKey != null);
if (!info.isKeyValidlyBound(certificationPubKey.getKeyID())) {
throw new KeyException.RevokedKeyException(fingerprint);
}
if (!info.isUsableForThirdPartyCertification()) {
throw new KeyException.UnacceptableThirdPartyCertificationKeyException(fingerprint);
}
Date expirationDate = info.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER);
if (expirationDate != null && expirationDate.before(now)) {
throw new KeyException.ExpiredKeyException(fingerprint, expirationDate);
}
PGPSecretKey secretKey = certificationKey.getSecretKey(certificationPubKey.getKeyID());
if (secretKey == null) {
throw new KeyException.MissingSecretKeyException(fingerprint, certificationPubKey.getKeyID());
}
return secretKey;
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* API for key certifications.
*/
package org.pgpainless.key.certification;

View file

@ -1,114 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>, 2021 Flowcrypt a.s.
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.collection;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPMarker;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.util.ArmorUtils;
/**
* This class describes a logic of handling a collection of different {@link PGPKeyRing}. The logic was inspired by
* {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection}.
*/
public class PGPKeyRingCollection {
private final PGPSecretKeyRingCollection pgpSecretKeyRingCollection;
private final PGPPublicKeyRingCollection pgpPublicKeyRingCollection;
public PGPKeyRingCollection(@Nonnull byte[] encoding, boolean isSilent) throws IOException, PGPException {
this(new ByteArrayInputStream(encoding), isSilent);
}
/**
* Build a {@link PGPKeyRingCollection} from the passed in input stream.
*
* @param in input stream containing data
* @param isSilent flag indicating that unsupported objects will be ignored
* @throws IOException if a problem parsing the base stream occurs
* @throws PGPException if an object is encountered which isn't a {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}
*/
public PGPKeyRingCollection(@Nonnull InputStream in, boolean isSilent) throws IOException, PGPException {
// Double getDecoderStream because of #96
InputStream decoderStream = ArmorUtils.getDecoderStream(in);
PGPObjectFactory pgpFact = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
Object obj;
List<PGPSecretKeyRing> secretKeyRings = new ArrayList<>();
List<PGPPublicKeyRing> publicKeyRings = new ArrayList<>();
while ((obj = pgpFact.nextObject()) != null) {
if (obj instanceof PGPMarker) {
// Skip marker packets
continue;
}
if (obj instanceof PGPSecretKeyRing) {
secretKeyRings.add((PGPSecretKeyRing) obj);
} else if (obj instanceof PGPPublicKeyRing) {
publicKeyRings.add((PGPPublicKeyRing) obj);
} else if (!isSilent) {
throw new PGPException(obj.getClass().getName() + " found where " +
PGPSecretKeyRing.class.getSimpleName() + " or " +
PGPPublicKeyRing.class.getSimpleName() + " expected");
}
}
pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings);
pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings);
}
public PGPKeyRingCollection(@Nonnull Collection<PGPKeyRing> collection, boolean isSilent)
throws PGPException {
List<PGPSecretKeyRing> secretKeyRings = new ArrayList<>();
List<PGPPublicKeyRing> publicKeyRings = new ArrayList<>();
for (PGPKeyRing pgpKeyRing : collection) {
if (pgpKeyRing instanceof PGPSecretKeyRing) {
secretKeyRings.add((PGPSecretKeyRing) pgpKeyRing);
} else if (pgpKeyRing instanceof PGPPublicKeyRing) {
publicKeyRings.add((PGPPublicKeyRing) pgpKeyRing);
} else if (!isSilent) {
throw new PGPException(pgpKeyRing.getClass().getName() + " found where " +
PGPSecretKeyRing.class.getSimpleName() + " or " +
PGPPublicKeyRing.class.getSimpleName() + " expected");
}
}
pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings);
pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings);
}
public @Nonnull PGPSecretKeyRingCollection getPGPSecretKeyRingCollection() {
return pgpSecretKeyRingCollection;
}
public @Nonnull PGPPublicKeyRingCollection getPgpPublicKeyRingCollection() {
return pgpPublicKeyRingCollection;
}
/**
* Return the number of rings in this collection.
*
* @return total size of {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection}
* in this collection
*/
public int size() {
return pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size();
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* OpenPGP key collections.
*/
package org.pgpainless.key.collection;

View file

@ -1,313 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.util.Strings;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.policy.Policy;
import org.pgpainless.provider.ProviderFactory;
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
import org.pgpainless.util.Passphrase;
public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
private static final long YEAR_IN_SECONDS = 1000L * 60 * 60 * 24 * 365;
private KeySpec primaryKeySpec;
private final List<KeySpec> subkeySpecs = new ArrayList<>();
private final Map<String, SelfSignatureSubpackets.Callback> userIds = new LinkedHashMap<>();
private Passphrase passphrase = Passphrase.emptyPassphrase();
private Date expirationDate = new Date(System.currentTimeMillis() + YEAR_IN_SECONDS * 5); // Expiration in 5 years
@Override
public KeyRingBuilder setPrimaryKey(@Nonnull KeySpec keySpec) {
verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy());
verifyMasterKeyCanCertify(keySpec);
this.primaryKeySpec = keySpec;
return this;
}
@Override
public KeyRingBuilder addSubkey(@Nonnull KeySpec keySpec) {
verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy());
this.subkeySpecs.add(keySpec);
return this;
}
@Override
public KeyRingBuilder addUserId(@Nonnull String userId) {
this.userIds.put(userId.trim(), null);
return this;
}
public KeyRingBuilder addUserId(
@Nonnull String userId,
@Nullable SelfSignatureSubpackets.Callback subpacketsCallback) {
this.userIds.put(userId.trim(), subpacketsCallback);
return this;
}
@Override
public KeyRingBuilder addUserId(@Nonnull byte[] userId) {
return addUserId(Strings.fromUTF8ByteArray(userId));
}
@Override
public KeyRingBuilder setExpirationDate(@Nullable Date expirationDate) {
if (expirationDate == null) {
// No expiration
this.expirationDate = null;
return this;
}
Date now = new Date();
if (now.after(expirationDate)) {
throw new IllegalArgumentException("Expiration date must be in the future.");
}
this.expirationDate = expirationDate;
return this;
}
@Override
public KeyRingBuilder setPassphrase(@Nonnull Passphrase passphrase) {
this.passphrase = passphrase;
return this;
}
private void verifyKeySpecCompliesToPolicy(KeySpec keySpec, Policy policy) {
PublicKeyAlgorithm publicKeyAlgorithm = keySpec.getKeyType().getAlgorithm();
int bitStrength = keySpec.getKeyType().getBitStrength();
if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
throw new IllegalArgumentException("Public key algorithm policy violation: " +
publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable.");
}
}
private void verifyMasterKeyCanCertify(KeySpec spec) {
if (!keyIsCertificationCapable(spec)) {
throw new IllegalArgumentException("Key algorithm " + spec.getKeyType().getName() + " is not capable of creating certifications.");
}
}
private boolean keyIsCertificationCapable(KeySpec keySpec) {
return keySpec.getKeyType().canCertify();
}
@Override
public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException {
PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator();
PBESecretKeyEncryptor secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator);
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
passphrase.clear();
// Generate Primary Key
PGPKeyPair certKey = generateKeyPair(primaryKeySpec);
PGPContentSignerBuilder signer = buildContentSigner(certKey);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signer);
SignatureSubpackets hashedSubPacketGenerator = primaryKeySpec.getSubpacketGenerator();
hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.getPublicKey());
if (expirationDate != null) {
hashedSubPacketGenerator.setKeyExpirationTime(certKey.getPublicKey(), expirationDate);
}
if (!userIds.isEmpty()) {
hashedSubPacketGenerator.setPrimaryUserId();
}
PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator();
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator);
PGPSignatureSubpacketVector hashedSubPackets = generator.generate();
PGPKeyRingGenerator ringGenerator;
if (userIds.isEmpty()) {
ringGenerator = new PGPKeyRingGenerator(
certKey,
keyFingerprintCalculator,
hashedSubPackets,
null,
signer,
secretKeyEncryptor);
} else {
String primaryUserId = userIds.entrySet().iterator().next().getKey();
ringGenerator = new PGPKeyRingGenerator(
SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
primaryUserId, keyFingerprintCalculator,
hashedSubPackets, null, signer, secretKeyEncryptor);
}
addSubKeys(certKey, ringGenerator);
// Generate secret key ring with only primary user id
PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing();
Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
// Attempt to add additional user-ids to the primary public key
PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey();
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor);
Iterator<Map.Entry<String, SelfSignatureSubpackets.Callback>> userIdIterator =
this.userIds.entrySet().iterator();
if (userIdIterator.hasNext()) {
userIdIterator.next(); // Skip primary user id
}
while (userIdIterator.hasNext()) {
Map.Entry<String, SelfSignatureSubpackets.Callback> additionalUserId = userIdIterator.next();
String userIdString = additionalUserId.getKey();
SelfSignatureSubpackets.Callback callback = additionalUserId.getValue();
SelfSignatureSubpackets subpackets = null;
if (callback == null) {
subpackets = hashedSubPacketGenerator;
subpackets.setPrimaryUserId(null);
// additional user-ids are not primary
} else {
subpackets = SignatureSubpackets.createHashedSubpackets(primaryPubKey);
callback.modifyHashedSubpackets(subpackets);
}
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey);
signatureGenerator.setHashedSubpackets(
SignatureSubpacketsHelper.toVector((SignatureSubpackets) subpackets));
PGPSignature additionalUserIdSignature =
signatureGenerator.generateCertification(userIdString, primaryPubKey);
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
userIdString, additionalUserIdSignature);
}
// "reassemble" secret key ring with modified primary key
PGPSecretKey primarySecKey = new PGPSecretKey(
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor);
List<PGPSecretKey> secretKeyList = new ArrayList<>();
secretKeyList.add(primarySecKey);
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next());
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
return secretKeyRing;
}
private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator)
throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException {
for (KeySpec subKeySpec : subkeySpecs) {
PGPKeyPair subKey = generateKeyPair(subKeySpec);
if (subKeySpec.isInheritedSubPackets()) {
ringGenerator.addSubKey(subKey);
} else {
PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets();
try {
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(
primaryKey, subKey, hashedSubpackets);
} catch (IOException e) {
throw new PGPException("Exception while adding primary key binding signature to signing subkey", e);
}
ringGenerator.addSubKey(subKey, hashedSubpackets, null);
}
}
}
private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary(
PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets)
throws PGPException, IOException {
int keyFlagMask = hashedSubpackets.getKeyFlags();
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
return hashedSubpackets;
}
PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey));
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey());
PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey());
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets);
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig);
return subpacketGenerator.generate();
}
private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) {
HashAlgorithm hashAlgorithm = PGPainless.getPolicy()
.getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
certKey.getPublicKey().getAlgorithm(),
hashAlgorithm.getAlgorithmId());
}
private PBESecretKeyEncryptor buildSecretKeyEncryptor(PGPDigestCalculator keyFingerprintCalculator) {
SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy()
.getSymmetricKeyEncryptionAlgorithmPolicy()
.getDefaultSymmetricKeyAlgorithm();
if (!passphrase.isValid()) {
throw new IllegalStateException("Passphrase was cleared.");
}
return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase);
}
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
if (!passphrase.isValid()) {
throw new IllegalStateException("Passphrase was cleared.");
}
return passphrase.isEmpty() ? null :
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
}
public static PGPKeyPair generateKeyPair(KeySpec spec)
throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException {
KeyType type = spec.getKeyType();
KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(),
ProviderFactory.getProvider());
certKeyGenerator.initialize(type.getAlgorithmSpec());
// Create raw Key Pair
KeyPair keyPair = certKeyGenerator.generateKeyPair();
Date keyCreationDate = spec.getKeyCreationDate() != null ? spec.getKeyCreationDate() : new Date();
// Form PGP key pair
PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance()
.getPGPKeyPair(type.getAlgorithm(), keyPair, keyCreationDate);
return pgpKeyPair;
}
}

View file

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.key.util.UserId;
import org.pgpainless.util.Passphrase;
public interface KeyRingBuilderInterface<B extends KeyRingBuilderInterface<B>> {
B setPrimaryKey(@Nonnull KeySpec keySpec);
default B setPrimaryKey(@Nonnull KeySpecBuilder builder) {
return setPrimaryKey(builder.build());
}
B addSubkey(@Nonnull KeySpec keySpec);
default B addSubkey(@Nonnull KeySpecBuilder builder) {
return addSubkey(builder.build());
}
default B addUserId(UserId userId) {
return addUserId(userId.toString());
}
B addUserId(@Nonnull String userId);
B addUserId(@Nonnull byte[] userId);
B setExpirationDate(@Nonnull Date expirationDate);
B setPassphrase(@Nonnull Passphrase passphrase);
PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException;
}

View file

@ -1,255 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
import org.pgpainless.key.generation.type.rsa.RsaLength;
import org.pgpainless.key.generation.type.xdh.XDHSpec;
import org.pgpainless.util.Passphrase;
public final class KeyRingTemplates {
public KeyRingTemplates() {
}
/**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
*
* @param userId userId or null
* @param length length of the RSA keys
* @return key
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId,
@Nonnull RsaLength length)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
return rsaKeyRing(userId, length, Passphrase.emptyPassphrase());
}
/**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
*
* @param userId userId or null
* @param length length of the RSA keys
* @param password passphrase to encrypt the key with
* @return key
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId,
@Nonnull RsaLength length,
@Nonnull String password)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
Passphrase passphrase = Passphrase.emptyPassphrase();
if (!isNullOrEmpty(password)) {
passphrase = Passphrase.fromPassword(password);
}
return rsaKeyRing(userId, length, passphrase);
}
/**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
*
* @param userId userId or null
* @param length length of the RSA keys
* @param passphrase passphrase to encrypt the key with
* @return key
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId,
@Nonnull RsaLength length,
@Nonnull Passphrase passphrase)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
KeyRingBuilder builder = PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
.addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE));
if (userId != null) {
builder.addUserId(userId.toString());
}
if (!passphrase.isEmpty()) {
builder.setPassphrase(passphrase);
}
return builder.build();
}
/**
* Creates a simple, unencrypted RSA KeyPair of length {@code length} with user-id {@code userId}.
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
*
* @param userId user id.
* @param length length in bits.
*
* @return {@link PGPSecretKeyRing} containing the KeyPair.
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
return simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase());
}
public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nonnull Passphrase passphrase)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
KeyRingBuilder builder = PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS))
.setPassphrase(passphrase);
if (userId != null) {
builder.addUserId(userId.toString());
}
return builder.build();
}
/**
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
*
* @param userId user id.
* @param length length in bits.
* @param password Password of the key. Can be null for unencrypted keys.
*
* @return {@link PGPSecretKeyRing} containing the KeyPair.
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nullable String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
Passphrase passphrase = Passphrase.emptyPassphrase();
if (!isNullOrEmpty(password)) {
passphrase = Passphrase.fromPassword(password);
}
return simpleRsaKeyRing(userId, length, passphrase);
}
/**
* Creates a key ring consisting of an ed25519 EdDSA primary key and a curve25519 XDH subkey.
* The EdDSA primary key is used for signing messages and certifying the sub key.
* The XDH subkey is used for encryption and decryption of messages.
*
* @param userId user-id
*
* @return {@link PGPSecretKeyRing} containing the key pairs.
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
return simpleEcKeyRing(userId, Passphrase.emptyPassphrase());
}
/**
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey.
* The EdDSA primary key is used for signing messages and certifying the sub key.
* The XDH subkey is used for encryption and decryption of messages.
*
* @param userId user-id
* @param password Password of the private key. Can be null for an unencrypted key.
*
* @return {@link PGPSecretKeyRing} containing the key pairs.
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
Passphrase passphrase = Passphrase.emptyPassphrase();
if (!isNullOrEmpty(password)) {
passphrase = Passphrase.fromPassword(password);
}
return simpleEcKeyRing(userId, passphrase);
}
public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
KeyRingBuilder builder = PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
.setPassphrase(passphrase);
if (userId != null) {
builder.addUserId(userId.toString());
}
return builder.build();
}
/**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
*
* @param userId primary user id
* @return key ring
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
return modernKeyRing(userId, Passphrase.emptyPassphrase());
}
/**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
*
* @param userId primary user id
* @param password passphrase or null if the key should be unprotected.
* @return key ring
*
* @throws InvalidAlgorithmParameterException in case of invalid key generation parameters
* @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider
* @throws PGPException in case of an OpenPGP related error
*/
public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nullable String password)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
Passphrase passphrase = (password != null ? Passphrase.fromPassword(password) : Passphrase.emptyPassphrase());
return modernKeyRing(userId, passphrase);
}
public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
KeyRingBuilder builder = PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
.addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
.setPassphrase(passphrase);
if (userId != null) {
builder.addUserId(userId.toString());
}
return builder.build();
}
private static boolean isNullOrEmpty(String password) {
return password == null || password.trim().isEmpty();
}
}

View file

@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.signature.subpackets.SignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
import java.util.Date;
public class KeySpec {
private final KeyType keyType;
private final SignatureSubpackets subpacketGenerator;
private final boolean inheritedSubPackets;
private final Date keyCreationDate;
KeySpec(@Nonnull KeyType type,
@Nonnull SignatureSubpackets subpacketGenerator,
boolean inheritedSubPackets,
@Nullable Date keyCreationDate) {
this.keyType = type;
this.subpacketGenerator = subpacketGenerator;
this.inheritedSubPackets = inheritedSubPackets;
this.keyCreationDate = keyCreationDate;
}
@Nonnull
public KeyType getKeyType() {
return keyType;
}
@Nonnull
public PGPSignatureSubpacketVector getSubpackets() {
return SignatureSubpacketsHelper.toVector(subpacketGenerator);
}
@Nonnull
public SignatureSubpackets getSubpacketGenerator() {
return subpacketGenerator;
}
boolean isInheritedSubPackets() {
return inheritedSubPackets;
}
@Nullable
public Date getKeyCreationDate() {
return keyCreationDate;
}
public static KeySpecBuilder getBuilder(KeyType type, KeyFlag... flags) {
return new KeySpecBuilder(type, flags);
}
}

View file

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.Nonnull;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.AlgorithmSuite;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.Feature;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
public class KeySpecBuilder implements KeySpecBuilderInterface {
private final KeyType type;
private final KeyFlag[] keyFlags;
private final SelfSignatureSubpackets hashedSubpackets = new SignatureSubpackets();
private final AlgorithmSuite algorithmSuite = PGPainless.getPolicy().getKeyGenerationAlgorithmSuite();
private Set<CompressionAlgorithm> preferredCompressionAlgorithms = algorithmSuite.getCompressionAlgorithms();
private Set<HashAlgorithm> preferredHashAlgorithms = algorithmSuite.getHashAlgorithms();
private Set<SymmetricKeyAlgorithm> preferredSymmetricAlgorithms = algorithmSuite.getSymmetricKeyAlgorithms();
private Date keyCreationDate;
KeySpecBuilder(@Nonnull KeyType type, KeyFlag... flags) {
if (flags == null) {
this.keyFlags = new KeyFlag[0];
} else {
SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, flags);
this.keyFlags = flags;
}
this.type = type;
}
@Override
public KeySpecBuilder overridePreferredCompressionAlgorithms(
@Nonnull CompressionAlgorithm... compressionAlgorithms) {
this.preferredCompressionAlgorithms = new LinkedHashSet<>(Arrays.asList(compressionAlgorithms));
return this;
}
@Override
public KeySpecBuilder overridePreferredHashAlgorithms(
@Nonnull HashAlgorithm... preferredHashAlgorithms) {
this.preferredHashAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredHashAlgorithms));
return this;
}
@Override
public KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(
@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms) {
for (SymmetricKeyAlgorithm algo : preferredSymmetricKeyAlgorithms) {
if (algo == SymmetricKeyAlgorithm.NULL) {
throw new IllegalArgumentException("NULL (unencrypted) is an invalid symmetric key algorithm preference.");
}
}
this.preferredSymmetricAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredSymmetricKeyAlgorithms));
return this;
}
@Override
public KeySpecBuilder setKeyCreationDate(@Nonnull Date creationDate) {
this.keyCreationDate = creationDate;
return this;
}
@Override
public KeySpec build() {
this.hashedSubpackets.setKeyFlags(keyFlags);
this.hashedSubpackets.setPreferredCompressionAlgorithms(preferredCompressionAlgorithms);
this.hashedSubpackets.setPreferredHashAlgorithms(preferredHashAlgorithms);
this.hashedSubpackets.setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms);
this.hashedSubpackets.setFeatures(Feature.MODIFICATION_DETECTION);
return new KeySpec(type, (SignatureSubpackets) hashedSubpackets, false, keyCreationDate);
}
}

View file

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation;
import javax.annotation.Nonnull;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import java.util.Date;
public interface KeySpecBuilderInterface {
KeySpecBuilder overridePreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... compressionAlgorithms);
KeySpecBuilder overridePreferredHashAlgorithms(@Nonnull HashAlgorithm... preferredHashAlgorithms);
KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms);
KeySpecBuilder setKeyCreationDate(@Nonnull Date creationDate);
KeySpec build();
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes related to OpenPGP key generation.
*/
package org.pgpainless.key.generation;

View file

@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation.type;
public interface KeyLength {
int getLength();
}

View file

@ -1,118 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation.type;
import java.security.spec.AlgorithmParameterSpec;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
import org.pgpainless.key.generation.type.ecc.ecdh.ECDH;
import org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA;
import org.pgpainless.key.generation.type.eddsa.EdDSA;
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
import org.pgpainless.key.generation.type.rsa.RsaLength;
import org.pgpainless.key.generation.type.rsa.RSA;
import org.pgpainless.key.generation.type.xdh.XDH;
import org.pgpainless.key.generation.type.xdh.XDHSpec;
public interface KeyType {
/**
* Return the encryption algorithm name.
*
* @return algorithm name.
*/
String getName();
/**
* Return the public key algorithm.
*
* @return public key algorithm
*/
PublicKeyAlgorithm getAlgorithm();
/**
* Return the strength of the key in bits.
* @return strength of the key in bits
*/
int getBitStrength();
/**
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key.
*
* @return algorithm parameter spec
*/
AlgorithmParameterSpec getAlgorithmSpec();
/**
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag.
* See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
*
* @return true if the key can sign.
*/
default boolean canSign() {
return getAlgorithm().isSigningCapable();
}
/**
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag.
* See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
*
* @return true if the key is able to certify other keys
*/
default boolean canCertify() {
return canSign();
}
/**
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag.
* See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
*
* @return true if the key can be used for authentication purposes.
*/
default boolean canAuthenticate() {
return canSign();
}
/**
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag.
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
*
* @return true if the key can encrypt communication
*/
default boolean canEncryptCommunication() {
return getAlgorithm().isEncryptionCapable();
}
/**
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag.
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*
* @return true if the key can encrypt for storage
*/
default boolean canEncryptStorage() {
return getAlgorithm().isEncryptionCapable();
}
static KeyType RSA(RsaLength length) {
return RSA.withLength(length);
}
static KeyType ECDH(EllipticCurve curve) {
return ECDH.fromCurve(curve);
}
static KeyType ECDSA(EllipticCurve curve) {
return ECDSA.fromCurve(curve);
}
static KeyType EDDSA(EdDSACurve curve) {
return EdDSA.fromCurve(curve);
}
static KeyType XDH(XDHSpec curve) {
return XDH.fromSpec(curve);
}
}

View file

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation.type.ecc;
import javax.annotation.Nonnull;
import org.pgpainless.key.generation.type.xdh.XDHSpec;
/**
* Elliptic curves for use with
* {@link org.pgpainless.key.generation.type.ecc.ecdh.ECDH}/{@link org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA}.
* For curve25519 related curve definitions see
* {@link XDHSpec} and {@link org.pgpainless.key.generation.type.eddsa.EdDSACurve}.
*/
public enum EllipticCurve {
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32
_P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
_P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
_SECP256K1("secp256k1", 256),
_BRAINPOOLP256R1("brainpoolP256r1", 256),
_BRAINPOOLP384R1("brainpoolP384r1", 384),
_BRAINPOOLP512R1("brainpoolP512r1", 512)
;
private final String name;
private final int bitStrength;
EllipticCurve(@Nonnull String name, int bitStrength) {
this.name = name;
this.bitStrength = bitStrength;
}
public String getName() {
return name;
}
public int getBitStrength() {
return bitStrength;
}
}

View file

@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation.type.ecc.ecdh;
import javax.annotation.Nonnull;
import java.security.spec.AlgorithmParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
public final class ECDH implements KeyType {
private final EllipticCurve curve;
private ECDH(EllipticCurve curve) {
this.curve = curve;
}
public static ECDH fromCurve(@Nonnull EllipticCurve curve) {
return new ECDH(curve);
}
@Override
public String getName() {
return "ECDH";
}
@Override
public PublicKeyAlgorithm getAlgorithm() {
return PublicKeyAlgorithm.ECDH;
}
@Override
public int getBitStrength() {
return curve.getBitStrength();
}
@Override
public AlgorithmParameterSpec getAlgorithmSpec() {
return new ECNamedCurveGenParameterSpec(curve.getName());
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes related to ECDH.
*/
package org.pgpainless.key.generation.type.ecc.ecdh;

View file

@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation.type.ecc.ecdsa;
import java.security.spec.AlgorithmParameterSpec;
import javax.annotation.Nonnull;
import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
import org.pgpainless.key.generation.type.KeyType;
public final class ECDSA implements KeyType {
private final EllipticCurve curve;
private ECDSA(@Nonnull EllipticCurve curve) {
this.curve = curve;
}
public static ECDSA fromCurve(@Nonnull EllipticCurve curve) {
return new ECDSA(curve);
}
@Override
public String getName() {
return "ECDSA";
}
@Override
public PublicKeyAlgorithm getAlgorithm() {
return PublicKeyAlgorithm.ECDSA;
}
@Override
public int getBitStrength() {
return curve.getBitStrength();
}
@Override
public AlgorithmParameterSpec getAlgorithmSpec() {
return new ECNamedCurveGenParameterSpec(curve.getName());
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Classes related to ECDSA.
*/
package org.pgpainless.key.generation.type.ecc.ecdsa;

Some files were not shown because too many files have changed in this diff Show more