mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-11 11:19:39 +02:00
Compare commits
No commits in common. "main" and "1.3.11" have entirely different histories.
607 changed files with 26926 additions and 32867 deletions
591
.editorconfig
591
.editorconfig
|
@ -1,51 +1,513 @@
|
|||
# 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-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
|
||||
#
|
||||
# It includes editor-specific config options for IntelliJ IDEA.
|
||||
#
|
||||
# If any option is wrong, PR are welcome
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[{*.kt,*.kts}]
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
ij_continuation_indent_size = 4 # was 8
|
||||
ij_java_names_count_to_use_import_on_demand = 9999
|
||||
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}]
|
||||
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 = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_assignment_wrap = normal
|
||||
ij_kotlin_allow_trailing_comma = false
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = false
|
||||
ij_kotlin_assignment_wrap = off
|
||||
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 = true
|
||||
ij_kotlin_call_parameters_new_line_after_left_paren = false
|
||||
ij_kotlin_call_parameters_right_paren_on_new_line = false
|
||||
ij_kotlin_call_parameters_wrap = on_every_item
|
||||
ij_kotlin_call_parameters_wrap = off
|
||||
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 = 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_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_else_on_new_line = false
|
||||
ij_kotlin_enum_constants_wrap = off
|
||||
ij_kotlin_extends_list_wrap = normal
|
||||
ij_kotlin_extends_list_wrap = off
|
||||
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
|
||||
|
@ -57,13 +519,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 = 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_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_parameter_annotation_wrap = off
|
||||
ij_kotlin_space_after_comma = true
|
||||
ij_kotlin_space_after_extend_colon = true
|
||||
|
@ -90,5 +552,72 @@ 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 = 1
|
||||
ij_kotlin_wrap_expression_body_functions = 0
|
||||
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
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# Ignore initial spotlessApply using ktfmt
|
||||
51e9bfc67f19e16a69790a8d92bd6b1c86a76a5f
|
35
.github/ISSUE_TEMPLATE/cli-application.md
vendored
35
.github/ISSUE_TEMPLATE/cli-application.md
vendored
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
name: CLI Application
|
||||
about: Report an issue with the pgpainless-cli utility
|
||||
title: ''
|
||||
labels: 'module: cli'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Version**
|
||||
<!-- What version of the software are you using? -->
|
||||
- `pgpainless-cli`:
|
||||
|
||||
**Installation Source**
|
||||
<!-- Where did you install / build pgpainless-cli from? -->
|
||||
- Debian Repository
|
||||
- Built locally (`gradle build...`)
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. `pgpainless-cli foo bar [...]`'
|
||||
2. ...
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context (test keys, test messages) about the problem here. -->
|
||||
```
|
||||
-----BEGIN PGP FOO BAR-----
|
||||
...
|
||||
```
|
28
.github/ISSUE_TEMPLATE/library.md
vendored
28
.github/ISSUE_TEMPLATE/library.md
vendored
|
@ -1,28 +0,0 @@
|
|||
---
|
||||
name: Library
|
||||
about: Report an issue with the libraries pgpainless-core or pgpainless-sop
|
||||
title: ''
|
||||
labels: 'module: core'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Version**
|
||||
<!-- What version of the software are you using? Delete lines which are not applicable. -->
|
||||
- `pgpainless-core`:
|
||||
- `pgpainless-sop`:
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
```
|
||||
Example Code Block with your Code
|
||||
```
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
12
.github/workflows/codeql-analysis.yml
vendored
12
.github/workflows/codeql-analysis.yml
vendored
|
@ -17,10 +17,10 @@ name: "CodeQL"
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, release/* ]
|
||||
branches: [ master, release/* ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '16 10 * * 0'
|
||||
|
||||
|
@ -36,7 +36,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'java-kotlin' ]
|
||||
language: [ 'java' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
|
@ -46,7 +46,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -57,7 +57,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -71,4 +71,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
|
27
.github/workflows/depencency-submission.yml
vendored
27
.github/workflows/depencency-submission.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2023 Paul Schaub <info@pgpainless.org>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: Dependencies
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Dependencies
|
||||
runs-on: ubuntu-latest
|
||||
permissions: # The Dependency Submission API requires write permission
|
||||
contents: write
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run snapshot action
|
||||
uses: mikepenz/gradle-dependency-submission@v0.8.6
|
||||
with:
|
||||
gradle-build-module: |-
|
||||
:pgpainless-core
|
||||
:pgpainless-sop
|
||||
:pgpainless-cli
|
||||
sub-module-mode: |-
|
||||
INDIVIDUAL_DEEP
|
10
.github/workflows/gradle_push.yml
vendored
10
.github/workflows/gradle_push.yml
vendored
|
@ -9,7 +9,7 @@
|
|||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Push
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -28,13 +28,9 @@ jobs:
|
|||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
- name: Build and Check
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
with:
|
||||
arguments: check jacocoRootReport
|
||||
- name: Coveralls
|
||||
- name: Build, Check and Coverage
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
with:
|
||||
arguments: coveralls
|
||||
arguments: check jacocoRootReport coveralls
|
33
.github/workflows/pr.yml
vendored
33
.github/workflows/pr.yml
vendored
|
@ -1,33 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
- name: Build, Check and Coverage
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
with:
|
||||
arguments: check jacocoRootReport
|
71
.reuse/dep5
Normal file
71
.reuse/dep5
Normal file
|
@ -0,0 +1,71 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: PGPainless
|
||||
Upstream-Contact: Paul Schaub <info@pgpainless.org>
|
||||
Source: https://pgpainless.org
|
||||
|
||||
# Sample paragraph, commented out:
|
||||
#
|
||||
# Files: src/*
|
||||
# Copyright: $YEAR $NAME <$CONTACT>
|
||||
# License: ...
|
||||
|
||||
# Documentation
|
||||
Files: docs/*
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: CC-BY-3.0
|
||||
|
||||
Files: .readthedocs.yaml
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
# Gradle build tool
|
||||
Files: gradle*
|
||||
Copyright: 2015 the original author or authors.
|
||||
License: Apache-2.0
|
||||
|
||||
# PGPainless Logo
|
||||
Files: assets/repository-open-graph.png
|
||||
Copyright: 2021 Paul Schaub <info@pgpainless.org>
|
||||
License: CC-BY-3.0
|
||||
|
||||
Files: assets/pgpainless.svg
|
||||
Copyright: 2021 Paul Schaub <info@pgpainless.org>
|
||||
License: CC-BY-3.0
|
||||
|
||||
Files: assets/logo.png
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: CC-BY-3.0
|
||||
|
||||
Files: assets/test_vectors/*
|
||||
Copyright: 2018 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: pgpainless-core/src/test/resources/*
|
||||
Copyright: 2020 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: audit/*
|
||||
Copyright: 2021 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
# GH Pages
|
||||
Files: CNAME
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: _config.yml
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: _layouts/*
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>, 2017 Steve Smith
|
||||
License: CC-BY-SA-3.0
|
||||
|
||||
# Man Pages
|
||||
Files: pgpainless-cli/rewriteManPages.sh
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: Apache-2.0
|
||||
|
||||
Files: pgpainless-cli/packaging/man/*
|
||||
Copyright: 2022 Paul Schaub <info@pgpainless.org>
|
||||
License: Apache-2.0
|
305
CHANGELOG.md
305
CHANGELOG.md
|
@ -5,311 +5,6 @@ SPDX-License-Identifier: CC0-1.0
|
|||
|
||||
# PGPainless Changelog
|
||||
|
||||
## 1.7.7-SNAPSHOT
|
||||
- Bump `bcpg-jdk8on` to `1.81`
|
||||
- Bump `bcprov-jdk18on` to `1.81`
|
||||
|
||||
## 1.7.6
|
||||
- Fix `RevocationSignatureBuilder` properly calculating third-party signatures of type `KeyRevocation` (delegation revocations)
|
||||
- Enable support for native images
|
||||
- Re-enable shadow plugin and build fat-jar
|
||||
|
||||
## 1.7.5
|
||||
- Actually attempt to fix Kotlin desugaring.
|
||||
- Bump javaSourceCompatibility and javaTargetCompatibility to 11
|
||||
- Bump gradle-wrapper to 8.8
|
||||
|
||||
## 1.7.4
|
||||
- Fix proper Kotlin desugaring for Java 8
|
||||
|
||||
## 1.7.3
|
||||
- Bump `bcpg-jdk8on` to `1.80`
|
||||
- Bump `bcprov-jdk18on` to `1.80`
|
||||
- Add dependency on `bcutil-jdk18on` as a workaround
|
||||
- Ignore unknown type signatures on certificates
|
||||
- Fix typo on signature creation bounds check (thanks @elduffy)
|
||||
- Fix superfluous newline added in CRLF encoding (thanks @bjansen)
|
||||
- Bump `sop-java` to `1.10.0`
|
||||
- SOP inline-sign: Do not apply compression
|
||||
|
||||
## 1.7.2
|
||||
- Fix bug in `KeyRingInfo.lastModified` (thanks to @Jerbell, @sosnovsky for reporting)
|
||||
- Bump `sop-java` to `10.0.3`
|
||||
- allow multiple arguments `--with-key-password` in `revoke-key` command
|
||||
- Properly pass `--old-key-password` and `--new-key-password` options as indirect arguments in `change-key-password` command
|
||||
|
||||
## 1.7.1
|
||||
- Bump `sop-java` to `10.0.2`
|
||||
- Downgrade `logback-core` and `logback-classic` to `1.2.13` (fix CLI spam)
|
||||
|
||||
## 1.7.0
|
||||
- Bump `bcpg-jdk8on` to `1.78.1`
|
||||
- Bump `bcprov-jdk18on` to `1.78.1`
|
||||
- Bump `logback-core` and `logback-classic` to `1.4.14`
|
||||
- `pgpainless-core`
|
||||
- Rewrote most of the codebase in Kotlin
|
||||
- Removed `OpenPgpMetadata` (`decryptionStream.getResult()`) in favor of `MessageMetadata` (`decryptionStream.getMetadata()`)
|
||||
- Removed support for generating EC keys over non-standard curve `secp256k1`
|
||||
- Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice)
|
||||
- Do not choke on unknown signature subpackets (thanks @Jerbell)
|
||||
- Prevent timing issues resulting in subkey binding signatures predating the subkey (@thanks Jerbell)
|
||||
- Rename LibrePGP-related `Feature` enums:
|
||||
- `GNUPG_AEAD_ENCRYPTED_DATA` -> `LIBREPGP_OCB_ENCRYPTED_DATA`
|
||||
- `GNUPG_VERSION_5_PUBLIC_KEY` -> `LIBREPGP_VERSION_5_PUBLIC_KEY`
|
||||
- Properly reject signatures by non-signing primary keys
|
||||
- Add `EncryptionBuilder.discardOutput()` (useful for detached signing)
|
||||
- Remove support for generation of keys over non-standard `secp256k1` curve
|
||||
- Add base support for padding packets
|
||||
- Do not choke on LibrePGP OED packets
|
||||
- Supersede `addPassphrase()`/`addDecryptionPassphrase()` methods with more clear `addMessagePassphrase()`
|
||||
- `pgpainless-sop`, `pgpainless-cli`
|
||||
- Bump `sop-java` to `10.0.1`, implementing [SOP Spec Revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html)
|
||||
- Change API of `sop.encrypt` to return a `ReadyWithResult<EncryptionResult>` to expose the session key
|
||||
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
|
||||
- Separate signature verification operations into `SOPV` interface
|
||||
- Add `version --sopv` option
|
||||
- Throw `BadData` error when passing KEYS where CERTS are expected.
|
||||
- `armor`: Remove `--label` option
|
||||
|
||||
## 1.6.8
|
||||
- Bump `sop-java` to `7.0.2`
|
||||
- SOP `change-key-password`: Fix reading password from indirect parameter instead of erroneously passing filename (fixes #453)
|
||||
- SOP `revoke-key`: Allow for multiple `--with-key-password` options
|
||||
|
||||
## 1.6.7
|
||||
- SOP: Fix OOM error when detached-signing large amounts of data (fix #432)
|
||||
- Move `CachingBcPublicKeyDataDecryptorFactory` from `org.bouncycastle` packet to `org.pgpainless.decryption_verification` to avoid package split (partially addresses #428)
|
||||
- Basic support for Java Modules for `pgpainless-core` and `pgpainless-sop`
|
||||
- Added `Automatic-Module-Name` directive to gradle build files
|
||||
|
||||
## 1.6.6
|
||||
- Downgrade `logback-core` and `logback-classic` to `1.2.13` to fix #426
|
||||
|
||||
## 1.6.5
|
||||
- Add `SecretKeyRingEditor.setExpirationDateOfSubkey()`
|
||||
|
||||
## 1.6.4
|
||||
- Bump `bcpg-jdk8on` to `1.77`
|
||||
- Bump `bcprov-jdk18on` to `1.77`
|
||||
- Bump `logback-core` and `logback-classic` to `1.4.13`
|
||||
- Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice)
|
||||
- Do not choke on unknown signature subpackets (thanks @Jerbell)
|
||||
- Prevent timing issues resuting in subkey binding signatures predating the subkey (@thanks Jerbell)
|
||||
|
||||
## 1.6.3
|
||||
- Bump `sop-java` to `7.0.1`
|
||||
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
|
||||
|
||||
## 1.6.2
|
||||
- Switch `bcpg` and `bcprov` artifacts from `-jdk15to18`variant to `-jdk18on`
|
||||
- Bump `bcpg-jdk8on` to `1.76`
|
||||
- Bump `bcprov-jdk18on` to `1.76`
|
||||
- Add `EncryptionOptions.setAllowEncryptionWithMissingKeyFlags()` to properly allow
|
||||
encrypting to legacy keys which do not carry any key flags.
|
||||
- Allow overriding of reference time in `EncryptionOptions` and `SigningOptions`.
|
||||
|
||||
## 1.6.1
|
||||
- `KeyRingBuilder`: Require UTF8 when adding user-ID via `addUserId(byte[])`
|
||||
- `pgpainless-sop`: Remove dependency on jetbrains annotations
|
||||
- Add `CertificateAuthority` interface to allow integration with [`pgpainless-wot`](https://github.com/pgpainless/pgpainless-wot)
|
||||
- Add `EncryptionOptions.addAuthenticatableRecipients()` method
|
||||
- Add `MessageMetadata.isAuthenticatablySignedBy()` method
|
||||
|
||||
## 1.6.0
|
||||
- Bump `sop-java` to `7.0.0`, implementing [SOP Spec Revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html)
|
||||
- Implement `revoke-key` subcommand and API
|
||||
- Implement `change-key-password` subcommand and API
|
||||
- `generate-key`: Add support for new `--signing-only` option
|
||||
- Move some methods related to password changing from `SecretKeyRingEditor` to `KeyRingUtils`
|
||||
|
||||
## 1.5.7
|
||||
- Bump `sop-java` to `6.1.1`
|
||||
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
|
||||
|
||||
## 1.5.6
|
||||
- Bump `jacoco` to `0.8.8` (thanks @hkos)
|
||||
- Ignore malformed, non-UTF8 user-IDs on certificates
|
||||
- `KeyRingReader.readPublicKeyRingCollection()`: Extract and return public keys from encountered secret keys
|
||||
- Add some utility methods to `KeyRingInfo`:
|
||||
- `getValidSubkeys()` only returns validly bound sub-keys
|
||||
- Add some utility methods to `SignatureUtils`:
|
||||
- `getDelegations()` returns all third-party signatures made over the primary key
|
||||
- `get3rdPartyCertificationsFor(userId)` returns all third-party certification signatures made over the given user-id
|
||||
- Add some utility methods to `SignatureSubpacketsUtil`:
|
||||
- `isExportable()` will return true if the signature is *not* marked as non-exportable
|
||||
- `getTrustDepthOr()` returns the signatures trust-depth, or a default value if there is no trust-signature subpacket
|
||||
- `getTrustAmountOr()` returns the signatures trust-amount, or a default value if there is no trust-signature subpacket
|
||||
|
||||
## 1.5.5
|
||||
- Bump `bcpg-jdk15to18` to `1.75`
|
||||
- Bump `bcprov-jdk15to18` to `1.75`
|
||||
- Bump `checkstyle` to `10.12.1` to fix build dependency on [vulnerable guava](https://github.com/pgpainless/pgpainless/security/dependabot/6).
|
||||
- `SecretKeyRingEditor`:
|
||||
- Rename `createRevocationCertificate()` to `createRevocation()`
|
||||
- Add `createMinimalRevocationCertificate()` method to generate OpenPGP v6-style self-certifying revocation certificates
|
||||
|
||||
## 1.5.4
|
||||
- Bump `bcpg-jdk15to18` to `1.74`
|
||||
- Bump `bcprov-jdk15to18` to `1.74`
|
||||
- Remove unused methods from `SignatureUtils`
|
||||
- Encryption: Allow anonymous recipients using wildcard key-IDs
|
||||
- Add `SignatureSubpacketsUtil.getRegularExpressions()`
|
||||
- Tests, tests, tests
|
||||
|
||||
## 1.5.3
|
||||
- Fix minimal bit-strength check for signing-subkeys accidentally comparing the bit-strength of the primary key
|
||||
- `SigningOptions`: Add new methods to add signatures using a single, chosen signing subkey
|
||||
|
||||
## 1.5.2
|
||||
- Bugfix: Create proper direct-key signatures
|
||||
- `KeyRingTemplates`:
|
||||
- Add `rsaKeyRing()` for generating RSA keys with primary key and dedicated signing, encryption subkeys
|
||||
- Reduce number of template methods by replacing `UserId`, `String` arguments with `CharSequence`
|
||||
- Add `MessageMetadata.getRecipientKeyIds()`
|
||||
- Work towards more null-safe API by annotating methods in `EncryptionOptions`, `SigningOptions`, `KeyRingInfo`, `PGPainless` with `@Nonnull`, `@Nullable`
|
||||
- `KeyRingUtils`: Removed `removeSecretKey()` in favour of `stripSecretKey()`
|
||||
- General code cleanup
|
||||
- SOP: generating keys with `rfc4880` profile now generates key with primary key and subkeys
|
||||
- Deprecate ElGamal key type
|
||||
- Key generation: Set expiration period of 5 years by default
|
||||
- Set AES-128 as default fallback symmetric algorithm
|
||||
- `ProducerOptions`: Allow setting custom version header when encrypting/signing message
|
||||
|
||||
## 1.5.2-rc1
|
||||
- Bump `sop-java` to `6.1.0`
|
||||
- Normalize `OpenPgpMessageInputStream.read()` behaviour when reading past the stream
|
||||
- Instead of throwing a `MalformedOpenPgpMessageException` which could throw off unsuspecting parsers,
|
||||
we now simply return `-1` like every other `InputStream`.
|
||||
|
||||
## 1.5.1
|
||||
- SOP: Emit signature `mode:{binary|text}` in `Verification` results
|
||||
- core: Relax constraints on decryption subkeys to improve interoperability with broken clients
|
||||
- Allow decryption with revoked keys
|
||||
- Allow decryption with expired keys
|
||||
- Allow decryption with erroneously addressed keys without encryption key flags
|
||||
|
||||
## 1.5.0
|
||||
- Bump `bcpg-jdk15to18` to `1.73`
|
||||
- Bump `bcprov-jdk15to18` to `1.73`
|
||||
- Introduce `OpenPgpv6Fingerprint` class
|
||||
- Bump `sop-java` to `5.0.0`, implementing [SOP Spec Revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html)
|
||||
- Add support for `list-profiles` subcommand (`generate-key` only for now)
|
||||
- `generate-key`: Add support for `--profile=` option
|
||||
- Add profile `draft-koch-eddsa-for-openpgp-00` which represents status quo.
|
||||
- Add profile `rfc4880` which generates keys based on 4096-bit RSA.
|
||||
- Bump `sop-java` to `6.0.0`, implementing [SOP Spec Revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html)
|
||||
- `encrypt`: Add support for `--profile=` option
|
||||
- Add profile `rfc4880` to reflect status quo
|
||||
- `version`: Add support for `--sop-spec` option
|
||||
|
||||
## 1.4.6
|
||||
- Bump `sop-java` to `4.1.2`
|
||||
- Fix `decrypt --verify-with` to not throw `NoSignature` exception (exit code 3) if `VERIFICAIONS` is empty.
|
||||
|
||||
## 1.4.5
|
||||
- Bugfix: Direct-Key signatures are calculated over the signee key only, not the signer key + signee key
|
||||
- Security: Fix faulty bit-strength policy check for signing subkeys
|
||||
|
||||
## 1.4.4
|
||||
- Fix expectations on subpackets of v3 signatures (thanks @bjansen)
|
||||
- Properly verify v3 signatures, which do not yet have signature subpackets, yet we required them to have
|
||||
a hashed creation date subpacket.
|
||||
|
||||
## 1.4.3
|
||||
- Bump `sop-java` to `4.1.1`
|
||||
- Reuse shared test suite of `sop-java`
|
||||
- Add `EncryptionOptions.hasEncryptionMethod()`
|
||||
- SOP `encrypt`: Throw `MissingArg` exception if no encryption method was provided
|
||||
- Fix redundant dot in exception message (thanks @DenBond7)
|
||||
|
||||
## 1.4.2
|
||||
- Properly decrypt messages without MDC packets when `ConsumerOptions.setIgnoreMDCErrors(true)` is set
|
||||
- Fix crash in `sop generate-key --with-key-password` when more than one user-id is given
|
||||
- Revert integration with `pgp-certificate-store`
|
||||
- Bump `sop-java` to `4.1.0`
|
||||
|
||||
## 1.4.1
|
||||
- Add `UserId.parse()` method to parse user-ids into their components
|
||||
|
||||
## 1.4.0
|
||||
- `sop generate-key`: Add support for keys without user-ids
|
||||
- `sop inline-sign --as=clearsigned`: Make signature in TEXT mode
|
||||
- Make countermeasures against [KOpenPGP](https://kopenpgp.com/) attacks configurable
|
||||
- Countermeasures are now disabled by default since they are costly and have a specific threat model
|
||||
- Can be enabled by calling `Policy.setEnableKeyParameterValidation(true)`
|
||||
|
||||
## 1.4.0-rc2
|
||||
- Bump `bcpg-jdk15to18` to `1.72.3`
|
||||
- Use BCs `PGPEncryptedDataList.extractSessionKeyEncryptedData()` method
|
||||
to do decryption using session keys. This enables decryption of messages
|
||||
without encrypted session key packets.
|
||||
- Use BCs `PGPEncryptedDataList.isIntegrityProtected()` to check for integrity protection
|
||||
- Depend on `pgp-certificate-store`
|
||||
- Add `ConsumerOptions.addVerificationCerts(PGPCertificateStore)` to allow sourcing certificates from
|
||||
e.g. a [certificate store implementation](https://github.com/pgpainless/cert-d-java).
|
||||
- Make `DecryptionStream.getMetadata()` first class
|
||||
- Deprecate `DecryptionStream.getResult()`
|
||||
|
||||
## 1.4.0-rc1
|
||||
- Reimplement message consumption via new `OpenPgpMessageInputStream`
|
||||
- Fix validation of prepended signatures (#314)
|
||||
- Fix validation of nested signatures (#319)
|
||||
- Reject malformed messages (#237)
|
||||
- Utilize new `PDA` syntax verifier class
|
||||
- Allow for custom message syntax via `Syntax` class
|
||||
- Gracefully handle `UnsupportedPacketVersionException` for signatures
|
||||
- Allow plugin decryption code (e.g. to add support for hardware-backed keys (see #318))
|
||||
- Add `HardwareSecurity` utility class
|
||||
- Add `GnuPGDummyKeyUtil` which can be used to mimic GnuPGs proprietary S2K extensions
|
||||
for keys which were placed on hardware tokens
|
||||
- Add `OpenPgpPacket` enum class to enumerate available packet tags
|
||||
- Remove old decryption classes in favor of new implementation
|
||||
- Removed `DecryptionStream` class and replaced with new abstract class
|
||||
- Removed `DecryptionStreamFactory`
|
||||
- Removed `FinalIOException`
|
||||
- Removed `MissingLiteralDataException` (replaced by `MalformedOpenPgpMessageException`)
|
||||
- Introduce `MessageMetadata` class as potential future replacement for `OpenPgpMetadata`.
|
||||
- can be obtained via `((OpenPgpMessageInputStream) decryptionStream).getMetadata();`
|
||||
- Add `CachingBcPublicKeyDataDecryptorFactory` which can be extended to prevent costly decryption
|
||||
of session keys
|
||||
- Fix: Only verify message integrity once
|
||||
- Remove unnecessary `@throws` declarations on `KeyRingReader` methods
|
||||
- Remove unnecessary `@throws` declarations on `KeyRingUtils` methods
|
||||
- Add `KeyIdUtil.formatKeyId(long id)` to format hexadecimal key-ids.
|
||||
- Add `KeyRingUtils.publicKeys(PGPKeyRing keys)`
|
||||
- Remove `BCUtil` class
|
||||
|
||||
## 1.3.18
|
||||
- Bump `sop-java` to `4.1.2`
|
||||
- Fix `decrypt --verify-with XYZ` not throwing `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty (#415)
|
||||
|
||||
## 1.3.17
|
||||
- Bugfix: Direct-Key signatures are calculated over the signee key only, not the signer key + signee key
|
||||
- Security: Fix faulty bit-strength policy check for signing subkeys
|
||||
|
||||
## 1.3.16
|
||||
- Bump `sop-java` to `4.1.0`
|
||||
- Bump `gradlew` to `7.5`
|
||||
|
||||
## 1.3.15
|
||||
- Fix crash in `sop generate-key --with-key-password` when more than one user-id is given
|
||||
- `sop generate-key`: Allow key generation without user-ids
|
||||
- `sop inline-sign --as=clearsigned`: Make signatures of type 'text' instead of 'binary'
|
||||
|
||||
## 1.3.14
|
||||
- Bump `bcpg` to `1.72.3`
|
||||
- Fix DSA key parameter check
|
||||
- Use proper method to unlock private signing keys when creating detached signatures
|
||||
|
||||
## 1.3.13
|
||||
- Bump `sop-java` to `4.0.7`
|
||||
|
||||
## 1.3.12
|
||||
- Bump `sop-java` to `4.0.5`
|
||||
- Fix: `sop inline-sign`: Adopt `--as=clearsigned` instead of `--as=cleartextsigned`
|
||||
- SOP: Hide `Version: PGPainless` armor header in all armored outputs
|
||||
- Fix: `sop armor`: Do not re-armor already armored data
|
||||
|
||||
## 1.3.11
|
||||
- Fix: When verifying subkey binding signatures with embedded recycled primary
|
||||
key binding signatures, do not reject signature if primary key binding
|
||||
|
|
12
README.md
12
README.md
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: Apache-2.0
|
|||
|
||||
[](https://github.com/pgpainless/pgpainless/actions/workflows/gradle_push.yml)
|
||||
[](https://coveralls.io/github/pgpainless/pgpainless?branch=main)
|
||||
[](https://tests.sequoia-pgp.org/)
|
||||
[](https://tests.sequoia-pgp.org/)
|
||||
[](https://keyoxide.org/7F9116FEA90A5983936C7CFAA027DB2F3E1E118A)
|
||||
[](https://api.reuse.software/info/github.com/pgpainless/pgpainless)
|
||||
[](https://pgpainless.readthedocs.io/en/latest/?badge=latest)
|
||||
|
@ -32,7 +32,7 @@ It also checks if signing subkeys are properly bound to their primary key, if ke
|
|||
if keys are allowed to create signatures in the first place.
|
||||
|
||||
These rigorous checks make PGPainless stand out from other Java-based OpenPGP libraries and are the reason why
|
||||
PGPainless currently [*scores first place* on Sequoia-PGPs Interoperability Test-Suite](https://tests.sequoia-pgp.org).
|
||||
PGPainless currently [*scores second place* on Sequoia-PGPs Interoperability Test-Suite](https://tests.sequoia-pgp.org).
|
||||
|
||||
> At FlowCrypt we are using PGPainless in our Kotlin code bases on Android and on server side.
|
||||
> The ergonomics of legacy PGP tooling on Java is not very good, and PGPainless improves it greatly.
|
||||
|
@ -132,7 +132,7 @@ Still it allows you to manually specify which algorithms to use of course.
|
|||
.addRecipient(aliceKey)
|
||||
.addRecipient(bobsKey)
|
||||
// optionally encrypt to a passphrase
|
||||
.addMessagePassphrase(Passphrase.fromPassword("password123"))
|
||||
.addPassphrase(Passphrase.fromPassword("password123"))
|
||||
// optionally override symmetric encryption algorithm
|
||||
.overrideEncryptionAlgorithm(SymmetricKeyAlgorithm.AES_192),
|
||||
new SigningOptions()
|
||||
|
@ -172,10 +172,10 @@ This behaviour can be modified though using the `Policy` class.
|
|||
decryptionStream.close();
|
||||
|
||||
// Result contains information like signature status etc.
|
||||
MessageMetadata metadata = decryptionStream.getMetadata();
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
```
|
||||
|
||||
*After* the `DecryptionStream` was closed, you can get metadata about the processed data by retrieving the `MessageMetadata`.
|
||||
*After* the `DecryptionStream` was closed, you can get metadata about the processed data by retrieving the `OpenPgpMetadata`.
|
||||
Again, this object will contain information about how the message was encrypted, who signed it and so on.
|
||||
|
||||
#### Many more examples can be found in the [examples package](pgpainless-core/src/test/java/org/pgpainless/example)!!!
|
||||
|
@ -191,7 +191,7 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.pgpainless:pgpainless-core:1.7.6'
|
||||
implementation 'org.pgpainless:pgpainless-core:1.3.11'
|
||||
}
|
||||
```
|
||||
|
||||
|
|
118
REUSE.toml
118
REUSE.toml
|
@ -1,118 +0,0 @@
|
|||
version = 1
|
||||
SPDX-PackageName = "PGPainless"
|
||||
SPDX-PackageSupplier = "Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-PackageDownloadLocation = "https://pgpainless.org"
|
||||
|
||||
[[annotations]]
|
||||
path = "REUSE.toml"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".git-blame-ignore-revs"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2023 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "docs/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC-BY-3.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".readthedocs.yaml"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "gradle**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2015 the original author or authors."
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".editorconfig"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "Facebook"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "assets/repository-open-graph.png"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC-BY-3.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "assets/pgpainless.svg"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC-BY-3.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "assets/logo.png"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC-BY-3.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "assets/test_vectors/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2018 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "pgpainless-core/src/test/resources/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "audit/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "CNAME"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "_config.yml"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "_layouts/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>, 2017 Steve Smith"
|
||||
SPDX-License-Identifier = "CC-BY-SA-3.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "pgpainless-cli/src/main/resources/META-INF/native-image/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "pgpainless-cli/rewriteManPages.sh"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "pgpainless-cli/packaging/man/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".github/ISSUE_TEMPLATE/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2024 Paul Schaub <info@pgpainless.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
13
SECURITY.md
13
SECURITY.md
|
@ -12,14 +12,11 @@ SPDX-License-Identifier: Apache-2.0
|
|||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported | Note |
|
||||
|---------|--------------------|------------|
|
||||
| 1.7.X | :white_check_mark: | |
|
||||
| 1.6.X | :white_check_mark: | LTS branch |
|
||||
| 1.5.X | :white_check_mark: | |
|
||||
| 1.4.X | :white_check_mark: | |
|
||||
| 1.3.X | :white_check_mark: | LTS branch |
|
||||
| < 1.3.X | :x: | |
|
||||
| Version | Supported |
|
||||
|---------| ------------------ |
|
||||
| 1.1.X | :white_check_mark: |
|
||||
| 1.0.X | :white_check_mark: |
|
||||
| < 1.0.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
75
build.gradle
75
build.gradle
|
@ -18,8 +18,7 @@ buildscript {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version "1.8.10"
|
||||
id 'com.diffplug.spotless' version '6.22.0' apply false
|
||||
id 'ru.vyarus.animalsniffer' version '1.5.3'
|
||||
}
|
||||
|
||||
apply from: 'version.gradle'
|
||||
|
@ -30,32 +29,37 @@ allprojects {
|
|||
apply plugin: 'eclipse'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'checkstyle'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'com.diffplug.spotless'
|
||||
|
||||
// Only generate jar for submodules
|
||||
// without this we would generate an empty pgpainless.jar for the project root
|
||||
// https://stackoverflow.com/a/25445035
|
||||
jar {
|
||||
reproducibleFileOrder = true
|
||||
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.25.0'
|
||||
}
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
ktfmt().dropboxStyle()
|
||||
}
|
||||
toolVersion = '8.18'
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
description = "Simple to use OpenPGP API for Java based on Bouncycastle"
|
||||
version = shortVersion
|
||||
|
||||
sourceCompatibility = javaSourceCompatibility
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
|
@ -70,17 +74,6 @@ allprojects {
|
|||
fileMode = 0644
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(javaSourceCompatibility)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
@ -106,7 +99,7 @@ allprojects {
|
|||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.8"
|
||||
toolVersion = "0.8.7"
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
|
@ -114,7 +107,7 @@ allprojects {
|
|||
sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs))
|
||||
classDirectories.setFrom(project.files(sourceSets.main.output))
|
||||
reports {
|
||||
xml.required = true
|
||||
xml.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,15 +125,15 @@ subprojects {
|
|||
apply plugin: 'signing'
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
archiveClassifier = 'sources'
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
archiveClassifier = 'javadoc'
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
task testsJar(type: Jar, dependsOn: testClasses) {
|
||||
archiveClassifier = 'tests'
|
||||
classifier = 'tests'
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
|
@ -237,7 +230,7 @@ task jacocoRootReport(type: JacocoReport) {
|
|||
classDirectories.setFrom(files(subprojects.sourceSets.main.output))
|
||||
executionData.setFrom(files(subprojects.jacocoTestReport.executionData))
|
||||
reports {
|
||||
xml.required = true
|
||||
xml.enabled true
|
||||
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
|
||||
}
|
||||
// We could remove the following setOnlyIf line, but then
|
||||
|
@ -248,6 +241,10 @@ task jacocoRootReport(type: JacocoReport) {
|
|||
}
|
||||
|
||||
task javadocAll(type: Javadoc) {
|
||||
def currentJavaVersion = JavaVersion.current()
|
||||
if (currentJavaVersion.compareTo(JavaVersion.VERSION_1_9) >= 0) {
|
||||
options.addStringOption("-release", "8");
|
||||
}
|
||||
source subprojects.collect {project ->
|
||||
project.sourceSets.main.allJava }
|
||||
destinationDir = new File(buildDir, 'javadoc')
|
||||
|
@ -260,23 +257,3 @@ task javadocAll(type: Javadoc) {
|
|||
"https://docs.oracle.com/javase/${sourceCompatibility.getMajorVersion()}/docs/api/",
|
||||
] as String[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch sha256 checksums of artifacts published to maven central.
|
||||
*
|
||||
* Example: gradle -Prelease=1.3.13 mavenCentralChecksums
|
||||
*/
|
||||
task mavenCentralChecksums() {
|
||||
description 'Fetch and display checksums for artifacts published to Maven Central'
|
||||
String ver = project.hasProperty('release') ? release : shortVersion
|
||||
doLast {
|
||||
for (Project p : rootProject.subprojects) {
|
||||
String url = "https://repo1.maven.org/maven2/org/pgpainless/${p.name}/${ver}/${p.name}-${ver}.jar.sha256"
|
||||
Process fetch = "curl -f $url".execute()
|
||||
if (fetch.waitFor() == 0) {
|
||||
print fetch.text.trim()
|
||||
println " ${p.name}/build/libs/${p.name}-${ver}.jar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,12 +114,18 @@ SPDX-License-Identifier: CC0-1.0
|
|||
</module>
|
||||
|
||||
<module name="JavadocMethod">
|
||||
<property name="accessModifiers" value="public"/>
|
||||
<!-- TODO stricten those checks -->
|
||||
<property name="scope" value="public"/>
|
||||
<!--<property name="allowUndeclaredRTE" value="true"/>-->
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingThrowsTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowMissingJavadoc" value="true"/>
|
||||
<property name="suppressLoadErrors" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocStyle">
|
||||
<property name="scope" value="public"/>
|
||||
<property name="checkEmptyJavadoc" value="true"/>
|
||||
<property name="checkHtml" value="false"/>
|
||||
</module>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
myst-parser>=0.17
|
||||
sphinxcontrib-mermaid>=0.7.1
|
||||
sphinx_rtd_theme>=2.0.0
|
||||
|
|
|
@ -47,13 +47,6 @@ The diagram below shows, how the different projects relate to one another.
|
|||
* `pgpainless-cert-d` - PGPainless-based implementation of `pgp-cert-d-java`
|
||||
* `pgpainless-cert-d-cli` - CLI frontend for `pgpainless-cert-d`
|
||||
|
||||
* {{ '[PGPainless-WOT](https://{}/pgpainless/pgpainless-wot)'.format(repo_host) }}
|
||||
Implementation of the [OpenPGP Web of Trust specification](https://sequoia-pgp.gitlab.io/sequoia-wot/) using PGPainless.
|
||||
* `pgpainless-wot` - Parse OpenPGP keyrings into a generic `Network` object
|
||||
* `wot-dijkstra` - Perform queries to find paths inside a `Network` object
|
||||
* `pgpainless-wot-cli` - CLI frontend for `pgpainless-wot` and `wot-dijkstra`
|
||||
* `wot-test-suite` - Test vectors ported from [Sequoia-PGPs WoT implementation](https://gitlab.com/sequoia-pgp/sequoia-wot/-/tree/main/tests/data)
|
||||
|
||||
* {{ '[PGPeasy](https://{}/pgpainless/pgpeasy)'.format(repo_host) }}
|
||||
Prototypical, comprehensive OpenPGP CLI application
|
||||
* `pgpeasy` - CLI application
|
|
@ -27,13 +27,6 @@ flowchart LR
|
|||
subgraph VKS-JAVA
|
||||
vks-java-cli-->vks-java
|
||||
end
|
||||
subgraph PGPAINLESS-WOT
|
||||
wot-test-suite-->pgpainless-wot
|
||||
pgpainless-wot-->wot-dijkstra
|
||||
pgpainless-wot-cli-->pgpainless-wot
|
||||
pgpainless-wot-->pgpainless-core
|
||||
pgpainless-wot-cli-->pgpainless-cert-d
|
||||
end
|
||||
subgraph PGPEASY
|
||||
pgpeasy-->pgpainless-cli
|
||||
pgpeasy-->wkd-java-cli
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 108 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
|
@ -42,36 +42,6 @@ $ gradle installDist
|
|||
Afterwards, an uncompressed distributable is installed in `build/install/`.
|
||||
To execute the application, you can call `build/install/bin/pgpainless-cli{.bat}`
|
||||
|
||||
Building / updating man pages is a two-step process.
|
||||
The contents of the man pages is largely defined by the `sop-java-picocli` source code.
|
||||
|
||||
In order to generate a fresh set of man pages from the `sop-java-picocli` source, you need to clone that repository
|
||||
next to the `pgpainless` repository:
|
||||
```shell
|
||||
$ ls
|
||||
pgpainless
|
||||
$ git clone https://github.com/pgpainless/sop-java.git
|
||||
$ ls
|
||||
pgpainless sop-java
|
||||
```
|
||||
|
||||
Next, you need to execute the `asciiDoctor` gradle task inside the sop-java repository:
|
||||
```shell
|
||||
$ cd sop-java
|
||||
$ gradle asciiDoctor
|
||||
```
|
||||
|
||||
This will generate generic sop manpages in `sop-java-picocli/build/docs/manpage/`.
|
||||
|
||||
Next, you need to execute a script for converting the `sop` manpages to fit the `pgpainless-cli` command with the help
|
||||
of a script in the `pgpainless` repository:
|
||||
```shell
|
||||
$ cd ../pgpainless/pgpainless-cli
|
||||
$ ./rewriteManPages.sh
|
||||
```
|
||||
|
||||
The resulting updated man pages are placed in `packaging/man/`.
|
||||
|
||||
## Usage
|
||||
|
||||
Hereafter, the program will be referred to as `pgpainless-cli`.
|
||||
|
@ -79,32 +49,26 @@ Hereafter, the program will be referred to as `pgpainless-cli`.
|
|||
```
|
||||
$ pgpainless-cli help
|
||||
Stateless OpenPGP Protocol
|
||||
Usage: pgpainless-cli [--stacktrace] [COMMAND]
|
||||
|
||||
Options:
|
||||
--stacktrace Print stacktrace
|
||||
Usage: pgpainless-cli [COMMAND]
|
||||
|
||||
Commands:
|
||||
version Display version information about the tool
|
||||
list-profiles Emit a list of profiles supported by the identified
|
||||
subcommand
|
||||
generate-key Generate a secret key
|
||||
change-key-password Update the password of a key
|
||||
revoke-key Generate revocation certificates
|
||||
extract-cert Extract a public key certificate from a secret key
|
||||
sign Create a detached message signature
|
||||
verify Verify a detached signature
|
||||
encrypt Encrypt a message from standard input
|
||||
decrypt Decrypt a message
|
||||
inline-detach Split signatures from a clearsigned message
|
||||
inline-sign Create an inline-signed message
|
||||
inline-verify Verify an inline-signed message
|
||||
armor Add ASCII Armor to standard input
|
||||
dearmor Remove ASCII Armor from standard input
|
||||
help Display usage information for the specified subcommand
|
||||
help Display usage information for the specified subcommand
|
||||
armor Add ASCII Armor to standard input
|
||||
dearmor Remove ASCII Armor from standard input
|
||||
decrypt Decrypt a message from standard input
|
||||
inline-detach Split signatures from a clearsigned message
|
||||
encrypt Encrypt a message from standard input
|
||||
extract-cert Extract a public key certificate from a secret key from
|
||||
standard input
|
||||
generate-key Generate a secret key
|
||||
sign Create a detached signature on the data from standard input
|
||||
verify Verify a detached signature over the data from standard input
|
||||
inline-sign Create an inline-signed message from data on standard input
|
||||
inline-verify Verify inline-signed data from standard input
|
||||
version Display version information about the tool
|
||||
|
||||
Exit Codes:
|
||||
0 Successful program execution
|
||||
0 Successful program execution.
|
||||
1 Generic program error
|
||||
3 Verification requested but no verifiable signature found
|
||||
13 Unsupported asymmetric algorithm
|
||||
|
@ -120,44 +84,8 @@ Exit Codes:
|
|||
61 Input file does not exist
|
||||
67 Cannot unlock password protected secret key
|
||||
69 Unsupported subcommand
|
||||
71 Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
71 Unsupported special prefix (e.g. "@env/@fd") of indirect parameter
|
||||
73 Ambiguous input (a filename matching the designator already exists)
|
||||
79 Key is not signing capable
|
||||
83 Options were supplied that are incompatible with each other
|
||||
89 The requested profile is unsupported, or the indicated subcommand does
|
||||
not accept profiles
|
||||
```
|
||||
|
||||
To get help on a subcommand, e.g. `encrypt`, just call the help subcommand followed by the subcommand you
|
||||
are interested in (e.g. `pgpainless-cli help encrypt`).
|
||||
|
||||
## Examples
|
||||
```shell
|
||||
$ # Generate a key
|
||||
$ pgpainless-cli generate-key "Alice <alice@pgpainless.org>" > key.asc
|
||||
$ # Extract a certificate from a key
|
||||
$ cat key.asc | pgpainless-cli extract-cert > cert.asc
|
||||
$ # Create an encrypted signed message
|
||||
$ echo "Hello, World!" | pgpainless-cli encrypt cert.asc --sign-with key.asc > msg.asc
|
||||
$ # Decrypt an encrypted message and verify the signature
|
||||
$ cat msg.asc | pgpainless-cli decrypt key.asc --verify-with cert.asc --verifications-out verifications.txt
|
||||
Hello, World!
|
||||
$ cat verifications.txt
|
||||
2022-11-15T21:25:48Z 4FF67C69150209ED8139DE22578CB2FABD5D7897 9000235358B8CEA6A368EC86DE56DC2D942ACAA4
|
||||
```
|
||||
|
||||
## Indirect Data Types
|
||||
|
||||
Some commands take options whose arguments are indirect data types. Those are arguments which are not used directly,
|
||||
but instead they point to a place where the argument value can be sourced from, such as a file, an environment variable
|
||||
or a file descriptor.
|
||||
|
||||
It is important to keep in mind, that options like `--with-password` or `--with-key-password` are examples for such
|
||||
indirect data types. If you want to unlock a key whose password is `sw0rdf1sh`, you *cannot* provide the password
|
||||
like `--with-key-password sw0rdf1sh`, but instead you have to either write out the password into a file and provide
|
||||
the file's path (e.g. `--with-key-password /path/to/file`), store the password in an environment variable and pass that
|
||||
(e.g. `--with-key-password @ENV:myvar`), or provide a numbered file descriptor from which the password can be read
|
||||
(e.g. `--with-key-password @FD:4`).
|
||||
|
||||
Note, that environment variables and file descriptors can only be used to pass input data to the program.
|
||||
For output parameters (e.g. `--verifications-out`) only file paths are allowed.
|
||||
Powered by picocli
|
||||
```
|
|
@ -50,15 +50,9 @@ There is a very good chance that you can find code examples there that fit your
|
|||
Reading keys from ASCII armored strings or from binary files is easy:
|
||||
|
||||
```java
|
||||
// Secret Keys
|
||||
String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"...;
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing()
|
||||
.secretKeyRing(key);
|
||||
|
||||
// Certificates (Public Keys)
|
||||
String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...";
|
||||
PGPPublicKeyRing certificate = PGPainless.readKeyRing()
|
||||
.publicKeyRing(cert);
|
||||
```
|
||||
|
||||
Similarly, keys or certificates can quickly be exported:
|
||||
|
@ -237,15 +231,6 @@ EncryptionOptions encOptions = EncryptionOptions.get()
|
|||
|
||||
Once again, it is possible to add multiple recipients by repeating the `addRecipient()` method call.
|
||||
|
||||
In order to prevent metadata leaks, you might want to add recipients anonymously.
|
||||
Anonymous recipients have their key-id hidden by replacing it with a wildcard.
|
||||
That way, it is not easily possible for an attacker to deduce the recipients of a message without further
|
||||
analysis of additional metadata.
|
||||
Anonymous recipients can be added like follows:
|
||||
```java
|
||||
encOptions.addHiddenRecipient(certificate);
|
||||
```
|
||||
|
||||
You can also encrypt a message to a password like this:
|
||||
```java
|
||||
encOptions.addPassphrase(Passphrase.fromPassword("sw0rdf1sh"));
|
||||
|
@ -257,7 +242,7 @@ or the passphrase.
|
|||
### Decrypt and/or Verify a Message
|
||||
Decryption and verification of a message is both done using the same API.
|
||||
Whether a message was actually signed / encrypted can be determined after the message has been processed by checking
|
||||
the `MessageMetadata` object which can be obtained from the `DecryptionStream`.
|
||||
the `OpenPgpMetadata` object which can be obtained from the `DecryptionStream`.
|
||||
|
||||
To configure the decryption / verification process, the `ConsumerOptions` object is used:
|
||||
|
||||
|
@ -298,16 +283,16 @@ Streams.pipeAll(consumerStream, plaintext);
|
|||
consumerStream.close(); // important!
|
||||
|
||||
// The result will contain metadata of the message
|
||||
MessageMetadata result = consumerStream.getMetadata();
|
||||
OpenPgpMetadata result = consumerStream.getResult();
|
||||
```
|
||||
|
||||
After the message has been processed, you can consult the `MessageMetadata` object to determine the nature of the message:
|
||||
After the message has been processed, you can consult the `OpenPgpMetadata` object to determine the nature of the message:
|
||||
|
||||
```java
|
||||
boolean wasEncrypted = result.isEncrypted();
|
||||
SubkeyIdentifier decryptionKey = result.getDecryptionKey();
|
||||
List<SignatureVerification> validSignatures = result.getVerifiedSignatures();
|
||||
boolean wasSignedByCert = result.isVerifiedSignedBy(certificate);
|
||||
Map<SubkeyIdentifier, PGPSignature> validSignatures = result.getVerifiedSignatures();
|
||||
boolean wasSignedByCert = result.containsVerifiedSignatureFrom(certificate);
|
||||
|
||||
// For files:
|
||||
String fileName = result.getFileName();
|
||||
|
@ -338,168 +323,6 @@ DecryptionStream verificationStream = PGPainless.decryptAndOrVerify()
|
|||
Streams.drain(verificationStream); // push all the data through the stream
|
||||
verificationStream.close(); // finish verification
|
||||
|
||||
MessageMetadata result = verificationStream.getMetadata(); // get metadata of signed message
|
||||
assertTrue(result.isVerifiedSignedBy(certificate)); // check if message was in fact signed
|
||||
```
|
||||
|
||||
### Legacy Compatibility
|
||||
Out of the box, PGPainless is configured to use secure defaults and perform checks for recommended
|
||||
security features. This means that for example messages generated using older OpenPGP
|
||||
implementations which do not follow those best practices might fail to decrypt/verify.
|
||||
|
||||
It is however possible to circumvent certain security checks to allow processing of such messages.
|
||||
|
||||
:::{note}
|
||||
It is not recommended to disable security checks, as that might enable certain attacks on the OpenPGP protocol.
|
||||
:::
|
||||
|
||||
#### Missing / broken MDC (modification detection code)
|
||||
RFC4880 has two different types of encrypted data packets. The *Symmetrically Encrypted Data* packet (SED) and the *Symmetrically Encrypted Integrity-Protected Data* packet.
|
||||
The latter has an added MDC packet which prevents modifications to the ciphertext.
|
||||
|
||||
While implementations are highly encouraged to only use the latter package type, some older implementations still generate
|
||||
encrypted data packets which are not integrity protected.
|
||||
|
||||
To allow PGPainless to decrypt such messages, you need to set a flag in the `ConsumerOptions` object:
|
||||
```java
|
||||
ConsumerOptions options = ConsumerOptions.get()
|
||||
.setIgnoreMDCErrors(true) // <-
|
||||
.setDecryptionKey(secretKey)
|
||||
...
|
||||
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertextIn)
|
||||
.withOptions(options);
|
||||
...
|
||||
```
|
||||
|
||||
:::{note}
|
||||
It is highly advised to only set this flag if you know what you are doing.
|
||||
It might also be a good idea to try decrypting a message without the flag set first and only re-try
|
||||
decryption with the flag set in case of a `MessageNotIntegrityProtectedException` (don't forget to rewind the ciphertextInputStream).
|
||||
:::
|
||||
|
||||
#### Weak keys and broken algorithms
|
||||
Some users might cling on to older keys using weak algorithms / small key sizes.
|
||||
PGPainless refuses to encrypt to weak certificates and sign with weak keys.
|
||||
By default, PGPainless follows the recommendations for acceptable key sizes of [the German BSI in 2021](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf).
|
||||
It can however be configured to accept older key material / algorithms too.
|
||||
|
||||
Minimal key lengths can be configured by changing PGPainless' policy:
|
||||
```java
|
||||
Map<PublicKeyAlgorithm, Integer> algorithms = new HashMap<>();
|
||||
// put all acceptable algorithms and their minimal key length
|
||||
algorithms.put(PublicKeyAlgorithm.RSA_GENERAL, 1024);
|
||||
algorithms.put(PublicKeyAlgorithm.ECDSA, 100);
|
||||
...
|
||||
Policy.PublicKeyAlgorithmPolicy pkPolicy =
|
||||
new Policy.PublicKeyAlgorithmPolicy(algorithms);
|
||||
// set the custom algorithm policy
|
||||
PGPainless.getPolicy().setPublicKeyAlgorithmPolicy();
|
||||
```
|
||||
|
||||
Since OpenPGP uses a hybrid encryption scheme of asymmetric and symmetric encryption algorithms,
|
||||
it also comes with a policy for symmetric encryption algorithms.
|
||||
This list can be modified to allow for weaker algorithms like follows:
|
||||
```java
|
||||
// default fallback algorithm for message encryption
|
||||
SymmetricKeyAlgorithm fallbackAlgorithm = SymmetricKeyAlgorithm.AES_256;
|
||||
// acceptable algorithms
|
||||
List<SymmetricKeyAlgorithm> algorithms = new ArrayList<>();
|
||||
algorithms.add(SymmetricKeyAlgorithm.AES_256);
|
||||
algorithms.add(SymmetricKeyAlgorithm.AES_192);
|
||||
algorithms.add(SymmetricKeyAlgorithm.AES_128);
|
||||
algorithms.add(SymmetricKeyAlgorithm.TWOFISH);
|
||||
algorithms.add(SymmetricKeyAlgorithm.BLOWFISH);
|
||||
...
|
||||
Policy.SymmetricKeyAlgorithmPolicy skPolicy =
|
||||
new SymmtricKeyAlgorithmPolicy(fallbackAlgorithm, algorithms);
|
||||
// set the custom algorithm policy
|
||||
// algorithm policy applicable when decrypting messages created by legacy senders:
|
||||
PGPainless.getPolicy()
|
||||
.setSymmetricKeyDecryptionAlgorithmPolicy(skPolicy);
|
||||
// algorithm policy applicable when generating messages for legacy recipients:
|
||||
PGPainless.getPolicy()
|
||||
.setSymmetricKeyEncryptionAlgorithmPolicy(skPolicy);
|
||||
```
|
||||
|
||||
Hash algorithms are used in OpenPGP to create signatures.
|
||||
Since signature verification is an integral part of the OpenPGP protocol, PGPainless comes
|
||||
with multiple policies for acceptable hash algorithms, depending on the use-case.
|
||||
Revocation signatures are critical, so you might want to handle revocation signatures differently from normal signatures.
|
||||
|
||||
By default, PGPainless uses a smart hash algorithm policy for both use-cases, which takes into consideration
|
||||
not only the hash algorithm itself, but also the creation date of the signature.
|
||||
That way, signatures using SHA-1 are acceptable if they were created before February 2013, but are rejected if their
|
||||
creation date is after that point in time.
|
||||
|
||||
A custom hash algorithm policy can be set like this:
|
||||
```java
|
||||
HashAlgorithm fallbackAlgorithm = HashAlgorithm.SHA512;
|
||||
Map<HashAlgorithm, Date> algorithms = new HashMap<>();
|
||||
// Accept MD5 on signatures made before 1997-02-01
|
||||
algorithms.put(HashAlgorithm.MD5,
|
||||
DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC"));
|
||||
// Accept SHA-1, regardless of signature creation time
|
||||
algorithms.put(HashAlgorithm.SHA1, null);
|
||||
...
|
||||
Policy.HashAlgorithmPolicy hPolicy =
|
||||
new Policy.HashAlgorithmPolicy(fallbackAlgorithm, algorithms);
|
||||
// set policy for revocation signatures
|
||||
PGPainless.getPolicy()
|
||||
.setRevocationSignatureHashAlgorithmPolicy(hPolicy);
|
||||
// set policy for normal signatures (certifications and document signatures)
|
||||
PGPainless.getPolicy()
|
||||
.setSignatureHashAlgorithmPolicy(hPolicy);
|
||||
```
|
||||
|
||||
Lastly, PGPainless comes with a policy on acceptable compression algorithms, which currently accepts any
|
||||
compression algorithm.
|
||||
A custom compression algorithm policy can be set in a similar way:
|
||||
```java
|
||||
CompressionAlgorithm fallback = CompressionAlgorithm.ZIP;
|
||||
List<CompressionAlgorithm> algorithms = new ArrayList<>();
|
||||
algorithms.add(CompressionAlgorith.ZIP);
|
||||
algorithms.add(CompressionAlgorithm.BZIP2);
|
||||
...
|
||||
Policy.CompressionAlgorithmPolicy cPolicy =
|
||||
new Policy.CompressionAlgorithmPolicy(fallback, algorithms);
|
||||
PGPainless.getPolicy()
|
||||
.setCompressionAlgorithmPolicy(cPolicy);
|
||||
```
|
||||
|
||||
To prevent a class of attacks described in the [paper](https://www.kopenpgp.com/#paper)
|
||||
"Victory by KO: Attacking OpenPGP Using Key Overwriting",
|
||||
PGPainless offers the option to validate private key material each time before using it,
|
||||
to make sure that an attacker didn't tamper with the corresponding public key parameters.
|
||||
|
||||
These checks are disabled by default, but they can be enabled as follows:
|
||||
```java
|
||||
PGPainless.getPolicy()
|
||||
.setEnableKeyParameterValidation(true);
|
||||
```
|
||||
|
||||
:::{note}
|
||||
Validation checks against KOpenPGP attacks are disabled by default, since they are very costly
|
||||
and only make sense in certain scenarios.
|
||||
Please read and understand the paper to decide, if enabling the checks makes sense for your use-case.
|
||||
:::
|
||||
|
||||
|
||||
### Known Notations
|
||||
In OpenPGP, signatures can contain [notation subpackets](https://www.rfc-editor.org/rfc/rfc4880#section-5.2.3.16).
|
||||
A notation can give meaning to a signature, or add additional contextual information.
|
||||
Signature subpackets can be marked as critical, meaning an implementation that does not know about
|
||||
a certain subpacket MUST reject the signature.
|
||||
The same is true for critical notations.
|
||||
|
||||
For that reason, PGPainless comes with a `NotationRegistry` class which can be used to register known notations,
|
||||
such that a signature containing a critical notation of a certain value is not rejected.
|
||||
To register a known notation, you can do the following:
|
||||
|
||||
```java
|
||||
NotationRegistry registry = PGPainless.getPolicy()
|
||||
.getNotationRegistry();
|
||||
|
||||
registry.addKnownNotation("sample@example.com");
|
||||
OpenPgpMetadata result = verificationStream.getResult(); // get metadata of signed message
|
||||
assertTrue(result.containsVerifiedSignatureFrom(certificate)); // check if message was in fact signed
|
||||
```
|
|
@ -29,19 +29,4 @@ UserId full = UserId.newBuilder()
|
|||
.withComment("Work Address")
|
||||
.build();
|
||||
assertEquals("Peter Pattern (Work Address) <peter@pgpainless.org>", full.toString());
|
||||
```
|
||||
|
||||
If you have a User-ID in form of a string (e.g. because a user provided it via a text field),
|
||||
you can parse it into its components like this:
|
||||
|
||||
```java
|
||||
String string = "John Doe <john@doe.corp>";
|
||||
UserId userId = UserId.parse(string);
|
||||
|
||||
// Now you can access the different components
|
||||
assertEquals("John Doe", userId.getName());
|
||||
assertEquals("john@doe.corp", userId.getEmail());
|
||||
assertNull(userId.getComment());
|
||||
```
|
||||
|
||||
The method `UserId.parse(String string)` will throw an `IllegalArgumentException` if the User-ID is malformed.
|
||||
```
|
|
@ -75,21 +75,10 @@ In both cases, the resulting output will be the UTF8 encoded, ASCII armored Open
|
|||
|
||||
To disable ASCII armoring, call `noArmor()` before calling `generate()`.
|
||||
|
||||
Revision `05` of the Stateless OpenPGP Protocol specification introduced the concept of profiles for
|
||||
certain operations.
|
||||
The key generation feature is the first operation to make use of profiles to specify different key algorithms.
|
||||
To set a profile, simply call `profile(String profileName)` and pass in one of the available profile identifiers.
|
||||
|
||||
To explore, which profiles are available, refer to the dedicated [section](#explore-profiles).
|
||||
|
||||
The default profile used by `pgpainless-sop` is called `draft-koch-eddsa-for-openpgp-00`.
|
||||
If this profile is used, the resulting OpenPGP secret key will consist of a certification-capable 256-bits
|
||||
At the time of writing, the resulting OpenPGP secret key will consist of a certification-capable 256-bits
|
||||
ed25519 EdDSA primary key, a 256-bits ed25519 EdDSA subkey used for signing, as well as a 256-bits X25519
|
||||
ECDH subkey for encryption.
|
||||
|
||||
Another profile defined by `pgpainless-sop` is `rfc4880`, which changes the key generation behaviour such that
|
||||
the resulting key is a single 4096-bit RSA key capable of certifying, signing and encrypting.
|
||||
|
||||
The whole key does not have an expiration date set.
|
||||
|
||||
### Extract a Certificate
|
||||
|
@ -114,56 +103,6 @@ To disable ASCII armoring, call `noArmor()` before calling `key(_)`.
|
|||
|
||||
In our example, `certificateBytes` can now safely be shared with anyone.
|
||||
|
||||
### Change Key Password
|
||||
|
||||
OpenPGP keys can (but don't need to) be password protected.
|
||||
The `changeKeyPassword()` API can be used to add, change or remove password protection from OpenPGP keys.
|
||||
While the input to this operation can be keys with different per-subkey passwords, the output will use at most one password.
|
||||
|
||||
Via `oldKeyPassphrase()`, multiple decryption passphrase candidates can be provided.
|
||||
These are tried one after another to unlock protected subkeys.
|
||||
|
||||
In order to successfully change the passphrase of an OpenPGP key, all of its subkeys needs to be successfully decrypted.
|
||||
If one or more subkeys cannot be decrypted, the operation fails with a `KeyIsProtected` exception.
|
||||
The result is either fully encrypted for a single passphrase (passed via `newKeyPassphrase()`),
|
||||
or unprotected if the new key passphrase is omitted.
|
||||
|
||||
|
||||
```java
|
||||
byte[] keyBefore = ...
|
||||
byte[] keyAfter = sop.changeKeyPassword()
|
||||
// Provide old passphrases - all subkeys need to be decryptable,
|
||||
// otherwise KeyIsProtected exception will be thrown
|
||||
.oldKeyPassphrase("4d4m5m1th")
|
||||
.oldKeyPassphrase("d4v1dR1c4rd0")
|
||||
// Provide the new passphrase - if omitted, key will be unprotected
|
||||
.newKeyPassphrase("fr1edr1ch3n93l5")
|
||||
.keys(keyBefore)
|
||||
.getBytes();
|
||||
```
|
||||
|
||||
### Generate Revocation Certificates
|
||||
|
||||
You might want to generate a revocation certificate for your OpenPGP key.
|
||||
This certificate can be published to a key server to let your contacts known that your key is no longer
|
||||
trustworthy.
|
||||
The `revokeKey()` API can be used to generate a "hard-revocation", which retroactively invalidates all
|
||||
signatures previously issued by the key.
|
||||
|
||||
If the input secret key is an OpenPGP v6 key, the result will be a minimal revocation certificate,
|
||||
consisting of only the bare primary public key and a revocation signature. For v4 keys, the result
|
||||
will consist of the whole public certificate plus a revocation signature.
|
||||
|
||||
```java
|
||||
byte[] keys = ...
|
||||
byte[] revoked = sop.revokeKey()
|
||||
// primary key password(s) if the key(s) are protected
|
||||
.withKeyPassword("5w0rdf1sh")
|
||||
// one or more secret keys
|
||||
.keys(keys)
|
||||
.getBytes();
|
||||
```
|
||||
|
||||
### Apply / Remove ASCII Armor
|
||||
|
||||
Perhaps you want to print your secret key onto a piece of paper for backup purposes,
|
||||
|
@ -180,6 +119,14 @@ byte[] armoredData = sop.armor()
|
|||
|
||||
The `data(_)` method can either be called by providing a byte array, or an `InputStream`.
|
||||
|
||||
:::{note}
|
||||
There is a `label(ArmorLabel label)` method, which could theoretically be used to define the label used in the
|
||||
ASCII armor header.
|
||||
However, this method is not (yet?) supported by `pgpainless-sop` and will currently throw an `UnsupportedOption`
|
||||
exception.
|
||||
Instead, the implementation will figure out the data type and set the respective label on its own.
|
||||
:::
|
||||
|
||||
To remove ASCII armor from armored data, simply use the `dearmor()` API:
|
||||
|
||||
```java
|
||||
|
@ -239,13 +186,6 @@ If any keys used for signing are password protected, you need to provide the sig
|
|||
It does not matter in which order signing keys and key passwords are provided, the implementation will figure out
|
||||
matches on its own. If different key passwords are used, the `withKeyPassword(_)` method can be called multiple times.
|
||||
|
||||
You can modify the behaviour of the encrypt operation by switching between different profiles via the
|
||||
`profile(String profileName)` method.
|
||||
At the time of writing, the only available profile for this operation is `rfc4880` which applies encryption
|
||||
as defined in [rfc4880](https://datatracker.ietf.org/doc/html/rfc4880).
|
||||
|
||||
To explore, which profiles are available, refer to the dedicated [section](#explore-profiles).
|
||||
|
||||
By default, the encrypted message will be ASCII armored. To disable ASCII armor, call `noArmor()` before the
|
||||
`plaintext(_)` method call.
|
||||
|
||||
|
@ -269,7 +209,7 @@ byte[] ciphertext = ...; // the encrypted message
|
|||
|
||||
ReadyWithResult<DecryptionResult> readyWithResult = sop.decrypt()
|
||||
.withKey(bobKey)
|
||||
.verifyWithCert(aliceCert)
|
||||
.verifyWith(aliceCert)
|
||||
.withKeyPassword("password123") // if decryption key is protected
|
||||
.ciphertext(ciphertext);
|
||||
```
|
||||
|
@ -524,23 +464,3 @@ By default, the signatures output will be ASCII armored. This can be disabled by
|
|||
prior to `message(_)`.
|
||||
|
||||
The detached signatures can now be verified like in the section above.
|
||||
|
||||
### Explore Profiles
|
||||
|
||||
Certain operations allow modification of their behaviour by selecting between different profiles.
|
||||
An example for this is the `generateKey()` operation, where different profiles result in different algorithms used
|
||||
during key generation.
|
||||
|
||||
To explore, which profiles are supported by a certain operation, you can use the `listProfiles()` operation.
|
||||
For example, this is how you can get a list of profiles supported by the `generateKey()` operation:
|
||||
|
||||
```java
|
||||
List<Profile> profiles = sop.listProfiles().subcommand("generate-key");
|
||||
```
|
||||
|
||||
:::{note}
|
||||
As you can see, the argument passed into the `subcommand()` method must match the operation name as defined in the
|
||||
[Stateless OpenPGP Protocol specification](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/).
|
||||
:::
|
||||
|
||||
At the time of writing (the latest revision of the SOP spec is 06), only `generate-key` and `encrypt` accept profiles.
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-rc-1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -67,7 +67,6 @@ graph LR
|
|||
enc -. "ε,ε/m" .-> pgpmsg
|
||||
enc -- "ε,#/ε" --> accept
|
||||
enc -- "Signature,o/ε" --> sig4ops
|
||||
accept -- "ε,ε/ε" --> accept
|
||||
```
|
||||
|
||||
Formally, the PDA is defined as $M = (\mathcal{Q}, \Sigma, \Upgamma, \delta, q_0, Z, F)$, where
|
||||
|
|
|
@ -4,13 +4,27 @@
|
|||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'org.graalvm.buildtools.native' version '0.10.6'
|
||||
id 'com.gradleup.shadow' version '8.3.6'
|
||||
id "com.github.johnrengelman.shadow" version "6.1.0"
|
||||
}
|
||||
def generatedVersionDir = "${buildDir}/generated-version"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
output.dir(generatedVersionDir, builtBy: 'generateVersionProperties')
|
||||
}
|
||||
}
|
||||
|
||||
graalvmNative {
|
||||
toolchainDetection = true
|
||||
task generateVersionProperties {
|
||||
doLast {
|
||||
def propertiesFile = file "$generatedVersionDir/version.properties"
|
||||
propertiesFile.parentFile.mkdirs()
|
||||
propertiesFile.createNewFile()
|
||||
// Instead of using a Properties object here, we directly write to the file
|
||||
// since Properties adds a timestamp, ruining reproducibility
|
||||
propertiesFile.write("version="+rootProject.version.toString())
|
||||
}
|
||||
}
|
||||
processResources.dependsOn generateVersionProperties
|
||||
|
||||
dependencies {
|
||||
|
||||
|
@ -18,12 +32,13 @@ dependencies {
|
|||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
// https://todd.ginsberg.com/post/testing-system-exit/
|
||||
testImplementation 'com.ginsberg:junit5-system-exit:1.1.2'
|
||||
|
||||
// implementation "ch.qos.logback:logback-core:1.2.6"
|
||||
// We want logback logging in tests and in the app
|
||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
// implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
implementation "org.slf4j:slf4j-nop:$slf4jVersion"
|
||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
implementation(project(":pgpainless-sop"))
|
||||
implementation "org.pgpainless:sop-java-picocli:$sopJavaVersion"
|
||||
|
@ -37,6 +52,22 @@ mainClassName = 'org.pgpainless.cli.PGPainlessCLI'
|
|||
application {
|
||||
mainClass = mainClassName
|
||||
}
|
||||
/**
|
||||
jar {
|
||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
} {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
run {
|
||||
// https://stackoverflow.com/questions/59445306/pipe-into-gradle-run
|
||||
|
@ -46,3 +77,5 @@ run {
|
|||
args Eval.me(appArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// tasks."jar".dependsOn(":pgpainless-core:assemble", ":pgpainless-sop:assemble")
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-armor
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-ARMOR" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-ARMOR" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -30,12 +31,17 @@
|
|||
pgpainless\-cli\-armor \- Add ASCII Armor to standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli armor\fP [\fB\-\-stacktrace\fP]
|
||||
\fBpgpainless\-cli armor\fP [\fB\-\-stacktrace\fP] [\fB\-\-label\fP=\fI{auto|sig|key|cert|message}\fP]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-label\fP=\fI{auto|sig|key|cert|message}\fP
|
||||
.RS 4
|
||||
Label to be used in the header and tail of the armoring
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
|
@ -1,67 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-change-key-password
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-CHANGE\-KEY\-PASSWORD" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
.nh
|
||||
.ad l
|
||||
.de URL
|
||||
\fI\\$2\fP <\\$1>\\$3
|
||||
..
|
||||
.als MTO URL
|
||||
.if \n[.g] \{\
|
||||
. mso www.tmac
|
||||
. am URL
|
||||
. ad l
|
||||
. .
|
||||
. am MTO
|
||||
. ad l
|
||||
. .
|
||||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-change\-key\-password \- Update the password of a key
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli change\-key\-password\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-new\-key\-password\fP
|
||||
[=\fIPASSWORD\fP]] [\fB\-\-old\-key\-password\fP=\fIPASSWORD\fP]...
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Unlock all secret keys from STDIN using the given old passwords and emit them re\-locked using the new password to STDOUT.
|
||||
If any (sub\-) key cannot be unlocked, this operation will exit with error code 67.
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-new\-key\-password\fP[=\fIPASSWORD\fP]
|
||||
.RS 4
|
||||
New password to lock the keys with.
|
||||
.sp
|
||||
If no new password is passed in, the keys will be emitted unlocked.
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
.RS 4
|
||||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-old\-key\-password\fP=\fIPASSWORD\fP
|
||||
.RS 4
|
||||
Old passwords to unlock the keys with.
|
||||
.sp
|
||||
Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys.
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-dearmor
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-DEARMOR" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-DEARMOR" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -37,5 +38,5 @@ pgpainless\-cli\-dearmor \- Remove ASCII Armor from standard input
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-decrypt
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-DECRYPT" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-DECRYPT" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,29 +28,19 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-decrypt \- Decrypt a message
|
||||
pgpainless\-cli\-decrypt \- Decrypt a message from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli decrypt\fP [\fB\-\-stacktrace\fP] [\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP]
|
||||
[\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP] [\fB\-\-verify\-not\-after\fP=\fIDATE\fP]
|
||||
[\fB\-\-verify\-not\-before\fP=\fIDATE\fP] [\fB\-\-verify\-with\fP=\fICERT\fP]...
|
||||
[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fB\-\-with\-password\fP=\fIPASSWORD\fP]...
|
||||
[\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP]... [\fIKEY\fP...]
|
||||
\fBpgpainless\-cli decrypt\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP]
|
||||
[\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP] [\fB\-\-verify\-out\fP=\fIVERIFICATIONS\fP]
|
||||
[\fB\-\-verify\-with\fP=\fICERT\fP]... [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]...
|
||||
[\fB\-\-with\-password\fP=\fIPASSWORD\fP]... [\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP]...
|
||||
[\fIKEY\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP
|
||||
.RS 4
|
||||
Can be used to learn the session key on successful decryption
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP, \fB\-\-verify\-not\-after\fP=\fIDATE\fP
|
||||
\fB\-\-not\-after\fP=\fIDATE\fP
|
||||
.RS 4
|
||||
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
|
||||
.sp
|
||||
|
@ -60,7 +51,7 @@ Defaults to current system time (\(aqnow\(aq).
|
|||
Accepts special value \(aq\-\(aq for end of time.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verify\-not\-before\fP=\fIDATE\fP
|
||||
\fB\-\-not\-before\fP=\fIDATE\fP
|
||||
.RS 4
|
||||
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
|
||||
.sp
|
||||
|
@ -69,6 +60,21 @@ Reject signatures with a creation date not in range.
|
|||
Defaults to beginning of time (\(aq\-\(aq).
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP
|
||||
.RS 4
|
||||
Can be used to learn the session key on successful decryption
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verify\-out\fP=\fIVERIFICATIONS\fP
|
||||
.RS 4
|
||||
Emits signature verification status to the designated output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verify\-with\fP=\fICERT\fP
|
||||
.RS 4
|
||||
Certificates for signature verification
|
||||
|
@ -87,7 +93,7 @@ Symmetric passphrase to decrypt the message with.
|
|||
.sp
|
||||
Enables decryption based on any "SKESK" packets in the "CIPHERTEXT".
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP
|
||||
|
@ -96,7 +102,7 @@ Symmetric message key (session key).
|
|||
.sp
|
||||
Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet.
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-encrypt
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-ENCRYPT" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-ENCRYPT" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -31,7 +32,6 @@ pgpainless\-cli\-encrypt \- Encrypt a message from standard input
|
|||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli encrypt\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text}\fP]
|
||||
[\fB\-\-profile\fP=\fIPROFILE\fP] [\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP]
|
||||
[\fB\-\-sign\-with\fP=\fIKEY\fP]... [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]...
|
||||
[\fB\-\-with\-password\fP=\fIPASSWORD\fP]... [\fICERTS\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
@ -48,19 +48,14 @@ Type of the input data. Defaults to \(aqbinary\(aq
|
|||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-profile\fP=\fIPROFILE\fP
|
||||
.RS 4
|
||||
Profile identifier to switch between profiles
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP, \fB\-\-sign\-with\fP=\fIKEY\fP
|
||||
\fB\-\-sign\-with\fP=\fIKEY\fP
|
||||
.RS 4
|
||||
Sign the output with a private key
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
|
@ -74,7 +69,7 @@ Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
|||
.RS 4
|
||||
Encrypt the message with a password.
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-extract-cert
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-EXTRACT\-CERT" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-EXTRACT\-CERT" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,13 +28,12 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-extract\-cert \- Extract a public key certificate from a secret key
|
||||
pgpainless\-cli\-extract\-cert \- Extract a public key certificate from a secret key from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli extract\-cert\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Read a secret key from STDIN and emit the public key certificate to STDOUT.
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
|
@ -43,5 +43,5 @@ ASCII armor the output
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-generate-completion
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source: generate-completion 4.6.3
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-GENERATE\-COMPLETION" "1" "" "generate\-completion 4.6.3" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-GENERATE\-COMPLETION" "1" "2022-11-06" "generate\-completion 4.6.3" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -50,7 +51,7 @@ Show this help message and exit.
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-V\fP, \fB\-\-version\fP
|
||||
|
@ -141,7 +142,7 @@ Unsupported subcommand
|
|||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
Unsupported special prefix (e.g. "@env/@fd") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
|
@ -152,14 +153,4 @@ Ambiguous input (a filename matching the designator already exists)
|
|||
\fB79\fP
|
||||
.RS 4
|
||||
Key is not signing capable
|
||||
.RE
|
||||
.sp
|
||||
\fB83\fP
|
||||
.RS 4
|
||||
Options were supplied that are incompatible with each other
|
||||
.RE
|
||||
.sp
|
||||
\fB89\fP
|
||||
.RS 4
|
||||
The requested profile is unsupported, or the indicated subcommand does not accept profiles
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-generate-key
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-GENERATE\-KEY" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-GENERATE\-KEY" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -30,8 +31,8 @@
|
|||
pgpainless\-cli\-generate\-key \- Generate a secret key
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli generate\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-signing\-only\fP] [\fB\-\-stacktrace\fP]
|
||||
[\fB\-\-profile\fP=\fIPROFILE\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP] [\fIUSERID\fP...]
|
||||
\fBpgpainless\-cli generate\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]
|
||||
[\fIUSERID\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
|
@ -41,19 +42,9 @@ pgpainless\-cli\-generate\-key \- Generate a secret key
|
|||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-profile\fP=\fIPROFILE\fP
|
||||
.RS 4
|
||||
Profile identifier to switch between profiles
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-signing\-only\fP
|
||||
.RS 4
|
||||
Generate a key that can only be used for signing
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-help
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-HELP" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-HELP" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -44,7 +45,7 @@ Show usage help for the help command and exit.
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
|
@ -136,7 +137,7 @@ Unsupported subcommand
|
|||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
Unsupported special prefix (e.g. "@env/@fd") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
|
@ -147,14 +148,4 @@ Ambiguous input (a filename matching the designator already exists)
|
|||
\fB79\fP
|
||||
.RS 4
|
||||
Key is not signing capable
|
||||
.RE
|
||||
.sp
|
||||
\fB83\fP
|
||||
.RS 4
|
||||
Options were supplied that are incompatible with each other
|
||||
.RE
|
||||
.sp
|
||||
\fB89\fP
|
||||
.RS 4
|
||||
The requested profile is unsupported, or the indicated subcommand does not accept profiles
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-inline-detach
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-DETACH" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-DETACH" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -47,5 +48,5 @@ Destination to which a detached signatures block will be written
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-inline-sign
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-SIGN" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-SIGN" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,22 +28,23 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-inline\-sign \- Create an inline\-signed message
|
||||
pgpainless\-cli\-inline\-sign \- Create an inline\-signed message from data on standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli inline\-sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP]
|
||||
\fBpgpainless\-cli inline\-sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=
|
||||
\fI{binary|text|cleartextsigned}\fP]
|
||||
[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fIKEYS\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP
|
||||
\fB\-\-as\fP=\fI{binary|text|cleartextsigned}\fP
|
||||
.RS 4
|
||||
Specify the signature format of the signed message.
|
||||
Specify the signature format of the signed message
|
||||
.sp
|
||||
\(aqtext\(aq and \(aqbinary\(aq will produce inline\-signed messages.
|
||||
.sp
|
||||
\(aqclearsigned\(aq will make use of the cleartext signature framework.
|
||||
\(aqcleartextsigned\(aq will make use of the cleartext signature framework.
|
||||
.sp
|
||||
Defaults to \(aqbinary\(aq.
|
||||
.sp
|
||||
|
@ -56,7 +58,7 @@ ASCII armor the output
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-inline-verify
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-VERIFY" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-VERIFY" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,11 +28,11 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-inline\-verify \- Verify an inline\-signed message
|
||||
pgpainless\-cli\-inline\-verify \- Verify inline\-signed data from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli inline\-verify\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP]
|
||||
[\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP] [\fICERT\fP...]
|
||||
[\fB\-\-verifications\-out\fP=\fI<verificationsOut>\fP] [\fICERT\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
|
@ -58,10 +59,10 @@ Defaults to beginning of time ("\-").
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP
|
||||
\fB\-\-verifications\-out\fP=\fI<verificationsOut>\fP
|
||||
.RS 4
|
||||
File to write details over successful verifications to
|
||||
.RE
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-list-profiles
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-LIST\-PROFILES" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
.nh
|
||||
.ad l
|
||||
.de URL
|
||||
\fI\\$2\fP <\\$1>\\$3
|
||||
..
|
||||
.als MTO URL
|
||||
.if \n[.g] \{\
|
||||
. mso www.tmac
|
||||
. am URL
|
||||
. ad l
|
||||
. .
|
||||
. am MTO
|
||||
. ad l
|
||||
. .
|
||||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-list\-profiles \- Emit a list of profiles supported by the identified subcommand
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli list\-profiles\fP [\fB\-\-stacktrace\fP] \fICOMMAND\fP
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
\fICOMMAND\fP
|
||||
.RS 4
|
||||
Subcommand for which to list profiles
|
||||
.RE
|
|
@ -1,54 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-revoke-key
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-REVOKE\-KEY" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
.nh
|
||||
.ad l
|
||||
.de URL
|
||||
\fI\\$2\fP <\\$1>\\$3
|
||||
..
|
||||
.als MTO URL
|
||||
.if \n[.g] \{\
|
||||
. mso www.tmac
|
||||
. am URL
|
||||
. ad l
|
||||
. .
|
||||
. am MTO
|
||||
. ad l
|
||||
. .
|
||||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-revoke\-key \- Generate revocation certificates
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli revoke\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Emit revocation certificates for secret keys from STDIN to STDOUT.
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
.RS 4
|
||||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
.RS 4
|
||||
Passphrase to unlock the secret key(s).
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-sign
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-SIGN" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-SIGN" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,7 +28,7 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-sign \- Create a detached message signature
|
||||
pgpainless\-cli\-sign \- Create a detached signature on the data from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text}\fP]
|
||||
|
@ -38,7 +39,7 @@ pgpainless\-cli\-sign \- Create a detached message signature
|
|||
.sp
|
||||
\fB\-\-as\fP=\fI{binary|text}\fP
|
||||
.RS 4
|
||||
Specify the output format of the signed message.
|
||||
Specify the output format of the signed message
|
||||
.sp
|
||||
Defaults to \(aqbinary\(aq.
|
||||
.sp
|
||||
|
@ -47,7 +48,7 @@ If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, sign fails with r
|
|||
.sp
|
||||
\fB\-\-micalg\-out\fP=\fIMICALG\fP
|
||||
.RS 4
|
||||
Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content\-Type (RFC3156).
|
||||
Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content\-Type (RFC3156)
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
|
@ -57,7 +58,7 @@ ASCII armor the output
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-verify
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-VERIFY" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-VERIFY" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -27,28 +28,16 @@
|
|||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
pgpainless\-cli\-verify \- Verify a detached signature
|
||||
pgpainless\-cli\-verify \- Verify a detached signature over the data from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli verify\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP] \fISIGNATURE\fP
|
||||
\fICERT\fP...
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Verify a detached signature over some data from STDIN.
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-not\-after\fP=\fIDATE\fP
|
||||
.RS 4
|
||||
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
|
||||
.sp
|
||||
Reject signatures with a creation date not in range.
|
||||
.sp
|
||||
Defaults to current system time ("now").
|
||||
.sp
|
||||
Accepts special value "\-" for end of time.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-not\-before\fP=\fIDATE\fP
|
||||
\fB\-\-not\-after\fP=\fIDATE\fP, \fB\-\-not\-before\fP=\fIDATE\fP
|
||||
.RS 4
|
||||
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
|
||||
.sp
|
||||
|
@ -59,7 +48,7 @@ Defaults to beginning of time ("\-").
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
|
@ -70,5 +59,4 @@ Detached signature
|
|||
.sp
|
||||
\fICERT\fP...
|
||||
.RS 4
|
||||
Public key certificates for signature verification
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli-version
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-VERSION" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI\-VERSION" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -30,7 +31,7 @@
|
|||
pgpainless\-cli\-version \- Display version information about the tool
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli version\fP [\fB\-\-stacktrace\fP] [\fB\-\-extended\fP | \fB\-\-backend\fP | \fB\-\-pgpainless\-cli\-spec\fP | \fB\-\-sopv\fP]
|
||||
\fBpgpainless\-cli version\fP [\fB\-\-stacktrace\fP] [\fB\-\-extended\fP | \fB\-\-backend\fP]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
|
@ -45,12 +46,7 @@ Print information about the cryptographic backend
|
|||
Print an extended version string
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-pgpainless\-cli\-spec\fP
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print the latest revision of the SOP specification targeted by the implementation
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-sopv\fP, \fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
|
@ -2,11 +2,12 @@
|
|||
.\" Title: pgpainless-cli
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2022-11-06
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI" "1" "" "" "PGPainless\-CLI Manual"
|
||||
.TH "PGPAINLESS\-CLI" "1" "2022-11-06" "" "PGPainless\-CLI Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
|
@ -37,73 +38,13 @@ pgpainless\-cli \- Stateless OpenPGP Protocol
|
|||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
Print Stacktrace
|
||||
.RE
|
||||
.SH "COMMANDS"
|
||||
.sp
|
||||
\fBversion\fP
|
||||
\fBhelp\fP
|
||||
.RS 4
|
||||
Display version information about the tool
|
||||
.RE
|
||||
.sp
|
||||
\fBlist\-profiles\fP
|
||||
.RS 4
|
||||
Emit a list of profiles supported by the identified subcommand
|
||||
.RE
|
||||
.sp
|
||||
\fBgenerate\-key\fP
|
||||
.RS 4
|
||||
Generate a secret key
|
||||
.RE
|
||||
.sp
|
||||
\fBchange\-key\-password\fP
|
||||
.RS 4
|
||||
Update the password of a key
|
||||
.RE
|
||||
.sp
|
||||
\fBrevoke\-key\fP
|
||||
.RS 4
|
||||
Generate revocation certificates
|
||||
.RE
|
||||
.sp
|
||||
\fBextract\-cert\fP
|
||||
.RS 4
|
||||
Extract a public key certificate from a secret key
|
||||
.RE
|
||||
.sp
|
||||
\fBsign\fP
|
||||
.RS 4
|
||||
Create a detached message signature
|
||||
.RE
|
||||
.sp
|
||||
\fBverify\fP
|
||||
.RS 4
|
||||
Verify a detached signature
|
||||
.RE
|
||||
.sp
|
||||
\fBencrypt\fP
|
||||
.RS 4
|
||||
Encrypt a message from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBdecrypt\fP
|
||||
.RS 4
|
||||
Decrypt a message
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-detach\fP
|
||||
.RS 4
|
||||
Split signatures from a clearsigned message
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-sign\fP
|
||||
.RS 4
|
||||
Create an inline\-signed message
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-verify\fP
|
||||
.RS 4
|
||||
Verify an inline\-signed message
|
||||
Stateless OpenPGP Protocol
|
||||
.RE
|
||||
.sp
|
||||
\fBarmor\fP
|
||||
|
@ -116,9 +57,54 @@ Add ASCII Armor to standard input
|
|||
Remove ASCII Armor from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBhelp\fP
|
||||
\fBdecrypt\fP
|
||||
.RS 4
|
||||
Stateless OpenPGP Protocol
|
||||
Decrypt a message from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-detach\fP
|
||||
.RS 4
|
||||
Split signatures from a clearsigned message
|
||||
.RE
|
||||
.sp
|
||||
\fBencrypt\fP
|
||||
.RS 4
|
||||
Encrypt a message from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBextract\-cert\fP
|
||||
.RS 4
|
||||
Extract a public key certificate from a secret key from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBgenerate\-key\fP
|
||||
.RS 4
|
||||
Generate a secret key
|
||||
.RE
|
||||
.sp
|
||||
\fBsign\fP
|
||||
.RS 4
|
||||
Create a detached signature on the data from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBverify\fP
|
||||
.RS 4
|
||||
Verify a detached signature over the data from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-sign\fP
|
||||
.RS 4
|
||||
Create an inline\-signed message from data on standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBinline\-verify\fP
|
||||
.RS 4
|
||||
Verify inline\-signed data from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBversion\fP
|
||||
.RS 4
|
||||
Display version information about the tool
|
||||
.RE
|
||||
.sp
|
||||
\fBgenerate\-completion\fP
|
||||
|
@ -209,7 +195,7 @@ Unsupported subcommand
|
|||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
Unsupported special prefix (e.g. "@env/@fd") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
|
@ -220,14 +206,4 @@ Ambiguous input (a filename matching the designator already exists)
|
|||
\fB79\fP
|
||||
.RS 4
|
||||
Key is not signing capable
|
||||
.RE
|
||||
.sp
|
||||
\fB83\fP
|
||||
.RS 4
|
||||
Options were supplied that are incompatible with each other
|
||||
.RE
|
||||
.sp
|
||||
\fB89\fP
|
||||
.RS 4
|
||||
The requested profile is unsupported, or the indicated subcommand does not accept profiles
|
||||
.RE
|
|
@ -4,7 +4,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|||
SOP_DIR=$(realpath $SCRIPT_DIR/../../sop-java)
|
||||
[ ! -d "$SOP_DIR" ] && echo "sop-java repository MUST be cloned next to pgpainless repo" && exit 1;
|
||||
SRC_DIR=$SOP_DIR/sop-java-picocli/build/docs/manpage
|
||||
[ ! -d "$SRC_DIR" ] && echo "No sop manpages found. Please run 'gradle asciidoctor' in the sop-java repo." && exit 1;
|
||||
[ ! -d "$SRC_DIR" ] && echo "No sop manpages found." && exit 1;
|
||||
DEST_DIR=$SCRIPT_DIR/packaging/man
|
||||
mkdir -p $DEST_DIR
|
||||
|
||||
|
@ -13,14 +13,12 @@ do
|
|||
SRC="${page##*/}"
|
||||
DEST="${SRC/sop/pgpainless-cli}"
|
||||
sed \
|
||||
-e 's/sopv/PLACEHOLDERV/g' \
|
||||
-e 's#.\\" Title: sop#.\\" Title: pgpainless-cli#g' \
|
||||
-e 's/Manual: Sop Manual/Manual: PGPainless-CLI Manual/g' \
|
||||
-e 's/.TH "SOP/.TH "PGPAINLESS\\-CLI/g' \
|
||||
-e 's/"Sop Manual"/"PGPainless\\-CLI Manual"/g' \
|
||||
-e 's/\\fBsop/\\fBpgpainless\\-cli/g' \
|
||||
-e 's/sop/pgpainless\\-cli/g' \
|
||||
-e 's/PLACEHOLDERV/sopv/g' \
|
||||
$page > $DEST_DIR/$DEST
|
||||
done
|
||||
|
||||
|
|
|
@ -14,18 +14,10 @@ import sop.cli.picocli.SopCLI;
|
|||
public class PGPainlessCLI {
|
||||
|
||||
static {
|
||||
// Prevent slf4j initialization logging
|
||||
// https://github.com/qos-ch/slf4j/issues/422#issuecomment-2277280185
|
||||
System.setProperty("slf4j.internal.verbosity", "WARN");
|
||||
|
||||
SopCLI.EXECUTABLE_NAME = "pgpainless-cli";
|
||||
SopCLI.setSopInstance(new SOPImpl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method of the CLI application.
|
||||
* @param args arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
int result = execute(args);
|
||||
if (result != 0) {
|
||||
|
@ -33,12 +25,6 @@ public class PGPainlessCLI {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given command and return the exit code of the program.
|
||||
*
|
||||
* @param args command string array (e.g. ["pgpainless-cli", "generate-key", "Alice"])
|
||||
* @return exit code
|
||||
*/
|
||||
public static int execute(String... args) {
|
||||
return SopCLI.execute(args);
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[
|
||||
]
|
|
@ -1,7 +0,0 @@
|
|||
[
|
||||
{
|
||||
"type":"agent-extracted",
|
||||
"classes":[
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,2 +0,0 @@
|
|||
[
|
||||
]
|
|
@ -1,891 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name":"[Ljava.lang.Object;"
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder",
|
||||
"queryAllPublicMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.classic.joran.SerializedModelConfigurator",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.classic.util.DefaultJoranConfigurator",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.ConsoleAppender",
|
||||
"queryAllPublicMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTarget","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.OutputStreamAppender",
|
||||
"methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.encoder.Encoder",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder",
|
||||
"methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase",
|
||||
"methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"ch.qos.logback.core.spi.ContextAware",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"groovy.lang.Closure"
|
||||
},
|
||||
{
|
||||
"name":"java.io.FilePermission"
|
||||
},
|
||||
{
|
||||
"name":"java.lang.Enum"
|
||||
},
|
||||
{
|
||||
"name":"java.lang.Object",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"java.lang.RuntimePermission"
|
||||
},
|
||||
{
|
||||
"name":"java.lang.System",
|
||||
"methods":[{"name":"console","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"java.lang.invoke.MethodHandle"
|
||||
},
|
||||
{
|
||||
"name":"java.net.NetPermission"
|
||||
},
|
||||
{
|
||||
"name":"java.net.SocketPermission"
|
||||
},
|
||||
{
|
||||
"name":"java.net.URLPermission",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.nio.channels.SelectionKey",
|
||||
"fields":[{"name":"attachment"}]
|
||||
},
|
||||
{
|
||||
"name":"java.nio.file.Path"
|
||||
},
|
||||
{
|
||||
"name":"java.nio.file.Paths",
|
||||
"methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.security.AllPermission"
|
||||
},
|
||||
{
|
||||
"name":"java.security.MessageDigestSpi"
|
||||
},
|
||||
{
|
||||
"name":"java.security.SecureRandomParameters"
|
||||
},
|
||||
{
|
||||
"name":"java.security.SecurityPermission"
|
||||
},
|
||||
{
|
||||
"name":"java.security.cert.PKIXRevocationChecker"
|
||||
},
|
||||
{
|
||||
"name":"java.sql.Connection"
|
||||
},
|
||||
{
|
||||
"name":"java.sql.Driver"
|
||||
},
|
||||
{
|
||||
"name":"java.sql.DriverManager",
|
||||
"methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.sql.Time",
|
||||
"methods":[{"name":"<init>","parameterTypes":["long"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.sql.Timestamp",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.Duration",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.Instant",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.LocalDate",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.LocalDateTime",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.LocalTime",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.MonthDay",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.OffsetDateTime",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.OffsetTime",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.Period",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.Year",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.YearMonth",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.ZoneId",
|
||||
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.ZoneOffset",
|
||||
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.time.ZonedDateTime",
|
||||
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
|
||||
},
|
||||
{
|
||||
"name":"java.util.HashSet"
|
||||
},
|
||||
{
|
||||
"name":"java.util.LinkedHashSet"
|
||||
},
|
||||
{
|
||||
"name":"java.util.PropertyPermission"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.ArrayBlockingQueue"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.atomic.AtomicReference",
|
||||
"fields":[{"name":"value"}]
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
|
||||
},
|
||||
{
|
||||
"name":"javax.smartcardio.CardPermission"
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.CONTEXT$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.Dilithium$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.EXTERNAL$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.Falcon$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLDSA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLKEM$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.SLHDSA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$EdDSA",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$XDH",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyPairGeneratorSpi",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Blake3$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.ExitCodeTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"successfulExecutionDoesNotTerminateJVM","parameterTypes":[] }, {"name":"testCommandWithUnknownOption_37","parameterTypes":[] }, {"name":"testUnknownCommand_69","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.TestUtils",
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.ArmorCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"armorAlreadyArmoredDataIsIdempotent","parameterTypes":[] }, {"name":"armorMessage","parameterTypes":[] }, {"name":"armorPublicKey","parameterTypes":[] }, {"name":"armorSecretKey","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.CLITest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"methods":[{"name":"cleanup","parameterTypes":[] }, {"name":"setup","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.DearmorCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"dearmorBrokenArmoredKeyFails","parameterTypes":[] }, {"name":"dearmorCertificate","parameterTypes":[] }, {"name":"dearmorGarbageEmitsEmpty","parameterTypes":[] }, {"name":"dearmorMessage","parameterTypes":[] }, {"name":"dearmorSecretKey","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.ExtractCertCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"extractCertFromGarbageFails","parameterTypes":[] }, {"name":"testExtractCert","parameterTypes":[] }, {"name":"testExtractCertFromCertFails","parameterTypes":[] }, {"name":"testExtractCertUnarmored","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.GenerateKeyCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testGenerateBinaryKey","parameterTypes":[] }, {"name":"testGenerateKey","parameterTypes":[] }, {"name":"testGenerateKeyWithMultipleUserIds","parameterTypes":[] }, {"name":"testGeneratePasswordProtectedKey_missingPasswordFile","parameterTypes":[] }, {"name":"testPasswordProtectedKey","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.InlineDetachCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessage","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessageNoArmor","parameterTypes":[] }, {"name":"detachMissingSignaturesFromCleartextSignedMessageFails","parameterTypes":[] }, {"name":"detachNonOpenPgpDataFails","parameterTypes":[] }, {"name":"existingSignatureOutCausesException","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.ListProfilesCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"listProfileOfGenerateKey","parameterTypes":[] }, {"name":"listProfilesOfEncrypt","parameterTypes":[] }, {"name":"listProfilesWithoutCommand","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.RoundTripEncryptDecryptCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"decryptGarbageFails","parameterTypes":[] }, {"name":"decryptMalformedMessageYieldsBadData","parameterTypes":[] }, {"name":"decryptMessageWithSessionKey","parameterTypes":[] }, {"name":"decryptMessageWithWrongKeyFails","parameterTypes":[] }, {"name":"decryptWithPasswordWithPendingWhitespaceWorks","parameterTypes":[] }, {"name":"decryptWithWhitespacePasswordWorks","parameterTypes":[] }, {"name":"decrypt_verifyWithGarbageCertFails","parameterTypes":[] }, {"name":"decrypt_withGarbageKeyFails","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }, {"name":"encryptAndDecryptMessageWithPassphrase","parameterTypes":[] }, {"name":"encryptWithGarbageCertFails","parameterTypes":[] }, {"name":"encryptWithPasswordADecryptWithPasswordBFails","parameterTypes":[] }, {"name":"encryptWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"encryptWithTrailingWhitespaceDecryptWithoutWorks","parameterTypes":[] }, {"name":"encrypt_signWithGarbageKeyFails","parameterTypes":[] }, {"name":"testDecryptVerifyOut_withoutVerifyWithFails","parameterTypes":[] }, {"name":"testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications","parameterTypes":[] }, {"name":"testDecryptWithoutDecryptionOptionFails","parameterTypes":[] }, {"name":"testEncryptDecryptRoundTripWithPasswordProtectedKey","parameterTypes":[] }, {"name":"testEncryptDecryptWithFreshRSAKey","parameterTypes":[] }, {"name":"testEncryptWithIncapableCert","parameterTypes":[] }, {"name":"testEncrypt_SignWithCertFails","parameterTypes":[] }, {"name":"testMissingArgumentsIfNoArgsSupplied","parameterTypes":[] }, {"name":"testSessionKeyOutWritesSessionKeyOut","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testVerificationsOutAlreadyExistFails","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.RoundTripInlineSignInlineVerifyCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"cannotVerifyEncryptedMessage","parameterTypes":[] }, {"name":"cannotVerifyMalformedMessage","parameterTypes":[] }, {"name":"createAndVerifyCleartextSignedMessage","parameterTypes":[] }, {"name":"createAndVerifyMultiKeyBinarySignedMessage","parameterTypes":[] }, {"name":"createAndVerifyTextSignedMessage","parameterTypes":[] }, {"name":"createCleartextSignedMessage","parameterTypes":[] }, {"name":"createMalformedMessage","parameterTypes":[] }, {"name":"createSignedMessageWithKeyAAndVerifyWithKeyBFails","parameterTypes":[] }, {"name":"createTextSignedMessageInlineDetachAndDetachedVerify","parameterTypes":[] }, {"name":"signWithProtectedKeyWithWrongPassphraseFails","parameterTypes":[] }, {"name":"testInlineSignWithMissingSecretKeysFails","parameterTypes":[] }, {"name":"testUnlockKeyWithOneOfMultiplePasswords","parameterTypes":[] }, {"name":"verifyPrependedSignedMessage","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.RoundTripInlineSignVerifyCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.RoundTripSignVerifyCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"createArmoredSignature","parameterTypes":[] }, {"name":"createUnarmoredSignature","parameterTypes":[] }, {"name":"signWithProtectedKey","parameterTypes":[] }, {"name":"signWithProtectedKey_missingPassphraseFails","parameterTypes":[] }, {"name":"signWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"testNotAfter","parameterTypes":[] }, {"name":"testNotBefore","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }, {"name":"unarmorArmoredSigAndVerify","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.commands.VersionCmdTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testExtendedVersion","parameterTypes":[] }, {"name":"testGetBackendVersion","parameterTypes":[] }, {"name":"testSopSpecVersion","parameterTypes":[] }, {"name":"testVersion","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"org.pgpainless.cli.misc.SignUsingPublicKeyBehaviorTest",
|
||||
"allDeclaredFields":true,
|
||||
"allDeclaredClasses":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicMethods":true,
|
||||
"queryAllDeclaredConstructors":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"picocli.AutoComplete$GenerateCompletion",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"picocli.CommandLine$AutoHelpMixin",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"picocli.CommandLine$HelpCommand",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.SopCLI",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.SopCLI$InitLocale",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.AbstractSopCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.ArmorCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.ChangeKeyPasswordCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.DearmorCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.DecryptCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.EncryptCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.ExtractCertCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.GenerateKeyCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.InlineDetachCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.InlineSignCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.InlineVerifyCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.ListProfilesCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.RevokeKeyCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.SignCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.VerifyCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.VersionCmd",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sop.cli.picocli.commands.VersionCmd$Exclusive",
|
||||
"allDeclaredFields":true,
|
||||
"queryAllDeclaredMethods":true,
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.NativePRNG",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.SHA",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
}
|
||||
]
|
|
@ -1,93 +0,0 @@
|
|||
{
|
||||
"resources":{
|
||||
"includes":[{
|
||||
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E"
|
||||
}, {
|
||||
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
|
||||
}, {
|
||||
"pattern":"\\Qjunit-platform.properties\\E"
|
||||
}, {
|
||||
"pattern":"\\Qlogback-test.scmo\\E"
|
||||
}, {
|
||||
"pattern":"\\Qlogback-test.xml\\E"
|
||||
}, {
|
||||
"pattern":"\\Qlogback.scmo\\E"
|
||||
}, {
|
||||
"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
|
||||
}, {
|
||||
"pattern":"\\Qpgpainless-sop.properties\\E"
|
||||
}, {
|
||||
"pattern":"\\Qsop-java-version.properties\\E"
|
||||
}, {
|
||||
"pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E"
|
||||
}]},
|
||||
"bundles":[{
|
||||
"name":"msg_armor",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_change-key-password",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_dearmor",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_decrypt",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_detached-sign",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_detached-verify",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_encrypt",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_extract-cert",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_generate-key",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_inline-detach",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_inline-sign",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_inline-verify",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_list-profiles",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_revoke-key",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_sop",
|
||||
"locales":["de", "und"]
|
||||
}, {
|
||||
"name":"msg_version",
|
||||
"locales":["de", "und"]
|
||||
}]
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"types":[
|
||||
{
|
||||
"name":"java.lang.Enum"
|
||||
},
|
||||
{
|
||||
"name":"java.lang.Object[]"
|
||||
},
|
||||
{
|
||||
"name":"java.util.HashSet"
|
||||
},
|
||||
{
|
||||
"name":"java.util.LinkedHashSet"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.ArrayBlockingQueue"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
|
||||
},
|
||||
{
|
||||
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
|
||||
}
|
||||
],
|
||||
"lambdaCapturingTypes":[
|
||||
],
|
||||
"proxies":[
|
||||
]
|
||||
}
|
|
@ -5,5 +5,22 @@ SPDX-License-Identifier: Apache-2.0
|
|||
-->
|
||||
|
||||
<configuration debug="false">
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="error">
|
||||
<appender-ref ref="STDERR" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -4,35 +4,28 @@
|
|||
|
||||
package org.pgpainless.cli;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.cli.commands.CLITest;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.IOException;
|
||||
public class ExitCodeTest {
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class ExitCodeTest extends CLITest {
|
||||
|
||||
public ExitCodeTest() {
|
||||
super(LoggerFactory.getLogger(ExitCodeTest.class));
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedSubcommand.EXIT_CODE)
|
||||
public void testUnknownCommand_69() {
|
||||
PGPainlessCLI.main(new String[] {"generate-kex"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCommand_69() throws IOException {
|
||||
assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE,
|
||||
executeCommand("unsupported-subcommand"));
|
||||
@ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE)
|
||||
public void testCommandWithUnknownOption_37() {
|
||||
PGPainlessCLI.main(new String[] {"generate-key", "-k", "\"k is unknown\""});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandWithUnknownOption_37() throws IOException {
|
||||
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE,
|
||||
executeCommand("generate-key", "-k", "\"k is unknown\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulExecutionDoesNotTerminateJVM() throws IOException {
|
||||
assertSuccess(executeCommand("version"));
|
||||
@FailOnSystemExit
|
||||
public void successfulExecutionDoesNotTerminateJVM() {
|
||||
PGPainlessCLI.main(new String[] {"version"});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class ArmorCmdTest extends CLITest {
|
||||
|
||||
|
@ -89,12 +90,11 @@ public class ArmorCmdTest extends CLITest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void armorAlreadyArmoredDataIsIdempotent() throws IOException {
|
||||
pipeStringToStdin(key);
|
||||
ByteArrayOutputStream armorOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
|
||||
String armored = armorOut.toString();
|
||||
assertEquals(key, armored);
|
||||
public void labelNotYetSupported() throws IOException {
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("armor", "--label", "Message");
|
||||
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,6 @@ public abstract class CLITest {
|
|||
}
|
||||
|
||||
public void assertSuccess(int exitCode) {
|
||||
assertEquals(0, exitCode,
|
||||
"Expected successful program execution");
|
||||
assertEquals(0, exitCode, "Expected successful program execution");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.pgpainless.cli.commands;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -16,7 +15,6 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class DearmorCmdTest extends CLITest {
|
||||
|
||||
|
@ -60,17 +58,6 @@ public class DearmorCmdTest extends CLITest {
|
|||
assertArrayEquals(secretKey.getEncoded(), dearmored.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorBrokenArmoredKeyFails() throws IOException {
|
||||
// contains a "-"
|
||||
String invalidBase64 = "lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+Wt32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGljZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTyD4NB0rxLT-IsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43fG03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2ANhL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9UeZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkrBgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1eywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXjm4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860wlB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMorsH85mUgCw==";
|
||||
pipeStringToStdin(invalidBase64);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("dearmor");
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorCertificate() throws IOException {
|
||||
|
@ -100,14 +87,4 @@ public class DearmorCmdTest extends CLITest {
|
|||
|
||||
assertEquals("Hello, World\n", out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorGarbageEmitsEmpty() throws IOException {
|
||||
String noArmoredData = "This is not armored.";
|
||||
pipeStringToStdin(noArmoredData);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("dearmor"));
|
||||
assertTrue(out.toString().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,7 @@ public class ExtractCertCmdTest extends CLITest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCert()
|
||||
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
||||
|
||||
|
|
|
@ -83,8 +83,7 @@ public class GenerateKeyCmdTest extends CLITest {
|
|||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isFullyEncrypted());
|
||||
|
||||
assertNotNull(UnlockSecretKey
|
||||
.unlockSecretKey(secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh")));
|
||||
assertNotNull(UnlockSecretKey.unlockSecretKey(secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -92,7 +91,6 @@ public class GenerateKeyCmdTest extends CLITest {
|
|||
int exit = executeCommand("generate-key",
|
||||
"--with-key-password", "nonexistent", "Alice <alice@pgpainless.org>");
|
||||
|
||||
assertEquals(SOPGPException.MissingInput.EXIT_CODE, exit,
|
||||
"Expected MISSING_INPUT (" + SOPGPException.MissingInput.EXIT_CODE + ")");
|
||||
assertEquals(SOPGPException.MissingInput.EXIT_CODE, exit, "Expected MISSING_INPUT (" + SOPGPException.MissingInput.EXIT_CODE + ")");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ public class InlineDetachCmdTest extends CLITest {
|
|||
pipeStringToStdin(msgOut.toString());
|
||||
ByteArrayOutputStream verifyOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath()));
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n",
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n",
|
||||
verifyOut.toString());
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ public class InlineDetachCmdTest extends CLITest {
|
|||
ByteArrayOutputStream verifyOut = pipeStdoutToStream();
|
||||
File certFile = writeFile("cert.asc", CERT);
|
||||
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath()));
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n",
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n",
|
||||
verifyOut.toString());
|
||||
}
|
||||
|
||||
|
@ -129,27 +129,4 @@ public class InlineDetachCmdTest extends CLITest {
|
|||
assertEquals(0, msgOut.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detachNonOpenPgpDataFails() throws IOException {
|
||||
File sig = nonExistentFile("sig.asc");
|
||||
pipeStringToStdin("This is non-OpenPGP data and therefore we cannot detach any signatures from it.");
|
||||
int exitCode = executeCommand("inline-detach", "--signatures-out", sig.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detachMissingSignaturesFromCleartextSignedMessageFails() throws IOException {
|
||||
String cleartextSignedNoSigs = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"Hello, World!\n" +
|
||||
"What's Up!??\n" +
|
||||
"\n" +
|
||||
"\n";
|
||||
pipeStringToStdin(cleartextSignedNoSigs);
|
||||
File sig = nonExistentFile("sig.asc");
|
||||
int exitCode = executeCommand("inline-detach", "--signatures-out", sig.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ListProfilesCmdTest extends CLITest {
|
||||
|
||||
public ListProfilesCmdTest() {
|
||||
super(LoggerFactory.getLogger(ListProfilesCmdTest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listProfilesWithoutCommand() throws IOException {
|
||||
assertNotEquals(0, executeCommand("list-profiles"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listProfileOfGenerateKey() throws IOException {
|
||||
ByteArrayOutputStream output = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("list-profiles", "generate-key"));
|
||||
|
||||
assertTrue(output.toString().contains("rfc4880"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listProfilesOfEncrypt() throws IOException {
|
||||
ByteArrayOutputStream output = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("list-profiles", "encrypt"));
|
||||
|
||||
assertTrue(output.toString().contains("rfc4880"));
|
||||
}
|
||||
}
|
|
@ -14,17 +14,17 @@ import java.io.IOException;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
|
||||
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
|
||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||
import org.pgpainless.key.generation.type.xdh.XDHSpec;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
|
@ -82,6 +82,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void encryptAndDecryptAMessage() throws IOException {
|
||||
// Juliets key and cert
|
||||
File julietKeyFile = pipeStdoutToFile("juliet.key");
|
||||
|
@ -128,7 +129,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
String romeosVerif = readStringFromFile(anotherVerificationsFile);
|
||||
assertEquals(julietsVerif, romeosVerif);
|
||||
assertFalse(julietsVerif.isEmpty());
|
||||
assertEquals(115, julietsVerif.length()); // 115 is number of symbols in [DATE, FINGER, FINGER, MODE] for V4
|
||||
assertEquals(103, julietsVerif.length()); // 103 is number of symbols in [DATE, FINGER, FINGER] for V4
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -138,7 +139,6 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Disabled, since we now read certificates from secret keys")
|
||||
public void testEncryptingForKeyFails() throws IOException {
|
||||
File notACert = writeFile("key.asc", KEY);
|
||||
|
||||
|
@ -168,8 +168,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
|
||||
File verifications = nonExistentFile("verifications");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", "--verifications-out",
|
||||
verifications.getAbsolutePath(), key.getAbsolutePath());
|
||||
int exitCode = executeCommand("decrypt", "--verifications-out", verifications.getAbsolutePath(), key.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
@ -212,8 +211,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
|
||||
pipeStringToStdin(ciphertext);
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--session-key-out",
|
||||
sessionKeyFile.getAbsolutePath(), key.getAbsolutePath()));
|
||||
assertSuccess(executeCommand("decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), key.getAbsolutePath()));
|
||||
|
||||
assertEquals(plaintext, plaintextOut.toString());
|
||||
String resultSessionKey = readStringFromFile(sessionKeyFile);
|
||||
|
@ -274,7 +272,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
|
||||
assertEquals(plaintext, out.toString());
|
||||
String verificationString = readStringFromFile(verifications);
|
||||
assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08 mode:binary\n",
|
||||
assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08\n",
|
||||
verificationString);
|
||||
}
|
||||
|
||||
|
@ -302,8 +300,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
|
||||
.addUserId("No Crypt <no@crypt.key>")
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519),
|
||||
KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
|
||||
.build();
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys);
|
||||
File certFile = writeFile("cert.pgp", cert.getEncoded());
|
||||
|
@ -317,13 +314,11 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSignWithIncapableKey()
|
||||
throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
public void testSignWithIncapableKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
|
||||
.addUserId("Cannot Sign <cannot@sign.key>")
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addSubkey(KeySpec.getBuilder(
|
||||
KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
.build();
|
||||
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
|
||||
File certFile = writeFile("cert.pgp", PGPainless.extractCertificate(secretKeys).getEncoded());
|
||||
|
@ -563,111 +558,4 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
|||
keyFile.getAbsolutePath()));
|
||||
assertEquals(msg, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptMalformedMessageYieldsBadData() throws IOException {
|
||||
// Message contains encrypted data packet which contains the plaintext directly - no literal data packet.
|
||||
// It is therefore malformed.
|
||||
String malformed = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.72b04\n" +
|
||||
"\n" +
|
||||
"hF4D831k4umlLu4SAQdApKA6VDKSLQvwS2kbWqlhcXD8XHdFkSccqv5tBptZnBgw\n" +
|
||||
"nZNXVhwUpap0ymb4jPTD+EVPKOfPyy04ouIGZAJKkfYDeSL/8sKcbnPPuQJYYjGQ\n" +
|
||||
"ySDNmidrtTonwcSuwAfRyn74BBqOVhrr8GXkVIfevIlZFQ==\n" +
|
||||
"=wIgl\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
File key = writeFile("key.asc", KEY);
|
||||
pipeStringToStdin(malformed);
|
||||
int exitCode = executeCommand("decrypt", key.getAbsolutePath());
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptWithPasswordWithPendingWhitespaceWorks() throws IOException {
|
||||
assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh", "sw0rdf1sh \n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptWithTrailingWhitespaceDecryptWithoutWorks() throws IOException {
|
||||
assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh \n", "sw0rdf1sh");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptWithWhitespacePasswordWorks() throws IOException {
|
||||
// is encrypted for "sw0rdf1sh \n"
|
||||
String encryptedToPasswordWithTrailingWhitespace = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"jA0ECQMC32tEJug0BCpg0kABfT3dKgA4K8XGpk2ul67BXLZD//fCCSmIQIWnNhE1\n" +
|
||||
"6q97xFQ628K8f/58+XoBzLqLDT+LEz9Bz+Yg9QfzkEFy\n" +
|
||||
"=2Y+K\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
pipeStringToStdin(encryptedToPasswordWithTrailingWhitespace);
|
||||
File password = writeFile("password", "sw0rdf1sh \n");
|
||||
ByteArrayOutputStream plaintext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-password", password.getAbsolutePath()));
|
||||
|
||||
assertEquals("Hello, World!\n", plaintext.toString());
|
||||
}
|
||||
|
||||
private void assertEncryptWithPasswordADecryptWithPasswordBWorks(String passwordA, String passwordB)
|
||||
throws IOException {
|
||||
File passwordAFile = writeFile("password", passwordA);
|
||||
File passwordBFile = writeFile("passwordWithWS", passwordB);
|
||||
|
||||
String msg = "Hello, World!\n";
|
||||
pipeStringToStdin(msg);
|
||||
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("encrypt", "--with-password", passwordAFile.getAbsolutePath()));
|
||||
|
||||
pipeStringToStdin(ciphertext.toString());
|
||||
ByteArrayOutputStream plaintext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-password", passwordBFile.getAbsolutePath()));
|
||||
|
||||
assertEquals(msg, plaintext.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptWithoutDecryptionOptionFails() throws IOException {
|
||||
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
|
||||
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
|
||||
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
|
||||
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
|
||||
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
|
||||
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
|
||||
"FpVU\n" +
|
||||
"=edS5\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
pipeStringToStdin(ciphertext);
|
||||
int exitCode = executeCommand("decrypt");
|
||||
assertEquals(SOPGPException.MissingArg.EXIT_CODE, exitCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptDecryptWithFreshRSAKey() throws IOException {
|
||||
// Generate key
|
||||
File passwordFile = writeFile("password", "sw0rdf1sh");
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "--profile=rfc4880", "--with-key-password", passwordFile.getAbsolutePath(), "Alice <alice@example.org>"));
|
||||
|
||||
File certFile = pipeStdoutToFile("cert.asc");
|
||||
pipeFileToStdin(keyFile);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
// Write plaintext
|
||||
File plaintextFile = writeFile("msg.txt", "Hello, World!\n");
|
||||
|
||||
// Encrypt
|
||||
File ciphertextFile = pipeStdoutToFile("msg.asc");
|
||||
pipeFileToStdin(plaintextFile);
|
||||
assertSuccess(executeCommand("encrypt", "--profile=rfc4880", certFile.getAbsolutePath()));
|
||||
|
||||
ByteArrayOutputStream decrypted = pipeStdoutToStream();
|
||||
pipeFileToStdin(ciphertextFile);
|
||||
assertSuccess(executeCommand("decrypt", "--with-key-password", passwordFile.getAbsolutePath(), keyFile.getAbsolutePath()));
|
||||
|
||||
assertEquals("Hello, World!\n", decrypted.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,28 +4,16 @@
|
|||
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
||||
|
||||
|
@ -138,10 +126,6 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
"\n" +
|
||||
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
|
||||
|
||||
private static final String MESSAGE_CRLF = "One does not simply use OpenPGP!\r\n" +
|
||||
"\r\n" +
|
||||
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
|
||||
|
||||
@Test
|
||||
public void createCleartextSignedMessage() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_1);
|
||||
|
@ -150,14 +134,14 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "clearsigned",
|
||||
"--as", "cleartextsigned",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
String cleartextSigned = ciphertextOut.toString();
|
||||
assertTrue(cleartextSigned.startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n" +
|
||||
"Hash: "));
|
||||
assertTrue(cleartextSigned.contains(MESSAGE_CRLF));
|
||||
assertTrue(cleartextSigned.contains(MESSAGE));
|
||||
assertTrue(cleartextSigned.contains("\n-----BEGIN PGP SIGNATURE-----\n"));
|
||||
assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n"));
|
||||
}
|
||||
|
@ -170,7 +154,7 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "clearsigned",
|
||||
"--as", "cleartextsigned",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
|
@ -187,55 +171,6 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndVerifyTextSignedMessage() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_1);
|
||||
File password = writeFile("password", KEY_1_PASSWORD);
|
||||
|
||||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "text",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
File cert = writeFile("cert.asc", CERT_1);
|
||||
File verifications = nonExistentFile("verifications");
|
||||
pipeStringToStdin(ciphertextOut.toString());
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-verify",
|
||||
"--verifications-out", verifications.getAbsolutePath(),
|
||||
cert.getAbsolutePath()));
|
||||
|
||||
assertEquals(MESSAGE_CRLF, plaintextOut.toString());
|
||||
String verificationString = readStringFromFile(verifications);
|
||||
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSignedMessageWithKeyAAndVerifyWithKeyBFails() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_1);
|
||||
File password = writeFile("password", KEY_1_PASSWORD);
|
||||
File cert = writeFile("cert.asc", CERT_2); // mismatch
|
||||
|
||||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
File verifications = nonExistentFile("verifications");
|
||||
pipeStringToStdin(ciphertextOut.toString());
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("inline-verify",
|
||||
"--verifications-out", verifications.getAbsolutePath(),
|
||||
cert.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
|
||||
assertEquals(MESSAGE, plaintextOut.toString()); // message is emitted nonetheless
|
||||
assertFalse(verifications.exists(), "Verifications file MUST NOT be written.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndVerifyMultiKeyBinarySignedMessage() throws IOException {
|
||||
File key1Pass = writeFile("password", KEY_1_PASSWORD);
|
||||
|
@ -277,7 +212,7 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "clearsigned",
|
||||
"--as", "cleartextsigned",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
|
@ -291,176 +226,11 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
File cert = writeFile("cert.asc", CERT_1);
|
||||
pipeStringToStdin(msgOut.toString());
|
||||
ByteArrayOutputStream verificationsOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", "--stacktrace",
|
||||
assertSuccess(executeCommand("verify",
|
||||
sigFile.getAbsolutePath(),
|
||||
cert.getAbsolutePath()));
|
||||
|
||||
String verificationString = verificationsOut.toString();
|
||||
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnlockKeyWithOneOfMultiplePasswords() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_1);
|
||||
File wrong1 = writeFile("wrong_1", "BuzzAldr1n");
|
||||
File correct = writeFile("correct", KEY_1_PASSWORD);
|
||||
File wrong2 = writeFile("wrong_2", "NeilArmstr0ng");
|
||||
|
||||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", wrong1.getAbsolutePath(),
|
||||
"--with-key-password", correct.getAbsolutePath(),
|
||||
"--with-key-password", wrong2.getAbsolutePath()));
|
||||
|
||||
File cert = writeFile("cert.asc", CERT_1);
|
||||
pipeStringToStdin(ciphertextOut.toString());
|
||||
ByteArrayOutputStream msgOut = pipeStdoutToStream();
|
||||
File verificationsFile = nonExistentFile("verifications");
|
||||
assertSuccess(executeCommand("inline-verify",
|
||||
"--verifications-out", verificationsFile.getAbsolutePath(),
|
||||
cert.getAbsolutePath()));
|
||||
|
||||
assertEquals(MESSAGE, msgOut.toString());
|
||||
String verificationString = readStringFromFile(verificationsFile);
|
||||
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotVerifyEncryptedMessage() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_2);
|
||||
File cert = writeFile("cert.asc", CERT_2);
|
||||
|
||||
String msg = "Hello, World!\n";
|
||||
pipeStringToStdin(msg);
|
||||
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("encrypt", cert.getAbsolutePath(),
|
||||
"--sign-with", key.getAbsolutePath()));
|
||||
|
||||
File verifications = nonExistentFile("verifications");
|
||||
pipeBytesToStdin(ciphertext.toByteArray());
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("inline-verify", cert.getAbsolutePath(),
|
||||
"--verifications-out", verifications.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMalformedMessage() throws IOException, PGPException {
|
||||
String msg = "Hello, World!\n";
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(KEY_2);
|
||||
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(ciphertext)
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get()
|
||||
.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key)
|
||||
).overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
||||
.setAsciiArmor(false));
|
||||
encryptionStream.write(msg.getBytes(StandardCharsets.UTF_8));
|
||||
encryptionStream.close();
|
||||
PGPSignature sig = encryptionStream.getResult().getDetachedSignatures().entrySet()
|
||||
.iterator().next().getValue().iterator().next();
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
armorOut.write(ciphertext.toByteArray());
|
||||
armorOut.write(sig.getEncoded());
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotVerifyMalformedMessage() throws IOException {
|
||||
// appended signature -> malformed
|
||||
String malformedSignedMessage = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.72b04\n" +
|
||||
"\n" +
|
||||
"yxRiAAAAAABIZWxsbywgV29ybGQhCoh1BAAWCgAnBQJjd52aCRCPvdNtAYMWcxYh\n" +
|
||||
"BHoHPt8nPJAnltJZUo+9020BgxZzAACThwD/Vr7CMitMOul40VK12XXjOv5f8vgi\n" +
|
||||
"ksqhrI2ysItID9oA/0Csgf3Sv2YenYVzqnd0hhiPe5IVPl8w4sTZKpriYMIG\n" +
|
||||
"=DPPU\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
File cert = writeFile("cert.asc", CERT_2);
|
||||
File verifications = nonExistentFile("verifications");
|
||||
|
||||
pipeStringToStdin(malformedSignedMessage);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("inline-verify", cert.getAbsolutePath(),
|
||||
"--verifications-out", verifications.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals("Hello, World!\n", out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyPrependedSignedMessage() throws IOException {
|
||||
// message with prepended signature
|
||||
String malformedSignedMessage = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||
"Version: BCPG v1.72b04\n" +
|
||||
"\n" +
|
||||
"iHUEABYKACcFAmN3nOUJEI+9020BgxZzFiEEegc+3yc8kCeW0llSj73TbQGDFnMA\n" +
|
||||
"ANPKAPkBxLVHvgeCkX/tTHdBH3CDeuUQF2wmtUmGXqhZA1IFtwD/dK0XQBHO3RO+\n" +
|
||||
"GHpzA7fDAroqF0zM72tu2W4PPw04FgKjATstksQAAh6pOTn5Ogrh+UU5KYpcAA==\n" +
|
||||
"=xtik\n" +
|
||||
"-----END PGP SIGNATURE-----";
|
||||
File cert = writeFile("cert.asc", CERT_2);
|
||||
File verifications = nonExistentFile("verifications");
|
||||
|
||||
pipeStringToStdin(malformedSignedMessage);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-verify", cert.getAbsolutePath(),
|
||||
"--verifications-out", verifications.getAbsolutePath()));
|
||||
assertEquals("Hello, World!\n", out.toString());
|
||||
String ver = readStringFromFile(verifications);
|
||||
assertEquals(
|
||||
"2022-11-18T14:55:33Z 7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F mode:binary\n", ver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInlineSignWithMissingSecretKeysFails() throws IOException {
|
||||
String missingSecretKeys = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Comment: 8677 37CA 1979 28FA 325A DE56 B455 9329 9882 36BE\n" +
|
||||
"Comment: Mrs. Secret Key <miss@secret.key>\n" +
|
||||
"\n" +
|
||||
"lEwEY3t3pRYJKwYBBAHaRw8BAQdA7lifUc85s7omw7eYNIaIj2mZrGeZ9KkG0WX2\n" +
|
||||
"hAx5qXT+AGUAR05VAhAAAAAAAAAAAAAAAAAAAAAAtCFNcnMuIFNlY3JldCBLZXkg\n" +
|
||||
"PG1pc3NAc2VjcmV0LmtleT6IjwQTFgoAQQUCY3t3pQkQtFWTKZiCNr4WIQSGdzfK\n" +
|
||||
"GXko+jJa3la0VZMpmII2vgKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAABNTQEA\n" +
|
||||
"uU5L9hJ1QKWxL5wetJwR08rXJTzsuX1LRfy8dlnlJl0BAKPSqydLoTEVlJQ/2sjO\n" +
|
||||
"xQmc6aedoOoXKKVNDW5ibrsEnFEEY3t3pRIKKwYBBAGXVQEFAQEHQA/WdwR+NFaY\n" +
|
||||
"7NeZnRwI3X9sI5fMq0vtEauMLfZjqTc/AwEIB/4AZQBHTlUCEAAAAAAAAAAAAAAA\n" +
|
||||
"AAAAAACIdQQYFgoAHQUCY3t3pQKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJELRV\n" +
|
||||
"kymYgja+8XMA/1quBVvaSf4QxbB2S7rKt93rAynDLqGQD8hC6wiZc+ihAQC87n2r\n" +
|
||||
"meZ9kiYLYiQuBTGvXyzDBtw5m7wQtMWTfXisBpxMBGN7d6UWCSsGAQQB2kcPAQEH\n" +
|
||||
"QMguDhFon0ZI//CIpC2ZndmtvKdJhcEAeVNkdcsIZajl/gBlAEdOVQIQAAAAAAAA\n" +
|
||||
"AAAAAAAAAAAAAIjVBBgWCgB9BQJje3elAp4BApsCBRYCAwEABAsJCAcFFQoJCAtf\n" +
|
||||
"IAQZFgoABgUCY3t3pQAKCRC14KclsvqqOstPAQDYiL7+4HucWKmd7dcd9XJZpdB6\n" +
|
||||
"lneoK0qku0wvTVjX7gEAtUt2eXMlBE4ox+ZmY964PCc2gEHuC7PBtsAzuF7GSQwA\n" +
|
||||
"CgkQtFWTKZiCNr7JKwEA3aLsOWAYzqvKgiboYSzle+SVBUb3chKlzf3YmckjmwgA\n" +
|
||||
"/3YN1W8CiQFvE9NvetZkr2wXB+QVkuL6cxM0ogEo4lAG\n" +
|
||||
"=9ZMl\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||
File key = writeFile("key.asc", missingSecretKeys);
|
||||
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("inline-sign", key.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signWithProtectedKeyWithWrongPassphraseFails() throws IOException {
|
||||
File key = writeFile("key.asc", KEY_1);
|
||||
File password = writeFile("password.asc", "not_correct!");
|
||||
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("inline-sign", key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RoundTripInlineSignVerifyCmdTest extends CLITest {
|
||||
|
||||
public RoundTripInlineSignVerifyCmdTest() {
|
||||
super(LoggerFactory.getLogger(RoundTripInlineSignVerifyCmdTest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptAndDecryptAMessage() throws IOException {
|
||||
// write password file
|
||||
File password = writeFile("password", "sw0rdf1sh");
|
||||
|
||||
// generate key
|
||||
File sigmundKey = pipeStdoutToFile("sigmund.key");
|
||||
assertSuccess(executeCommand("generate-key", "--with-key-password=" + password.getAbsolutePath(),
|
||||
"Sigmund Freud <sigmund@pgpainless.org>"));
|
||||
|
||||
// extract cert
|
||||
File sigmundCert = pipeStdoutToFile("sigmund.cert");
|
||||
pipeFileToStdin(sigmundKey);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
// sign message
|
||||
pipeBytesToStdin("Hello, World!\n".getBytes(StandardCharsets.UTF_8));
|
||||
File signedMsg = pipeStdoutToFile("signed.asc");
|
||||
assertSuccess(executeCommand("inline-sign", "--with-key-password=" + password.getAbsolutePath(),
|
||||
sigmundKey.getAbsolutePath()));
|
||||
|
||||
// verify message
|
||||
File verifyFile = nonExistentFile("verify.txt");
|
||||
pipeFileToStdin(signedMsg);
|
||||
assertSuccess(executeCommand("inline-verify", "--verifications-out", verifyFile.getAbsolutePath(),
|
||||
sigmundCert.getAbsolutePath()));
|
||||
|
||||
String verifications = readStringFromFile(verifyFile);
|
||||
assertFalse(verifications.trim().isEmpty());
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
@ -25,8 +24,8 @@ import org.pgpainless.algorithm.KeyFlag;
|
|||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
|
||||
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
|
||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||
import org.pgpainless.key.generation.type.xdh.XDHSpec;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
@ -95,7 +94,7 @@ public class RoundTripSignVerifyCmdTest extends CLITest {
|
|||
"=VWAZ\n" +
|
||||
"-----END PGP SIGNATURE-----";
|
||||
private static final String BINARY_SIG_VERIFICATION =
|
||||
"2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:binary\n";
|
||||
"2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n";
|
||||
private static final String TEXT_SIG = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
|
@ -105,16 +104,8 @@ public class RoundTripSignVerifyCmdTest extends CLITest {
|
|||
"=s5xn\n" +
|
||||
"-----END PGP SIGNATURE-----";
|
||||
private static final String TEXT_SIG_VERIFICATION =
|
||||
"2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:text\n";
|
||||
private static final Date TEXT_SIG_CREATION;
|
||||
|
||||
static {
|
||||
try {
|
||||
TEXT_SIG_CREATION = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z");
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
"2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n";
|
||||
private static final Date TEXT_SIG_CREATION = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z");
|
||||
|
||||
@Test
|
||||
public void createArmoredSignature() throws IOException {
|
||||
|
@ -198,12 +189,11 @@ public class RoundTripSignVerifyCmdTest extends CLITest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSignWithIncapableKey()
|
||||
throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
public void testSignWithIncapableKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
|
||||
.addUserId("Cannot Sign <cannot@sign.key>")
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
.build();
|
||||
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public class VersionCmdTest extends CLITest {
|
|||
public void testGetBackendVersion() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("version", "--backend"));
|
||||
assertTrue(out.toString().startsWith("PGPainless "));
|
||||
assertTrue(out.toString().startsWith("Bouncy Castle "));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,12 +41,4 @@ public class VersionCmdTest extends CLITest {
|
|||
assertTrue(info.contains("Bouncy Castle"));
|
||||
assertTrue(info.contains("Stateless OpenPGP Protocol"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSopSpecVersion() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("version", "--sop-spec"));
|
||||
String info = out.toString();
|
||||
assertTrue(info.startsWith("draft-dkg-openpgp-stateless-cli-"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,28 @@
|
|||
|
||||
package org.pgpainless.cli.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.cli.commands.CLITest;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class SignUsingPublicKeyBehaviorTest extends CLITest {
|
||||
public class SignUsingPublicKeyBehaviorTest {
|
||||
|
||||
public static final String KEY_THAT_IS_A_CERT = "" +
|
||||
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
|
@ -79,24 +89,61 @@ public class SignUsingPublicKeyBehaviorTest extends CLITest {
|
|||
"=oJQ2\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
public SignUsingPublicKeyBehaviorTest() {
|
||||
super(LoggerFactory.getLogger(SignUsingPublicKeyBehaviorTest.class));
|
||||
|
||||
private static File tempDir;
|
||||
private static PrintStream originalSout;
|
||||
|
||||
@BeforeAll
|
||||
public static void prepare() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE)
|
||||
public void testSignatureCreationAndVerification() throws IOException {
|
||||
originalSout = System.out;
|
||||
InputStream originalIn = System.in;
|
||||
|
||||
// Write alice key to disc
|
||||
File aliceKeyFile = writeFile("alice.key", KEY_THAT_IS_A_CERT);
|
||||
File aliceKeyFile = new File(tempDir, "alice.key");
|
||||
assertTrue(aliceKeyFile.createNewFile());
|
||||
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceKeyOut);
|
||||
aliceKeyOut.close();
|
||||
|
||||
// Write alice pub key to disc
|
||||
File aliceCertFile = new File(tempDir, "alice.pub");
|
||||
assertTrue(aliceCertFile.createNewFile());
|
||||
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceCertOut);
|
||||
aliceCertOut.close();
|
||||
|
||||
// Write test data to disc
|
||||
File dataFile = writeFile("data", "If privacy is outlawed, only outlaws will have privacy.\n");
|
||||
String data = "If privacy is outlawed, only outlaws will have privacy.\n";
|
||||
|
||||
File dataFile = new File(tempDir, "data");
|
||||
assertTrue(dataFile.createNewFile());
|
||||
FileOutputStream dataOut = new FileOutputStream(dataFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
|
||||
dataOut.close();
|
||||
|
||||
// Sign test data
|
||||
File sigFile = pipeStdoutToFile("sig.asc");
|
||||
pipeFileToStdin(dataFile);
|
||||
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE,
|
||||
executeCommand("sign", "--armor", aliceKeyFile.getAbsolutePath()));
|
||||
FileInputStream dataIn = new FileInputStream(dataFile);
|
||||
System.setIn(dataIn);
|
||||
File sigFile = new File(tempDir, "sig.asc");
|
||||
assertTrue(sigFile.createNewFile());
|
||||
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
||||
System.setOut(new PrintStream(sigOut));
|
||||
PGPainlessCLI.main(new String[] {"sign", "--armor", aliceKeyFile.getAbsolutePath()});
|
||||
|
||||
assertTrue(readStringFromFile(sigFile).trim().isEmpty());
|
||||
System.setIn(originalIn);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
System.setOut(originalSout);
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(tempDir.getAbsolutePath());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,18 +20,10 @@ dependencies {
|
|||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
// Bouncy Castle
|
||||
api "org.bouncycastle:bcprov-jdk18on:$bouncyCastleVersion"
|
||||
api "org.bouncycastle:bcpg-jdk18on:$bouncyPgVersion"
|
||||
api "org.bouncycastle:bcutil-jdk18on:$bouncyCastleVersion"
|
||||
api "org.bouncycastle:bcprov-jdk15to18:$bouncyCastleVersion"
|
||||
api "org.bouncycastle:bcpg-jdk15to18:$bouncyPgVersion"
|
||||
// api(files("../libs/bcpg-jdk18on-1.70.jar"))
|
||||
|
||||
// @Nullable, @Nonnull annotations
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
}
|
||||
|
||||
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_modular_auto
|
||||
tasks.named('jar') {
|
||||
manifest {
|
||||
attributes('Automatic-Module-Name': 'org.pgpainless.core')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.gnupg;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
|
||||
public enum GnuPGDummyExtension {
|
||||
|
||||
/**
|
||||
* Do not store the secret part at all.
|
||||
*/
|
||||
NO_PRIVATE_KEY(S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY),
|
||||
|
||||
/**
|
||||
* A stub to access smartcards.
|
||||
*/
|
||||
DIVERT_TO_CARD(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD),
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
GnuPGDummyExtension(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.gnupg;
|
||||
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.bcpg.SecretKeyPacket;
|
||||
import org.bouncycastle.bcpg.SecretSubkeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class can be used to remove private keys from secret software-keys by replacing them with
|
||||
* stub secret keys in the style of GnuPGs proprietary extensions.
|
||||
*
|
||||
* @see <a href="https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;hb=HEAD#l1489">
|
||||
* GnuPGs doc/DETAILS - GNU extensions to the S2K algorithm</a>
|
||||
*/
|
||||
public final class GnuPGDummyKeyUtil {
|
||||
|
||||
private GnuPGDummyKeyUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key-ids of all keys which appear to be stored on a hardware token / smartcard by GnuPG.
|
||||
* Note, that this functionality is based on GnuPGs proprietary S2K extensions, which are not strictly required
|
||||
* for dealing with hardware-backed keys.
|
||||
*
|
||||
* @param secretKeys secret keys
|
||||
* @return set of keys with S2K type GNU_DUMMY_S2K and protection mode DIVERT_TO_CARD
|
||||
*/
|
||||
public static Set<SubkeyIdentifier> getIdsOfKeysWithGnuPGS2KDivertedToCard(@Nonnull PGPSecretKeyRing secretKeys) {
|
||||
Set<SubkeyIdentifier> hardwareBackedKeys = new HashSet<>();
|
||||
for (PGPSecretKey secretKey : secretKeys) {
|
||||
S2K s2K = secretKey.getS2K();
|
||||
if (s2K == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int type = s2K.getType();
|
||||
int mode = s2K.getProtectionMode();
|
||||
// TODO: Is GNU_DUMMY_S2K appropriate?
|
||||
if (type == S2K.GNU_DUMMY_S2K && mode == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
|
||||
SubkeyIdentifier hardwareBackedKey = new SubkeyIdentifier(secretKeys, secretKey.getKeyID());
|
||||
hardwareBackedKeys.add(hardwareBackedKey);
|
||||
}
|
||||
}
|
||||
return hardwareBackedKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the given {@link PGPSecretKeyRing}.
|
||||
*
|
||||
* @param secretKeys secret keys
|
||||
* @return builder
|
||||
*/
|
||||
public static Builder modify(@Nonnull PGPSecretKeyRing secretKeys) {
|
||||
return new Builder(secretKeys);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final PGPSecretKeyRing keys;
|
||||
|
||||
private Builder(@Nonnull PGPSecretKeyRing keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#NO_PRIVATE_KEY}.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing removePrivateKeys(@Nonnull KeyFilter filter) {
|
||||
return replacePrivateKeys(GnuPGDummyExtension.NO_PRIVATE_KEY, null, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#DIVERT_TO_CARD}.
|
||||
* This method will set the serial number of the card to 0x00000000000000000000000000000000.
|
||||
* NOTE: This method does not actually move any keys to a card.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing divertPrivateKeysToCard(@Nonnull KeyFilter filter) {
|
||||
return divertPrivateKeysToCard(filter, new byte[16]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#DIVERT_TO_CARD}.
|
||||
* This method will include the card serial number into the encoded dummy key.
|
||||
* NOTE: This method does not actually move any keys to a card.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @param cardSerialNumber serial number of the card (at most 16 bytes long)
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing divertPrivateKeysToCard(@Nonnull KeyFilter filter, @Nullable byte[] cardSerialNumber) {
|
||||
if (cardSerialNumber != null && cardSerialNumber.length > 16) {
|
||||
throw new IllegalArgumentException("Card serial number length cannot exceed 16 bytes.");
|
||||
}
|
||||
return replacePrivateKeys(GnuPGDummyExtension.DIVERT_TO_CARD, cardSerialNumber, filter);
|
||||
}
|
||||
|
||||
private PGPSecretKeyRing replacePrivateKeys(@Nonnull GnuPGDummyExtension extension,
|
||||
@Nullable byte[] serial,
|
||||
@Nonnull KeyFilter filter) {
|
||||
byte[] encodedSerial = serial != null ? encodeSerial(serial) : null;
|
||||
S2K s2k = extensionToS2K(extension);
|
||||
|
||||
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
||||
for (PGPSecretKey secretKey : keys) {
|
||||
if (!filter.filter(secretKey.getKeyID())) {
|
||||
// No conversion, do not modify subkey
|
||||
secretKeyList.add(secretKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublicKeyPacket publicKeyPacket = secretKey.getPublicKey().getPublicKeyPacket();
|
||||
if (secretKey.isMasterKey()) {
|
||||
SecretKeyPacket keyPacket = new SecretKeyPacket(publicKeyPacket,
|
||||
0, SecretKeyPacket.USAGE_SHA1, s2k, null, encodedSerial);
|
||||
PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey());
|
||||
secretKeyList.add(onCard);
|
||||
} else {
|
||||
SecretSubkeyPacket keyPacket = new SecretSubkeyPacket(publicKeyPacket,
|
||||
0, SecretKeyPacket.USAGE_SHA1, s2k, null, encodedSerial);
|
||||
PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey());
|
||||
secretKeyList.add(onCard);
|
||||
}
|
||||
}
|
||||
|
||||
return new PGPSecretKeyRing(secretKeyList);
|
||||
}
|
||||
|
||||
private byte[] encodeSerial(@Nonnull byte[] serial) {
|
||||
byte[] encoded = new byte[serial.length + 1];
|
||||
encoded[0] = (byte) (serial.length & 0xff);
|
||||
System.arraycopy(serial, 0, encoded, 1, serial.length);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private S2K extensionToS2K(@Nonnull GnuPGDummyExtension extension) {
|
||||
return S2K.gnuDummyS2K(extension == GnuPGDummyExtension.DIVERT_TO_CARD ?
|
||||
S2K.GNUDummyParams.divertToCard() : S2K.GNUDummyParams.noPrivateKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for selecting keys.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface KeyFilter {
|
||||
|
||||
/**
|
||||
* Return true, if the given key should be selected, false otherwise.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @return select
|
||||
*/
|
||||
boolean filter(long keyId);
|
||||
|
||||
/**
|
||||
* Select any key.
|
||||
*
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter any() {
|
||||
return keyId -> true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select only the given keyId.
|
||||
*
|
||||
* @param onlyKeyId only acceptable key id
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter only(long onlyKeyId) {
|
||||
return keyId -> keyId == onlyKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all keyIds which are contained in the given set of ids.
|
||||
*
|
||||
* @param ids set of acceptable keyIds
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter selected(Collection<Long> ids) {
|
||||
// noinspection Convert2MethodRef
|
||||
return keyId -> ids.contains(keyId);
|
||||
}
|
||||
}
|
||||
}
|
222
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
222
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
|
@ -0,0 +1,222 @@
|
|||
// 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
|
||||
*/
|
||||
public static KeyRingTemplates generateKeyRing() {
|
||||
return new KeyRingTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a custom OpenPGP key ring.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
public static KeyRingBuilder buildKeyRing() {
|
||||
return new KeyRingBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an existing OpenPGP key ring.
|
||||
* @return builder
|
||||
*/
|
||||
public static KeyRingReader readKeyRing() {
|
||||
return new KeyRingReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a public key certificate from a secret key.
|
||||
*
|
||||
* @param secretKey secret key
|
||||
* @return public key certificate
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys) {
|
||||
return modifyKeyRing(secretKeys, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys, 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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
public static KeyRingInfo inspectKeyRing(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
|
||||
*/
|
||||
public static KeyRingInfo inspectKeyRing(PGPKeyRing keyRing, Date referenceTime) {
|
||||
return new KeyRingInfo(keyRing, referenceTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
|
||||
*
|
||||
* @return policy
|
||||
*/
|
||||
public static Policy getPolicy() {
|
||||
return Policy.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create different kinds of signatures on other keys.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
public static CertifyCertificate certify() {
|
||||
return new CertifyCertificate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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 <CR><LF>}
|
||||
* </pre>.
|
||||
*/
|
||||
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
|
||||
;
|
||||
|
||||
final SignatureType signatureType;
|
||||
|
||||
DocumentSignatureType(SignatureType signatureType) {
|
||||
this.signatureType = signatureType;
|
||||
}
|
||||
|
||||
public SignatureType getSignatureType() {
|
||||
return signatureType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// 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
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
// 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>
|
||||
*/
|
||||
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>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// 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 other 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// 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 https://tools.ietf.org/html/rfc4880#section-13.5
|
||||
*/
|
||||
@Deprecated
|
||||
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true),
|
||||
|
||||
/**
|
||||
* RSA with usage of creating signatures.
|
||||
*
|
||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
||||
*/
|
||||
@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 https://tools.ietf.org/html/rfc4880#section-13.8
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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
|
||||
}
|
|
@ -0,0 +1,462 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Classes related to algorithm negotiation.
|
||||
*/
|
||||
package org.pgpainless.algorithm.negotiation;
|
|
@ -0,0 +1,8 @@
|
|||
// 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;
|
|
@ -0,0 +1,39 @@
|
|||
// 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;
|
||||
|
||||
public abstract class CloseForResultInputStream extends InputStream {
|
||||
|
||||
protected final OpenPgpMetadata.Builder resultBuilder;
|
||||
private boolean isClosed = false;
|
||||
|
||||
public CloseForResultInputStream(@Nonnull OpenPgpMetadata.Builder resultBuilder) {
|
||||
this.resultBuilder = resultBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.isClosed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the decryption.
|
||||
* The result contains metadata about the decryption, such as signatures, used keys and algorithms, as well as information
|
||||
* about the decrypted file/stream.
|
||||
*
|
||||
* Can only be obtained once the stream got successfully closed ({@link #close()}).
|
||||
* @return metadata
|
||||
*/
|
||||
public OpenPgpMetadata getResult() {
|
||||
if (!isClosed) {
|
||||
throw new IllegalStateException("Stream MUST be closed before the result can be accessed.");
|
||||
}
|
||||
return resultBuilder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
// 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.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
||||
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();
|
||||
|
||||
// Set of verification keys
|
||||
private final Set<PGPPublicKeyRing> certificates = new HashSet<>();
|
||||
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<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.add(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;
|
||||
}
|
||||
|
||||
public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException {
|
||||
List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream);
|
||||
return addVerificationOfDetachedSignatures(signatures);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
|
||||
return Collections.unmodifiableSet(decryptionKeys.keySet());
|
||||
}
|
||||
|
||||
public @Nonnull Set<Passphrase> getDecryptionPassphrases() {
|
||||
return Collections.unmodifiableSet(decryptionPassphrases);
|
||||
}
|
||||
|
||||
public @Nonnull Set<PGPPublicKeyRing> getCertificates() {
|
||||
return Collections.unmodifiableSet(certificates);
|
||||
}
|
||||
|
||||
public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() {
|
||||
return missingCertificateCallback;
|
||||
}
|
||||
|
||||
public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
|
||||
return decryptionKeys.get(decryptionKeyRing);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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 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 DecryptionStreamFactory.create(inputStream, consumerOptions);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// 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.util.io.Streams;
|
||||
|
||||
/**
|
||||
* Decryption Stream that handles updating and verification of detached signatures,
|
||||
* as well as verification of integrity-protected input streams once the stream gets closed.
|
||||
*/
|
||||
public class DecryptionStream extends CloseForResultInputStream {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final IntegrityProtectedInputStream integrityProtectedInputStream;
|
||||
private final InputStream armorStream;
|
||||
|
||||
/**
|
||||
* Create an input stream that handles decryption and - if necessary - integrity protection verification.
|
||||
*
|
||||
* @param wrapped underlying input stream
|
||||
* @param resultBuilder builder for decryption metadata like algorithms, recipients etc.
|
||||
* @param integrityProtectedInputStream in case of data encrypted using SEIP packet close this stream to check integrity
|
||||
* @param armorStream armor stream to verify CRC checksums
|
||||
*/
|
||||
DecryptionStream(@Nonnull InputStream wrapped,
|
||||
@Nonnull OpenPgpMetadata.Builder resultBuilder,
|
||||
IntegrityProtectedInputStream integrityProtectedInputStream,
|
||||
InputStream armorStream) {
|
||||
super(resultBuilder);
|
||||
this.inputStream = wrapped;
|
||||
this.integrityProtectedInputStream = integrityProtectedInputStream;
|
||||
this.armorStream = armorStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (armorStream != null) {
|
||||
Streams.drain(armorStream);
|
||||
}
|
||||
inputStream.close();
|
||||
if (integrityProtectedInputStream != null) {
|
||||
integrityProtectedInputStream.close();
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int r = inputStream.read();
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@Nonnull byte[] bytes, int offset, int length) throws IOException {
|
||||
int read = inputStream.read(bytes, offset, length);
|
||||
return read;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,650 @@
|
|||
// 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||
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.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSessionKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil;
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
||||
import org.pgpainless.exception.FinalIOException;
|
||||
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||
import org.pgpainless.exception.MissingLiteralDataException;
|
||||
import org.pgpainless.exception.MissingPassphraseException;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.exception.UnacceptableAlgorithmException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
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.signature.SignatureUtils;
|
||||
import org.pgpainless.signature.consumer.DetachedSignatureCheck;
|
||||
import org.pgpainless.signature.consumer.OnePassSignatureCheck;
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.pgpainless.util.SessionKey;
|
||||
import org.pgpainless.util.Tuple;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class DecryptionStreamFactory {
|
||||
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DecryptionStreamFactory.class);
|
||||
// Maximum nesting depth of packets (e.g. compression, encryption...)
|
||||
private static final int MAX_PACKET_NESTING_DEPTH = 16;
|
||||
|
||||
private final ConsumerOptions options;
|
||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||
private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>();
|
||||
private final List<DetachedSignatureCheck> detachedSignatureChecks = new ArrayList<>();
|
||||
private final Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert = new HashMap<>();
|
||||
|
||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
||||
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||
private IntegrityProtectedInputStream integrityProtectedEncryptedInputStream;
|
||||
|
||||
|
||||
public static DecryptionStream create(@Nonnull InputStream inputStream,
|
||||
@Nonnull ConsumerOptions options)
|
||||
throws PGPException, IOException {
|
||||
DecryptionStreamFactory factory = new DecryptionStreamFactory(options);
|
||||
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream);
|
||||
return factory.parseOpenPGPDataAndCreateDecryptionStream(openPgpIn);
|
||||
}
|
||||
|
||||
public DecryptionStreamFactory(ConsumerOptions options) {
|
||||
this.options = options;
|
||||
initializeDetachedSignatures(options.getDetachedSignatures());
|
||||
}
|
||||
|
||||
private void initializeDetachedSignatures(Set<PGPSignature> signatures) {
|
||||
for (PGPSignature signature : signatures) {
|
||||
long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature);
|
||||
PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
|
||||
if (signingKeyRing == null) {
|
||||
SignatureValidationException ex = new SignatureValidationException(
|
||||
"Missing verification certificate " + Long.toHexString(issuerKeyId));
|
||||
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex);
|
||||
continue;
|
||||
}
|
||||
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
|
||||
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
|
||||
try {
|
||||
signature.init(verifierBuilderProvider, signingKey);
|
||||
DetachedSignatureCheck detachedSignature =
|
||||
new DetachedSignatureCheck(signature, signingKeyRing, signingKeyIdentifier);
|
||||
detachedSignatureChecks.add(detachedSignature);
|
||||
} catch (PGPException e) {
|
||||
SignatureValidationException ex = new SignatureValidationException(
|
||||
"Cannot verify detached signature made by " + signingKeyIdentifier + ".", e);
|
||||
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, signingKeyIdentifier), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DecryptionStream parseOpenPGPDataAndCreateDecryptionStream(OpenPgpInputStream openPgpIn)
|
||||
throws IOException, PGPException {
|
||||
|
||||
InputStream pgpInStream;
|
||||
InputStream outerDecodingStream;
|
||||
PGPObjectFactory objectFactory;
|
||||
|
||||
// Non-OpenPGP data. We are probably verifying detached signatures
|
||||
if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) {
|
||||
outerDecodingStream = openPgpIn;
|
||||
pgpInStream = wrapInVerifySignatureStream(outerDecodingStream, null);
|
||||
return new DecryptionStream(pgpInStream, resultBuilder, integrityProtectedEncryptedInputStream, null);
|
||||
}
|
||||
|
||||
// Data appears to be OpenPGP message,
|
||||
// or we handle it as such, since user provided a session-key for decryption
|
||||
if (openPgpIn.isLikelyOpenPgpMessage() ||
|
||||
(openPgpIn.isBinaryOpenPgp() && options.getSessionKey() != null)) {
|
||||
outerDecodingStream = openPgpIn;
|
||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(outerDecodingStream);
|
||||
// Parse OpenPGP message
|
||||
pgpInStream = processPGPPackets(objectFactory, 1);
|
||||
return new DecryptionStream(pgpInStream,
|
||||
resultBuilder, integrityProtectedEncryptedInputStream, null);
|
||||
}
|
||||
|
||||
if (openPgpIn.isAsciiArmored()) {
|
||||
ArmoredInputStream armoredInputStream = ArmoredInputStreamFactory.get(openPgpIn);
|
||||
if (armoredInputStream.isClearText()) {
|
||||
resultBuilder.setCleartextSigned();
|
||||
return parseCleartextSignedMessage(armoredInputStream);
|
||||
} else {
|
||||
outerDecodingStream = armoredInputStream;
|
||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(outerDecodingStream);
|
||||
// Parse OpenPGP message
|
||||
pgpInStream = processPGPPackets(objectFactory, 1);
|
||||
return new DecryptionStream(pgpInStream,
|
||||
resultBuilder, integrityProtectedEncryptedInputStream,
|
||||
outerDecodingStream);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PGPException("Not sure how to handle the input stream.");
|
||||
}
|
||||
|
||||
private DecryptionStream parseCleartextSignedMessage(ArmoredInputStream armorIn)
|
||||
throws IOException, PGPException {
|
||||
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
||||
.setFileEncoding(StreamEncoding.TEXT);
|
||||
|
||||
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
|
||||
PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, multiPassStrategy.getMessageOutputStream());
|
||||
|
||||
for (PGPSignature signature : signatures) {
|
||||
options.addVerificationOfDetachedSignature(signature);
|
||||
}
|
||||
|
||||
initializeDetachedSignatures(options.getDetachedSignatures());
|
||||
|
||||
InputStream verifyIn = wrapInVerifySignatureStream(multiPassStrategy.getMessageInputStream(), null);
|
||||
return new DecryptionStream(verifyIn, resultBuilder, integrityProtectedEncryptedInputStream,
|
||||
null);
|
||||
}
|
||||
|
||||
private InputStream wrapInVerifySignatureStream(InputStream bufferedIn, @Nullable PGPObjectFactory objectFactory) {
|
||||
return new SignatureInputStream.VerifySignatures(
|
||||
bufferedIn, objectFactory, onePassSignatureChecks,
|
||||
onePassSignaturesWithMissingCert, detachedSignatureChecks, options,
|
||||
resultBuilder);
|
||||
}
|
||||
|
||||
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth)
|
||||
throws IOException, PGPException {
|
||||
if (depth >= MAX_PACKET_NESTING_DEPTH) {
|
||||
throw new PGPException("Maximum depth of nested packages exceeded.");
|
||||
}
|
||||
Object nextPgpObject;
|
||||
try {
|
||||
while ((nextPgpObject = objectFactory.nextObject()) != null) {
|
||||
if (nextPgpObject instanceof PGPEncryptedDataList) {
|
||||
return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPCompressedData) {
|
||||
return processPGPCompressedData((PGPCompressedData) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPOnePassSignatureList) {
|
||||
return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPLiteralData) {
|
||||
return processPGPLiteralData(objectFactory, (PGPLiteralData) nextPgpObject, depth);
|
||||
}
|
||||
}
|
||||
} catch (FinalIOException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
if (depth == 1 && e.getMessage().contains("invalid armor")) {
|
||||
throw e;
|
||||
}
|
||||
if (depth == 1 && e.getMessage().contains("unknown object in stream:")) {
|
||||
throw new MissingLiteralDataException("No Literal Data Packet found.");
|
||||
} else {
|
||||
throw new FinalIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new MissingLiteralDataException("No Literal Data Packet found");
|
||||
}
|
||||
|
||||
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
|
||||
throws PGPException, IOException {
|
||||
LOGGER.debug("Depth {}: Encountered PGPEncryptedDataList", depth);
|
||||
|
||||
SessionKey sessionKey = options.getSessionKey();
|
||||
if (sessionKey != null) {
|
||||
integrityProtectedEncryptedInputStream = decryptWithProvidedSessionKey(pgpEncryptedDataList, sessionKey);
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(integrityProtectedEncryptedInputStream);
|
||||
PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
return processPGPPackets(factory, ++depth);
|
||||
}
|
||||
|
||||
InputStream decryptedDataStream = decryptSessionKey(pgpEncryptedDataList);
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
|
||||
PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
return processPGPPackets(factory, ++depth);
|
||||
}
|
||||
|
||||
private IntegrityProtectedInputStream decryptWithProvidedSessionKey(
|
||||
PGPEncryptedDataList pgpEncryptedDataList,
|
||||
SessionKey sessionKey)
|
||||
throws PGPException {
|
||||
SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getSessionKeyDataDecryptorFactory(sessionKey);
|
||||
InputStream decryptedDataStream = null;
|
||||
PGPEncryptedData encryptedData = null;
|
||||
for (PGPEncryptedData pgpEncryptedData : pgpEncryptedDataList) {
|
||||
encryptedData = pgpEncryptedData;
|
||||
if (!options.isIgnoreMDCErrors() && !encryptedData.isIntegrityProtected()) {
|
||||
throw new MessageNotIntegrityProtectedException();
|
||||
}
|
||||
|
||||
if (encryptedData instanceof PGPPBEEncryptedData) {
|
||||
PGPPBEEncryptedData pbeEncrypted = (PGPPBEEncryptedData) encryptedData;
|
||||
decryptedDataStream = pbeEncrypted.getDataStream(decryptorFactory);
|
||||
break;
|
||||
} else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||
PGPPublicKeyEncryptedData pkEncrypted = (PGPPublicKeyEncryptedData) encryptedData;
|
||||
decryptedDataStream = pkEncrypted.getDataStream(decryptorFactory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (decryptedDataStream == null) {
|
||||
throw new PGPException("No valid PGP data encountered.");
|
||||
}
|
||||
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
|
||||
integrityProtectedEncryptedInputStream =
|
||||
new IntegrityProtectedInputStream(decryptedDataStream, encryptedData, options);
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
}
|
||||
|
||||
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
|
||||
throws PGPException, IOException {
|
||||
try {
|
||||
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.requireFromId(pgpCompressedData.getAlgorithm());
|
||||
LOGGER.debug("Depth {}: Encountered PGPCompressedData: {}", depth, compressionAlgorithm);
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new PGPException("Unknown compression algorithm encountered.", e);
|
||||
}
|
||||
|
||||
InputStream inflatedDataStream = pgpCompressedData.getDataStream();
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(inflatedDataStream);
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
|
||||
return processPGPPackets(objectFactory, ++depth);
|
||||
}
|
||||
|
||||
private InputStream processOnePassSignatureList(
|
||||
@Nonnull PGPObjectFactory objectFactory,
|
||||
PGPOnePassSignatureList onePassSignatures,
|
||||
int depth)
|
||||
throws PGPException, IOException {
|
||||
LOGGER.debug("Depth {}: Encountered PGPOnePassSignatureList of size {}", depth, onePassSignatures.size());
|
||||
initOnePassSignatures(onePassSignatures);
|
||||
return processPGPPackets(objectFactory, depth);
|
||||
}
|
||||
|
||||
private InputStream processPGPLiteralData(
|
||||
@Nonnull PGPObjectFactory objectFactory,
|
||||
PGPLiteralData pgpLiteralData,
|
||||
int depth) {
|
||||
LOGGER.debug("Depth {}: Found PGPLiteralData", depth);
|
||||
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
||||
|
||||
resultBuilder.setFileName(pgpLiteralData.getFileName())
|
||||
.setModificationDate(pgpLiteralData.getModificationTime())
|
||||
.setFileEncoding(StreamEncoding.requireFromCode(pgpLiteralData.getFormat()));
|
||||
|
||||
if (onePassSignatureChecks.isEmpty() && onePassSignaturesWithMissingCert.isEmpty()) {
|
||||
LOGGER.debug("No OnePassSignatures found -> We are done");
|
||||
return literalDataInputStream;
|
||||
}
|
||||
|
||||
return new SignatureInputStream.VerifySignatures(literalDataInputStream, objectFactory,
|
||||
onePassSignatureChecks, onePassSignaturesWithMissingCert, detachedSignatureChecks, options, resultBuilder) {
|
||||
};
|
||||
}
|
||||
|
||||
private InputStream decryptSessionKey(@Nonnull PGPEncryptedDataList encryptedDataList)
|
||||
throws PGPException {
|
||||
Iterator<PGPEncryptedData> encryptedDataIterator = encryptedDataList.getEncryptedDataObjects();
|
||||
if (!encryptedDataIterator.hasNext()) {
|
||||
throw new PGPException("Decryption failed - EncryptedDataList has no items");
|
||||
}
|
||||
|
||||
PGPPrivateKey decryptionKey = null;
|
||||
PGPPublicKeyEncryptedData encryptedSessionKey = null;
|
||||
|
||||
List<PGPPBEEncryptedData> passphraseProtected = new ArrayList<>();
|
||||
List<PGPPublicKeyEncryptedData> publicKeyProtected = new ArrayList<>();
|
||||
List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponedDueToMissingPassphrase = new ArrayList<>();
|
||||
|
||||
// Sort PKESK and SKESK packets
|
||||
while (encryptedDataIterator.hasNext()) {
|
||||
PGPEncryptedData encryptedData = encryptedDataIterator.next();
|
||||
|
||||
if (!encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
|
||||
throw new MessageNotIntegrityProtectedException();
|
||||
}
|
||||
|
||||
// SKESK
|
||||
if (encryptedData instanceof PGPPBEEncryptedData) {
|
||||
passphraseProtected.add((PGPPBEEncryptedData) encryptedData);
|
||||
}
|
||||
// PKESK
|
||||
else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||
publicKeyProtected.add((PGPPublicKeyEncryptedData) encryptedData);
|
||||
}
|
||||
}
|
||||
|
||||
// Try decryption with passphrases first
|
||||
for (PGPPBEEncryptedData pbeEncryptedData : passphraseProtected) {
|
||||
for (Passphrase passphrase : options.getDecryptionPassphrases()) {
|
||||
PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance()
|
||||
.getPBEDataDecryptorFactory(passphrase);
|
||||
try {
|
||||
InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
|
||||
|
||||
PGPSessionKey pgpSessionKey = pbeEncryptedData.getSessionKey(passphraseDecryptor);
|
||||
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
|
||||
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
|
||||
|
||||
integrityProtectedEncryptedInputStream =
|
||||
new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
|
||||
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
} catch (PGPException e) {
|
||||
LOGGER.debug("Probable passphrase mismatch, skip PBE encrypted data block", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then try decryption with public key encryption
|
||||
for (PGPPublicKeyEncryptedData publicKeyEncryptedData : publicKeyProtected) {
|
||||
PGPPrivateKey privateKey = null;
|
||||
if (options.getDecryptionKeys().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
long keyId = publicKeyEncryptedData.getKeyID();
|
||||
// Wildcard KeyID
|
||||
if (keyId == 0L) {
|
||||
LOGGER.debug("Hidden recipient detected. Try to decrypt with all available secret keys.");
|
||||
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
||||
if (privateKey != null) {
|
||||
break;
|
||||
}
|
||||
KeyRingInfo info = new KeyRingInfo(secretKeys);
|
||||
List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
|
||||
for (PGPPublicKey pubkey : encryptionSubkeys) {
|
||||
PGPSecretKey secretKey = secretKeys.getSecretKey(pubkey.getKeyID());
|
||||
// Skip missing secret key
|
||||
if (secretKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-wildcard key-id
|
||||
else {
|
||||
LOGGER.debug("PGPEncryptedData is encrypted for key {}", Long.toHexString(keyId));
|
||||
resultBuilder.addRecipientKeyId(keyId);
|
||||
|
||||
PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId);
|
||||
if (secretKeys == null) {
|
||||
LOGGER.debug("Missing certificate of {}. Skip.", Long.toHexString(keyId));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that the recipient key is encryption capable and non-expired
|
||||
KeyRingInfo info = new KeyRingInfo(secretKeys);
|
||||
List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
|
||||
|
||||
PGPSecretKey secretKey = null;
|
||||
for (PGPPublicKey pubkey : encryptionSubkeys) {
|
||||
if (pubkey.getKeyID() == keyId) {
|
||||
secretKey = secretKeys.getSecretKey(keyId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
LOGGER.debug("Key " + Long.toHexString(keyId) + " is not valid or not capable for decryption.");
|
||||
} else {
|
||||
privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, true);
|
||||
}
|
||||
}
|
||||
if (privateKey == null) {
|
||||
continue;
|
||||
}
|
||||
decryptionKey = privateKey;
|
||||
encryptedSessionKey = publicKeyEncryptedData;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try postponed keys with missing passphrases (will cause missing passphrase callbacks to fire)
|
||||
if (encryptedSessionKey == null) {
|
||||
|
||||
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||
// Non-interactive mode: Throw an exception with all locked decryption keys
|
||||
Set<SubkeyIdentifier> keyIds = new HashSet<>();
|
||||
for (Tuple<SubkeyIdentifier, ?> k : postponedDueToMissingPassphrase) {
|
||||
keyIds.add(k.getA());
|
||||
}
|
||||
if (!keyIds.isEmpty()) {
|
||||
throw new MissingPassphraseException(keyIds);
|
||||
}
|
||||
}
|
||||
else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||
// Interactive mode: Fire protector callbacks to get passphrases interactively
|
||||
for (Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData> missingPassphrases : postponedDueToMissingPassphrase) {
|
||||
SubkeyIdentifier keyId = missingPassphrases.getA();
|
||||
PGPPublicKeyEncryptedData publicKeyEncryptedData = missingPassphrases.getB();
|
||||
PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId.getKeyId());
|
||||
PGPSecretKey secretKey = secretKeys.getSecretKey(keyId.getSubkeyId());
|
||||
|
||||
PGPPrivateKey privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, false);
|
||||
if (privateKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
decryptionKey = privateKey;
|
||||
encryptedSessionKey = publicKeyEncryptedData;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid PostponedKeysStrategy set in consumer options.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return decryptWith(encryptedSessionKey, decryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try decryption of the provided public-key-encrypted-data using the given secret key.
|
||||
* If the secret key is encrypted and the secret key protector does not have a passphrase available and the boolean
|
||||
* postponeIfMissingPassphrase is true, data decryption is postponed by pushing a tuple of the encrypted data decryption key
|
||||
* identifier to the postponed list.
|
||||
*
|
||||
* This method only returns a non-null private key, if the private key is able to decrypt the message successfully.
|
||||
*
|
||||
* @param secretKeys secret key ring
|
||||
* @param secretKey secret key
|
||||
* @param publicKeyEncryptedData encrypted data which is tried to decrypt using the secret key
|
||||
* @param postponed list of postponed decryptions due to missing secret key passphrases
|
||||
* @param postponeIfMissingPassphrase flag to specify whether missing secret key passphrases should result in postponed decryption
|
||||
* @return private key if decryption is successful, null if decryption is unsuccessful or postponed
|
||||
*
|
||||
* @throws PGPException in case of an OpenPGP error
|
||||
*/
|
||||
private PGPPrivateKey tryPublicKeyDecryption(
|
||||
PGPSecretKeyRing secretKeys,
|
||||
PGPSecretKey secretKey,
|
||||
PGPPublicKeyEncryptedData publicKeyEncryptedData,
|
||||
List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponed,
|
||||
boolean postponeIfMissingPassphrase) throws PGPException {
|
||||
SecretKeyRingProtector protector = options.getSecretKeyProtector(secretKeys);
|
||||
|
||||
if (postponeIfMissingPassphrase && !protector.hasPassphraseFor(secretKey.getKeyID())) {
|
||||
// Postpone decryption with key with missing passphrase
|
||||
SubkeyIdentifier identifier = new SubkeyIdentifier(secretKeys, secretKey.getKeyID());
|
||||
postponed.add(new Tuple<>(identifier, publicKeyEncryptedData));
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(
|
||||
secretKey, protector.getDecryptor(secretKey.getKeyID()));
|
||||
|
||||
// test if we have the right private key
|
||||
PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getPublicKeyDataDecryptorFactory(privateKey);
|
||||
try {
|
||||
publicKeyEncryptedData.getSymmetricAlgorithm(decryptorFactory); // will only succeed if we have the right secret key
|
||||
LOGGER.debug("Found correct decryption key {}.", Long.toHexString(secretKey.getKeyID()));
|
||||
resultBuilder.setDecryptionKey(new SubkeyIdentifier(secretKeys, privateKey.getKeyID()));
|
||||
return privateKey;
|
||||
} catch (PGPException | ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream decryptWith(PGPPublicKeyEncryptedData encryptedSessionKey, PGPPrivateKey decryptionKey)
|
||||
throws PGPException {
|
||||
if (decryptionKey == null || encryptedSessionKey == null) {
|
||||
throw new MissingDecryptionMethodException("Decryption failed - No suitable decryption key or passphrase found");
|
||||
}
|
||||
|
||||
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
|
||||
.getPublicKeyDataDecryptorFactory(decryptionKey);
|
||||
|
||||
PGPSessionKey pgpSessionKey = encryptedSessionKey.getSessionKey(dataDecryptor);
|
||||
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm = sessionKey.getAlgorithm();
|
||||
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
|
||||
LOGGER.debug("Message is unencrypted");
|
||||
} else {
|
||||
LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
|
||||
}
|
||||
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
|
||||
|
||||
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(
|
||||
encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
}
|
||||
|
||||
private void throwIfAlgorithmIsRejected(SymmetricKeyAlgorithm algorithm)
|
||||
throws UnacceptableAlgorithmException {
|
||||
if (!PGPainless.getPolicy().getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) {
|
||||
throw new UnacceptableAlgorithmException("Data is "
|
||||
+ (algorithm == SymmetricKeyAlgorithm.NULL ?
|
||||
"unencrypted" :
|
||||
"encrypted with symmetric algorithm " + algorithm) + " which is not acceptable as per PGPainless' policy.\n" +
|
||||
"To mark this algorithm as acceptable, use PGPainless.getPolicy().setSymmetricKeyDecryptionAlgorithmPolicy().");
|
||||
}
|
||||
}
|
||||
|
||||
private void initOnePassSignatures(@Nonnull PGPOnePassSignatureList onePassSignatureList)
|
||||
throws PGPException {
|
||||
Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Verification failed - No OnePassSignatures found");
|
||||
}
|
||||
|
||||
processOnePassSignatures(iterator);
|
||||
}
|
||||
|
||||
private void processOnePassSignatures(Iterator<PGPOnePassSignature> signatures)
|
||||
throws PGPException {
|
||||
while (signatures.hasNext()) {
|
||||
PGPOnePassSignature signature = signatures.next();
|
||||
processOnePassSignature(signature);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOnePassSignature(PGPOnePassSignature signature)
|
||||
throws PGPException {
|
||||
final long keyId = signature.getKeyID();
|
||||
|
||||
LOGGER.debug("Encountered OnePassSignature from {}", Long.toHexString(keyId));
|
||||
|
||||
// Find public key
|
||||
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
|
||||
if (verificationKeyRing == null) {
|
||||
onePassSignaturesWithMissingCert.put(keyId, new OnePassSignatureCheck(signature, null));
|
||||
return;
|
||||
}
|
||||
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
|
||||
|
||||
signature.init(verifierBuilderProvider, verificationKey);
|
||||
OnePassSignatureCheck onePassSignature = new OnePassSignatureCheck(signature, verificationKeyRing);
|
||||
onePassSignatureChecks.add(onePassSignature);
|
||||
}
|
||||
|
||||
private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
|
||||
for (PGPSecretKeyRing key : options.getDecryptionKeys()) {
|
||||
if (key.getSecretKey(keyId) != null) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) {
|
||||
PGPPublicKeyRing verificationKeyRing = null;
|
||||
for (PGPPublicKeyRing publicKeyRing : options.getCertificates()) {
|
||||
PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId);
|
||||
if (verificationKey != null) {
|
||||
LOGGER.debug("Found public key {} for signature verification", Long.toHexString(keyId));
|
||||
verificationKeyRing = publicKeyRing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (verificationKeyRing == null && options.getMissingCertificateCallback() != null) {
|
||||
verificationKeyRing = options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId);
|
||||
}
|
||||
|
||||
return verificationKeyRing;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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;
|
||||
|
||||
public class IntegrityProtectedInputStream extends InputStream {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final PGPEncryptedData encryptedData;
|
||||
private final ConsumerOptions options;
|
||||
|
||||
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 (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
|
||||
try {
|
||||
if (!encryptedData.verify()) {
|
||||
throw new ModificationDetectionException();
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
throw new IOException("Failed to verify integrity protection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue