mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-13 20:29:39 +02:00
Compare commits
No commits in common. "main" and "1.3.2" have entirely different histories.
629 changed files with 26740 additions and 37234 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
|
17
.github/FUNDING.yml
vendored
17
.github/FUNDING.yml
vendored
|
@ -1,17 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: vanitasvitae # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
#patreon: # Replace with a single Patreon username
|
||||
#open_collective: # Replace with a single Open Collective username
|
||||
#ko_fi: # Replace with a single Ko-fi username
|
||||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
#liberapay: # Replace with a single Liberapay username
|
||||
#issuehunt: # Replace with a single IssueHunt username
|
||||
#otechie: # Replace with a single Otechie username
|
||||
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
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
|
40
.github/workflows/gradle_push.yml
vendored
40
.github/workflows/gradle_push.yml
vendored
|
@ -1,40 +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: Push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
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 and Check
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
with:
|
||||
arguments: check jacocoRootReport
|
||||
- name: Coveralls
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
with:
|
||||
arguments: 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
|
45
.reuse/dep5
Normal file
45
.reuse/dep5
Normal file
|
@ -0,0 +1,45 @@
|
|||
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/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
|
363
CHANGELOG.md
363
CHANGELOG.md
|
@ -5,369 +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
|
||||
predates subkey binding
|
||||
- SOP `verify`: Forcefully expect `data()` to be non-OpenPGP data
|
||||
- SOP `sign`: Fix matching of keys and passphrases
|
||||
- CLI: Added tons of tests \o/
|
||||
|
||||
## 1.3.10
|
||||
- Bump `sop-java` to `4.0.3`
|
||||
- Fix: Fix NPE when verifying signature made by key without key flags on direct-key signature
|
||||
|
||||
## 1.3.9
|
||||
- Bump `sop-java` to `4.0.2`
|
||||
- SOP: Improve exception handling
|
||||
|
||||
## 1.3.8
|
||||
- Bump `bcprov` to `1.72`
|
||||
- Bump `bcpg` to `1.72.1`
|
||||
- Add `ProducerOptions.setHideArmorHeaders(boolean)` to hide automatically added armor headers
|
||||
in encrypted messages
|
||||
|
||||
## 1.3.7
|
||||
- Bugfix: Fix signature verification when `DecryptionStream` is drained byte-by-byte using `read()` call
|
||||
- Add `KeyRingUtils.injectCertification(keys, certification)`
|
||||
- Add `PGPainless.asciiArmor(key, outputStream)`
|
||||
- Add `PGPainless.asciiArmor(signature)`
|
||||
|
||||
## 1.3.6
|
||||
- Remove deprecated methods
|
||||
- `ArmorUtils.createArmoredOutputStreamFor()` -> use `ArmorUtils.toAsciiArmoredStream()` instead
|
||||
- `EncryptionResult.getSymmetricKeyAlgorithm()` -> use `EncryptionResult.getEncryptionAlgorithm()` instead
|
||||
- Add `KeyRingInfo.getRevocationState()`
|
||||
- Better way to determine whether a key is revoked
|
||||
- Add `SigningOptions.addDetachedSignature(protector, key)` shortcut method
|
||||
- Add `EncryptionOptions.get()`, `ConsumerOptions.get()` factory methods
|
||||
- Add support for generating keys without user-id (only using `PGPainless.buildKeyRing()` for now)
|
||||
- Switch to `SHA256` as default `S2K` hash algorithm for secret key protection
|
||||
- Allow to set custom reference time when modifying secret keys
|
||||
- Add diagnostic test to explore system PRNG performance
|
||||
|
||||
## 1.3.5
|
||||
- Add `KeyRingInfo.isCapableOfSigning()`
|
||||
- Add `KeyRingReader.readKeyRing(*)` methods that can take both secret- and public keys
|
||||
- Add manpages
|
||||
- Add script to generate manpages from sop-java-picocli
|
||||
- Build website from main branch
|
||||
|
||||
## 1.3.4
|
||||
- Fix `KeyRingInfo.isUsableForEncryption()`, `KeyRingInfo.isUsableForSigning()` not detecting revoked primary keys
|
||||
- Bump `sop-java` and `sop-java-picocli` to `4.0.1`
|
||||
- Fixes help text strings being resolved properly while allowing to override executable name
|
||||
|
||||
## 1.3.3
|
||||
- Improve test compatibility against older JUnit versions
|
||||
- Fix tests that read from jar-embedded resources (thanks @jcharaoui)
|
||||
- `pgpainless-cli help`: Fix i18n strings
|
||||
|
||||
## 1.3.2
|
||||
- Add `KeyRingInfo(Policy)` constructor
|
||||
- Delete unused `KeyRingValidator` class
|
||||
|
|
1
CNAME
1
CNAME
|
@ -1 +0,0 @@
|
|||
gh.pgpainless.org
|
|
@ -1,99 +0,0 @@
|
|||
Creative Commons Attribution-ShareAlike 3.0 Unported
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
|
||||
|
||||
b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License.
|
||||
|
||||
c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License.
|
||||
|
||||
d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
|
||||
|
||||
e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
|
||||
|
||||
f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
|
||||
|
||||
g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
|
||||
|
||||
h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
|
||||
|
||||
i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
|
||||
|
||||
j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
|
||||
|
||||
k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
|
||||
|
||||
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
|
||||
|
||||
a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
|
||||
|
||||
b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
|
||||
|
||||
c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
|
||||
|
||||
d. to Distribute and Publicly Perform Adaptations.
|
||||
|
||||
e. For the avoidance of doubt:
|
||||
|
||||
i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
|
||||
|
||||
ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
|
||||
|
||||
iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
|
||||
|
||||
a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
|
||||
|
||||
b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
|
||||
|
||||
c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
|
||||
|
||||
d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
|
||||
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
|
||||
|
||||
b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
|
||||
|
||||
8. Miscellaneous
|
||||
|
||||
a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
|
||||
|
||||
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
|
||||
|
||||
c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
|
||||
|
||||
d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
|
||||
|
||||
e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
|
||||
|
||||
f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
|
||||
|
||||
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
|
||||
|
||||
Creative Commons may be contacted at http://creativecommons.org/.
|
39
README.md
39
README.md
|
@ -6,18 +6,16 @@ SPDX-License-Identifier: Apache-2.0
|
|||
|
||||
# PGPainless - Use OpenPGP Painlessly!
|
||||
|
||||
[](https://github.com/pgpainless/pgpainless/actions/workflows/gradle_push.yml)
|
||||
[](https://coveralls.io/github/pgpainless/pgpainless?branch=main)
|
||||
[](https://tests.sequoia-pgp.org/)
|
||||
[](https://travis-ci.com/pgpainless/pgpainless)
|
||||
[](https://search.maven.org/artifact/org.pgpainless/pgpainless-core)
|
||||
[](https://coveralls.io/github/pgpainless/pgpainless?branch=master)
|
||||
[](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)
|
||||
|
||||
**PGPainless is an easy-to-use OpenPGP library for Java and Android applications**
|
||||
|
||||
[](https://repology.org/project/pgpainless/versions)
|
||||
[](https://search.maven.org/artifact/org.pgpainless/pgpainless-core)
|
||||
|
||||
## About
|
||||
|
||||
PGPainless aims to make using OpenPGP in Java projects as simple as possible.
|
||||
|
@ -32,7 +30,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 +130,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 +170,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 +189,7 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.pgpainless:pgpainless-core:1.7.6'
|
||||
implementation 'org.pgpainless:pgpainless-core:1.3.2'
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -200,8 +198,6 @@ Do you need a custom feature? Are you unsure of what's the best way to integrate
|
|||
We offer paid professional services. Don't hesitate to send an inquiry to [info@pgpainless.org](mailto:info@pgpainless.org).
|
||||
|
||||
## Development
|
||||
Join the projects IRC channel [**#pgpainless**](ircs://irc.oftc.net:6697/#pgpainless) on OFTC if you have any questions!
|
||||
|
||||
PGPainless is developed in - and accepts contributions from - the following places:
|
||||
|
||||
* [Github](https://github.com/pgpainless/pgpainless)
|
||||
|
@ -212,6 +208,18 @@ We are using SemVer (MAJOR.MINOR.PATCH) versioning, although MINOR releases coul
|
|||
If you want to contribute a bug fix, please check the `release/X.Y` branches first to see, what the oldest release is
|
||||
which contains the bug you are fixing. That way we can update older revisions of the library easily.
|
||||
|
||||
### Branches
|
||||
* `release/X.Y` contains the state of the latest `X.Y.Z` PATCH release + next PATCH snapshot definition.
|
||||
* `master` contains the state of the latest MINOR release + some smaller changes that will make it into the next PATCH release.
|
||||
* `development` contains new features that will make it into the next MINOR release.
|
||||
|
||||
#### Example:
|
||||
Latest release: 1.1.4
|
||||
* `release/1.0` contains the state of `1.0.5-SNAPSHOT`
|
||||
* `release/1.1` contains the state of `1.1.5-SNAPSHOT`
|
||||
* `master` contains the state `release/1.1` plus patch level changes that will make it into `1.1.5`.
|
||||
* `development` contains the state which will at some point become `1.2.0`.
|
||||
|
||||
Please follow the [code of conduct](CODE_OF_CONDUCT.md) if you want to be part of the project.
|
||||
|
||||
## Acknowledgements
|
||||
|
@ -222,7 +230,4 @@ Parts of PGPainless development ([project page](https://nlnet.nl/project/PGPainl
|
|||
NGI Assure is made possible with financial support from the [European Commission](https://ec.europa.eu/)'s [Next Generation Internet](https://ngi.eu/) programme, under the aegis of [DG Communications Networks, Content and Technology](https://ec.europa.eu/info/departments/communications-networks-content-and-technology_en).
|
||||
[](https://nlnet.nl/assure/)
|
||||
|
||||
Big thank you also to those who decided to support the work by donating!
|
||||
Notably @msfjarvis
|
||||
|
||||
You make my day!
|
||||
Continuous Integration is kindly provided by [Travis-CI.com](https://travis-ci.com/).
|
||||
|
|
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
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
logo: /assets/logo.png
|
||||
theme: jekyll-theme-minimal
|
||||
|
||||
exclude:
|
||||
- CHANGELOG.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- SECURITY.md
|
||||
- docs
|
|
@ -1,76 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>{{ site.title | default: site.github.repository_name }} by {{
|
||||
site.github.owner_name }}</title>
|
||||
|
||||
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append:
|
||||
site.github.build_revision | relative_url }}">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<!--[if lt IE 9]>
|
||||
<script
|
||||
src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper" style="width: 1060px">
|
||||
<header>
|
||||
{% if site.logo %}
|
||||
<img src="{{site.logo | relative_url}}" alt="Logo" />
|
||||
{% endif %}
|
||||
<p>{{ site.description | default: site.github.project_tagline
|
||||
}}</p>
|
||||
|
||||
<a href="https://pgpainless.org">Home</a>
|
||||
<br>
|
||||
<a href="https://search.maven.org/search?q=g:org.pgpainless%20AND%20a:pgpainless-core&core=gav">Releases</a>
|
||||
<br>
|
||||
<a href="https://pgpainless.rtfd.io">Documentation</a>
|
||||
<br>
|
||||
<a href=" https://javadoc.io/doc/org.pgpainless ">Javadoc</a>
|
||||
<br>
|
||||
<a href="https://coveralls.io/github/pgpainless/pgpainless">Coverage</a>
|
||||
<br>
|
||||
|
||||
|
||||
{% if site.github.is_project_page %}
|
||||
<p class="view"><a href="{{ site.github.repository_url
|
||||
}}">View the Project on GitHub <small>{{ github_name }}</small></a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if site.github.is_user_page %}
|
||||
<p class="view"><a href="{{ site.github.owner_url }}">View My
|
||||
GitHub Profile</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if site.show_downloads %}
|
||||
<ul>
|
||||
<li><a href="{{ site.github.zip_url }}">Download <strong>ZIP
|
||||
File</strong></a></li>
|
||||
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR
|
||||
Ball</strong></a></li>
|
||||
<li><a href="{{ site.github.repository_url }}">View On
|
||||
<strong>GitHub</strong></a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</header>
|
||||
<section style="width: 780px">
|
||||
|
||||
{{ content }}
|
||||
|
||||
</section>
|
||||
<footer>
|
||||
{% if site.github.is_project_page %}
|
||||
<p>This project is maintained by <a href="{{
|
||||
site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
|
||||
{% endif %}
|
||||
<p><small>Hosted on GitHub Pages — Theme by <a
|
||||
href="https://github.com/orderedlist">orderedlist</a></small></p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="{{ '/assets/js/scale.fix.js' | relative_url
|
||||
}}"></script>
|
||||
</body>
|
||||
</html>
|
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
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>
|
||||
|
|
|
@ -17,15 +17,7 @@ To build:
|
|||
$ make {html|epub|latexpdf}
|
||||
```
|
||||
|
||||
Note: Diagrams are currently not built from source.
|
||||
Instead, pre-built image files are used directly, because there are issues with mermaid in CLI systems.
|
||||
|
||||
If you want to build the diagrams from source, you need `mermaid-cli` to be installed on your system.
|
||||
Note: Building diagrams from source requires `mermaid-cli` to be installed.
|
||||
```shell
|
||||
$ npm install -g @mermaid-js/mermaid-cli
|
||||
```
|
||||
|
||||
You can then use `mmdc` to build/update single diagram files like this:
|
||||
```shell
|
||||
mmdc --theme default --width 1600 --backgroundColor transparent -i ecosystem_dia.md -o ecosystem_dia.svg
|
||||
```
|
||||
|
|
|
@ -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 |
|
@ -20,11 +20,6 @@ PGPainless' goal is to empower you to use OpenPGP without needing to write all t
|
|||
Bouncy Castle.
|
||||
It aims to be secure by default while allowing customization if required.
|
||||
|
||||
From its inception in 2018 as part of a `Google Summer of Code project <https://summerofcode.withgoogle.com/archive/2018/projects/6037508810866688>`_,
|
||||
the library was steadily advanced.
|
||||
Since 2020, FlowCrypt is the primary sponsor of its development.
|
||||
In 2022, PGPainless received a `grant from NLnet for creating a Web-of-Trust implementation <https://nlnet.nl/project/PGPainless/>`_ as part of NGI Assure.
|
||||
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
|
|
@ -10,18 +10,6 @@ You can use it to generate keys, encrypt, sign and decrypt messages, as well as
|
|||
Essentially, `pgpainless-cli` is just a very small composing module, which injects `pgpainless-sop` as a
|
||||
concrete implementation of `sop-java` into `sop-java-picocli`.
|
||||
|
||||
## Install
|
||||
|
||||
The `pgpainless-cli` command line application is available in Debian unstable / Ubuntu 22.10 and can be installed via APT:
|
||||
```shell
|
||||
$ sudo apt install pgpainless-cli
|
||||
```
|
||||
|
||||
This method comes with man-pages:
|
||||
```shell
|
||||
$ man pgpainless-cli
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
To build a standalone *fat*-jar:
|
||||
|
@ -42,36 +30,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 +37,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 +72,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
|
||||
```
|
|
@ -42,7 +42,7 @@ There are certain operations that require you to provide the passphrase for a ke
|
|||
Examples are decryption of messages, or creating signatures / certifications.
|
||||
|
||||
The primary way of telling PGPainless, which password to use for a certain key is the `SecretKeyRingProtector`
|
||||
interface which maps `Passphrases` to (sub-)keys.
|
||||
interface.
|
||||
There are multiple implementations of this interface, which may or may not suite your needs:
|
||||
|
||||
```java
|
||||
|
@ -57,33 +57,4 @@ SecretKeyRingProtector singlePassphrase = SecretKeyRingProtector
|
|||
// If you want to be flexible, use this:
|
||||
CachingSecretKeyRingProtector flexible = SecretKeyRingProtector
|
||||
.defaultSecretKeyRingProtector(passphraseCallback);
|
||||
```
|
||||
|
||||
`SecretKeyRingProtector.unprotectedKeys()` will return an empty passphrase for any key.
|
||||
It is best used when dealing with unencrypted secret keys.
|
||||
|
||||
`SecretKeyRingProtector.unlockAnyKeyWith(passphrase)` will return the same exact passphrase for any given key.
|
||||
You should use this if you have a single key with a static passphrase.
|
||||
|
||||
The last example shows how to instantiate the `CachingSecretKeyRingProtector` with a `SecretKeyPassphraseProvider`
|
||||
as argument.
|
||||
As the name suggests, the `CachingSecretKeyRingProtector` caches passphrases it knows about in a map.
|
||||
That way, you only have to provide the passphrase for a certain key only once, after which it will be remembered.
|
||||
If you try to unlock a protected secret key for which no passphrase is cached, the `getPassphraseFor()` method of
|
||||
the `SecretKeyPassphraseProvider` callback will be called to interactively ask for the missing passphrase.
|
||||
Afterwards, the acquired passphrase will be cached for future use.
|
||||
|
||||
:::{note}
|
||||
While especially the `CachingSecretKeyRingProtector` can handle multiple keys without problems, it is advised
|
||||
to use individual `SecretKeyRingProtector` objects per key.
|
||||
The reason for this is, that internally the 64bit key-id is used to resolve `Passphrase` objects and collisions are not
|
||||
unlikely in this key-space.
|
||||
Furthermore, multiple OpenPGP keys could contain the same subkey, but with different passphrases set.
|
||||
If the same `SecretKeyRingProtector` is used for two OpenPGP keys with the same subkey, but different passwords,
|
||||
the key-id collision will cause the password to be overwritten for one of the keys, which might result in issues.
|
||||
See `FLO-04-004 WP2` of the [2021 security audit](https://cure53.de/pentest-report_pgpainless.pdf) for more details.
|
||||
:::
|
||||
|
||||
Most `SecretKeyRingProtector` implementations can be instantiated with custom `KeyRingProtectionSettings`.
|
||||
By default, most implementations use `KeyRingProtectionSettings.secureDefaultSettings()` which corresponds to iterated
|
||||
and salted S2K using AES256 and SHA256 with an iteration count of 65536.
|
||||
```
|
|
@ -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:
|
||||
|
@ -97,409 +91,16 @@ PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
|
|||
```
|
||||
|
||||
### Apply / Remove ASCII Armor
|
||||
ASCII armor is a layer of radix64 encoding that can be used to wrap binary OpenPGP data in order to make it save to
|
||||
transport via text-based channels (e.g. email bodies).
|
||||
TODO
|
||||
|
||||
The way in which ASCII armor can be applied depends on the type of data that you want to protect.
|
||||
The easies way to ASCII armor an OpenPGP key or certificate is by using PGPainless' `asciiArmor()` method:
|
||||
### Encrypt a Message
|
||||
TODO
|
||||
|
||||
```java
|
||||
PGPPublicKey certificate = ...;
|
||||
String asciiArmored = PGPainless.asciiArmor(certificate);
|
||||
```
|
||||
### Decrypt a Message
|
||||
TODO
|
||||
|
||||
If you want to ASCII armor ciphertext, you can enable ASCII armoring during encrypting/signing by requesting
|
||||
PGPainless to armor the result:
|
||||
|
||||
```java
|
||||
ProducerOptions producerOptions = ...; // prepare as usual (see next section)
|
||||
|
||||
producerOptions.setAsciiArmor(true); // enable armoring
|
||||
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(out)
|
||||
.withOptions(producerOptions);
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
If you have an already encrypted / signed binary message and want to add ASCII armoring retrospectively, you need
|
||||
to make use of BouncyCastle's `ArmoredOutputStream` as follows:
|
||||
|
||||
```java
|
||||
InputStream binaryOpenPgpIn = ...; // e.g. new ByteArrayInputStream(binaryMessage);
|
||||
|
||||
OutputStream output = ...; // e.g. new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(output);
|
||||
|
||||
Streams.pipeAll(binaryOpenPgpIn, armorOut);
|
||||
armorOut.close(); // important!
|
||||
```
|
||||
|
||||
The output stream will now contain the ASCII armored representation of the binary data.
|
||||
|
||||
If the data you want to wrap in ASCII armor is non-OpenPGP data (e.g. the String "Hello World!"),
|
||||
you need to use the following code:
|
||||
|
||||
```java
|
||||
InputStream inputStream = ...;
|
||||
OutputStream output = ...;
|
||||
|
||||
EncryptionStream armorStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(output)
|
||||
.withOptions(ProducerOptions.noEncryptionNoSigning()
|
||||
.setAsciiArmor(true));
|
||||
|
||||
Streams.pipeAll(inputStream, armorStream);
|
||||
armorStream.close();
|
||||
```
|
||||
|
||||
To remove ASCII armor, you can make use of BouncyCastle's `ArmoredInputStream` as follows:
|
||||
|
||||
```java
|
||||
InputStream input = ...; // e.g. new ByteArrayInputStream(armoredString.getBytes(StandardCharsets.UTF8));
|
||||
OutputStream output = ...;
|
||||
|
||||
ArmoredInputStream armorIn = new ArmoredInputStream(input);
|
||||
Streams.pipeAll(armorIn, output);
|
||||
armorIn.close();
|
||||
```
|
||||
|
||||
The output stream will now contain the binary OpenPGP data.
|
||||
|
||||
### Encrypt and/or Sign a Message
|
||||
Encrypting and signing messages is done using the same API in PGPainless.
|
||||
The type of action depends on the configuration of the `ProducerOptions` class, which in term accepts
|
||||
`SigningOptions` and `EncryptionOptions` objects:
|
||||
|
||||
```java
|
||||
// Encrypt only
|
||||
ProducerOptions options = ProducerOptions.encrypt(encryptionOptions);
|
||||
|
||||
// Sign only
|
||||
ProducerOptions options = ProducerOptions.sign(signingOptions);
|
||||
|
||||
// Sign and encrypt
|
||||
ProducerOptions options = ProducerOptions.signAndEncrypt(signingOptions, encryptionOptions);
|
||||
```
|
||||
|
||||
The `ProducerOptions` object can then be passed into the `encryptAndOrSign()` API:
|
||||
|
||||
```java
|
||||
InputStream plaintext = ...; // The data that shall be encrypted and/or signed
|
||||
OutputStream ciphertext = ...; // Destination for the ciphertext
|
||||
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(ciphertext)
|
||||
.withOptions(options); // pass in the options object
|
||||
|
||||
Streams.pipeAll(plaintext, encryptionStream); // pipe the data through
|
||||
encryptionStream.close(); // important! Close the stream to finish encryption/signing
|
||||
|
||||
EncryptionResult result = encryptionStream.getResult(); // metadata
|
||||
```
|
||||
|
||||
The `ciphertext` output stream now contains the encrypted and/or signed data.
|
||||
|
||||
Now lets take a look at the configuration of the `SigningOptions` object and how to instruct PGPainless to add a simple
|
||||
signature to the message:
|
||||
|
||||
```java
|
||||
PGPSecretKeyRing signingKey = ...; // Key used for signing
|
||||
SecretKeyRingProtector protector = ...; // Protector to unlock the signing key
|
||||
|
||||
SigningOptions signOptions = SigningOptions.get()
|
||||
.addSignature(protector, signingKey);
|
||||
```
|
||||
This will add an inline signature to the message.
|
||||
|
||||
It is possible to add multiple signatures from different keys by repeating the `addSignature()` method call.
|
||||
|
||||
If instead of an inline signature, you want to create a detached signature instead (e.g. because you do not want
|
||||
to alter the data you are signing), you can add the signature as follows:
|
||||
|
||||
```java
|
||||
signOptions.addDetachedSignature(protector, signingKey);
|
||||
```
|
||||
|
||||
Passing in the `SigningOptions` object like this will result in the signature not being added to the message itself.
|
||||
Instead, the signature can later be acquired from the `EncryptionResult` object via `EncryptionResult.getDetachedSignatures()`.
|
||||
That way, it can be distributed independent of the message.
|
||||
|
||||
The `EncryptionOptions` object can be configured in a similar way:
|
||||
|
||||
```java
|
||||
PGPPublicKey certificate = ...;
|
||||
|
||||
EncryptionOptions encOptions = EncryptionOptions.get()
|
||||
.addRecipient(certificate);
|
||||
```
|
||||
|
||||
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"));
|
||||
```
|
||||
|
||||
Both methods can be used in combination to create a message which can be decrypted with either a recipients secret key
|
||||
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`.
|
||||
|
||||
To configure the decryption / verification process, the `ConsumerOptions` object is used:
|
||||
|
||||
```java
|
||||
PGPPublicKeyRing verificationCert = ...; // optional, signers certificate for signature verification
|
||||
PGPSecretKeyRing decryptionKey = ...; // optional, decryption key
|
||||
|
||||
ConsumerOptions options = ConsumerOptions.get()
|
||||
.addVerificationCert(verificationCert) // add a verification cert for signature verification
|
||||
.addDecryptionKey(decryptionKey); // add a secret key for message decryption
|
||||
```
|
||||
|
||||
Both verification certificates and decryption keys are optional.
|
||||
If you know the message is signed, but not encrypted you can omit providing a decryption key.
|
||||
Same goes for if you know that the message is encrypted, but not signed.
|
||||
In this case you can omit the verification certificate.
|
||||
|
||||
On the other hand, providing these parameters does not hurt.
|
||||
PGPainless will ignore unused keys / certificates, so if you provide a decryption key and the message is not encrypted,
|
||||
nothing bad will happen.
|
||||
|
||||
It is possible to provide multiple verification certs and decryption keys. PGPainless will pick suitable ones on the fly.
|
||||
If the message is signed with key `0xAAAA` and you provide certificates `0xAAAA` and `0xBBBB`, it will verify
|
||||
with cert `0xAAAA` and ignore `0xBBBB`.
|
||||
|
||||
To do the actual decryption / verification of the message, do the following:
|
||||
|
||||
```java
|
||||
InputStream ciphertext = ...; // encrypted and/or signed message
|
||||
OutputStream plaintext = ...; // destination for the plaintext
|
||||
|
||||
ConsumerOptions options = ...; // see above
|
||||
DecryptionStream consumerStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertext)
|
||||
.withOptions(options);
|
||||
|
||||
Streams.pipeAll(consumerStream, plaintext);
|
||||
consumerStream.close(); // important!
|
||||
|
||||
// The result will contain metadata of the message
|
||||
MessageMetadata result = consumerStream.getMetadata();
|
||||
```
|
||||
|
||||
After the message has been processed, you can consult the `MessageMetadata` 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);
|
||||
|
||||
// For files:
|
||||
String fileName = result.getFileName();
|
||||
Date modificationData = result.getModificationDate();
|
||||
```
|
||||
### Sign a Message
|
||||
TODO
|
||||
|
||||
### Verify a Signature
|
||||
In some cases, detached signatures are distributed alongside the message.
|
||||
This is the case for example with Debians `Release` and `Release.gpg` files.
|
||||
Here, `Release` is the plaintext message, which is unaltered by the signing process while `Release.gpg` contains
|
||||
the detached OpenPGP signature.
|
||||
|
||||
To verify a detached signature, you need to call the PGPainless API like this:
|
||||
|
||||
```java
|
||||
InputStream plaintext = ...; // e.g. new FileInputStream(releaseFile);
|
||||
InputStream detachedSignature = ...; // e.g. new FileInputStream(releaseGpgFile);
|
||||
PGPPublicKeyRing certificate = ...; // e.g. debians public signing key
|
||||
|
||||
ConsumerOptions options = ConsumerOptions.get()
|
||||
.addVerificationCert(certificate) // provide certificate for verification
|
||||
.addVerificationOfDetachedSignatures(detachedSignature) // provide detached signature
|
||||
|
||||
DecryptionStream verificationStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(plaintext)
|
||||
.withOptions(options);
|
||||
|
||||
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");
|
||||
```
|
||||
TODO
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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")
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-armor
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-ARMOR" "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\-armor \- Add ASCII Armor to standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli armor\fP [\fB\-\-stacktrace\fP]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
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
|
|
@ -1,41 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-dearmor
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-DEARMOR" "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\-dearmor \- Remove ASCII Armor from standard input
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli dearmor\fP [\fB\-\-stacktrace\fP]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
|
@ -1,106 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-decrypt
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-DECRYPT" "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\-decrypt \- Decrypt a message
|
||||
.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...]
|
||||
.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
|
||||
.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 (\(aqnow\(aq).
|
||||
.sp
|
||||
Accepts special value \(aq\-\(aq for end of time.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verify\-not\-before\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 beginning of time (\(aq\-\(aq).
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verify\-with\fP=\fICERT\fP
|
||||
.RS 4
|
||||
Certificates for signature verification
|
||||
.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
|
||||
.sp
|
||||
\fB\-\-with\-password\fP=\fIPASSWORD\fP
|
||||
.RS 4
|
||||
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...).
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP
|
||||
.RS 4
|
||||
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...).
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fIKEY\fP...]
|
||||
.RS 4
|
||||
Secret keys to attempt decryption with
|
||||
.RE
|
|
@ -1,84 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-encrypt
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-ENCRYPT" "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\-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"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-as\fP=\fI{binary|text}\fP
|
||||
.RS 4
|
||||
Type of the input data. Defaults to \(aqbinary\(aq
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
.RS 4
|
||||
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
|
||||
.RS 4
|
||||
Sign the output with a private key
|
||||
.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
|
||||
.sp
|
||||
\fB\-\-with\-password\fP=\fIPASSWORD\fP
|
||||
.RS 4
|
||||
Encrypt the message with a password.
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fICERTS\fP...]
|
||||
.RS 4
|
||||
Certificates the message gets encrypted to
|
||||
.RE
|
|
@ -1,47 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-extract-cert
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-EXTRACT\-CERT" "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\-extract\-cert \- Extract a public key certificate from a secret key
|
||||
.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
|
||||
.RS 4
|
||||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
|
@ -1,165 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-generate-completion
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" 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"
|
||||
.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\-generate\-completion \- Stateless OpenPGP Protocol
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli generate\-completion\fP [\fB\-hV\fP] [\fB\-\-stacktrace\fP]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
Generate bash/zsh completion script for pgpainless\-cli.
|
||||
Run the following command to give \f(CRpgpainless\-cli\fP TAB completion in the current shell:
|
||||
.sp
|
||||
.if n .RS 4
|
||||
.nf
|
||||
source <(pgpainless\-cli generate\-completion)
|
||||
.fi
|
||||
.if n .RE
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-h\fP, \fB\-\-help\fP
|
||||
.RS 4
|
||||
Show this help message and exit.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-V\fP, \fB\-\-version\fP
|
||||
.RS 4
|
||||
Print version information and exit.
|
||||
.RE
|
||||
.SH "EXIT CODES:"
|
||||
.sp
|
||||
\fB0\fP
|
||||
.RS 4
|
||||
Successful program execution
|
||||
.RE
|
||||
.sp
|
||||
\fB1\fP
|
||||
.RS 4
|
||||
Generic program error
|
||||
.RE
|
||||
.sp
|
||||
\fB3\fP
|
||||
.RS 4
|
||||
Verification requested but no verifiable signature found
|
||||
.RE
|
||||
.sp
|
||||
\fB13\fP
|
||||
.RS 4
|
||||
Unsupported asymmetric algorithm
|
||||
.RE
|
||||
.sp
|
||||
\fB17\fP
|
||||
.RS 4
|
||||
Certificate is not encryption capable
|
||||
.RE
|
||||
.sp
|
||||
\fB19\fP
|
||||
.RS 4
|
||||
Usage error: Missing argument
|
||||
.RE
|
||||
.sp
|
||||
\fB23\fP
|
||||
.RS 4
|
||||
Incomplete verification instructions
|
||||
.RE
|
||||
.sp
|
||||
\fB29\fP
|
||||
.RS 4
|
||||
Unable to decrypt
|
||||
.RE
|
||||
.sp
|
||||
\fB31\fP
|
||||
.RS 4
|
||||
Password is not human\-readable
|
||||
.RE
|
||||
.sp
|
||||
\fB37\fP
|
||||
.RS 4
|
||||
Unsupported Option
|
||||
.RE
|
||||
.sp
|
||||
\fB41\fP
|
||||
.RS 4
|
||||
Invalid data or data of wrong type encountered
|
||||
.RE
|
||||
.sp
|
||||
\fB53\fP
|
||||
.RS 4
|
||||
Non\-text input received where text was expected
|
||||
.RE
|
||||
.sp
|
||||
\fB59\fP
|
||||
.RS 4
|
||||
Output file already exists
|
||||
.RE
|
||||
.sp
|
||||
\fB61\fP
|
||||
.RS 4
|
||||
Input file does not exist
|
||||
.RE
|
||||
.sp
|
||||
\fB67\fP
|
||||
.RS 4
|
||||
Cannot unlock password protected secret key
|
||||
.RE
|
||||
.sp
|
||||
\fB69\fP
|
||||
.RS 4
|
||||
Unsupported subcommand
|
||||
.RE
|
||||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
.RS 4
|
||||
Ambiguous input (a filename matching the designator already exists)
|
||||
.RE
|
||||
.sp
|
||||
\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
|
|
@ -1,71 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-generate-key
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-GENERATE\-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\-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...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
.RS 4
|
||||
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
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
|
||||
.RS 4
|
||||
Password to protect the private key with
|
||||
.sp
|
||||
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fIUSERID\fP...]
|
||||
.RS 4
|
||||
User\-ID, e.g. "Alice <\c
|
||||
.MTO "alice\(atexample.com" "" ">""
|
||||
.RE
|
|
@ -1,160 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-help
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-HELP" "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\-help \- Stateless OpenPGP Protocol
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli help\fP [\fB\-h\fP] [\fB\-\-stacktrace\fP] [\fICOMMAND\fP]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
When no COMMAND is given, the usage help for the main command is displayed.
|
||||
If a COMMAND is specified, the help for that command is shown.
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-h\fP, \fB\-\-help\fP
|
||||
.RS 4
|
||||
Show usage help for the help command and exit.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fICOMMAND\fP]
|
||||
.RS 4
|
||||
The COMMAND to display the usage help message for.
|
||||
.RE
|
||||
.SH "EXIT CODES:"
|
||||
.sp
|
||||
\fB0\fP
|
||||
.RS 4
|
||||
Successful program execution
|
||||
.RE
|
||||
.sp
|
||||
\fB1\fP
|
||||
.RS 4
|
||||
Generic program error
|
||||
.RE
|
||||
.sp
|
||||
\fB3\fP
|
||||
.RS 4
|
||||
Verification requested but no verifiable signature found
|
||||
.RE
|
||||
.sp
|
||||
\fB13\fP
|
||||
.RS 4
|
||||
Unsupported asymmetric algorithm
|
||||
.RE
|
||||
.sp
|
||||
\fB17\fP
|
||||
.RS 4
|
||||
Certificate is not encryption capable
|
||||
.RE
|
||||
.sp
|
||||
\fB19\fP
|
||||
.RS 4
|
||||
Usage error: Missing argument
|
||||
.RE
|
||||
.sp
|
||||
\fB23\fP
|
||||
.RS 4
|
||||
Incomplete verification instructions
|
||||
.RE
|
||||
.sp
|
||||
\fB29\fP
|
||||
.RS 4
|
||||
Unable to decrypt
|
||||
.RE
|
||||
.sp
|
||||
\fB31\fP
|
||||
.RS 4
|
||||
Password is not human\-readable
|
||||
.RE
|
||||
.sp
|
||||
\fB37\fP
|
||||
.RS 4
|
||||
Unsupported Option
|
||||
.RE
|
||||
.sp
|
||||
\fB41\fP
|
||||
.RS 4
|
||||
Invalid data or data of wrong type encountered
|
||||
.RE
|
||||
.sp
|
||||
\fB53\fP
|
||||
.RS 4
|
||||
Non\-text input received where text was expected
|
||||
.RE
|
||||
.sp
|
||||
\fB59\fP
|
||||
.RS 4
|
||||
Output file already exists
|
||||
.RE
|
||||
.sp
|
||||
\fB61\fP
|
||||
.RS 4
|
||||
Input file does not exist
|
||||
.RE
|
||||
.sp
|
||||
\fB67\fP
|
||||
.RS 4
|
||||
Cannot unlock password protected secret key
|
||||
.RE
|
||||
.sp
|
||||
\fB69\fP
|
||||
.RS 4
|
||||
Unsupported subcommand
|
||||
.RE
|
||||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
.RS 4
|
||||
Ambiguous input (a filename matching the designator already exists)
|
||||
.RE
|
||||
.sp
|
||||
\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
|
|
@ -1,51 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-inline-detach
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-DETACH" "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\-inline\-detach \- Split signatures from a clearsigned message
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli inline\-detach\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-signatures\-out\fP=\fISIGNATURES\fP]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-[no\-]armor\fP
|
||||
.RS 4
|
||||
ASCII armor the output
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-signatures\-out\fP=\fISIGNATURES\fP
|
||||
.RS 4
|
||||
Destination to which a detached signatures block will be written
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
|
@ -1,73 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-inline-sign
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-SIGN" "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\-inline\-sign \- Create an inline\-signed message
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli inline\-sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP]
|
||||
[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fIKEYS\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP
|
||||
.RS 4
|
||||
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.
|
||||
.sp
|
||||
Defaults to \(aqbinary\(aq.
|
||||
.sp
|
||||
If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, inline\-sign fails with return code 53.
|
||||
.RE
|
||||
.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
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fIKEYS\fP...]
|
||||
.RS 4
|
||||
Secret keys used for signing
|
||||
.RE
|
|
@ -1,73 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-inline-verify
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-INLINE\-VERIFY" "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\-inline\-verify \- Verify an inline\-signed message
|
||||
.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...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.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
|
||||
.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 beginning of time ("\-").
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP
|
||||
.RS 4
|
||||
File to write details over successful verifications to
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fICERT\fP...]
|
||||
.RS 4
|
||||
Public key certificates for signature verification
|
||||
.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
|
|
@ -1,74 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-sign
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-SIGN" "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\-sign \- Create a detached message signature
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text}\fP]
|
||||
[\fB\-\-micalg\-out\fP=\fIMICALG\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fIKEYS\fP...]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-as\fP=\fI{binary|text}\fP
|
||||
.RS 4
|
||||
Specify the output format of the signed message.
|
||||
.sp
|
||||
Defaults to \(aqbinary\(aq.
|
||||
.sp
|
||||
If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, sign fails with return code 53.
|
||||
.RE
|
||||
.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).
|
||||
.RE
|
||||
.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
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
[\fIKEYS\fP...]
|
||||
.RS 4
|
||||
Secret keys used for signing
|
||||
.RE
|
|
@ -1,74 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-verify
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-VERIFY" "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\-verify \- Verify a detached signature
|
||||
.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
|
||||
.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 beginning of time ("\-").
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.SH "ARGUMENTS"
|
||||
.sp
|
||||
\fISIGNATURE\fP
|
||||
.RS 4
|
||||
Detached signature
|
||||
.RE
|
||||
.sp
|
||||
\fICERT\fP...
|
||||
.RS 4
|
||||
Public key certificates for signature verification
|
||||
.RE
|
|
@ -1,56 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli-version
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI\-VERSION" "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\-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]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-backend\fP
|
||||
.RS 4
|
||||
Print information about the cryptographic backend
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-extended\fP
|
||||
.RS 4
|
||||
Print an extended version string
|
||||
.RE
|
||||
.sp
|
||||
\fB\-\-pgpainless\-cli\-spec\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
|
||||
.RE
|
|
@ -1,233 +0,0 @@
|
|||
'\" t
|
||||
.\" Title: pgpainless-cli
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Manual: PGPainless-CLI Manual
|
||||
.\" Source:
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "PGPAINLESS\-CLI" "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 \- Stateless OpenPGP Protocol
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBpgpainless\-cli\fP [\fB\-\-stacktrace\fP] [COMMAND]
|
||||
.SH "DESCRIPTION"
|
||||
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-\-stacktrace\fP
|
||||
.RS 4
|
||||
Print stacktrace
|
||||
.RE
|
||||
.SH "COMMANDS"
|
||||
.sp
|
||||
\fBversion\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
|
||||
.RE
|
||||
.sp
|
||||
\fBarmor\fP
|
||||
.RS 4
|
||||
Add ASCII Armor to standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBdearmor\fP
|
||||
.RS 4
|
||||
Remove ASCII Armor from standard input
|
||||
.RE
|
||||
.sp
|
||||
\fBhelp\fP
|
||||
.RS 4
|
||||
Stateless OpenPGP Protocol
|
||||
.RE
|
||||
.sp
|
||||
\fBgenerate\-completion\fP
|
||||
.RS 4
|
||||
Stateless OpenPGP Protocol
|
||||
.RE
|
||||
.SH "EXIT CODES:"
|
||||
.sp
|
||||
\fB0\fP
|
||||
.RS 4
|
||||
Successful program execution
|
||||
.RE
|
||||
.sp
|
||||
\fB1\fP
|
||||
.RS 4
|
||||
Generic program error
|
||||
.RE
|
||||
.sp
|
||||
\fB3\fP
|
||||
.RS 4
|
||||
Verification requested but no verifiable signature found
|
||||
.RE
|
||||
.sp
|
||||
\fB13\fP
|
||||
.RS 4
|
||||
Unsupported asymmetric algorithm
|
||||
.RE
|
||||
.sp
|
||||
\fB17\fP
|
||||
.RS 4
|
||||
Certificate is not encryption capable
|
||||
.RE
|
||||
.sp
|
||||
\fB19\fP
|
||||
.RS 4
|
||||
Usage error: Missing argument
|
||||
.RE
|
||||
.sp
|
||||
\fB23\fP
|
||||
.RS 4
|
||||
Incomplete verification instructions
|
||||
.RE
|
||||
.sp
|
||||
\fB29\fP
|
||||
.RS 4
|
||||
Unable to decrypt
|
||||
.RE
|
||||
.sp
|
||||
\fB31\fP
|
||||
.RS 4
|
||||
Password is not human\-readable
|
||||
.RE
|
||||
.sp
|
||||
\fB37\fP
|
||||
.RS 4
|
||||
Unsupported Option
|
||||
.RE
|
||||
.sp
|
||||
\fB41\fP
|
||||
.RS 4
|
||||
Invalid data or data of wrong type encountered
|
||||
.RE
|
||||
.sp
|
||||
\fB53\fP
|
||||
.RS 4
|
||||
Non\-text input received where text was expected
|
||||
.RE
|
||||
.sp
|
||||
\fB59\fP
|
||||
.RS 4
|
||||
Output file already exists
|
||||
.RE
|
||||
.sp
|
||||
\fB61\fP
|
||||
.RS 4
|
||||
Input file does not exist
|
||||
.RE
|
||||
.sp
|
||||
\fB67\fP
|
||||
.RS 4
|
||||
Cannot unlock password protected secret key
|
||||
.RE
|
||||
.sp
|
||||
\fB69\fP
|
||||
.RS 4
|
||||
Unsupported subcommand
|
||||
.RE
|
||||
.sp
|
||||
\fB71\fP
|
||||
.RS 4
|
||||
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
|
||||
.RE
|
||||
.sp
|
||||
\fB73\fP
|
||||
.RS 4
|
||||
Ambiguous input (a filename matching the designator already exists)
|
||||
.RE
|
||||
.sp
|
||||
\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
|
|
@ -1,26 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
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;
|
||||
DEST_DIR=$SCRIPT_DIR/packaging/man
|
||||
mkdir -p $DEST_DIR
|
||||
|
||||
for page in $SRC_DIR/*
|
||||
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"});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,96 +5,87 @@
|
|||
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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
|
||||
public class ArmorCmdTest extends CLITest {
|
||||
public class ArmorCmdTest {
|
||||
|
||||
public ArmorCmdTest() {
|
||||
super(LoggerFactory.getLogger(ArmorCmdTest.class));
|
||||
private static PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void saveSout() {
|
||||
originalSout = System.out;
|
||||
}
|
||||
|
||||
private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" +
|
||||
"Comment: alice@pgpainless.org\n" +
|
||||
"\n" +
|
||||
"lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" +
|
||||
"t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" +
|
||||
"ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" +
|
||||
"D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" +
|
||||
"8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" +
|
||||
"G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" +
|
||||
"8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" +
|
||||
"hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" +
|
||||
"CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" +
|
||||
"eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" +
|
||||
"BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" +
|
||||
"ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" +
|
||||
"AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" +
|
||||
"ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" +
|
||||
"m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" +
|
||||
"lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" +
|
||||
"rsH85mUgCw==\n" +
|
||||
"=EMKf\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorSecretKey() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
byte[] binary = secretKeys.getEncoded();
|
||||
@FailOnSystemExit
|
||||
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org");
|
||||
byte[] bytes = secretKey.getEncoded();
|
||||
|
||||
pipeBytesToStdin(binary);
|
||||
ByteArrayOutputStream armorOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
|
||||
assertArrayEquals(secretKeys.getEncoded(), armored.getEncoded());
|
||||
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorPublicKey() throws IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
@FailOnSystemExit
|
||||
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org");
|
||||
PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey);
|
||||
byte[] bytes = publicKey.getEncoded();
|
||||
|
||||
pipeBytesToStdin(bytes);
|
||||
ByteArrayOutputStream armorOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
|
||||
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorMessage() throws IOException {
|
||||
@FailOnSystemExit
|
||||
public void armorMessage() {
|
||||
String message = "Hello, World!\n";
|
||||
|
||||
pipeStringToStdin(message);
|
||||
ByteArrayOutputStream armorOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
String armored = armorOut.toString();
|
||||
|
||||
assertTrue(armored.startsWith("-----BEGIN PGP MESSAGE-----\n"));
|
||||
assertTrue(armored.contains("SGVsbG8sIFdvcmxkIQo="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorAlreadyArmoredDataIsIdempotent() throws IOException {
|
||||
pipeStringToStdin(key);
|
||||
ByteArrayOutputStream armorOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
|
||||
String armored = armorOut.toString();
|
||||
assertEquals(key, armored);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,167 +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.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
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 java.nio.file.Files;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.opentest4j.TestAbortedException;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import org.pgpainless.sop.SOPImpl;
|
||||
import org.slf4j.Logger;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
|
||||
public abstract class CLITest {
|
||||
|
||||
protected File testDirectory;
|
||||
protected InputStream stdin;
|
||||
protected PrintStream stdout;
|
||||
|
||||
protected final Logger LOGGER;
|
||||
|
||||
|
||||
public CLITest(@Nonnull Logger logger) {
|
||||
LOGGER = logger;
|
||||
SopCLI.setSopInstance(new SOPImpl());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws IOException {
|
||||
testDirectory = TestUtils.createTempDirectory();
|
||||
testDirectory.deleteOnExit();
|
||||
LOGGER.debug(testDirectory.getAbsolutePath());
|
||||
stdin = System.in;
|
||||
stdout = System.out;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws IOException {
|
||||
resetStreams();
|
||||
}
|
||||
|
||||
public File nonExistentFile(String name) {
|
||||
File file = new File(testDirectory, name);
|
||||
if (file.exists()) {
|
||||
throw new TestAbortedException("File " + file.getAbsolutePath() + " already exists.");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public File pipeStdoutToFile(String name) throws IOException {
|
||||
File file = new File(testDirectory, name);
|
||||
file.deleteOnExit();
|
||||
if (!file.createNewFile()) {
|
||||
throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath());
|
||||
}
|
||||
System.setOut(new PrintStream(Files.newOutputStream(file.toPath())));
|
||||
return file;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream pipeStdoutToStream() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
pipeStdoutToStream(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
public void pipeStdoutToStream(OutputStream stream) {
|
||||
System.setOut(new PrintStream(stream));
|
||||
}
|
||||
|
||||
public void pipeFileToStdin(File file) throws IOException {
|
||||
System.setIn(Files.newInputStream(file.toPath()));
|
||||
}
|
||||
|
||||
public void pipeBytesToStdin(byte[] bytes) {
|
||||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public void pipeStringToStdin(String string) {
|
||||
System.setIn(new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
public void resetStdout() {
|
||||
if (System.out != stdout) {
|
||||
System.out.flush();
|
||||
System.out.close();
|
||||
}
|
||||
System.setOut(stdout);
|
||||
}
|
||||
|
||||
public void resetStdin() throws IOException {
|
||||
if (System.in != stdin) {
|
||||
System.in.close();
|
||||
}
|
||||
System.setIn(stdin);
|
||||
}
|
||||
|
||||
public void resetStreams() throws IOException {
|
||||
resetStdout();
|
||||
resetStdin();
|
||||
}
|
||||
|
||||
public File writeFile(String name, String data) throws IOException {
|
||||
return writeFile(name, data.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public File writeFile(String name, byte[] bytes) throws IOException {
|
||||
return writeFile(name, new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public File writeFile(String name, InputStream data) throws IOException {
|
||||
File file = new File(testDirectory, name);
|
||||
if (!file.createNewFile()) {
|
||||
throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath());
|
||||
}
|
||||
file.deleteOnExit();
|
||||
try (FileOutputStream fileOut = new FileOutputStream(file)) {
|
||||
Streams.pipeAll(data, fileOut);
|
||||
fileOut.flush();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public byte[] readBytesFromFile(File file) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
try (FileInputStream fileIn = new FileInputStream(file)) {
|
||||
Streams.pipeAll(fileIn, buffer);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new TestAbortedException("File " + file.getAbsolutePath() + " does not exist!", e);
|
||||
} catch (IOException e) {
|
||||
throw new TestAbortedException("Cannot read from file " + file.getAbsolutePath(), e);
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public String readStringFromFile(File file) {
|
||||
return new String(readBytesFromFile(file), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public int executeCommand(String... command) throws IOException {
|
||||
int exitCode = SopCLI.execute(command);
|
||||
resetStreams();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public void assertSuccess(int exitCode) {
|
||||
assertEquals(0, exitCode,
|
||||
"Expected successful program execution");
|
||||
}
|
||||
}
|
|
@ -6,87 +6,74 @@ 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
|
||||
public class DearmorCmdTest extends CLITest {
|
||||
public class DearmorCmdTest {
|
||||
|
||||
public DearmorCmdTest() {
|
||||
super(LoggerFactory.getLogger(DearmorCmdTest.class));
|
||||
private PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void saveSout() {
|
||||
this.originalSout = System.out;
|
||||
}
|
||||
|
||||
private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" +
|
||||
"Comment: alice@pgpainless.org\n" +
|
||||
"\n" +
|
||||
"lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" +
|
||||
"t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" +
|
||||
"ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" +
|
||||
"D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" +
|
||||
"8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" +
|
||||
"G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" +
|
||||
"8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" +
|
||||
"hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" +
|
||||
"CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" +
|
||||
"eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" +
|
||||
"BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" +
|
||||
"ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" +
|
||||
"AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" +
|
||||
"ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" +
|
||||
"m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" +
|
||||
"lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" +
|
||||
"rsH85mUgCw==\n" +
|
||||
"=EMKf\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||
|
||||
@Test
|
||||
public void dearmorSecretKey() throws IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
|
||||
pipeStringToStdin(key);
|
||||
ByteArrayOutputStream dearmored = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("dearmor"));
|
||||
|
||||
assertArrayEquals(secretKey.getEncoded(), dearmored.toByteArray());
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
@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");
|
||||
@FailOnSystemExit
|
||||
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org");
|
||||
String armored = PGPainless.asciiArmor(secretKey);
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void dearmorCertificate() throws IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
@FailOnSystemExit
|
||||
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org");
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
|
||||
String armoredCert = PGPainless.asciiArmor(certificate);
|
||||
String armored = PGPainless.asciiArmor(certificate);
|
||||
|
||||
pipeStringToStdin(armoredCert);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("dearmor"));
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorMessage() throws IOException {
|
||||
@FailOnSystemExit
|
||||
public void dearmorMessage() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
"\n" +
|
||||
|
@ -94,20 +81,11 @@ public class DearmorCmdTest extends CLITest {
|
|||
"=fkLo\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
pipeStringToStdin(armored);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("dearmor"));
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,97 +4,42 @@
|
|||
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
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.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class ExtractCertCmdTest extends CLITest {
|
||||
|
||||
public ExtractCertCmdTest() {
|
||||
super(LoggerFactory.getLogger(ExtractCertCmdTest.class));
|
||||
}
|
||||
public class ExtractCertCmdTest {
|
||||
|
||||
@Test
|
||||
public void testExtractCert()
|
||||
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
@FailOnSystemExit
|
||||
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(secretKeys.getEncoded());
|
||||
System.setIn(inputStream);
|
||||
|
||||
pipeBytesToStdin(secretKeys.getEncoded());
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("extract-cert", "--armor"));
|
||||
|
||||
assertTrue(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
|
||||
assertFalse(info.isSecretKey());
|
||||
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCertFromCertFails() throws IOException {
|
||||
// Generate key
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
|
||||
|
||||
// extract cert from key (success)
|
||||
pipeFileToStdin(keyFile);
|
||||
File certFile = pipeStdoutToFile("cert.asc");
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
// extract cert from cert (fail)
|
||||
pipeFileToStdin(certFile);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("extract-cert");
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractCertFromGarbageFails() throws IOException {
|
||||
pipeStringToStdin("This is a bunch of garbage!");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("extract-cert");
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCertUnarmored() throws IOException {
|
||||
// Generate key
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
|
||||
|
||||
// extract cert from key (success)
|
||||
pipeFileToStdin(keyFile);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("extract-cert", "--no-armor"));
|
||||
|
||||
assertFalse(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
|
||||
|
||||
pipeBytesToStdin(out.toByteArray());
|
||||
ByteArrayOutputStream armored = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("armor"));
|
||||
|
||||
assertTrue(armored.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.pgpainless.cli.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import org.pgpainless.key.info.KeyInfo;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public class GenerateCertCmdTest {
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testKeyGeneration() throws IOException, PGPException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("generate-key", "--armor", "Juliet Capulet <juliet@capulet.lit>");
|
||||
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isFullyDecrypted());
|
||||
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
|
||||
|
||||
for (PGPSecretKey key : secretKeys) {
|
||||
assertTrue(testPassphrase(key, null));
|
||||
}
|
||||
|
||||
byte[] outBegin = new byte[37];
|
||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
||||
assertArrayEquals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testGenerateKeyWithPassword() throws IOException, PGPException {
|
||||
PrintStream orig = System.out;
|
||||
try {
|
||||
// Write password to file
|
||||
File tempDir = TestUtils.createTempDirectory();
|
||||
File passwordFile = TestUtils.writeTempFile(tempDir, "sw0rdf1sh".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("generate-key", "Juliet Capulet <juliet@capulet.lit>",
|
||||
"--with-key-password", passwordFile.getAbsolutePath());
|
||||
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertFalse(info.isFullyDecrypted());
|
||||
assertTrue(info.isFullyEncrypted());
|
||||
|
||||
for (PGPSecretKey key : secretKeys) {
|
||||
assertTrue(testPassphrase(key, "sw0rdf1sh"));
|
||||
}
|
||||
} finally {
|
||||
System.setOut(orig);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean testPassphrase(PGPSecretKey key, String passphrase) throws PGPException {
|
||||
if (KeyInfo.isEncrypted(key)) {
|
||||
UnlockSecretKey.unlockSecretKey(key, Passphrase.fromPassword(passphrase));
|
||||
} else {
|
||||
if (passphrase != null) {
|
||||
return false;
|
||||
}
|
||||
UnlockSecretKey.unlockSecretKey(key, (PBESecretKeyDecryptor) null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testNoArmor() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
PGPainlessCLI.execute("generate-key", "--no-armor", "Test <test@test.test>");
|
||||
|
||||
byte[] outBegin = new byte[37];
|
||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
||||
assertFalse(Arrays.equals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES));
|
||||
}
|
||||
}
|
|
@ -1,98 +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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class GenerateKeyCmdTest extends CLITest {
|
||||
|
||||
public GenerateKeyCmdTest() {
|
||||
super(LoggerFactory.getLogger(GenerateKeyCmdTest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateKey() throws IOException {
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
|
||||
|
||||
String key = readStringFromFile(keyFile);
|
||||
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isFullyDecrypted());
|
||||
assertEquals(Collections.singletonList("Alice <alice@pgpainless.org>"), info.getUserIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateBinaryKey() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("generate-key", "--no-armor",
|
||||
"Alice <alice@pgpainless.org>"));
|
||||
|
||||
byte[] key = out.toByteArray();
|
||||
String firstHexOctet = Hex.toHexString(key, 0, 1);
|
||||
assertTrue(firstHexOctet.equals("c5") || firstHexOctet.equals("94"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateKeyWithMultipleUserIds() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("generate-key",
|
||||
"Alice <alice@pgpainless.org>", "Alice <alice@openpgp.org>"));
|
||||
|
||||
String key = out.toString();
|
||||
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isFullyDecrypted());
|
||||
assertEquals(Arrays.asList("Alice <alice@pgpainless.org>", "Alice <alice@openpgp.org>"), info.getUserIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordProtectedKey() throws IOException, PGPException {
|
||||
File passwordFile = writeFile("password", "sw0rdf1sh");
|
||||
passwordFile.deleteOnExit();
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("generate-key",
|
||||
"--with-key-password", passwordFile.getAbsolutePath(), "Alice <alice@pgpainless.org>"));
|
||||
|
||||
String key = out.toString();
|
||||
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isFullyEncrypted());
|
||||
|
||||
assertNotNull(UnlockSecretKey
|
||||
.unlockSecretKey(secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeneratePasswordProtectedKey_missingPasswordFile() throws IOException {
|
||||
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 + ")");
|
||||
}
|
||||
}
|
|
@ -6,18 +6,33 @@ package org.pgpainless.cli.commands;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
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.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class InlineDetachCmdTest extends CLITest {
|
||||
public class InlineDetachCmdTest {
|
||||
|
||||
private PrintStream originalSout;
|
||||
private static File tempDir;
|
||||
private static File certFile;
|
||||
|
||||
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"Version: BCPG v1.64\n" +
|
||||
|
@ -34,6 +49,28 @@ public class InlineDetachCmdTest extends CLITest {
|
|||
"=a1W7\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
@BeforeAll
|
||||
public static void createTempDir() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
|
||||
certFile = new File(tempDir, "cert.asc");
|
||||
assertTrue(certFile.createNewFile());
|
||||
try (FileOutputStream out = new FileOutputStream(certFile)) {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(CERT.getBytes(StandardCharsets.UTF_8));
|
||||
Streams.pipeAll(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void saveSout() {
|
||||
this.originalSout = System.out;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
private static final String CLEAR_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
|
||||
"Hash: SHA512\n" +
|
||||
"\n" +
|
||||
|
@ -66,90 +103,91 @@ public class InlineDetachCmdTest extends CLITest {
|
|||
"Unfold the imagined happiness that both\n" +
|
||||
"Receive in either by this dear encounter.";
|
||||
|
||||
public InlineDetachCmdTest() {
|
||||
super(LoggerFactory.getLogger(InlineDetachCmdTest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detachInbandSignatureAndMessage() throws IOException {
|
||||
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
|
||||
ByteArrayOutputStream msgOut = pipeStdoutToStream();
|
||||
File sigFile = nonExistentFile("sig.out");
|
||||
// Clearsigned In
|
||||
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
|
||||
System.setIn(clearSignedIn);
|
||||
|
||||
assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath()));
|
||||
assertTrue(sigFile.exists(), "Signature file must have been written.");
|
||||
// Plaintext Out
|
||||
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(msgOut));
|
||||
|
||||
// Detach
|
||||
File tempSigFile = new File(tempDir, "sig.out");
|
||||
PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath()});
|
||||
|
||||
// Test equality with expected values
|
||||
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
|
||||
String sig = readStringFromFile(sigFile);
|
||||
TestUtils.assertSignatureIsArmored(sig.getBytes());
|
||||
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig);
|
||||
try (FileInputStream sigIn = new FileInputStream(tempSigFile)) {
|
||||
ByteArrayOutputStream sigBytes = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(sigIn, sigBytes);
|
||||
String sig = sigBytes.toString();
|
||||
TestUtils.assertSignatureIsArmored(sigBytes.toByteArray());
|
||||
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig);
|
||||
} catch (FileNotFoundException e) {
|
||||
fail("Signature File must have been written.", e);
|
||||
}
|
||||
|
||||
// Check if produced signature still checks out
|
||||
File certFile = writeFile("cert.asc", CERT);
|
||||
pipeStringToStdin(msgOut.toString());
|
||||
ByteArrayOutputStream verifyOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath()));
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n",
|
||||
verifyOut.toString());
|
||||
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
|
||||
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(verifyOut));
|
||||
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
|
||||
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detachInbandSignatureAndMessageNoArmor() throws IOException {
|
||||
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
|
||||
ByteArrayOutputStream msgOut = pipeStdoutToStream();
|
||||
File sigFile = nonExistentFile("sig.out");
|
||||
// Clearsigned In
|
||||
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
|
||||
System.setIn(clearSignedIn);
|
||||
|
||||
assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath(), "--no-armor"));
|
||||
// Plaintext Out
|
||||
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(msgOut));
|
||||
|
||||
// Detach
|
||||
File tempSigFile = new File(tempDir, "sig.asc");
|
||||
PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath(), "--no-armor"});
|
||||
|
||||
// Test equality with expected values
|
||||
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
|
||||
assertTrue(sigFile.exists(), "Signature file must have been written.");
|
||||
byte[] sig = readBytesFromFile(sigFile);
|
||||
|
||||
TestUtils.assertSignatureIsNotArmored(sig);
|
||||
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig);
|
||||
try (FileInputStream sigIn = new FileInputStream(tempSigFile)) {
|
||||
ByteArrayOutputStream sigBytes = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(sigIn, sigBytes);
|
||||
byte[] sig = sigBytes.toByteArray();
|
||||
TestUtils.assertSignatureIsNotArmored(sig);
|
||||
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig);
|
||||
} catch (FileNotFoundException e) {
|
||||
fail("Signature File must have been written.", e);
|
||||
}
|
||||
|
||||
// Check if produced signature still checks out
|
||||
pipeBytesToStdin(msgOut.toByteArray());
|
||||
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",
|
||||
verifyOut.toString());
|
||||
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
|
||||
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(verifyOut));
|
||||
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
|
||||
|
||||
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
|
||||
public void existingSignatureOutCausesException() throws IOException {
|
||||
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
|
||||
ByteArrayOutputStream msgOut = pipeStdoutToStream();
|
||||
File existingSigFile = writeFile("sig.asc", CLEAR_SIGNED_SIGNATURE);
|
||||
int exit = executeCommand("inline-detach", "--signatures-out", existingSigFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.OutputExists.EXIT_CODE, exit);
|
||||
assertEquals(0, msgOut.size());
|
||||
// Clearsigned In
|
||||
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
|
||||
System.setIn(clearSignedIn);
|
||||
|
||||
// Plaintext Out
|
||||
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(msgOut));
|
||||
|
||||
// Detach
|
||||
File existingSigFile = new File(tempDir, "sig.existing");
|
||||
assertTrue(existingSigFile.createNewFile());
|
||||
PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + existingSigFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@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"));
|
||||
}
|
||||
}
|
|
@ -5,669 +5,111 @@
|
|||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
|
||||
public class RoundTripEncryptDecryptCmdTest extends CLITest {
|
||||
public class RoundTripEncryptDecryptCmdTest {
|
||||
|
||||
public RoundTripEncryptDecryptCmdTest() {
|
||||
super(LoggerFactory.getLogger(RoundTripEncryptDecryptCmdTest.class));
|
||||
private static File tempDir;
|
||||
private static PrintStream originalSout;
|
||||
|
||||
@BeforeAll
|
||||
public static void prepare() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" +
|
||||
"Comment: Alice <alice@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"lFgEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
|
||||
"d1M7WwsAAP0R1ELnfdJhXcfjaYPLHzwy1i34FxP5g3tvdgg9Q7VmchActBxBbGlj\n" +
|
||||
"ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmNr3kYJECwNPBI89RwI\n" +
|
||||
"FiEEouwHf8l34V3Xme/5LA08Ejz1HAgCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" +
|
||||
"AQAAe6YA/2sO483Vi2Fgs4ejv8FykyO96IVrMoYhw3Od4LyWEyDfAQDi15LxJJm6\n" +
|
||||
"T2sXdENVigdwDJiELxjOtbmivuJutxkWCJxdBGNr3kYSCisGAQQBl1UBBQEBB0CS\n" +
|
||||
"zXjySHqlicxG3QlrVeTIqwKitL1sWsx0MCDmT1C8dAMBCAcAAP9VNkfMQvYAlYSP\n" +
|
||||
"aYEkwEOc8/XpiloVKtPzxwVCPlXFeBDCiHUEGBYKAB0FAmNr3kYCngECmwwFFgID\n" +
|
||||
"AQAECwkIBwUVCgkICwAKCRAsDTwSPPUcCOT4AQDZcN5a/e8Qr+LNBIyXXLgJWGsL\n" +
|
||||
"59nsKHBbDURnxbEnMQEAybS8u+Rsb82yW4CfaA4CLRTC3eDc5Y4QwYWzLogWNwic\n" +
|
||||
"WARja95GFgkrBgEEAdpHDwEBB0DcdwQufWLq6ASku4JWBBd9JplRVhK0cXWuTE73\n" +
|
||||
"uWltuwABAI0bVQXvgDnxTs6kUO7JIWtokM5lI/1bfG4L1YOfnXIgD7CI1QQYFgoA\n" +
|
||||
"fQUCY2veRgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ\n" +
|
||||
"7NC/hj9lyaWVAwEA3ze1LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGz\n" +
|
||||
"ZMFF/iQ/rOcSAsHPp4ggezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxG\n" +
|
||||
"u/s4maOFozcO4JoCZTsLHGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9\n" +
|
||||
"aesZ2IHgpwA7jlyHSgwLDw==\n" +
|
||||
"=H3HU\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" +
|
||||
"Comment: Alice <alice@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"mDMEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
|
||||
"d1M7Wwu0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2ve\n" +
|
||||
"RgkQLA08Ejz1HAgWIQSi7Ad/yXfhXdeZ7/ksDTwSPPUcCAKeAQKbAQUWAgMBAAQL\n" +
|
||||
"CQgHBRUKCQgLApkBAAB7pgD/aw7jzdWLYWCzh6O/wXKTI73ohWsyhiHDc53gvJYT\n" +
|
||||
"IN8BAOLXkvEkmbpPaxd0Q1WKB3AMmIQvGM61uaK+4m63GRYIuDgEY2veRhIKKwYB\n" +
|
||||
"BAGXVQEFAQEHQJLNePJIeqWJzEbdCWtV5MirAqK0vWxazHQwIOZPULx0AwEIB4h1\n" +
|
||||
"BBgWCgAdBQJja95GAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQLA08Ejz1HAjk\n" +
|
||||
"+AEA2XDeWv3vEK/izQSMl1y4CVhrC+fZ7ChwWw1EZ8WxJzEBAMm0vLvkbG/NsluA\n" +
|
||||
"n2gOAi0Uwt3g3OWOEMGFsy6IFjcIuDMEY2veRhYJKwYBBAHaRw8BAQdA3HcELn1i\n" +
|
||||
"6ugEpLuCVgQXfSaZUVYStHF1rkxO97lpbbuI1QQYFgoAfQUCY2veRgKeAQKbAgUW\n" +
|
||||
"AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ7NC/hj9lyaWVAwEA3ze1\n" +
|
||||
"LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGzZMFF/iQ/rOcSAsHPp4gg\n" +
|
||||
"ezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxGu/s4maOFozcO4JoCZTsL\n" +
|
||||
"HGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9aesZ2IHgpwA7jlyHSgwL\n" +
|
||||
"Dw==\n" +
|
||||
"=c1PZ\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void encryptAndDecryptAMessage() throws IOException {
|
||||
// Juliets key and cert
|
||||
File julietKeyFile = pipeStdoutToFile("juliet.key");
|
||||
assertSuccess(executeCommand("generate-key", "Juliet <juliet@capulet.lit>"));
|
||||
originalSout = System.out;
|
||||
File julietKeyFile = new File(tempDir, "juliet.key");
|
||||
assertTrue(julietKeyFile.createNewFile());
|
||||
|
||||
pipeFileToStdin(julietKeyFile);
|
||||
File julietCertFile = pipeStdoutToFile("juliet.cert");
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
File julietCertFile = new File(tempDir, "juliet.asc");
|
||||
assertTrue(julietCertFile.createNewFile());
|
||||
|
||||
// Romeos key and cert
|
||||
File romeoKeyFile = pipeStdoutToFile("romeo.key");
|
||||
assertSuccess(executeCommand("generate-key", "Romeo <romeo@montague.lit>"));
|
||||
File romeoKeyFile = new File(tempDir, "romeo.key");
|
||||
assertTrue(romeoKeyFile.createNewFile());
|
||||
|
||||
File romeoCertFile = pipeStdoutToFile("romeo.cert");
|
||||
pipeFileToStdin(romeoKeyFile);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
File romeoCertFile = new File(tempDir, "romeo.asc");
|
||||
assertTrue(romeoCertFile.createNewFile());
|
||||
|
||||
File msgAscFile = new File(tempDir, "msg.asc");
|
||||
assertTrue(msgAscFile.createNewFile());
|
||||
|
||||
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
|
||||
System.setOut(new PrintStream(julietKeyOut));
|
||||
PGPainlessCLI.execute("generate-key", "Juliet Capulet <juliet@capulet.lit>");
|
||||
julietKeyOut.close();
|
||||
|
||||
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
|
||||
System.setIn(julietKeyIn);
|
||||
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
|
||||
System.setOut(new PrintStream(julietCertOut));
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
julietKeyIn.close();
|
||||
julietCertOut.close();
|
||||
|
||||
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
|
||||
System.setOut(new PrintStream(romeoKeyOut));
|
||||
PGPainlessCLI.execute("generate-key", "Romeo Montague <romeo@montague.lit>");
|
||||
romeoKeyOut.close();
|
||||
|
||||
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
|
||||
System.setIn(romeoKeyIn);
|
||||
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
|
||||
System.setOut(new PrintStream(romeoCertOut));
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
romeoKeyIn.close();
|
||||
romeoCertOut.close();
|
||||
|
||||
// Romeo encrypts signs and encrypts for Juliet and himself
|
||||
String msg = "Hello World!\n";
|
||||
File encryptedMessageFile = pipeStdoutToFile("msg.asc");
|
||||
pipeStringToStdin(msg);
|
||||
assertSuccess(executeCommand("encrypt", "--sign-with", romeoKeyFile.getAbsolutePath(),
|
||||
julietCertFile.getAbsolutePath(), romeoCertFile.getAbsolutePath()));
|
||||
ByteArrayInputStream msgIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
|
||||
System.setIn(msgIn);
|
||||
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
|
||||
System.setOut(new PrintStream(msgAscOut));
|
||||
PGPainlessCLI.execute("encrypt",
|
||||
"--sign-with", romeoKeyFile.getAbsolutePath(),
|
||||
julietCertFile.getAbsolutePath());
|
||||
msgAscOut.close();
|
||||
|
||||
// Juliet can decrypt and verify with Romeos cert
|
||||
pipeFileToStdin(encryptedMessageFile);
|
||||
File verificationsFile = nonExistentFile("verifications");
|
||||
ByteArrayOutputStream decrypted = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--verifications-out", verificationsFile.getAbsolutePath(),
|
||||
File verifyFile = new File(tempDir, "verify.txt");
|
||||
|
||||
FileInputStream msgAscIn = new FileInputStream(msgAscFile);
|
||||
System.setIn(msgAscIn);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PrintStream pOut = new PrintStream(out);
|
||||
System.setOut(pOut);
|
||||
PGPainlessCLI.execute("decrypt",
|
||||
"--verify-out", verifyFile.getAbsolutePath(),
|
||||
"--verify-with", romeoCertFile.getAbsolutePath(),
|
||||
julietKeyFile.getAbsolutePath()));
|
||||
assertEquals(msg, decrypted.toString());
|
||||
|
||||
// Romeo can decrypt and verify too
|
||||
pipeFileToStdin(encryptedMessageFile);
|
||||
File anotherVerificationsFile = nonExistentFile("anotherVerifications");
|
||||
decrypted = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--verifications-out", anotherVerificationsFile.getAbsolutePath(),
|
||||
"--verify-with", romeoCertFile.getAbsolutePath(),
|
||||
romeoKeyFile.getAbsolutePath()));
|
||||
assertEquals(msg, decrypted.toString());
|
||||
|
||||
String julietsVerif = readStringFromFile(verificationsFile);
|
||||
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
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingArgumentsIfNoArgsSupplied() throws IOException {
|
||||
int exit = executeCommand("encrypt");
|
||||
assertEquals(SOPGPException.MissingArg.EXIT_CODE, exit);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Disabled, since we now read certificates from secret keys")
|
||||
public void testEncryptingForKeyFails() throws IOException {
|
||||
File notACert = writeFile("key.asc", KEY);
|
||||
|
||||
pipeStringToStdin("Hello, World!");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", notACert.getAbsolutePath());
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncrypt_SignWithCertFails() throws IOException {
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
// noinspection UnnecessaryLocalVariable
|
||||
File notAKey = cert;
|
||||
|
||||
pipeStringToStdin("Hello, World!");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", "--sign-with", notAKey.getAbsolutePath(), cert.getAbsolutePath());
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptVerifyOut_withoutVerifyWithFails() throws IOException {
|
||||
File key = writeFile("key.asc", KEY);
|
||||
|
||||
File verifications = nonExistentFile("verifications");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", "--verifications-out",
|
||||
verifications.getAbsolutePath(), key.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerificationsOutAlreadyExistFails() throws IOException {
|
||||
File key = writeFile("key.asc", KEY);
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
|
||||
File verifications = writeFile("verifications", "this file is not empty");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", "--verify-with", cert.getAbsolutePath(),
|
||||
"--verifications-out", verifications.getAbsolutePath(),
|
||||
key.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.OutputExists.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionKeyOutWritesSessionKeyOut() throws IOException {
|
||||
File key = writeFile("key.asc", KEY);
|
||||
File sessionKeyFile = nonExistentFile("session.key");
|
||||
|
||||
String plaintext = "Hello, World!\n";
|
||||
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-----";
|
||||
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
|
||||
|
||||
pipeStringToStdin(ciphertext);
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--session-key-out",
|
||||
sessionKeyFile.getAbsolutePath(), key.getAbsolutePath()));
|
||||
|
||||
assertEquals(plaintext, plaintextOut.toString());
|
||||
String resultSessionKey = readStringFromFile(sessionKeyFile);
|
||||
assertEquals(sessionKey, resultSessionKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptMessageWithSessionKey() throws IOException {
|
||||
String plaintext = "Hello, World!\n";
|
||||
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-----";
|
||||
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
|
||||
|
||||
File sessionKeyFile = writeFile("session.key", sessionKey);
|
||||
|
||||
pipeStringToStdin(ciphertext);
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath()));
|
||||
|
||||
assertEquals(plaintext, plaintextOut.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications() throws IOException {
|
||||
String plaintext = "Hello, World!\n";
|
||||
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-----";
|
||||
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
|
||||
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
File sessionKeyFile = writeFile("session.key", sessionKey);
|
||||
File verifications = nonExistentFile("verifications");
|
||||
|
||||
pipeStringToStdin(ciphertext);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath(),
|
||||
"--verify-with", cert.getAbsolutePath(), "--verifications-out", verifications.getAbsolutePath()));
|
||||
|
||||
assertEquals(plaintext, out.toString());
|
||||
String verificationString = readStringFromFile(verifications);
|
||||
assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08 mode:binary\n",
|
||||
verificationString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptAndDecryptMessageWithPassphrase() throws IOException {
|
||||
File passwordFile = writeFile("password", "c1tiz€n4");
|
||||
String message = "I cannot think of meaningful messages for test vectors rn";
|
||||
|
||||
pipeStringToStdin(message);
|
||||
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("encrypt", "--with-password", passwordFile.getAbsolutePath()));
|
||||
|
||||
String ciphertextString = ciphertext.toString();
|
||||
assertTrue(ciphertextString.startsWith("-----BEGIN PGP MESSAGE-----\n"));
|
||||
|
||||
pipeBytesToStdin(ciphertext.toByteArray());
|
||||
ByteArrayOutputStream plaintext = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-password", passwordFile.getAbsolutePath()));
|
||||
|
||||
assertEquals(message, plaintext.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptWithIncapableCert() throws PGPException,
|
||||
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))
|
||||
.build();
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys);
|
||||
File certFile = writeFile("cert.pgp", cert.getEncoded());
|
||||
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", certFile.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
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))
|
||||
.build();
|
||||
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
|
||||
File certFile = writeFile("cert.pgp", PGPainless.extractCertificate(secretKeys).getEncoded());
|
||||
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
|
||||
certFile.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptDecryptRoundTripWithPasswordProtectedKey() throws IOException {
|
||||
// generate password protected key
|
||||
File passwordFile = writeFile("password", "fooBarBaz420");
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key",
|
||||
"--with-key-password", passwordFile.getAbsolutePath(),
|
||||
"Pascal Password <pascal@password.protected>"));
|
||||
|
||||
// extract cert
|
||||
File certFile = pipeStdoutToFile("cert.asc");
|
||||
pipeFileToStdin(keyFile);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
// encrypt and sign message
|
||||
String msg = "Hello, World!\n";
|
||||
pipeStringToStdin(msg);
|
||||
File encryptedFile = pipeStdoutToFile("msg.asc");
|
||||
assertSuccess(executeCommand("encrypt",
|
||||
"--sign-with", keyFile.getAbsolutePath(),
|
||||
"--with-key-password", passwordFile.getAbsolutePath(),
|
||||
"--no-armor",
|
||||
"--as", "binary",
|
||||
certFile.getAbsolutePath()));
|
||||
|
||||
// Decrypt
|
||||
File verificationsFile = nonExistentFile("verifications");
|
||||
pipeFileToStdin(encryptedFile);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt",
|
||||
"--verify-with", certFile.getAbsolutePath(),
|
||||
"--verifications-out", verificationsFile.getAbsolutePath(),
|
||||
"--with-key-password", passwordFile.getAbsolutePath(),
|
||||
keyFile.getAbsolutePath()));
|
||||
julietKeyFile.getAbsolutePath());
|
||||
msgAscIn.close();
|
||||
|
||||
assertEquals(msg, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptGarbageFails() throws IOException {
|
||||
File keyFile = writeFile("key.asc", KEY);
|
||||
pipeStringToStdin("Some Garbage!");
|
||||
int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptMessageWithWrongKeyFails() throws IOException {
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "Bob <bob@pgpainless.org>"));
|
||||
// message was *not* created with key above
|
||||
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);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptWithPasswordADecryptWithPasswordBFails() throws IOException {
|
||||
File password1 = writeFile("password1", "swordfish");
|
||||
File password2 = writeFile("password2", "orange");
|
||||
|
||||
pipeStringToStdin("Bonjour, le monde!\n");
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("encrypt", "--with-password", password1.getAbsolutePath()));
|
||||
|
||||
pipeBytesToStdin(ciphertextOut.toByteArray());
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", "--with-password", password2.getAbsolutePath());
|
||||
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptWithGarbageCertFails() throws IOException {
|
||||
File garbageCert = writeFile("cert.asc", "This is garbage!");
|
||||
|
||||
pipeStringToStdin("Hallo, Welt!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", garbageCert.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encrypt_signWithGarbageKeyFails() throws IOException {
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
File garbageKey = writeFile("key.asc", "This is not a key!");
|
||||
|
||||
pipeStringToStdin("Salut!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", "--sign-with", garbageKey.getAbsolutePath(),
|
||||
cert.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_withGarbageKeyFails() throws IOException {
|
||||
File key = writeFile("key.asc", "this is garbage");
|
||||
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);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", key.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_verifyWithGarbageCertFails() throws IOException {
|
||||
File key = writeFile("key.asc", KEY);
|
||||
File cert = writeFile("cert.asc", "now this is garbage");
|
||||
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-----";
|
||||
File verificationsFile = nonExistentFile("verifications");
|
||||
|
||||
pipeStringToStdin(ciphertext);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("decrypt", key.getAbsolutePath(),
|
||||
"--verify-with", cert.getAbsolutePath(),
|
||||
"--verifications-out", verificationsFile.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptWithProtectedKey_wrongPassphraseFails() throws IOException {
|
||||
File password = writeFile("passphrase1", "orange");
|
||||
File wrongPassword = writeFile("passphrase2", "blue");
|
||||
|
||||
File keyFile = pipeStdoutToFile("key.asc");
|
||||
assertSuccess(executeCommand("generate-key", "Pedro <pedro@pgpainless.org>",
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
File certFile = pipeStdoutToFile("cert.asc");
|
||||
pipeFileToStdin(keyFile);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
// Use no passphrase to unlock the key
|
||||
String msg = "Guten Tag, Welt!\n";
|
||||
pipeStringToStdin(msg);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
|
||||
certFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
// use wrong passphrase to unlock key when signing message
|
||||
pipeStringToStdin("Guten Tag, Welt!\n");
|
||||
out = pipeStdoutToStream();
|
||||
exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
|
||||
"--with-key-password", wrongPassword.getAbsolutePath(),
|
||||
certFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
// use correct passphrase and encrypt+sign message
|
||||
pipeStringToStdin("Guten Tag, Welt!\n");
|
||||
out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath(),
|
||||
certFile.getAbsolutePath()));
|
||||
String ciphertext = out.toString();
|
||||
|
||||
// Use no passphrase to decrypt key when decrypting
|
||||
pipeStringToStdin(ciphertext);
|
||||
out = pipeStdoutToStream();
|
||||
exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
// Use wrong passphrase to decrypt key when decrypting
|
||||
pipeStringToStdin(ciphertext);
|
||||
out = pipeStdoutToStream();
|
||||
exitCode = executeCommand("decrypt", "--with-key-password", wrongPassword.getAbsolutePath(),
|
||||
keyFile.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
// User correct passphrase to decrypt key when decrypting
|
||||
pipeStringToStdin(ciphertext);
|
||||
out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("decrypt", "--with-key-password", password.getAbsolutePath(),
|
||||
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());
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
System.setOut(originalSout);
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(tempDir.getAbsolutePath());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,466 +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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
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;
|
||||
|
||||
public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
||||
|
||||
public RoundTripInlineSignInlineVerifyCmdTest() {
|
||||
super(LoggerFactory.getLogger(RoundTripInlineSignInlineVerifyCmdTest.class));
|
||||
}
|
||||
|
||||
private static final String KEY_1_PASSWORD = "takeDemHobbits2Isengard";
|
||||
private static final String KEY_1 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" +
|
||||
"Comment: Legolas <legolas@fellowship.ring>\n" +
|
||||
"\n" +
|
||||
"lIYEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" +
|
||||
"2VpV1rf+CQMCVICwUO0SkvdgcPdvXO1cW4KIp6HCVVV6VgU5cvBlmrk9PNUQVBkb\n" +
|
||||
"6S7oXQu0CgGwJ+QdbooBQqOjMy2MDy+UXaURTaVyWcmetsZJZzD2wrQhTGVnb2xh\n" +
|
||||
"cyA8bGVnb2xhc0BmZWxsb3dzaGlwLnJpbmc+iI8EExYKAEEFAmNsCncJEJ/whzjf\n" +
|
||||
"wAIkFiEEWfTsfUqHPmlwKY/en/CHON/AAiQCngECmwEFFgIDAQAECwkIBwUVCgkI\n" +
|
||||
"CwKZAQAAE10BAN9tN4Le1p4giS6P/yFuKFlDBOeiq1S4EqwYG7qdcqemAP45O3w4\n" +
|
||||
"3sXliOJBGDR/l/lOMHdPcTOb7VRwWbpIqx8LBJyLBGNsCncSCisGAQQBl1UBBQEB\n" +
|
||||
"B0AMc+7s6uBqAQcDvfKkD5zYbmB9ZfwIjRWQq/XF+g8KQwMBCAf+CQMCVICwUO0S\n" +
|
||||
"kvdgHLmKhKW1xxCNZAqQcIHa9F/cqb6Sq/oVFHj2bEYzmGVvFCVUpP7KJWGTeFT+\n" +
|
||||
"BYK779quIqjxHOfzC3Jmo3BHkUPWYOa0rIh1BBgWCgAdBQJjbAp3Ap4BApsMBRYC\n" +
|
||||
"AwEABAsJCAcFFQoJCAsACgkQn/CHON/AAiRUewD9HtKrCUf3S1yR28emzITWPgJS\n" +
|
||||
"UA5mkzEMnYspV7zU4jgA/R6jj/5QqPszElCQNZGtvsDUwYo10iRlQkxPshcPNakJ\n" +
|
||||
"nIYEY2wKdxYJKwYBBAHaRw8BAQdAYxpRGib/f/tu65gbsV22nmysVVmVgiQuDxyH\n" +
|
||||
"rz7VCi/+CQMCVICwUO0SkvdgOYYbWltjQRDM3SW/Zw/DiZN9MYZYa0MTgs0SHoaM\n" +
|
||||
"5LU7jMxNmPR1UtSqEO36QqW91q4fpEkGrdWE4gwjm1bth8pyYKiSFojVBBgWCgB9\n" +
|
||||
"BQJjbAp3Ap4BApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCW\n" +
|
||||
"K491s9xIMHwKAQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJ\n" +
|
||||
"ouSKKuiH6Ck2OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZx\n" +
|
||||
"NU+t4wQlWOkSsjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfs\n" +
|
||||
"YgztJA1z/rmm3fmFgMMG\n" +
|
||||
"=daDH\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
private static final String CERT_1 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" +
|
||||
"Comment: Legolas <legolas@fellowship.ring>\n" +
|
||||
"\n" +
|
||||
"mDMEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" +
|
||||
"2VpV1re0IUxlZ29sYXMgPGxlZ29sYXNAZmVsbG93c2hpcC5yaW5nPoiPBBMWCgBB\n" +
|
||||
"BQJjbAp3CRCf8Ic438ACJBYhBFn07H1Khz5pcCmP3p/whzjfwAIkAp4BApsBBRYC\n" +
|
||||
"AwEABAsJCAcFFQoJCAsCmQEAABNdAQDfbTeC3taeIIkuj/8hbihZQwTnoqtUuBKs\n" +
|
||||
"GBu6nXKnpgD+OTt8ON7F5YjiQRg0f5f5TjB3T3Ezm+1UcFm6SKsfCwS4OARjbAp3\n" +
|
||||
"EgorBgEEAZdVAQUBAQdADHPu7OrgagEHA73ypA+c2G5gfWX8CI0VkKv1xfoPCkMD\n" +
|
||||
"AQgHiHUEGBYKAB0FAmNsCncCngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCf8Ic4\n" +
|
||||
"38ACJFR7AP0e0qsJR/dLXJHbx6bMhNY+AlJQDmaTMQydiylXvNTiOAD9HqOP/lCo\n" +
|
||||
"+zMSUJA1ka2+wNTBijXSJGVCTE+yFw81qQm4MwRjbAp3FgkrBgEEAdpHDwEBB0Bj\n" +
|
||||
"GlEaJv9/+27rmBuxXbaebKxVWZWCJC4PHIevPtUKL4jVBBgWCgB9BQJjbAp3Ap4B\n" +
|
||||
"ApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCWK491s9xIMHwK\n" +
|
||||
"AQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJouSKKuiH6Ck2\n" +
|
||||
"OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZxNU+t4wQlWOkS\n" +
|
||||
"sjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfsYgztJA1z/rmm\n" +
|
||||
"3fmFgMMG\n" +
|
||||
"=/lYl\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
private static final String CERT_1_SIGNING_KEY =
|
||||
"D8906FEB9842569834FEDA9E962B8F75B3DC4830 59F4EC7D4A873E6970298FDE9FF08738DFC00224";
|
||||
|
||||
private static final String KEY_2 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" +
|
||||
"Comment: Gollum <gollum@deep.cave>\n" +
|
||||
"\n" +
|
||||
"lFgEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" +
|
||||
"jpPdjaEAAP9Edg7yeIGEeNP0GrndUpNeZyFAXAlCHJObDbS80G6BBw9ktBlHb2xs\n" +
|
||||
"dW0gPGdvbGx1bUBkZWVwLmNhdmU+iI8EExYKAEEFAmNsCqYJECZWDSrlPbhvFiEE\n" +
|
||||
"rqD9LImdP8B3gV8AJlYNKuU9uG8CngECmwEFFgIDAQAECwkIBwUVCgkICwKZAQAA\n" +
|
||||
"KSkBAOMq6ymNH83E5CBA/mn3DYLhnujzC9cVf/iX2zrsdXMvAQCWdfFy/PlGhP3K\n" +
|
||||
"M+ej6WIRsx24Yy/NhNPcRJUzcv6dC5xdBGNsCqYSCisGAQQBl1UBBQEBB0DiN/5n\n" +
|
||||
"AFQafWjnSkKhctFCNkfVRrnAea/2T/D8fYWeYwMBCAcAAP9HbxOhwxqz8I+pwk3e\n" +
|
||||
"kZXNolWqagrYZkpNvqlBb/JJWBGViHUEGBYKAB0FAmNsCqYCngECmwwFFgIDAQAE\n" +
|
||||
"CwkIBwUVCgkICwAKCRAmVg0q5T24bw2EAP4pUHVA2pkVspzEttIaQxdoHcnbwjae\n" +
|
||||
"q12TmWqWDFFvwgD+O2EqHn0iXW49EOQrlP8g+bdWUlT0ZIW3C3Fv7nNA3AScWARj\n" +
|
||||
"bAqmFgkrBgEEAdpHDwEBB0BHsmdF1Q0aU3YRVDeXGb904Nb7H/cxcasDhcbu2FTo\n" +
|
||||
"HAAA/j1+WzozN/3lefo76eyENKkXl4f1rQlUreqytuaTsb0WEq6I1QQYFgoAfQUC\n" +
|
||||
"Y2wKpgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73T\n" +
|
||||
"bQGDFnN9OwD/QDDi1qq7DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwt\n" +
|
||||
"L1M49UzQS7OIGP12/9cT66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/Iit\n" +
|
||||
"ntMexrSK5jCd9JdCCNb2rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2\n" +
|
||||
"cjK8WE1pfIwQ0ArPCQ==\n" +
|
||||
"=SzrG\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
private static final String CERT_2 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" +
|
||||
"Comment: Gollum <gollum@deep.cave>\n" +
|
||||
"\n" +
|
||||
"mDMEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" +
|
||||
"jpPdjaG0GUdvbGx1bSA8Z29sbHVtQGRlZXAuY2F2ZT6IjwQTFgoAQQUCY2wKpgkQ\n" +
|
||||
"JlYNKuU9uG8WIQSuoP0siZ0/wHeBXwAmVg0q5T24bwKeAQKbAQUWAgMBAAQLCQgH\n" +
|
||||
"BRUKCQgLApkBAAApKQEA4yrrKY0fzcTkIED+afcNguGe6PML1xV/+JfbOux1cy8B\n" +
|
||||
"AJZ18XL8+UaE/coz56PpYhGzHbhjL82E09xElTNy/p0LuDgEY2wKphIKKwYBBAGX\n" +
|
||||
"VQEFAQEHQOI3/mcAVBp9aOdKQqFy0UI2R9VGucB5r/ZP8Px9hZ5jAwEIB4h1BBgW\n" +
|
||||
"CgAdBQJjbAqmAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQJlYNKuU9uG8NhAD+\n" +
|
||||
"KVB1QNqZFbKcxLbSGkMXaB3J28I2nqtdk5lqlgxRb8IA/jthKh59Il1uPRDkK5T/\n" +
|
||||
"IPm3VlJU9GSFtwtxb+5zQNwEuDMEY2wKphYJKwYBBAHaRw8BAQdAR7JnRdUNGlN2\n" +
|
||||
"EVQ3lxm/dODW+x/3MXGrA4XG7thU6ByI1QQYFgoAfQUCY2wKpgKeAQKbAgUWAgMB\n" +
|
||||
"AAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73TbQGDFnN9OwD/QDDi1qq7\n" +
|
||||
"DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwtL1M49UzQS7OIGP12/9cT\n" +
|
||||
"66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/IitntMexrSK5jCd9JdCCNb2\n" +
|
||||
"rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2cjK8WE1pfIwQ0ArPCQ==\n" +
|
||||
"=j1LR\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
private static final String CERT_2_SIGNING_KEY =
|
||||
"7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F";
|
||||
|
||||
private static final String MESSAGE = "One does not simply use OpenPGP!\n" +
|
||||
"\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);
|
||||
File password = writeFile("password", KEY_1_PASSWORD);
|
||||
|
||||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "clearsigned",
|
||||
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("\n-----BEGIN PGP SIGNATURE-----\n"));
|
||||
assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndVerifyCleartextSignedMessage() 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", "clearsigned",
|
||||
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, plaintextOut.toString());
|
||||
String verificationString = readStringFromFile(verifications);
|
||||
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);
|
||||
File key1 = writeFile("key1.asc", KEY_1);
|
||||
File key2 = writeFile("key2.asc", KEY_2);
|
||||
|
||||
pipeStringToStdin(MESSAGE);
|
||||
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-sign",
|
||||
"--as", "binary",
|
||||
"--no-armor",
|
||||
key2.getAbsolutePath(),
|
||||
"--with-key-password", key1Pass.getAbsolutePath(),
|
||||
key1.getAbsolutePath()));
|
||||
|
||||
assertFalse(ciphertextOut.toString().startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n"));
|
||||
byte[] unarmoredMessage = ciphertextOut.toByteArray();
|
||||
|
||||
File cert1 = writeFile("cert1.asc", CERT_1);
|
||||
File cert2 = writeFile("cert2.asc", CERT_2);
|
||||
File verificationFile = nonExistentFile("verifications");
|
||||
pipeBytesToStdin(unarmoredMessage);
|
||||
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-verify",
|
||||
"--verifications-out", verificationFile.getAbsolutePath(),
|
||||
cert1.getAbsolutePath(), cert2.getAbsolutePath()));
|
||||
|
||||
assertEquals(MESSAGE, plaintextOut.toString());
|
||||
String verification = readStringFromFile(verificationFile);
|
||||
assertTrue(verification.contains(CERT_1_SIGNING_KEY));
|
||||
assertTrue(verification.contains(CERT_2_SIGNING_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTextSignedMessageInlineDetachAndDetachedVerify() 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", "clearsigned",
|
||||
key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
File sigFile = nonExistentFile("sig.asc");
|
||||
pipeStringToStdin(ciphertextOut.toString());
|
||||
ByteArrayOutputStream msgOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("inline-detach",
|
||||
"--signatures-out", sigFile.getAbsolutePath()));
|
||||
assertEquals(MESSAGE, msgOut.toString());
|
||||
|
||||
File cert = writeFile("cert.asc", CERT_1);
|
||||
pipeStringToStdin(msgOut.toString());
|
||||
ByteArrayOutputStream verificationsOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", "--stacktrace",
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -5,331 +5,129 @@
|
|||
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.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
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.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
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.info.KeyRingInfo;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTCUtil;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
|
||||
public class RoundTripSignVerifyCmdTest extends CLITest {
|
||||
public class RoundTripSignVerifyCmdTest {
|
||||
|
||||
public RoundTripSignVerifyCmdTest() {
|
||||
super(LoggerFactory.getLogger(RoundTripSignVerifyCmdTest.class));
|
||||
}
|
||||
private static File tempDir;
|
||||
private static PrintStream originalSout;
|
||||
|
||||
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" +
|
||||
"Comment: Sigmund <sigmund@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"lFgEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" +
|
||||
"kedJ3ZUAAQCZy5p1cvQvRIWUopHwhnrD/oVAa1dNT/nA3cihQ5gkZBHPtCBTaWdt\n" +
|
||||
"dW5kIDxzaWdtdW5kQHBncGFpbmxlc3Mub3JnPoiPBBMWCgBBBQJja/OSCRAJmxG/\n" +
|
||||
"KWo3PhYhBJ2glCPJ+UukzKMJUQmbEb8pajc+Ap4BApsBBRYCAwEABAsJCAcFFQoJ\n" +
|
||||
"CAsCmQEAACM9AP9APloI2waD5gXsJqzenRVU4n/VmZUvcdUyhlbpab/0HQEAlaTw\n" +
|
||||
"ZvxVyaf8EMFSJOY+LcgacHaZDHRPA1nS3bIfKwycXQRja/OSEgorBgEEAZdVAQUB\n" +
|
||||
"AQdA1WL4QKgRxbvzW91ICM6PoICSNh2QHK6j0pIdN/cqXz0DAQgHAAD/bOk3WqbF\n" +
|
||||
"QAE8xxm0w/KDZzL1N0yPcBQ5z4XKmu77FCgQ04h1BBgWCgAdBQJja/OSAp4BApsM\n" +
|
||||
"BRYCAwEABAsJCAcFFQoJCAsACgkQCZsRvylqNz6rgQEAzoG6HnPCYi2i2c6/ufuy\n" +
|
||||
"pBkLby2u1JjD0CWSbrM4dZ0A/j/pI4a9b8LcrZcuY2QwHqsXPAJp8QtOOQN6gTvN\n" +
|
||||
"WcQNnFgEY2vzkhYJKwYBBAHaRw8BAQdAsxcDCvst/GbWxQvvOpChSvmbqWeuBgm3\n" +
|
||||
"1vRoujFVFcYAAP9Ww46yfWipb8OivTSX+PvgdUhEeVgxENpsyOQLLhQP/RFziNUE\n" +
|
||||
"GBYKAH0FAmNr85ICngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OS\n" +
|
||||
"AAoJENqfQTmGIR3GtsMBAL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855\n" +
|
||||
"AP4uAXDiaNxYTugqnG471jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOX\n" +
|
||||
"AP45LPV6I4+D3h8etdiEA2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoR\n" +
|
||||
"WxCFj+gDgqDNLzwoA4iNo1VMtQc=\n" +
|
||||
"=/Np6\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" +
|
||||
"Comment: Sigmund <sigmund@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"mDMEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" +
|
||||
"kedJ3ZW0IFNpZ211bmQgPHNpZ211bmRAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEF\n" +
|
||||
"AmNr85IJEAmbEb8pajc+FiEEnaCUI8n5S6TMowlRCZsRvylqNz4CngECmwEFFgID\n" +
|
||||
"AQAECwkIBwUVCgkICwKZAQAAIz0A/0A+WgjbBoPmBewmrN6dFVTif9WZlS9x1TKG\n" +
|
||||
"Vulpv/QdAQCVpPBm/FXJp/wQwVIk5j4tyBpwdpkMdE8DWdLdsh8rDLg4BGNr85IS\n" +
|
||||
"CisGAQQBl1UBBQEBB0DVYvhAqBHFu/Nb3UgIzo+ggJI2HZAcrqPSkh039ypfPQMB\n" +
|
||||
"CAeIdQQYFgoAHQUCY2vzkgKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJEAmbEb8p\n" +
|
||||
"ajc+q4EBAM6Buh5zwmItotnOv7n7sqQZC28trtSYw9Alkm6zOHWdAP4/6SOGvW/C\n" +
|
||||
"3K2XLmNkMB6rFzwCafELTjkDeoE7zVnEDbgzBGNr85IWCSsGAQQB2kcPAQEHQLMX\n" +
|
||||
"Awr7Lfxm1sUL7zqQoUr5m6lnrgYJt9b0aLoxVRXGiNUEGBYKAH0FAmNr85ICngEC\n" +
|
||||
"mwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OSAAoJENqfQTmGIR3GtsMB\n" +
|
||||
"AL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855AP4uAXDiaNxYTugqnG47\n" +
|
||||
"1jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOXAP45LPV6I4+D3h8etdiE\n" +
|
||||
"A2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoRWxCFj+gDgqDNLzwoA4iN\n" +
|
||||
"o1VMtQc=\n" +
|
||||
"=KuJ4\n" +
|
||||
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||
private static final String PLAINTEXT = "Hello, World!\n";
|
||||
private static final String BINARY_SIG = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"iHUEABYKACcFAmNr9BgJENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" +
|
||||
"AKocAP48P2C3TU33T3Zy73clw0eBa1oW9pwxTGuFxhgOBzmoSwEArj0781GlpTB0\n" +
|
||||
"Vnr2PjPYEqzB+ZuOzOnGhsVGob4c3Ao=\n" +
|
||||
"=VWAZ\n" +
|
||||
"-----END PGP SIGNATURE-----";
|
||||
private static final String BINARY_SIG_VERIFICATION =
|
||||
"2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:binary\n";
|
||||
private static final String TEXT_SIG = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"iHUEARYKACcFAmNr9E4JENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" +
|
||||
"AG+CAQD1B3GAAlyxahSiGhvJv7YAI1m6qGcI7dIXcV7FkAFPSgEAlZ0UpCC8oGR+\n" +
|
||||
"hi/mQlex4z0hDWSA4abAjclPTJ+qkAI=\n" +
|
||||
"=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);
|
||||
}
|
||||
@BeforeAll
|
||||
public static void prepare() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createArmoredSignature() throws IOException {
|
||||
File keyFile = writeFile("key.asc", KEY);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("sign", "--as", "text", keyFile.getAbsolutePath()));
|
||||
assertTrue(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n"));
|
||||
}
|
||||
@FailOnSystemExit
|
||||
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
originalSout = System.out;
|
||||
InputStream originalIn = System.in;
|
||||
|
||||
@Test
|
||||
public void createUnarmoredSignature() throws IOException {
|
||||
File keyFile = writeFile("key.asc", KEY);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("sign", "--no-armor", keyFile.getAbsolutePath()));
|
||||
assertFalse(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n"));
|
||||
}
|
||||
// Write alice key to disc
|
||||
File aliceKeyFile = new File(tempDir, "alice.key");
|
||||
assertTrue(aliceKeyFile.createNewFile());
|
||||
PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice");
|
||||
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut);
|
||||
aliceKeyOut.close();
|
||||
|
||||
@Test
|
||||
public void unarmorArmoredSigAndVerify() throws IOException {
|
||||
File certFile = writeFile("cert.asc", CERT);
|
||||
// Write alice pub key to disc
|
||||
File aliceCertFile = new File(tempDir, "alice.pub");
|
||||
assertTrue(aliceCertFile.createNewFile());
|
||||
PGPPublicKeyRing alicePub = KeyRingUtils.publicKeyRingFrom(aliceKeys);
|
||||
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(alicePub.getEncoded()), aliceCertOut);
|
||||
aliceCertOut.close();
|
||||
|
||||
pipeStringToStdin(BINARY_SIG);
|
||||
File unarmoredSigFile = pipeStdoutToFile("sig.pgp");
|
||||
assertSuccess(executeCommand("dearmor"));
|
||||
// Write test data to disc
|
||||
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();
|
||||
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", unarmoredSigFile.getAbsolutePath(), certFile.getAbsolutePath()));
|
||||
// Define micalg output file
|
||||
File micalgOut = new File(tempDir, "micalg");
|
||||
|
||||
assertEquals(BINARY_SIG_VERIFICATION, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotBefore() throws IOException {
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
File sigFile = writeFile("sig.asc", TEXT_SIG);
|
||||
Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60);
|
||||
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
|
||||
"--not-before", UTCUtil.formatUTCDate(plus1Minute));
|
||||
|
||||
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
out = pipeStdoutToStream();
|
||||
exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
|
||||
"--not-before", UTCUtil.formatUTCDate(minus1Minute));
|
||||
|
||||
assertSuccess(exitCode);
|
||||
assertEquals(TEXT_SIG_VERIFICATION, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotAfter() throws IOException {
|
||||
File cert = writeFile("cert.asc", CERT);
|
||||
File sigFile = writeFile("sig.asc", TEXT_SIG);
|
||||
|
||||
Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
|
||||
"--not-after", UTCUtil.formatUTCDate(minus1Minute));
|
||||
|
||||
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
|
||||
Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
out = pipeStdoutToStream();
|
||||
exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
|
||||
"--not-after", UTCUtil.formatUTCDate(plus1Minute));
|
||||
|
||||
assertSuccess(exitCode);
|
||||
assertEquals(TEXT_SIG_VERIFICATION, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
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))
|
||||
.build();
|
||||
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
|
||||
|
||||
pipeStringToStdin("Hello, World!\n");
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("sign", keyFile.getAbsolutePath());
|
||||
|
||||
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureCreationAndVerification()
|
||||
throws IOException {
|
||||
// Create key and cert
|
||||
File aliceKeyFile = pipeStdoutToFile("alice.key");
|
||||
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
|
||||
File aliceCertFile = pipeStdoutToFile("alice.cert");
|
||||
pipeFileToStdin(aliceKeyFile);
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
File micalgOut = nonExistentFile("micalg");
|
||||
String msg = "If privacy is outlawed, only outlaws will have privacy.\n";
|
||||
File dataFile = writeFile("data", msg);
|
||||
|
||||
// sign data
|
||||
File sigFile = pipeStdoutToFile("sig.asc");
|
||||
pipeFileToStdin(dataFile);
|
||||
assertSuccess(executeCommand("sign",
|
||||
"--armor",
|
||||
"--as", "binary",
|
||||
"--micalg-out", micalgOut.getAbsolutePath(),
|
||||
aliceKeyFile.getAbsolutePath()));
|
||||
// Sign test data
|
||||
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.execute("sign", "--armor", "--micalg-out", micalgOut.getAbsolutePath(), aliceKeyFile.getAbsolutePath());
|
||||
sigOut.close();
|
||||
|
||||
// verify test data signature
|
||||
pipeFileToStdin(dataFile);
|
||||
ByteArrayOutputStream verificationsOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath()));
|
||||
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(verifyOut));
|
||||
dataIn = new FileInputStream(dataFile);
|
||||
System.setIn(dataIn);
|
||||
PGPainlessCLI.execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
||||
dataIn.close();
|
||||
|
||||
// Test verification output
|
||||
|
||||
PGPPublicKeyRing cert = PGPainless.readKeyRing().publicKeyRing(readBytesFromFile(aliceCertFile));
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(cert);
|
||||
|
||||
// [date] [signing-key-fp] [primary-key-fp] signed by [key.pub]
|
||||
String verification = verificationsOut.toString();
|
||||
String verification = verifyOut.toString();
|
||||
String[] split = verification.split(" ");
|
||||
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(cert);
|
||||
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(info.getSigningSubkeys().get(0));
|
||||
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
|
||||
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
|
||||
assertEquals(signingKeyFingerprint.toString(), split[1].trim(), verification);
|
||||
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
|
||||
|
||||
// Test micalg output
|
||||
String content = readStringFromFile(micalgOut);
|
||||
assertEquals("pgp-sha512", content);
|
||||
assertTrue(micalgOut.exists());
|
||||
FileReader fileReader = new FileReader(micalgOut);
|
||||
BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||
String line = bufferedReader.readLine();
|
||||
assertNull(bufferedReader.readLine());
|
||||
bufferedReader.close();
|
||||
assertEquals("pgp-sha512", line);
|
||||
|
||||
System.setIn(originalIn);
|
||||
}
|
||||
|
||||
private static final String PROTECTED_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 738E EAB2 503D 322D 613A C42A B18E 8BF8 884F C050\n" +
|
||||
"Comment: Axel <axel@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"lIYEY2v6aRYJKwYBBAHaRw8BAQdA3PXtH19zYpVQ9zTU3zlY+iXUptelAO3z4vK/\n" +
|
||||
"M2FkmrP+CQMCYgVa6K+InVJguITSDIA+HQ6vhOZ5Dbanqx7GFbJbJLD2fWrxhTSr\n" +
|
||||
"BUWGaUWTqN647auD/kUI8phH1cedVL6CzVR+YWvaWj9zZHr/CYXLobQaQXhlbCA8\n" +
|
||||
"YXhlbEBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2v6aQkQsY6L+IhPwFAWIQRz\n" +
|
||||
"juqyUD0yLWE6xCqxjov4iE/AUAKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAACq\n" +
|
||||
"zgEAkxB+dUI7Jjcg5zRvT1EfE9DKCI1qTsxOAU/ZXLcSXLkBAJtWRsyptetZvjzB\n" +
|
||||
"Ze2A7ArOl4q+IvKvun/d783YyRMInIsEY2v6aRIKKwYBBAGXVQEFAQEHQPFmlZ+o\n" +
|
||||
"jCGEo2X0474vJfRG7blctuZXmCbC0sLO7MgzAwEIB/4JAwJiBVror4idUmDFhBq4\n" +
|
||||
"lEhJxjCVc6aSD6+EWRT3YdplqCmNdynnrPombUFst6LfJFzns3H3d0rCeXHfQr93\n" +
|
||||
"GrHTLkHfW8G3x0PJJPiqFkBviHUEGBYKAB0FAmNr+mkCngECmwwFFgIDAQAECwkI\n" +
|
||||
"BwUVCgkICwAKCRCxjov4iE/AUNC2AP9WDx4lHt9oYFLSrM8vMLRFI31U8TkYrtCe\n" +
|
||||
"pYICE76cIAEA5+wEbtE5vQrLxOqIRueVVdzwK9kTeMvSIQfc9PNoyQKchgRja/pp\n" +
|
||||
"FgkrBgEEAdpHDwEBB0CyAEVlCUbFr3dBBG3MQ84hjCPfYqSx9kYsTN8j5Og6uP4J\n" +
|
||||
"AwJiBVror4idUmCIFuAYXia0YpEhEpB/Lrn/D6/WAUPEgZjNLMvJzL//EmhkWfEa\n" +
|
||||
"OfQz/fslj1erWNjLKNiW5C/TvGapDfjbn596AkNlcd1JiNUEGBYKAH0FAmNr+mkC\n" +
|
||||
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/ppAAoJELRgil1uCuQj\n" +
|
||||
"VUYBAJecbedwwqWQITVqucEBIraTRoc6ZGkN8jytDp8z9CsBAQDrb/W/J/kze6ln\n" +
|
||||
"nRyJSriWF3SjcKOGIRkUslmdJEPPCQAKCRCxjov4iE/AUAvbAQDBBgQFG8acTT5L\n" +
|
||||
"cyIi1Ix9/XBG7G23SSs6l7Beap8M+wEAmK13NYuq7Mv/mct8iIKZbBFH9aAiY+nX\n" +
|
||||
"3Uct4Q5f0w0=\n" +
|
||||
"=K65R\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
private static final String PASSPHRASE = "orange";
|
||||
private static final String SIGNING_KEY = "9846F3606EE875FB77EC8808B4608A5D6E0AE423 738EEAB2503D322D613AC42AB18E8BF8884FC050";
|
||||
|
||||
@Test
|
||||
public void signWithProtectedKey_missingPassphraseFails() throws IOException {
|
||||
File key = writeFile("key.asc", PROTECTED_KEY);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("sign", key.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
System.setOut(originalSout);
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(tempDir.getAbsolutePath());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signWithProtectedKey_wrongPassphraseFails() throws IOException {
|
||||
File password = writeFile("password", "blue");
|
||||
File key = writeFile("key.asc", PROTECTED_KEY);
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
int exitCode = executeCommand("sign", key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath());
|
||||
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signWithProtectedKey() throws IOException {
|
||||
File password = writeFile("password", PASSPHRASE);
|
||||
File key = writeFile("key.asc", PROTECTED_KEY);
|
||||
pipeStringToStdin(PROTECTED_KEY);
|
||||
File cert = pipeStdoutToFile("cert.asc");
|
||||
assertSuccess(executeCommand("extract-cert"));
|
||||
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
File sigFile = pipeStdoutToFile("sig.asc");
|
||||
assertSuccess(executeCommand("sign", key.getAbsolutePath(),
|
||||
"--with-key-password", password.getAbsolutePath()));
|
||||
|
||||
pipeStringToStdin(PLAINTEXT);
|
||||
ByteArrayOutputStream verificationOut = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath()));
|
||||
assertTrue(verificationOut.toString().contains(SIGNING_KEY));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class VersionCmdTest extends CLITest {
|
||||
|
||||
public VersionCmdTest() {
|
||||
super(LoggerFactory.getLogger(VersionCmdTest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersion() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("version"));
|
||||
assertTrue(out.toString().startsWith("PGPainless-SOP "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBackendVersion() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("version", "--backend"));
|
||||
assertTrue(out.toString().startsWith("PGPainless "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedVersion() throws IOException {
|
||||
ByteArrayOutputStream out = pipeStdoutToStream();
|
||||
assertSuccess(executeCommand("version", "--extended"));
|
||||
String info = out.toString();
|
||||
assertTrue(info.startsWith("PGPainless-SOP "));
|
||||
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,9 @@ 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(files("../libs/bcpg-jdk18on-1.70.jar"))
|
||||
api "org.bouncycastle:bcprov-jdk15to18:$bouncyCastleVersion"
|
||||
api "org.bouncycastle:bcpg-jdk15to18:$bouncyCastleVersion"
|
||||
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Utility classes related to creating keys with GNU DUMMY S2K values.
|
||||
*/
|
||||
package org.gnupg;
|
176
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
176
pgpainless-core/src/main/java/org/pgpainless/PGPainless.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
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 org.bouncycastle.bcpg.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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 key ring.
|
||||
* This method can be used to change key expiration dates and passphrases, or add/remove/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 new SecretKeyRingEditor(secretKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 inspectionDate date of inspection
|
||||
* @return access object
|
||||
*/
|
||||
public static KeyRingInfo inspectKeyRing(PGPKeyRing keyRing, Date inspectionDate) {
|
||||
return new KeyRingInfo(keyRing, inspectionDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,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,141 @@
|
|||
// 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 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!!!
|
||||
*
|
||||
* @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!!!
|
||||
*
|
||||
* @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)
|
||||
;
|
||||
|
||||
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,116 @@
|
|||
// 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"),
|
||||
;
|
||||
|
||||
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,23 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm;
|
||||
|
||||
public enum RevocationState {
|
||||
|
||||
/**
|
||||
* 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,37 @@
|
|||
// 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;
|
||||
|
||||
public interface HashAlgorithmNegotiator {
|
||||
|
||||
HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> orderedHashAlgorithmPreferencesSet);
|
||||
|
||||
static HashAlgorithmNegotiator negotiateSignatureHashAlgorithm(Policy policy) {
|
||||
return negotiateByPolicy(policy.getSignatureHashAlgorithmPolicy());
|
||||
}
|
||||
|
||||
static HashAlgorithmNegotiator negotiateRevocationSignatureAlgorithm(Policy policy) {
|
||||
return negotiateByPolicy(policy.getRevocationSignatureHashAlgorithmPolicy());
|
||||
}
|
||||
|
||||
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,88 @@
|
|||
// 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.
|
||||
*
|
||||
* @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);
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
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