diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ba2e0ff --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: singpolyma +liberapay: singpolyma +patreon: singpolyma diff --git a/.gitignore b/.gitignore index e48cc98..695f336 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ .tmp pkg tmp +composer.lock +.phpunit.result.cache +.idea +vendor/ diff --git a/.travis.dhall b/.travis.dhall new file mode 100644 index 0000000..0e098bf --- /dev/null +++ b/.travis.dhall @@ -0,0 +1,32 @@ +let Prelude = https://prelude.dhall-lang.org/v17.0.0/package.dhall +let phpseclib = \(max: Natural) -> \(filter: (Natural -> Bool)) -> + Prelude.List.map Natural Text + (\(m: Natural) -> "PHPSECLIB='2.0.${Prelude.Natural.show m}'") + (Prelude.List.filter Natural filter (Prelude.Natural.enumerate max)) +let Exclusion = { php: Text, env: Text } +in +{ + language = "php", + php = [ + "7.3", + "7.4", + "8.0", + "8.1", + "8.2" + ], + dist = "xenial", + env = [ + "PHPSECLIB='^2.0 !=2.0.8'" + ] # (phpseclib 28 (\(m: Natural) -> Prelude.Bool.not (Prelude.Natural.equal m 8)) + ), + matrix = { + exclude = Prelude.List.concatMap Text Exclusion (\(php: Text) -> + Prelude.List.map Text Exclusion (\(env: Text) -> + { php = php, env = env } + ) (phpseclib 7 (\(_: Natural) -> True)) + ) ["7.3", "7.4", "8.0", "8.1", "8.2"], + fast_finish = True + }, + before_script = '' + sed -i "s/\"phpseclib\/phpseclib\": \"[^\"]*/\"phpseclib\/phpseclib\": \"$PHPSECLIB/" composer.json && composer install --prefer-source'' +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6a95ca0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,112 @@ +# Code generated by dhall-to-yaml. DO NOT EDIT. +before_script: "sed -i \"s/\\\"phpseclib\\/phpseclib\\\": \\\"[^\\\"]*/\\\"phpseclib\\/phpseclib\\\": \\\"$PHPSECLIB/\" composer.json && composer install --prefer-source" +dist: xenial +env: + - "PHPSECLIB='^2.0 !=2.0.8'" + - "PHPSECLIB='2.0.0'" + - "PHPSECLIB='2.0.1'" + - "PHPSECLIB='2.0.2'" + - "PHPSECLIB='2.0.3'" + - "PHPSECLIB='2.0.4'" + - "PHPSECLIB='2.0.5'" + - "PHPSECLIB='2.0.6'" + - "PHPSECLIB='2.0.7'" + - "PHPSECLIB='2.0.9'" + - "PHPSECLIB='2.0.10'" + - "PHPSECLIB='2.0.11'" + - "PHPSECLIB='2.0.12'" + - "PHPSECLIB='2.0.13'" + - "PHPSECLIB='2.0.14'" + - "PHPSECLIB='2.0.15'" + - "PHPSECLIB='2.0.16'" + - "PHPSECLIB='2.0.17'" + - "PHPSECLIB='2.0.18'" + - "PHPSECLIB='2.0.19'" + - "PHPSECLIB='2.0.20'" + - "PHPSECLIB='2.0.21'" + - "PHPSECLIB='2.0.22'" + - "PHPSECLIB='2.0.23'" + - "PHPSECLIB='2.0.24'" + - "PHPSECLIB='2.0.25'" + - "PHPSECLIB='2.0.26'" + - "PHPSECLIB='2.0.27'" +language: php +matrix: + exclude: + - env: "PHPSECLIB='2.0.0'" + php: '7.3' + - env: "PHPSECLIB='2.0.1'" + php: '7.3' + - env: "PHPSECLIB='2.0.2'" + php: '7.3' + - env: "PHPSECLIB='2.0.3'" + php: '7.3' + - env: "PHPSECLIB='2.0.4'" + php: '7.3' + - env: "PHPSECLIB='2.0.5'" + php: '7.3' + - env: "PHPSECLIB='2.0.6'" + php: '7.3' + - env: "PHPSECLIB='2.0.0'" + php: '7.4' + - env: "PHPSECLIB='2.0.1'" + php: '7.4' + - env: "PHPSECLIB='2.0.2'" + php: '7.4' + - env: "PHPSECLIB='2.0.3'" + php: '7.4' + - env: "PHPSECLIB='2.0.4'" + php: '7.4' + - env: "PHPSECLIB='2.0.5'" + php: '7.4' + - env: "PHPSECLIB='2.0.6'" + php: '7.4' + - env: "PHPSECLIB='2.0.0'" + php: '8.0' + - env: "PHPSECLIB='2.0.1'" + php: '8.0' + - env: "PHPSECLIB='2.0.2'" + php: '8.0' + - env: "PHPSECLIB='2.0.3'" + php: '8.0' + - env: "PHPSECLIB='2.0.4'" + php: '8.0' + - env: "PHPSECLIB='2.0.5'" + php: '8.0' + - env: "PHPSECLIB='2.0.6'" + php: '8.0' + - env: "PHPSECLIB='2.0.0'" + php: '8.1' + - env: "PHPSECLIB='2.0.1'" + php: '8.1' + - env: "PHPSECLIB='2.0.2'" + php: '8.1' + - env: "PHPSECLIB='2.0.3'" + php: '8.1' + - env: "PHPSECLIB='2.0.4'" + php: '8.1' + - env: "PHPSECLIB='2.0.5'" + php: '8.1' + - env: "PHPSECLIB='2.0.6'" + php: '8.1' + - env: "PHPSECLIB='2.0.0'" + php: '8.2' + - env: "PHPSECLIB='2.0.1'" + php: '8.2' + - env: "PHPSECLIB='2.0.2'" + php: '8.2' + - env: "PHPSECLIB='2.0.3'" + php: '8.2' + - env: "PHPSECLIB='2.0.4'" + php: '8.2' + - env: "PHPSECLIB='2.0.5'" + php: '8.2' + - env: "PHPSECLIB='2.0.6'" + php: '8.2' + fast_finish: true +php: + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..a3f7542 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1890 @@ +# Doxyfile 1.8.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed +# in front of the TAG it is preceding . +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "OpenPGP PHP" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Latvian, Lithuanian, Norwegian, Macedonian, +# Persian, Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, +# Slovak, Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES (the +# default) will make doxygen replace the get and set methods by a property in +# the documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields or simple typedef fields will be shown +# inline in the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO (the default), structs, classes, and unions are shown on a separate +# page (for HTML and Man pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can +# be an expensive process and often the same symbol appear multiple times in +# the code, doxygen keeps a cache of pre-resolved symbols. If the cache is too +# small doxygen will become slower. If the cache is too large, memory is wasted. +# The cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid +# range is 0..9, the default is 0, corresponding to a cache size of 2^16 = 65536 +# symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if section-label ... \endif +# and \cond section-label ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = "git log --pretty=\"format:%ci, author:%aN <%aE>, commit:%h\" -1" + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. Do not use +# file names with spaces, bibtex cannot handle them. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = lib/ README.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = examples/ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be ignored. +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and +# SVG. The default value is HTML-CSS, which is slower, but has the best +# compatibility. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript +# pieces of code that will be used on startup of the MathJax code. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. +# There are two flavours of web server based search depending on the +# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for +# searching and an index file used by the script. When EXTERNAL_SEARCH is +# enabled the indexing and searching needs to be provided by external tools. +# See the manual for details. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain +# the search results. Doxygen ships with an example indexer (doxyindexer) and +# search engine (doxysearch.cgi) which are based on the open source search +# engine library Xapian. See the manual for configuration details. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will returned the search results when EXTERNAL_SEARCH is enabled. +# Doxygen ships with an example search engine (doxysearch) which is based on +# the open source search engine library Xapian. See the manual for configuration +# details. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id +# of to a relative location where the documentation can be found. +# The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ... + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4 will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images +# or other source files which should be copied to the LaTeX output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES Doxygen will generate DOCBOOK files +# that can be used to generate PDF. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the DOCBOOK pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. If left blank docbook will be used as the default path. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed +# in the related pages index. If set to NO, only the current project's +# pages will be listed. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# manageable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/README.md b/README.md index 64ec4e7..3e164e9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ +[![Build Status](https://travis-ci.org/singpolyma/openpgp-php.svg?branch=master)](https://travis-ci.org/singpolyma/openpgp-php) + OpenPGP.php: OpenPGP for PHP ============================ This is a pure-PHP implementation of the OpenPGP Message Format (RFC 4880). -* +* -### About OpenPGP +About OpenPGP +------------- OpenPGP is the most widely-used e-mail encryption standard in the world. It is defined by the OpenPGP Working Group of the Internet Engineering Task @@ -13,8 +16,8 @@ Force (IETF) Proposed Standard RFC 4880. The OpenPGP standard was originally derived from PGP (Pretty Good Privacy), first created by Phil Zimmermann in 1991. -* -* +* +* Features -------- @@ -22,34 +25,49 @@ Features * Encodes and decodes ASCII-armored OpenPGP messages. * Parses OpenPGP messages into their constituent packets. * Supports both old-format (PGP 2.6.x) and new-format (RFC 4880) packets. +* Helper class for verifying, signing, encrypting, and decrypting messages +* Helper class for encrypting and decrypting messages and keys using + * openssl or mcrypt required for CAST5 encryption and decryption + +Bugs, Feature Requests, Patches +------------------------------- + +This project is primarily maintained by a single volunteer with many other +things vying for their attention, please be patient. + +Bugs, feature request, pull requests, patches, and general discussion may +be submitted publicly via email to: dev@singpolyma.net + +Github users may alternately submit on the web there. Users ----- OpenPGP.php is currently being used in the following projects: -* +* +* [Passbolt API](https://github.com/passbolt/passbolt_api) Download -------- To get a local working copy of the development repository, do: - % git clone git://github.com/bendiken/openpgp-php.git + git clone https://github.com/singpolyma/openpgp-php.git Alternatively, you can download the latest development version as a tarball as follows: - % wget http://github.com/bendiken/openpgp-php/tarball/master + wget https://github.com/singpolyma/openpgp-php/tarball/master Authors ------- -* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - -* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) - +* [Arto Bendiken](mailto:arto.bendiken@gmail.com) (Original author) - +* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) (Maintainer) - License ------- OpenPGP.php is free and unencumbered public domain software. For more -information, see or the accompanying UNLICENSE file. +information, see or the accompanying UNLICENSE file. diff --git a/VERSION b/VERSION index 8acdd82..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.1 +0.3.0 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c7c1011 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "singpolyma/openpgp-php", + "description": "Pure-PHP implementation of the OpenPGP Message Format (RFC 4880)", + "license": "Unlicense", + "authors": [ + { + "name": "Arto Bendiken", + "email": "arto.bendiken@gmail.com" + }, + { + "name": "Stephen Paul Weber", + "email": "singpolyma@singpolyma.net" + } + ], + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "phpseclib/phpseclib": "^3.0.14" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-mcrypt": "required if you use encryption cast5", + "ext-openssl": "required if you use encryption cast5" + }, + "autoload": { + "classmap": ["lib/"] + } +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..3f3c3b3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +OpenPGP.php Examples +==================== + +The scripts in this folder show how to use this library to perform various tasks +such as [generating a new key](keygen.php), [signing a message](sign.php), and +[verifying a message](verify.php) that has been signed. + +To use these examples, make sure [`phpseclib`](http://phpseclib.sourceforge.net/) is available. You can install it +using [Composer](https://getcomposer.org/): + +```sh +git clone https://github.com/singpolyma/openpgp-php.git # Clone the repository. +cd openpgp-php +composer install # Use Composer to install the requirements. +``` + +Once Composer has installed the requirements, run the examples using PHP: + +```sh +# Generate a new OpenPGP key; see the `keygen.php` file for parameters. +php ./examples/keygen.php > mykey.gpg +``` diff --git a/examples/armorEncryptSignCompress.php b/examples/armorEncryptSignCompress.php new file mode 100644 index 0000000..59d5af4 --- /dev/null +++ b/examples/armorEncryptSignCompress.php @@ -0,0 +1,21 @@ + 'u']); +$signed = $signer->sign($data); + +$compressed = new OpenPGP_CompressedDataPacket($signed); +$encrypted = OpenPGP_Crypt_Symmetric::encrypt([$recipientPublicKey, $key], new OpenPGP_Message([$compressed])); + +echo OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE'); + + diff --git a/examples/clearsign.php b/examples/clearsign.php new file mode 100644 index 0000000..1445fa7 --- /dev/null +++ b/examples/clearsign.php @@ -0,0 +1,30 @@ + 'u', 'filename' => 'stuff.txt')); +$data->normalize(true); // Clearsign-style normalization of the LiteralDataPacket + +/* Create a signer from the key */ +$sign = new OpenPGP_Crypt_RSA($wkey); + +/* The message is the signed data packet */ +$m = $sign->sign($data); + +/* Generate clearsigned data */ +$packets = $m->signatures()[0]; +echo "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n"; +// Output normalised data. You could convert line endings here +// without breaking the signature, but do not add any +// trailing whitespace to lines. +echo preg_replace("/^-/", "- -", $packets[0]->data)."\n"; +echo OpenPGP::enarmor($packets[1][0]->to_bytes(), "PGP SIGNATURE"); diff --git a/examples/deASCIIdeCrypt.php b/examples/deASCIIdeCrypt.php new file mode 100644 index 0000000..0326ba6 --- /dev/null +++ b/examples/deASCIIdeCrypt.php @@ -0,0 +1,28 @@ +decrypt($msg); + + var_dump($decrypted); +} diff --git a/examples/encryptDecrypt.php b/examples/encryptDecrypt.php new file mode 100644 index 0000000..b9c1a52 --- /dev/null +++ b/examples/encryptDecrypt.php @@ -0,0 +1,16 @@ + 'u', 'filename' => 'stuff.txt')); +$encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data))); + +// Now decrypt it with the same key +$decryptor = new OpenPGP_Crypt_RSA($key); +$decrypted = $decryptor->decrypt($encrypted); + +var_dump($decrypted); diff --git a/examples/keygen.php b/examples/keygen.php new file mode 100644 index 0000000..4741ce1 --- /dev/null +++ b/examples/keygen.php @@ -0,0 +1,36 @@ +getPublicKey(); + +$privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1')); + +$nkey = new OpenPGP_SecretKeyPacket(array( + 'n' => $privateKeyComponents["modulus"]->toBytes(), + 'e' => $privateKeyComponents["publicExponent"]->toBytes(), + 'd' => $privateKeyComponents["privateExponent"]->toBytes(), + 'p' => $privateKeyComponents["primes"][1]->toBytes(), + 'q' => $privateKeyComponents["primes"][2]->toBytes(), + 'u' => $privateKeyComponents["coefficients"][2]->toBytes() +)); + +$uid = new OpenPGP_UserIDPacket('Test '); + +$wkey = new OpenPGP_Crypt_RSA($nkey); +$m = $wkey->sign_key_userid(array($nkey, $uid)); + +// Serialize private key +print $m->to_bytes(); + +// Serialize public key message +$pubm = clone($m); +$pubm[0] = new OpenPGP_PublicKeyPacket($pubm[0]); + +$public_bytes = $pubm->to_bytes(); diff --git a/examples/keygenEncrypted.php b/examples/keygenEncrypted.php new file mode 100644 index 0000000..d560ee0 --- /dev/null +++ b/examples/keygenEncrypted.php @@ -0,0 +1,32 @@ +getPublicKey(); + +$privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1')); + +$nkey = new OpenPGP_SecretKeyPacket(array( + 'n' => $privateKeyComponents["modulus"]->toBytes(), + 'e' => $privateKeyComponents["publicExponent"]->toBytes(), + 'd' => $privateKeyComponents["privateExponent"]->toBytes(), + 'p' => $privateKeyComponents["primes"][1]->toBytes(), + 'q' => $privateKeyComponents["primes"][2]->toBytes(), + 'u' => $privateKeyComponents["coefficients"][2]->toBytes() +)); + +$uid = new OpenPGP_UserIDPacket('Test '); + +$wkey = new OpenPGP_Crypt_RSA($nkey); +$m = $wkey->sign_key_userid(array($nkey, $uid)); +$m[0] = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $nkey); + +// Serialize encrypted private key +print $m->to_bytes(); diff --git a/examples/keygenSubkeys.php b/examples/keygenSubkeys.php new file mode 100644 index 0000000..bdf6f36 --- /dev/null +++ b/examples/keygenSubkeys.php @@ -0,0 +1,118 @@ +toString('PKCS1')); + +$nkey = new OpenPGP_SecretKeyPacket(array( + 'n' => $privateKeyComponents["modulus"]->toBytes(), + 'e' => $privateKeyComponents["publicExponent"]->toBytes(), + 'd' => $privateKeyComponents["privateExponent"]->toBytes(), + 'p' => $privateKeyComponents["primes"][1]->toBytes(), + 'q' => $privateKeyComponents["primes"][2]->toBytes(), + 'u' => $privateKeyComponents["coefficients"][2]->toBytes() +)); + +// Start assembling packets for our eventual OpenPGP_Message +$packets = array($nkey); + +$wkey = new OpenPGP_Crypt_RSA($nkey); +$fingerprint = $wkey->key()->fingerprint; +$key = $wkey->private_key(); +$key = $key->withHash('sha256'); +$keyid = substr($fingerprint, -16); + +// Add multiple UID packets and signatures + +$uids = array( + new OpenPGP_UserIDPacket('Support', '', 'support@example.com'), + new OpenPGP_UserIDPacket('Security', '', 'security@example.com'), +); + +foreach($uids as $uid) { + // Append the UID packet + $packets[] = $uid; + + $sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($nkey, $uid)), 'RSA', 'SHA256'); + $sig->signature_type = 0x13; + $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02)); // Certify + sign bits + $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); + $m = $wkey->sign_key_userid(array($nkey, $uid, $sig)); + + // Append the UID signature from the master key + $packets[] = $m->packets[2]; +} + +// Generate an encryption subkey + +$rsa_subkey = RSA::createKey(512); +$privateKeyComponents = PKCS1::load($rsa_subkey->toString('PKCS1')); + +$subkey = new OpenPGP_SecretKeyPacket(array( + 'n' => $privateKeyComponents["modulus"]->toBytes(), + 'e' => $privateKeyComponents["publicExponent"]->toBytes(), + 'd' => $privateKeyComponents["privateExponent"]->toBytes(), + 'p' => $privateKeyComponents["primes"][2]->toBytes(), + 'q' => $privateKeyComponents["primes"][1]->toBytes(), + 'u' => $privateKeyComponents["coefficients"][2]->toBytes() +)); + +// Append the encryption subkey +$packets[] = $subkey; + +$sub_wkey = new OpenPGP_Crypt_RSA($subkey); + +/* + * Sign the encryption subkey with the master key + * + * OpenPGP_SignaturePacket assumes any message starting with an + * OpenPGP_PublicKeyPacket is followed by a OpenPGP_UserIDPacket. We need + * to pass `null` in the constructor and generate the `->data` ourselves. + */ +$sub_sig = new OpenPGP_SignaturePacket(null, 'RSA', 'SHA256'); +$sub_sig->signature_type = 0x18; +$sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time()); +$sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x0C)); // Encrypt bits +$sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); +$sub_sig->data = implode('', $nkey->fingerprint_material()) . implode('', $subkey->fingerprint_material()); +$sub_sig->sign_data(array('RSA' => array('SHA256' => function($data) use($key) { + return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; +}))); + +// Append the subkey signature +$packets[] = $sub_sig; + +// Build the OpenPGP_Message for the secret key from our packets +$m = new OpenPGP_Message($packets); + +// Serialize the private key +print $m->to_bytes(); + +// Clone a public key message from the secret key +$pubm = clone($m); + +// Convert the private key packets to public so we only export public data +// (n+e in RSA) +foreach($pubm as $idx => $p) { + if($p instanceof OpenPGP_SecretSubkeyPacket) { + $pubm[$idx] = new OpenPGP_PublicSubkeyPacket($p); + } else if($p instanceof OpenPGP_SecretKeyPacket) { + $pubm[$idx] = new OpenPGP_PublicKeyPacket($p); + } +} + +// Serialize the public key +$public_bytes = $pubm->to_bytes(); + +// Note: If using PHP 7.4 CLI, disable deprecated warnings: +// php -d error_reporting="E_ALL & ~E_DEPRECATED" examples/keygenSubkeys.php > mykey.gpg diff --git a/examples/sign.php b/examples/sign.php new file mode 100644 index 0000000..fcffc59 --- /dev/null +++ b/examples/sign.php @@ -0,0 +1,21 @@ + 'u', 'filename' => 'stuff.txt')); + +/* Create a signer from the key */ +$sign = new OpenPGP_Crypt_RSA($wkey); + +/* The message is the signed data packet */ +$m = $sign->sign($data); + +/* Output the raw message bytes to STDOUT */ +echo $m->to_bytes(); diff --git a/examples/verify.php b/examples/verify.php new file mode 100644 index 0000000..b5cf5b4 --- /dev/null +++ b/examples/verify.php @@ -0,0 +1,17 @@ +verify($m)); diff --git a/lib/openpgp.php b/lib/openpgp.php index 308c80a..d7b8147 100644 --- a/lib/openpgp.php +++ b/lib/openpgp.php @@ -5,10 +5,10 @@ * (RFC 4880). * * @package OpenPGP - * @version 0.0.1 + * @version 0.7.0 * @author Arto Bendiken * @author Stephen Paul Weber - * @link http://github.com/bendiken/openpgp-php + * @see http://github.com/bendiken/openpgp-php */ ////////////////////////////////////////////////////////////////////////////// @@ -18,6 +18,8 @@ * @see http://tools.ietf.org/html/rfc4880 */ class OpenPGP { + const VERSION = array(0, 7, 0); + /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6.2 @@ -28,8 +30,8 @@ static function enarmor($data, $marker = 'MESSAGE', array $headers = array()) { foreach ($headers as $key => $value) { $text .= $key . ': ' . (string)$value . "\n"; } - $text .= "\n" . base64_encode($data); - $text .= '=' . substr(pack('N', self::crc24($data)), 1) . "\n"; + $text .= "\n" . wordwrap(base64_encode($data), 76, "\n", true); + $text .= "\n".'=' . base64_encode(substr(pack('N', self::crc24($data)), 1)) . "\n"; $text .= self::footer($marker) . "\n"; return $text; } @@ -42,9 +44,15 @@ static function unarmor($text, $header = 'PGP PUBLIC KEY BLOCK') { $header = self::header($header); $text = str_replace(array("\r\n", "\r"), array("\n", ''), $text); if (($pos1 = strpos($text, $header)) !== FALSE && - ($pos1 = strpos($text, "\n\n", $pos1 += strlen($header))) !== FALSE && - ($pos2 = strpos($text, "\n=", $pos1 += 2)) !== FALSE) { - return base64_decode($text = substr($text, $pos1, $pos2 - $pos1)); + ($pos1 = strpos($text, "\n\n", $pos1 += strlen($header))) !== FALSE) { + $pos2 = strpos($text, "\n=", $pos1 += 2); + if ($pos2 === FALSE) { + trigger_error("Invalid ASCII armor, missing CRC"); + $pos2 = strpos($text, "-----END"); + if ($pos2 === FALSE) return NULL; + } + $text = substr($text, $pos1, $pos2 - $pos1); + return base64_decode($text, true); } } @@ -86,6 +94,114 @@ static function crc24($data) { static function bitlength($data) { return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1; } + + static function decode_s2k_count($c) { + return ((int)16 + ($c & 15)) << (($c >> 4) + 6); + } + + static function encode_s2k_count($iterations) { + if($iterations >= 65011712) return 255; + + $count = $iterations >> 6; + $c = 0; + while($count >= 32) { + $count = $count >> 1; + $c++; + } + $result = ($c << 4) | ($count - 16); + + if(OpenPGP::decode_s2k_count($result) < $iterations) { + return $result + 1; + } + + return $result; + } +} + +class OpenPGP_S2K { + public $type, $hash_algorithm, $salt, $count; + + function __construct($salt='BADSALT', $hash_algorithm=10, $count=65536, $type=3) { + $this->type = $type; + $this->hash_algorithm = $hash_algorithm; + $this->salt = $salt; + $this->count = $count; + } + + static function parse(&$input) { + $s2k = new OpenPGP_S2k(); + switch($s2k->type = ord($input[0])) { + case 0: + $s2k->hash_algorithm = ord($input[1]); + $input = substr($input, 2); + break; + case 1: + $s2k->hash_algorithm = ord($input[1]); + $s2k->salt = substr($input, 2, 8); + $input = substr($input, 10); + break; + case 3: + $s2k->hash_algorithm = ord($input[1]); + $s2k->salt = substr($input, 2, 8); + $s2k->count = OpenPGP::decode_s2k_count(ord($input[10])); + $input = substr($input, 11); + break; + } + + return $s2k; + } + + function to_bytes() { + $bytes = chr($this->type); + switch($this->type) { + case 0: + $bytes .= chr($this->hash_algorithm); + break; + case 1: + if(strlen($this->salt) != 8) throw new Exception("Invalid salt length"); + $bytes .= chr($this->hash_algorithm); + $bytes .= $this->salt; + break; + case 3: + if(strlen($this->salt) != 8) throw new Exception("Invalid salt length"); + $bytes .= chr($this->hash_algorithm); + $bytes .= $this->salt; + $bytes .= chr(OpenPGP::encode_s2k_count($this->count)); + break; + } + return $bytes; + } + + function raw_hash($s) { + return hash(strtolower(OpenPGP_SignaturePacket::$hash_algorithms[$this->hash_algorithm]), $s, true); + } + + function sized_hash($s, $size) { + $hash = $this->raw_hash($s); + while(strlen($hash) < $size) { + $s = "\0" . $s; + $hash .= $this->raw_hash($s); + } + + return substr($hash, 0, $size); + } + + function iterate($s) { + if(strlen($s) >= $this->count) return $s; + $s = str_repeat($s, ceil($this->count / strlen($s))); + return substr($s, 0, $this->count); + } + + function make_key($pass, $size) { + switch($this->type) { + case 0: + return $this->sized_hash($pass, $size); + case 1: + return $this->sized_hash($this->salt . $pass, $size); + case 3: + return $this->sized_hash($this->iterate($this->salt . $pass), $size); + } + } } ////////////////////////////////////////////////////////////////////////////// @@ -150,51 +266,148 @@ function to_bytes() { } /** - * Function to verify signature number $index - * $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: message and signature + * Extract signed objects from a well-formatted message + * + * Recurses into CompressedDataPacket + * + * @see http://tools.ietf.org/html/rfc4880#section-11 */ - function verify($verifiers, $index=0) { + function signatures() { $msg = $this; - while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]; - - $i = 0; - foreach($msg as $p) { - if($p instanceof OpenPGP_SignaturePacket) { - if($i == $index) $signature_packet = $p; - $i++; + while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]->data; + + $key = NULL; + $userid = NULL; + $subkey = NULL; + $sigs = array(); + $final_sigs = array(); + + foreach($msg as $idx => $p) { + if($p instanceof OpenPGP_LiteralDataPacket) { + return array(array($p, array_values(array_filter($msg->packets, function($p) { + return $p instanceof OpenPGP_SignaturePacket; + })))); + } else if($p instanceof OpenPGP_PublicSubkeyPacket || $p instanceof OpenPGP_SecretSubkeyPacket) { + if($userid) { + array_push($final_sigs, array($key, $userid, $sigs)); + $userid = NULL; + } else if($subkey) { + array_push($final_sigs, array($key, $subkey, $sigs)); + $key = NULL; + } + $sigs = array(); + $subkey = $p; + } else if($p instanceof OpenPGP_PublicKeyPacket) { + if($userid) { + array_push($final_sigs, array($key, $userid, $sigs)); + $userid = NULL; + } else if($subkey) { + array_push($final_sigs, array($key, $subkey, $sigs)); + $subkey = NULL; + } else if($key) { + array_push($final_sigs, array($key, $sigs)); + $key = NULL; + } + $sigs = array(); + $key = $p; + } else if($p instanceof OpenPGP_UserIDPacket) { + if($userid) { + array_push($final_sigs, array($key, $userid, $sigs)); + $userid = NULL; + } else if($key) { + array_push($final_sigs, array($key, $sigs)); + } + $sigs = array(); + $userid = $p; + } else if($p instanceof OpenPGP_SignaturePacket) { + $sigs[] = $p; } - if($p instanceof OpenPGP_LiteralDataPacket) $data_packet = $p; - if($signature_packet && $data_packet) break; } - if(!$signature_packet || !$data_packet) return NULL; // No signature or no data + if($userid) { + array_push($final_sigs, array($key, $userid, $sigs)); + } else if($subkey) { + array_push($final_sigs, array($key, $subkey, $sigs)); + } else if($key) { + array_push($final_sigs, array($key, $sigs)); + } - $verifier = $verifiers[$signature_packet->key_algorithm_name()][$signature_packet->hash_algorithm_name()]; - if(!$verifier) return NULL; // No verifier + return $final_sigs; + } - return call_user_func($verifier, $data_packet->data.$signature_packet->trailer, $signature_packet->data); + /** + * Function to extract verified signatures + * $verifiers is an array of callbacks formatted like array('RSA' => CALLBACK) or array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet + */ + function verified_signatures($verifiers) { + $signed = $this->signatures(); + $vsigned = array(); + + foreach($signed as $sign) { + $signatures = array_pop($sign); + $vsigs = array(); + + foreach($signatures as $sig) { + $verifier = $verifiers[$sig->key_algorithm_name()]; + if(is_array($verifier)) $verifier = $verifier[$sig->hash_algorithm_name()]; + if($verifier && $this->verify_one($verifier, $sign, $sig)) { + $vsigs[] = $sig; + } + } + array_push($sign, $vsigs); + $vsigned[] = $sign; + } + + return $vsigned; + } + + function verify_one($verifier, $sign, $sig) { + if($sign[0] instanceof OpenPGP_LiteralDataPacket) { + $sign[0]->normalize(); + $raw = $sign[0]->data; + } else if(isset($sign[1]) && $sign[1] instanceof OpenPGP_UserIDPacket) { + $raw = implode('', array_merge($sign[0]->fingerprint_material(), array(chr(0xB4), + pack('N', strlen($sign[1]->body())), $sign[1]->body()))); + } else if(isset($sign[1]) && ($sign[1] instanceof OpenPGP_PublicSubkeyPacket || $sign[1] instanceof OpenPGP_SecretSubkeyPacket)) { + $raw = implode('', array_merge($sign[0]->fingerprint_material(), $sign[1]->fingerprint_material())); + } else if($sign[0] instanceof OpenPGP_PublicKeyPacket) { + $raw = implode('', $sign[0]->fingerprint_material()); + } else { + return NULL; + } + return call_user_func($verifier, $raw.$sig->trailer, $sig); } // IteratorAggregate interface + // function getIterator(): \Traversable { // when php 5 support is dropped + #[\ReturnTypeWillChange] function getIterator() { return new ArrayIterator($this->packets); } // ArrayAccess interface + // function offsetExists($offset): bool // when php 5 support is dropped + #[\ReturnTypeWillChange] function offsetExists($offset) { return isset($this->packets[$offset]); } + // function offsetGet($offset): mixed // when php 7.4 support is dropped + #[\ReturnTypeWillChange] function offsetGet($offset) { return $this->packets[$offset]; } + // function offsetSet($offset, $value): void // when php 5 support is dropped + #[\ReturnTypeWillChange] function offsetSet($offset, $value) { - return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; + is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; } + // function offsetUnset($offset): void // when php 5 support is dropped + #[\ReturnTypeWillChange] function offsetUnset($offset) { unset($this->packets[$offset]); } @@ -220,23 +433,41 @@ static function class_for($tag) { /** * Parses an OpenPGP packet. * + * Partial body lengths based on https://github.com/toofishes/python-pgpdump/blob/master/pgpdump/packet.py + * * @see http://tools.ietf.org/html/rfc4880#section-4.2 */ static function parse(&$input) { $packet = NULL; if (strlen($input) > 0) { $parser = ord($input[0]) & 64 ? 'parse_new_format' : 'parse_old_format'; - list($tag, $head_length, $data_length) = self::$parser($input); - $input = substr($input, $head_length); + + $header_start0 = 0; + $consumed = 0; + $packet_data = ""; + do { + list($tag, $data_offset, $data_length, $partial) = self::$parser($input, $header_start0); + + $data_start0 = $header_start0 + $data_offset; + $header_start0 = $data_start0 + $data_length - 1; + $packet_data .= substr($input, $data_start0, $data_length); + + $consumed += $data_offset + $data_length; + if ($partial) { + $consumed -= 1; + } + } while ($partial === true && $parser === 'parse_new_format'); + if ($tag && ($class = self::class_for($tag))) { $packet = new $class(); $packet->tag = $tag; - $packet->input = substr($input, 0, $data_length); - $packet->length = $data_length; + $packet->input = $packet_data; + $packet->length = strlen($packet_data); $packet->read(); unset($packet->input); + unset($packet->length); } - $input = substr($input, $data_length); + $input = substr($input, $consumed); } return $packet; } @@ -246,19 +477,21 @@ static function parse(&$input) { * * @see http://tools.ietf.org/html/rfc4880#section-4.2.2 */ - static function parse_new_format($input) { + static function parse_new_format($input, $header_start = 0) { $tag = ord($input[0]) & 63; - $len = ord($input[1]); + $len = ord($input[$header_start + 1]); if($len < 192) { // One octet length - return array($tag, 2, $len); + return array($tag, 2, $len, false); } if($len > 191 && $len < 224) { // Two octet length - return array($tag, 3, (($len - 192) << 8) + ord($input[2]) + 192); + return array($tag, 3, (($len - 192) << 8) + ord($input[$header_start + 2]) + 192, false); } if($len == 255) { // Five octet length - return array($tag, 6, array_pop(unpack('N', substr($input, 2, 4)))); + $unpacked = unpack('N', substr($input, $header_start + 2, 4)); + return array($tag, 6, reset($unpacked), false); } - // TODO: Partial body lengths. 1 << ($len & 0x1F) + // Partial body lengths + return array($tag, 2, 1 << ($len & 0x1f), true); } /** @@ -289,11 +522,12 @@ static function parse_old_format($input) { $data_length = strlen($input) - $head_length; break; } - return array($tag, $head_length, $data_length); + return array($tag, $head_length, $data_length, false); } - function __construct() { + function __construct($data=NULL) { $this->tag = array_search(substr(substr(get_class($this), 8), 0, -6), self::$tags); + $this->data = $data; } function read() { @@ -304,7 +538,7 @@ function body() { } function header_and_body() { - $body = $this->body(); // Get body first, we will need it's length + $body = $this->body() ?? ''; // Get body first, we will need it's length $tag = chr($this->tag | 0xC0); // First two bits are 1 for new packet format $size = chr(255).pack('N', strlen($body)); // Use 5-octet lengths return array('header' => $tag.$size, 'body' => $body); @@ -336,11 +570,11 @@ function read_mpi() { */ function read_unpacked($count, $format) { $unpacked = unpack($format, $this->read_bytes($count)); - return $unpacked[1]; + return is_array($unpacked) ? reset($unpacked) : NULL; } function read_byte() { - return ($bytes = $this->read_bytes()) ? $bytes[0] : NULL; + return !is_null($bytes = $this->read_bytes()) ? $bytes[0] : NULL; } function read_bytes($count = 1) { @@ -380,21 +614,131 @@ function read_bytes($count = 1) { * @see http://tools.ietf.org/html/rfc4880#section-5.1 */ class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet { - // TODO + public $version, $keyid, $key_algorithm, $encrypted_data; + + public $input; + + public $length; + + function __construct($key_algorithm='', $keyid='', $encrypted_data='', $version=3) { + parent::__construct(); + $this->version = $version; + $this->keyid = substr($keyid, -16); + $this->key_algorithm = $key_algorithm; + $this->encrypted_data = $encrypted_data; + } + + function read() { + switch($this->version = ord($this->read_byte())) { + case 3: + $rawkeyid = $this->read_bytes(8); + $this->keyid = ''; + for($i = 0; $i < strlen($rawkeyid); $i++) { // Store KeyID in Hex + $this->keyid .= sprintf('%02X',ord($rawkeyid[$i])); + } + + $this->key_algorithm = ord($this->read_byte()); + + $this->encrypted_data = $this->input; + break; + default: + throw new Exception("Unsupported AsymmetricSessionKeyPacket version: " . $this->version); + } + } + + function body() { + $bytes = chr($this->version); + + for($i = 0; $i < strlen($this->keyid); $i += 2) { + $bytes .= chr(hexdec($this->keyid[$i].$this->keyid[$i+1])); + } + + $bytes .= chr($this->key_algorithm); + $bytes .= $this->encrypted_data; + return $bytes; + } } /** * OpenPGP Signature packet (tag 2). + * Be sure to NULL the trailer if you update a signature packet! * * @see http://tools.ietf.org/html/rfc4880#section-5.2 */ class OpenPGP_SignaturePacket extends OpenPGP_Packet { public $version, $signature_type, $hash_algorithm, $key_algorithm, $hashed_subpackets, $unhashed_subpackets, $hash_head; public $trailer; // This is the literal bytes that get tacked on the end of the message when verifying the signature + + public $input; + + public $length; + + function __construct($data=NULL, $key_algorithm=NULL, $hash_algorithm=NULL) { + parent::__construct(); + $this->version = 4; // Default to version 4 sigs + if(is_string($this->hash_algorithm = $hash_algorithm)) { + $this->hash_algorithm = array_search($this->hash_algorithm, self::$hash_algorithms); + } + if(is_string($this->key_algorithm = $key_algorithm)) { + $this->key_algorithm = array_search($this->key_algorithm, OpenPGP_PublicKeyPacket::$algorithms); + } + if($data) { // If we have any data, set up the creation time + $this->hashed_subpackets = array(new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time())); + } + if($data instanceof OpenPGP_LiteralDataPacket) { + $this->signature_type = ($data->format == 'b') ? 0x00 : 0x01; + $data->normalize(); + $data = $data->data; + } else if($data instanceof OpenPGP_Message && $data[0] instanceof OpenPGP_PublicKeyPacket) { + // $data is a message with PublicKey first, UserID second + $key = implode('', $data[0]->fingerprint_material()); + $user_id = $data[1]->body(); + $data = $key . chr(0xB4) . pack('N', strlen($user_id)) . $user_id; + } + $this->data = $data; // Store to-be-signed data in here until the signing happens + } + + /** + * $this->data must be set to the data to sign (done by constructor) + * $signers in the same format as $verifiers for OpenPGP_Message. + */ + function sign_data($signers) { + $this->trailer = $this->calculate_trailer(); + $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()]; + $signed = call_user_func($signer, $this->data.$this->trailer); + $this->data = array($signed["signed"]); + $unpacked = unpack('n', substr($signed["hash"], 0, 2)); + $this->hash_head = reset($unpacked); + } + function read() { switch($this->version = ord($this->read_byte())) { + case 2: case 3: - // TODO: V3 sigs + if(ord($this->read_byte()) != 5) { + throw new Exception("Invalid version 2 or 3 SignaturePacket"); + } + $this->signature_type = ord($this->read_byte()); + $creation_time = $this->read_timestamp(); + $keyid = $this->read_bytes(8); + $keyidHex = ''; + for($i = 0; $i < strlen($keyid); $i++) { // Store KeyID in Hex + $keyidHex .= sprintf('%02X',ord($keyid[$i])); + } + + $this->hashed_subpackets = array(); + $this->unhashed_subpackets = array( + new OpenPGP_SignaturePacket_SignatureCreationTimePacket($creation_time), + new OpenPGP_SignaturePacket_IssuerPacket($keyidHex) + ); + + $this->key_algorithm = ord($this->read_byte()); + $this->hash_algorithm = ord($this->read_byte()); + $this->hash_head = $this->read_unpacked(2, 'n'); + $this->data = array(); + while(strlen($this->input) > 0) { + $this->data[] = $this->read_mpi(); + } break; case 4: $this->signature_type = ord($this->read_byte()); @@ -413,30 +757,82 @@ function read() { $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size)); $this->hash_head = $this->read_unpacked(2, 'n'); - $this->data = $this->read_mpi(); + + $this->data = array(); + while(strlen($this->input) > 0) { + $this->data[] = $this->read_mpi(); + } break; } } - function body() { + function calculate_trailer() { + // The trailer is just the top of the body plus some crap + $body = $this->body_start(); + return $body.chr(4).chr(0xff).pack('N', strlen($body)); + } + + function body_start() { $body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); $hashed_subpackets = ''; - foreach($this->hashed_subpackets as $p) { + foreach((array)$this->hashed_subpackets as $p) { $hashed_subpackets .= $p->to_bytes(); } $body .= pack('n', strlen($hashed_subpackets)).$hashed_subpackets; - $unhashed_subpackets = ''; - foreach($this->unhashed_subpackets as $p) { - $unhashed_subpackets .= $p->to_bytes(); - } - $body .= pack('n', strlen($unhashed_subpackets)).$unhashed_subpackets; + return $body; + } + + function body() { + switch($this->version) { + case 2: + case 3: + $body = chr($this->version) . chr(5) . chr($this->signature_type); - $body .= pack('n', $this->hash_head); - $body .= pack('n', floor((strlen($this->data) - 7)*8)).$this->data; + foreach((array)$this->unhashed_subpackets as $p) { + if($p instanceof OpenPGP_SignaturePacket_SignatureCreationTimePacket) { + $body .= pack('N', $p->data); + break; + } + } - return $body; + foreach((array)$this->unhashed_subpackets as $p) { + if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) { + for($i = 0; $i < strlen($p->data); $i += 2) { + $body .= chr(hexdec($p->data[$i].$p->data[$i+1])); + } + break; + } + } + + $body .= chr($this->key_algorithm); + $body .= chr($this->hash_algorithm); + $body .= pack('n', $this->hash_head); + + foreach($this->data as $mpi) { + $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi; + } + + return $body; + case 4: + if(!$this->trailer) $this->trailer = $this->calculate_trailer(); + $body = substr($this->trailer, 0, -6); + + $unhashed_subpackets = ''; + foreach((array)$this->unhashed_subpackets as $p) { + $unhashed_subpackets .= $p->to_bytes(); + } + $body .= pack('n', strlen($unhashed_subpackets)).$unhashed_subpackets; + + $body .= pack('n', $this->hash_head); + + foreach((array)$this->data as $mpi) { + $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi; + } + + return $body; + } } function key_algorithm_name() { @@ -447,6 +843,16 @@ function hash_algorithm_name() { return self::$hash_algorithms[$this->hash_algorithm]; } + function issuer() { + foreach($this->hashed_subpackets as $p) { + if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data; + } + foreach($this->unhashed_subpackets as $p) { + if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data; + } + return NULL; + } + /** * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1 */ @@ -471,18 +877,26 @@ static function get_subpacket(&$input) { } if($len == 255) { // Five octet length $length_of_length = 5; - $len = array_pop(unpack('N', substr($input, 1, 4))); + $unpacked = unpack('N', substr($input, 1, 4)); + $len = reset($unpacked); } $input = substr($input, $length_of_length); // Chop off length header $tag = ord($input[0]); + // Is the subpacket critical? + $criticalFlagMask = 0x80; + $typeMask = 0x7F; + $isCritical = ($tag & $criticalFlagMask) === $criticalFlagMask; + $tag = $tag & $typeMask; $class = self::class_for($tag); if($class) { $packet = new $class(); $packet->tag = $tag; + $packet->isCritical = $isCritical; $packet->input = substr($input, 1, $len-1); $packet->length = $len-1; $packet->read(); unset($packet->input); + unset($packet->length); } $input = substr($input, $len); // Chop off the data from this packet return $packet; @@ -535,19 +949,39 @@ static function get_subpacket(&$input) { ); static function class_for($tag) { - if(!self::$subpacket_types[$tag]) return NULL; + if(!isset(self::$subpacket_types[$tag])) return 'OpenPGP_SignaturePacket_Subpacket'; return 'OpenPGP_SignaturePacket_'.self::$subpacket_types[$tag].'Packet'; } } class OpenPGP_SignaturePacket_Subpacket extends OpenPGP_Packet { + public $input; + + public $isCritical = false; + + public $length; + + function __construct($data=NULL) { + parent::__construct($data); + $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types); + } + function header_and_body() { $body = $this->body(); // Get body first, we will need it's length $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet $tag = chr($this->tag); return array('header' => $size.$tag, 'body' => $body); } + + /* Defaults for unsupported packets */ + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } /** @@ -564,35 +998,109 @@ function body() { } class OpenPGP_SignaturePacket_SignatureExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = $this->read_timestamp(); + } + + function body() { + return pack('N', $this->data); + } } class OpenPGP_SignaturePacket_ExportableCertificationPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = (ord($this->input) != 0); + } + + function body() { + return chr($this->data ? 1 : 0); + } } class OpenPGP_SignaturePacket_TrustSignaturePacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->depth = ord($this->input[0]); + $this->trust = ord($this->input[1]); + } + + function body() { + return chr($this->depth) . chr($this->trust); + } } class OpenPGP_SignaturePacket_RegularExpressionPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = substr($this->input, 0, -1); + } + + function body() { + return $this->data . chr(0); + } } class OpenPGP_SignaturePacket_RevocablePacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = (ord($this->input) != 0); + } + + function body() { + return chr($this->data ? 1 : 0); + } } class OpenPGP_SignaturePacket_KeyExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = $this->read_timestamp(); + } + + function body() { + return pack('N', $this->data); + } } class OpenPGP_SignaturePacket_PreferredSymmetricAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = array(); + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } + + function body() { + $bytes = ''; + foreach($this->data as $algo) { + $bytes .= chr($algo); + } + return $bytes; + } } class OpenPGP_SignaturePacket_RevocationKeyPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $key_algorithm, $fingerprint, $sensitive; + + function read() { + // bitfield must have bit 0x80 set, says the spec + $bitfield = ord($this->read_byte()); + $this->sensitive = $bitfield & 0x40 == 0x40; + $this->key_algorithm = ord($this->read_byte()); + + $this->fingerprint = ''; + while(strlen($this->input) > 0) { + $this->fingerprint .= sprintf('%02X',ord($this->read_byte())); + } + } + + function body() { + $bytes = ''; + $bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00)); + $bytes .= chr($this->key_algorithm); + + for($i = 0; $i < strlen($this->fingerprint); $i += 2) { + $bytes .= chr(hexdec($this->fingerprint[$i].$this->fingerprint[$i+1])); + } + + return $bytes; + } } /** @@ -601,69 +1109,196 @@ class OpenPGP_SignaturePacket_RevocationKeyPacket extends OpenPGP_SignaturePacke class OpenPGP_SignaturePacket_IssuerPacket extends OpenPGP_SignaturePacket_Subpacket { function read() { for($i = 0; $i < 8; $i++) { // Store KeyID in Hex - $this->data .= dechex(ord($this->read_byte())); + $this->data .= sprintf('%02X',ord($this->read_byte())); } } function body() { $bytes = ''; for($i = 0; $i < strlen($this->data); $i += 2) { - $bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1})); + $bytes .= chr(hexdec($this->data[$i].$this->data[$i+1])); } return $bytes; } } class OpenPGP_SignaturePacket_NotationDataPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $human_readable, $name; + + function read() { + $flags = $this->read_bytes(4); + $namelen = $this->read_unpacked(2, 'n'); + $datalen = $this->read_unpacked(2, 'n'); + $this->human_readable = ord($flags[0]) & 0x80 == 0x80; + $this->name = $this->read_bytes($namelen); + $this->data = $this->read_bytes($datalen); + } + + function body () { + return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" . + pack('n', strlen($this->name)) . pack('n', strlen($this->data)) . + $this->name . $this->data; + } } class OpenPGP_SignaturePacket_PreferredHashAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = array(); + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } + + function body() { + $bytes = ''; + foreach($this->data as $algo) { + $bytes .= chr($algo); + } + return $bytes; + } } class OpenPGP_SignaturePacket_PreferredCompressionAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = array(); + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } + + function body() { + $bytes = ''; + foreach($this->data as $algo) { + $bytes .= chr($algo); + } + return $bytes; + } } class OpenPGP_SignaturePacket_KeyServerPreferencesPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $no_modify; + + function read() { + $flags = ord($this->input); + $this->no_modify = $flags & 0x80 == 0x80; + } + + function body() { + return chr($this->no_modify ? 0x80 : 0x00); + } } class OpenPGP_SignaturePacket_PreferredKeyServerPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } class OpenPGP_SignaturePacket_PrimaryUserIDPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = (ord($this->input) != 0); + } + + function body() { + return chr($this->data ? 1 : 0); + } + } class OpenPGP_SignaturePacket_PolicyURIPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } class OpenPGP_SignaturePacket_KeyFlagsPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $flags; + + function __construct($flags=array()) { + parent::__construct(); + $this->flags = $flags; + } + + function read() { + $this->flags = array(); + while($this->input) { + $this->flags[] = ord($this->read_byte()); + } + } + + function body() { + $bytes = ''; + foreach($this->flags as $f) { + $bytes .= chr($f); + } + return $bytes; + } } class OpenPGP_SignaturePacket_SignersUserIDPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } class OpenPGP_SignaturePacket_ReasonforRevocationPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $code; + + function read() { + $this->code = ord($this->read_byte()); + $this->data = $this->input; + } + + function body() { + return chr($this->code) . $this->data; + } } -class OpenPGP_SignaturePacket_FeaturesPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + +class OpenPGP_SignaturePacket_FeaturesPacket extends OpenPGP_SignaturePacket_KeyFlagsPacket { + // Identical functionality to parent } class OpenPGP_SignaturePacket_SignatureTargetPacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO + public $key_algorithm, $hash_algorithm; + + function read() { + $this->key_algorithm = ord($this->read_byte()); + $this->hash_algorithm = ord($this->read_byte()); + $this->data = $this->input; + } + + function body() { + return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data; + } + } -class OpenPGP_SignaturePacket_EmbeddedSignaturePacket extends OpenPGP_SignaturePacket_Subpacket { - // TODO +class OpenPGP_SignaturePacket_EmbeddedSignaturePacket extends OpenPGP_SignaturePacket { + // TODO: This is duplicated from subpacket... improve? + function __construct($data=NULL) { + parent::__construct($data); + $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types); + } + + function header_and_body() { + $body = $this->body(); // Get body first, we will need it's length + $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet + $tag = chr($this->tag); + return array('header' => $size.$tag, 'body' => $body); + } } /** @@ -672,7 +1307,31 @@ class OpenPGP_SignaturePacket_EmbeddedSignaturePacket extends OpenPGP_SignatureP * @see http://tools.ietf.org/html/rfc4880#section-5.3 */ class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet { - // TODO + public $version, $symmetric_algorithm, $s2k, $encrypted_data; + + public $input; + + public $length; + + function __construct($s2k=NULL, $encrypted_data='', $symmetric_algorithm=9, $version=3) { + parent::__construct(); + $this->version = $version; + $this->symmetric_algorithm = $symmetric_algorithm; + $this->s2k = $s2k; + $this->encrypted_data = $encrypted_data; + } + + function read() { + $this->version = ord($this->read_byte()); + $this->symmetric_algorithm = ord($this->read_byte()); + $this->s2k = OpenPGP_S2k::parse($this->input); + $this->encrypted_data = $this->input; + } + + function body() { + return chr($this->version) . chr($this->symmetric_algorithm) . + $this->s2k->to_bytes() . $this->encrypted_data; + } } /** @@ -682,13 +1341,18 @@ class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet { */ class OpenPGP_OnePassSignaturePacket extends OpenPGP_Packet { public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested; + + public $input; + + public $length; + function read() { $this->version = ord($this->read_byte()); $this->signature_type = ord($this->read_byte()); $this->hash_algorithm = ord($this->read_byte()); $this->key_algorithm = ord($this->read_byte()); for($i = 0; $i < 8; $i++) { // Store KeyID in Hex - $this->key_id .= dechex(ord($this->read_byte())); + $this->key_id .= sprintf('%02X',ord($this->read_byte())); } $this->nested = ord($this->read_byte()); } @@ -696,7 +1360,7 @@ function read() { function body() { $body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm); for($i = 0; $i < strlen($this->key_id); $i += 2) { - $body .= chr(hexdec($this->key_id{$i}.$this->key_id{$i+1})); + $body .= chr(hexdec($this->key_id[$i].$this->key_id[$i+1])); } $body .= chr((int)$this->nested); return $body; @@ -714,20 +1378,94 @@ function body() { class OpenPGP_PublicKeyPacket extends OpenPGP_Packet { public $version, $timestamp, $algorithm; public $key, $key_id, $fingerprint; + public $v3_days_of_validity; + + public $input; + + public $length; + + function __construct($key=array(), $algorithm='RSA', $timestamp=NULL, $version=4) { + parent::__construct(); + + if($key instanceof OpenPGP_PublicKeyPacket) { + $this->algorithm = $key->algorithm; + $this->key = array(); + + // Restrict to only the fields we need + foreach (self::$key_fields[$this->algorithm] as $field) { + $this->key[$field] = $key->key[$field]; + } + + $this->key_id = $key->key_id; + $this->fingerprint = $key->fingerprint; + $this->timestamp = $key->timestamp; + $this->version = $key->version; + $this->v3_days_of_validity = $key->v3_days_of_validity; + } else { + $this->key = $key; + if(is_string($this->algorithm = $algorithm)) { + $this->algorithm = array_search($this->algorithm, self::$algorithms); + } + $this->timestamp = $timestamp ? $timestamp : time(); + $this->version = $version; + + if(count($this->key) > 0) { + $this->key_id = substr($this->fingerprint(), -8); + } + } + } + + // Find self signatures in a message, these often contain metadata about the key + function self_signatures($message) { + $sigs = array(); + $keyid16 = strtoupper(substr($this->fingerprint, -16)); + foreach($message as $p) { + if($p instanceof OpenPGP_SignaturePacket) { + if(strtoupper($p->issuer()) == $keyid16) { + $sigs[] = $p; + } else { + if(!is_array($p->hashed_subpackets)) { + break; + } + foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) { + if($s instanceof OpenPGP_SignaturePacket_EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) { + $sigs[] = $p; + break; + } + } + } + } else if(count($sigs)) break; // After we've seen a self sig, the next non-sig stop all self-sigs + } + return $sigs; + } + + // Find expiry time of this key based on the self signatures in a message + function expires($message) { + foreach($this->self_signatures($message) as $p) { + foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) { + if($s instanceof OpenPGP_SignaturePacket_KeyExpirationTimePacket) { + return $this->timestamp + $s->data; + } + } + } + return NULL; // Never expires + } /** * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 */ function read() { switch ($this->version = ord($this->read_byte())) { - case 2: case 3: - return FALSE; // TODO + $this->timestamp = $this->read_timestamp(); + $this->v3_days_of_validity = $this->read_unpacked(2, 'n'); + $this->algorithm = ord($this->read_byte()); + $this->read_key_material(); + break; case 4: $this->timestamp = $this->read_timestamp(); $this->algorithm = ord($this->read_byte()); $this->read_key_material(); - return TRUE; } } @@ -735,17 +1473,53 @@ function read() { * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 */ function read_key_material() { - static $key_fields = array( - 1 => array('n', 'e'), // RSA - 16 => array('p', 'g', 'y'), // ELG-E - 17 => array('p', 'q', 'g', 'y'), // DSA - ); - foreach ($key_fields[$this->algorithm] as $field) { - $this->key[$field] = $this->read_mpi(); + foreach (self::$key_fields[$this->algorithm] as $field) { + if (strlen($field) == 1) { + $this->key[$field] = $this->read_mpi(); + } else if ($field == 'oid') { + $len = ord($this->read_byte()); + $this->key[$field] = $this->read_bytes($len); + } else { + $this->key[$field] = ord($this->read_byte()); + } } $this->key_id = substr($this->fingerprint(), -8); } + function fingerprint_material() { + switch ($this->version) { + case 3: + $material = array(); + foreach (self::$key_fields[$this->algorithm] as $i) { + $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); + $material[] = $this->key[$i]; + } + return $material; + case 4: + $head = array( + chr(0x99), NULL, + chr($this->version), pack('N', $this->timestamp), + chr($this->algorithm), + ); + $material = array(); + foreach (self::$key_fields[$this->algorithm] as $i) { + if (strlen($i) == 1) { + $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); + $material[] = $this->key[$i]; + } else if ($i == 'oid') { + $material[] = chr(strlen($this->key[$i])); + $material[] = $this->key[$i]; + } else { + $material[] = chr($this->key[$i]); + } + } + $material = implode('', $material); + $head[1] = pack('n', 6 + strlen($material)); + $head[] = $material; + return $head; + } + } + /** * @see http://tools.ietf.org/html/rfc4880#section-12.2 * @see http://tools.ietf.org/html/rfc4880#section-3.3 @@ -754,21 +1528,35 @@ function fingerprint() { switch ($this->version) { case 2: case 3: - return $this->fingerprint = md5($this->key['n'] . $this->key['e']); + return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material()))); case 4: - $material = array( - chr(0x99), pack('n', $this->length), - chr($this->version), pack('N', $this->timestamp), - chr($this->algorithm), + return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material()))); + } + } + + function body() { + switch ($this->version) { + case 2: + case 3: + return implode('', array_merge(array( + chr($this->version) . pack('N', $this->timestamp) . + pack('n', $this->v3_days_of_validity) . chr($this->algorithm) + ), $this->fingerprint_material()) ); - foreach ($this->key as $data) { - $material[] = pack('n', OpenPGP::bitlength($data)); - $material[] = $data; - } - return $this->fingerprint = sha1(implode('', $material)); + case 4: + return implode('', array_slice($this->fingerprint_material(), 2)); } } + static $key_fields = array( + 1 => array('n', 'e'), + 16 => array('p', 'g', 'y'), + 17 => array('p', 'q', 'g', 'y'), + 18 => array('oid', 'p', 'len', 'future', 'hash', 'algorithm'), + 19 => array('oid', 'p'), + 22 => array('oid', 'p') + ); + static $algorithms = array( 1 => 'RSA', 2 => 'RSA', @@ -777,7 +1565,8 @@ function fingerprint() { 17 => 'DSA', 18 => 'ECC', 19 => 'ECDSA', - 21 => 'DH' + 21 => 'DH', + 22 => 'EdDSA' ); } @@ -791,6 +1580,10 @@ function fingerprint() { * @see http://tools.ietf.org/html/rfc4880#section-12 */ class OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket { + public $input; + + public $length; + // TODO } @@ -803,7 +1596,68 @@ class OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket { * @see http://tools.ietf.org/html/rfc4880#section-12 */ class OpenPGP_SecretKeyPacket extends OpenPGP_PublicKeyPacket { - // TODO + public $s2k_useage, $s2k, $symmetric_algorithm, $private_hash, $encrypted_data; + function read() { + parent::read(); // All the fields from PublicKey + $this->s2k_useage = ord($this->read_byte()); + if($this->s2k_useage == 255 || $this->s2k_useage == 254) { + $this->symmetric_algorithm = ord($this->read_byte()); + $this->s2k = OpenPGP_S2k::parse($this->input); + } else if($this->s2k_useage > 0) { + $this->symmetric_algorithm = $this->s2k_useage; + } + if($this->s2k_useage > 0) { + $this->encrypted_data = $this->input; // Rest of input is MPIs and checksum (encrypted) + } else { + $this->key_from_input(); + $this->private_hash = $this->read_bytes(2); // TODO: Validate checksum? + } + } + + static $secret_key_fields = array( + 1 => array('d', 'p', 'q', 'u'), // RSA + 2 => array('d', 'p', 'q', 'u'), // RSA-E + 3 => array('d', 'p', 'q', 'u'), // RSA-S + 16 => array('x'), // ELG-E + 17 => array('x'), // DSA + 18 => array('x'), // ECDH + 19 => array('x'), // ECDSA + 22 => array('x'), // EdDSA + ); + + function key_from_input() { + foreach(self::$secret_key_fields[$this->algorithm] as $field) { + $this->key[$field] = $this->read_mpi(); + } + } + + function body() { + $bytes = parent::body() . chr($this->s2k_useage); + $secret_material = NULL; + if($this->s2k_useage == 255 || $this->s2k_useage == 254) { + $bytes .= chr($this->symmetric_algorithm); + $bytes .= $this->s2k->to_bytes(); + } + if($this->s2k_useage > 0) { + $bytes .= $this->encrypted_data; + } else { + $secret_material = ''; + foreach(self::$secret_key_fields[$this->algorithm] as $f) { + $f = $this->key[$f]; + $secret_material .= pack('n', OpenPGP::bitlength($f)); + $secret_material .= $f; + } + $bytes .= $secret_material; + + // 2-octet checksum + $chk = 0; + for($i = 0; $i < strlen($secret_material); $i++) { + $chk = ($chk + ord($secret_material[$i])) % 65536; + } + $bytes .= pack('n', $chk); + } + return $bytes; + } } /** @@ -827,6 +1681,17 @@ class OpenPGP_CompressedDataPacket extends OpenPGP_Packet implements IteratorAgg public $algorithm; /* see http://tools.ietf.org/html/rfc4880#section-9.3 */ static $algorithms = array(0 => 'Uncompressed', 1 => 'ZIP', 2 => 'ZLIB', 3 => 'BZip2'); + + public $input; + + public $length; + + function __construct($m=NULL, $algorithm=1) { + parent::__construct(); + $this->algorithm = $algorithm; + $this->data = $m ? $m : new OpenPGP_Message(); + } + function read() { $this->algorithm = ord($this->read_byte()); $this->data = $this->read_bytes($this->length); @@ -870,25 +1735,33 @@ function body() { } // IteratorAggregate interface - + // function getIterator(): \Traversable { // when PHP 5 support is dropped + #[\ReturnTypeWillChange] function getIterator() { return new ArrayIterator($this->data->packets); } // ArrayAccess interface - + // function offsetExists($offset): bool { // when PHP 5 support is dropped + #[\ReturnTypeWillChange] function offsetExists($offset) { return isset($this->data[$offset]); } + // function offsetGet($offset): mixed { // when PHP 7 support is dropped + #[\ReturnTypeWillChange] function offsetGet($offset) { return $this->data[$offset]; } + // function offsetSet($offset, $value): void { // when PHP 5 support is dropped + #[\ReturnTypeWillChange] function offsetSet($offset, $value) { - return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value; + is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value; } + #[\ReturnTypeWillChange] + // function offsetUnset($offset): void { // PHP 5 support is dropped function offsetUnset($offset) { unset($this->data[$offset]); } @@ -901,7 +1774,17 @@ function offsetUnset($offset) { * @see http://tools.ietf.org/html/rfc4880#section-5.7 */ class OpenPGP_EncryptedDataPacket extends OpenPGP_Packet { - // TODO + public $input; + + public $length; + + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } /** @@ -920,6 +1803,34 @@ class OpenPGP_MarkerPacket extends OpenPGP_Packet { */ class OpenPGP_LiteralDataPacket extends OpenPGP_Packet { public $format, $filename, $timestamp; + + public $input; + + public $length; + + function __construct($data=NULL, $opt=array()) { + parent::__construct(); + $this->data = $data; + $this->format = isset($opt['format']) ? $opt['format'] : 'b'; + $this->filename = isset($opt['filename']) ? $opt['filename'] : 'data'; + $this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time(); + } + + function normalize($clearsign=false) { + if($clearsign && ($this->format != 'u' && $this->format != 't')) { + $this->format = 'u'; // Clearsign must be text + } + + if($this->format == 'u' || $this->format == 't') { // Normalize line endings + $this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data))); + } + + if($clearsign) { + // When clearsigning, do not sign over trailing whitespace + $this->data = preg_replace('/\s+\r/', "\r", $this->data); + } + } + function read() { $this->size = $this->length - 1 - 4; $this->format = $this->read_byte(); @@ -941,7 +1852,17 @@ function body() { * @see http://tools.ietf.org/html/rfc4880#section-5.10 */ class OpenPGP_TrustPacket extends OpenPGP_Packet { - // TODO + public $input; + + public $length; + + function read() { + $this->data = $this->input; + } + + function body() { + return $this->data; + } } /** @@ -953,28 +1874,44 @@ class OpenPGP_TrustPacket extends OpenPGP_Packet { class OpenPGP_UserIDPacket extends OpenPGP_Packet { public $name, $comment, $email; + public $input; + + public $length; + + function __construct($name='', $comment='', $email='') { + parent::__construct(); + if(!$comment && !$email) { + $this->input = $name; + $this->read(); + } else { + $this->name = $name; + $this->comment = $comment; + $this->email = $email; + } + } + function read() { - $this->text = $this->input; + $this->data = $this->input; // User IDs of the form: "name (comment) " - if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->text, $matches)) { + if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) { $this->name = trim($matches[1]); $this->comment = trim($matches[2]); $this->email = trim($matches[3]); } // User IDs of the form: "name " - else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->text, $matches)) { + else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) { $this->name = trim($matches[1]); $this->comment = NULL; $this->email = trim($matches[2]); } // User IDs of the form: "name" - else if (preg_match('/^([^<]+)$/', $this->text, $matches)) { + else if (preg_match('/^([^<]+)$/', $this->data, $matches)) { $this->name = trim($matches[1]); $this->comment = NULL; $this->email = NULL; } // User IDs of the form: "" - else if (preg_match('/^<([^>]+)>$/', $this->text, $matches)) { + else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) { $this->name = NULL; $this->comment = NULL; $this->email = trim($matches[2]); @@ -988,6 +1925,10 @@ function __toString() { if ($this->email) { $text[] = "<{$this->email}>"; } return implode(' ', $text); } + + function body() { + return ''.$this; // Convert to string is the body + } } /** @@ -999,6 +1940,10 @@ function __toString() { class OpenPGP_UserAttributePacket extends OpenPGP_Packet { public $packets; + public $input; + + public $length; + // TODO } @@ -1007,8 +1952,27 @@ class OpenPGP_UserAttributePacket extends OpenPGP_Packet { * * @see http://tools.ietf.org/html/rfc4880#section-5.13 */ -class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_Packet { - // TODO +class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_EncryptedDataPacket { + public $version; + + public $input; + + public $length; + + function __construct($data='', $version=1) { + parent::__construct(); + $this->version = $version; + $this->data = $data; + } + + function read() { + $this->version = ord($this->read_byte()); + $this->data = $this->input; + } + + function body() { + return chr($this->version) . $this->data; + } } /** @@ -1017,7 +1981,25 @@ class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_Packet { * @see http://tools.ietf.org/html/rfc4880#section-5.14 */ class OpenPGP_ModificationDetectionCodePacket extends OpenPGP_Packet { - // TODO + function __construct($sha1='') { + parent::__construct(); + $this->data = $sha1; + } + + function read() { + $this->data = $this->input; + if(strlen($this->input) != 20) throw new Exception("Bad ModificationDetectionCodePacket"); + } + + function header_and_body() { + $body = $this->body(); // Get body first, we will need it's length + if(strlen($body) != 20) throw new Exception("Bad ModificationDetectionCodePacket"); + return array('header' => "\xD3\x14", 'body' => $body); + } + + function body() { + return $this->data; + } } /** diff --git a/lib/openpgp_crypt_rsa.php b/lib/openpgp_crypt_rsa.php new file mode 100644 index 0000000..b04c946 --- /dev/null +++ b/lib/openpgp_crypt_rsa.php @@ -0,0 +1,301 @@ +key = $packet; + } else { + $this->message = $packet; + } + } + + function key($keyid=NULL) { + if(!$this->key) return NULL; // No key + if($this->key instanceof OpenPGP_Message) { + foreach($this->key as $p) { + if($p instanceof OpenPGP_PublicKeyPacket) { + if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p; + } + } + } + return $this->key; + } + + // Get Crypt_RSA for the public key + function public_key($keyid=NULL) { + return self::convert_public_key($this->key($keyid)); + } + + // Get Crypt_RSA for the private key + function private_key($keyid=NULL) { + return self::convert_private_key($this->key($keyid)); + } + + // Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with + // Second optional parameter to specify which signature to verify (if there is more than one) + function verify($packet) { + $self = $this; // For old PHP + if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); + if(!$this->message) { + $m = $packet; + $verifier = function($m, $s) use($self) { + $key = $self->public_key($s->issuer()); + if(!$key) return false; + $key = $key->withHash(strtolower($s->hash_algorithm_name())); + return $key->verify($m, reset($s->data)); + }; + } else { + if(!($packet instanceof Crypt_RSA)) { + $packet = new self($packet); + } + + $m = $this->message; + $verifier = function($m, $s) use($self, $packet) { + if(!($packet instanceof Crypt_RSA)) { + $key = $packet->public_key($s->issuer()); + } + if(!$key) return false; + $key = $key->withHash(strtolower($s->hash_algorithm_name())); + return $key->verify($m, reset($s->data)); + }; + } + + return $m->verified_signatures(array('RSA' => array( + 'MD5' => $verifier, + 'SHA1' => $verifier, + 'SHA224' => $verifier, + 'SHA256' => $verifier, + 'SHA384' => $verifier, + 'SHA512' => $verifier + ))); + } + + // Pass a message to sign with this key, or a secret key to sign this message with + // Second parameter is hash algorithm to use (default SHA256) + // Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet + function sign($packet, $hash='SHA256', $keyid=NULL) { + if(!is_object($packet)) { + if($this->key) { + $packet = new OpenPGP_LiteralDataPacket($packet); + } else { + $packet = OpenPGP_Message::parse($packet); + } + } + + if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA + || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { + $key = $packet; + $message = $this->message; + } else { + $key = $this->key; + $message = $packet; + } + + if(!$key || !$message) return NULL; // Missing some data + + if($message instanceof OpenPGP_Message) { + $sign = $message->signatures(); + $message = $sign[0][0]; + } + + if(!($key instanceof Crypt_RSA)) { + $key = new self($key); + if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16); + $key = $key->private_key($keyid); + } + $key = $key->withHash(strtolower($hash)); + + $sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash)); + $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); + $sig->sign_data(array('RSA' => array($hash => function($data) use($key) { + return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; + }))); + + return new OpenPGP_Message(array($sig, $message)); + } + + /** Pass a message with a key and userid packet to sign */ + // TODO: merge this with the normal sign function + function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) { + if(is_array($packet)) { + $packet = new OpenPGP_Message($packet); + } else if(!is_object($packet)) { + $packet = OpenPGP_Message::parse($packet); + } + + $key = $this->private_key($keyid); + if(!$key || !$packet) return NULL; // Missing some data + + if(!$keyid) $keyid = substr($this->key->fingerprint, -16); + $key = $key->withHash(strtolower($hash)); + + $sig = NULL; + foreach($packet as $p) { + if($p instanceof OpenPGP_SignaturePacket) $sig = $p; + } + if(!$sig) { + $sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash)); + $sig->signature_type = 0x13; + $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02)); + $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); + $packet[] = $sig; + } + + $sig->sign_data(array('RSA' => array($hash => function($data) use($key) { + return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; + }))); + + return $packet; + } + + function decrypt($packet) { + if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); + + if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA + || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { + $keys = $packet; + $message = $this->message; + } else { + $keys = $this->key; + $message = $packet; + } + + if(!$keys || !$message) return NULL; // Missing some data + + if(!($keys instanceof Crypt_RSA)) { + $keys = new self($keys); + } + + $session_key = NULL; + foreach($message as $p) { + if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) { + $session_key = $p; + if($keys instanceof Crypt_RSA) { + $sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2)); + } else if(strlen(str_replace('0', '', $p->keyid)) < 1) { + foreach($keys->key as $k) { + $sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2)); + if($sk) break; + } + } else { + $key = $keys->private_key($p->keyid); + $sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2)); + } + + if(!$sk) continue; + + $r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]); + if($r) return $r; + } + } + + if (!$session_key) throw new Exception("Not an asymmetrically encrypted message"); + + return NULL; /* Failed */ + } + + static function try_decrypt_session($key, $edata) { + $key = $key->withPadding(CRYPT_RSA_ENCRYPTION_PKCS1 | CRYPT_RSA_SIGNATURE_PKCS1); + try { + $data = $key->decrypt($edata); + } catch (\RuntimeException $e) { + return NULL; + } + + if(!$data) return NULL; + $sk = substr($data, 1, strlen($data)-3); + $chk = unpack('n', substr($data, -2)); + $chk = reset($chk); + + $sk_chk = 0; + for($i = 0; $i < strlen($sk); $i++) { + $sk_chk = ($sk_chk + ord($sk[$i])) % 65536; + } + + if($sk_chk != $chk) return NULL; + return array(ord($data[0]), $sk); + } + + static function crypt_rsa_key($mod, $exp, $hash='SHA256') { + return Crypt_RSA::loadPublicKey([ + 'e' => new Math_BigInteger($exp, 256), + 'n' => new Math_BigInteger($mod, 256), + ]) + ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) + ->withHash(strtolower($hash)); + } + + static function convert_key($packet, $private=false) { + if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); + if($packet instanceof OpenPGP_Message) $packet = $packet[0]; + + $exp = $packet->key['e']; + if($private) $exp = $packet->key['d']; + if(!$exp) return NULL; // Packet doesn't have needed data + + /** + * @see https://github.com/phpseclib/phpseclib/issues/1113 + * Primes and coefficients now use BigIntegers. + **/ + + if($private) { + // Invert p and q to make u work out as q' + $rawKey = [ + 'e' => new Math_BigInteger($packet->key['e'], 256), + 'n' => new Math_BigInteger($packet->key['n'], 256), + 'd' => new Math_BigInteger($packet->key['d'], 256), + 'q' => new Math_BigInteger($packet->key['p'], 256), + 'p' => new Math_BigInteger($packet->key['q'], 256), + ]; + if (array_key_exists('u', $packet->key)) { + // possible keys for 'u': https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Crypt/RSA/Formats/Keys/Raw.php#L108 + $rawKey['inerseq'] = new Math_BigInteger($packet->key['u'], 256); + } + + return publickeyloader::loadPrivateKey($rawKey) + ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) + ->withHash('sha256'); + } else { + + return publickeyloader::loadPublicKey([ + 'e' => new Math_BigInteger($packet->key['e'], 256), + 'n' => new Math_BigInteger($packet->key['n'], 256), + ]) + ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) + ->withHash('sha256'); + } + } + + static function convert_public_key($packet) { + return self::convert_key($packet, false); + } + + static function convert_private_key($packet) { + return self::convert_key($packet, true); + } + +} + +?> diff --git a/lib/openpgp_crypt_symmetric.php b/lib/openpgp_crypt_symmetric.php new file mode 100644 index 0000000..17ef96a --- /dev/null +++ b/lib/openpgp_crypt_symmetric.php @@ -0,0 +1,242 @@ +setKey($key); + + $to_encrypt = $prefix . $message->to_bytes(); + $mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true)); + $to_encrypt .= $mdc->to_bytes(); + $encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt))); + + if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) { + $passphrases_and_keys = (array)$passphrases_and_keys; + } + + foreach($passphrases_and_keys as $pass) { + if($pass instanceof OpenPGP_PublicKeyPacket) { + if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported."); + $crypt_rsa = new OpenPGP_Crypt_RSA($pass); + $rsa = $crypt_rsa->public_key()->withPadding(CRYPT_RSA_ENCRYPTION_PKCS1 | CRYPT_RSA_SIGNATURE_PKCS1); + $esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key))); + $esk = pack('n', OpenPGP::bitlength($esk)) . $esk; + array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk)); + } else if(is_string($pass)) { + $s2k = new OpenPGP_S2K(Random::string(8)); + $cipher->setKey($s2k->make_key($pass, $key_bytes)); + $esk = $cipher->encrypt(chr($symmetric_algorithm) . $key); + array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm)); + } + } + + return new OpenPGP_Message($encrypted); + } + + public static function decryptSymmetric($pass, $m) { + $epacket = self::getEncryptedData($m); + + foreach($m as $p) { + if($p instanceof OpenPGP_SymmetricSessionKeyPacket) { + if(strlen($p->encrypted_data) > 0) { + list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm); + if(!$cipher) continue; + $cipher->setKey($p->s2k->make_key($pass, $key_bytes)); + + $padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes); + $data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data)); + $decrypted = self::decryptPacket($epacket, ord($data[0]), substr($data, 1)); + } else { + list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm); + $decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes)); + } + + if($decrypted) return $decrypted; + } + } + + return NULL; /* If we get here, we failed */ + } + + public static function encryptSecretKey($pass, $packet, $symmetric_algorithm=9) { + $packet = clone $packet; // Do not mutate original + $packet->s2k_useage = 254; + $packet->symmetric_algorithm = $symmetric_algorithm; + + list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm); + if(!$cipher) throw new Exception("Unsupported cipher"); + + $material = ''; + foreach(OpenPGP_SecretKeyPacket::$secret_key_fields[$packet->algorithm] as $field) { + $f = $packet->key[$field]; + $material .= pack('n', OpenPGP::bitlength($f)) . $f; + unset($packet->key[$field]); + } + $material .= hash('sha1', $material, true); + + $iv = Random::string($key_block_bytes); + if(!$packet->s2k) $packet->s2k = new OpenPGP_S2K(Random::string(8)); + $cipher->setKey($packet->s2k->make_key($pass, $key_bytes)); + $cipher->setIV($iv); + $packet->encrypted_data = $iv . $cipher->encrypt($material); + + return $packet; + } + + public static function decryptSecretKey($pass, $packet) { + $packet = clone $packet; // Do not mutate orinigal + + list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm); + if(!$cipher) throw new Exception("Unsupported cipher"); + $cipher->setKey($packet->s2k->make_key($pass, $key_bytes)); + $cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes)); + $material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes)); + + if($packet->s2k_useage == 254) { + $chk = substr($material, -20); + $material = substr($material, 0, -20); + if($chk != hash('sha1', $material, true)) return NULL; + } else { + $chk = unpack('n', substr($material, -2)); + $chk = reset($chk); + $material = substr($material, 0, -2); + + $mkChk = self::checksum($material); + if($chk != $mkChk) return NULL; + } + + $packet->s2k = NULL; + $packet->s2k_useage = 0; + $packet->symmetric_algorithm = 0; + $packet->encrypted_data = NULL; + $packet->input = $material; + $packet->key_from_input(); + unset($packet->input); + return $packet; + } + + public static function decryptPacket($epacket, $symmetric_algorithm, $key) { + list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm); + if(!$cipher) return NULL; + $cipher->setKey($key); + + if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) { + $padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes); + $data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data)); + $prefix = substr($data, 0, $key_block_bytes + 2); + $mdc = substr(substr($data, -22, 22), 2); + $data = substr($data, $key_block_bytes + 2, -22); + + $mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true); + if($mkMDC !== $mdc) return false; + + try { + $msg = OpenPGP_Message::parse($data); + } catch (Exception $ex) { $msg = NULL; } + if($msg) return $msg; /* Otherwise keep trying */ + } else { + // No MDC mean decrypt with resync + $iv = substr($epacket->data, 2, $key_block_bytes); + $edata = substr($epacket->data, $key_block_bytes + 2); + $padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes); + + $cipher->setIV($iv); + $data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata)); + + try { + $msg = OpenPGP_Message::parse($data); + } catch (Exception $ex) { $msg = NULL; } + if($msg) return $msg; /* Otherwise keep trying */ + } + + return NULL; /* Failed */ + } + + public static function getCipher($algo) { + $cipher = NULL; + + // https://datatracker.ietf.org/doc/html/rfc4880#section-13.9 + // " 1. The feedback register (FR) is set to the IV, which is all zeros." + switch($algo) { + case NULL: + case 0: + throw new Exception("Data is already unencrypted"); + case 2: + $cipher = new Crypt_TripleDES('cfb'); + $cipher->setIV(str_repeat(pack('x'), 8)); + $key_bytes = 24; + $key_block_bytes = 8; + break; + case 3: + if(class_exists('OpenSSLWrapper')) { + $cipher = new OpenSSLWrapper("CAST5-CFB"); + } else if(defined('MCRYPT_CAST_128')) { + $cipher = new MCryptWrapper(MCRYPT_CAST_128); + } + break; + case 4: + $cipher = new Crypt_Blowfish('cfb'); + $cipher->setIV(str_repeat(pack('x'), 8)); + $key_bytes = 16; + $key_block_bytes = 8; + break; + case 7: + $cipher = new Crypt_AES('cfb'); + $cipher->setKeyLength(128); + $cipher->setIV(str_repeat(pack('x'), 16)); + break; + case 8: + $cipher = new Crypt_AES('cfb'); + $cipher->setKeyLength(192); + $cipher->setIV(str_repeat(pack('x'), 16)); + break; + case 9: + $cipher = new Crypt_AES('cfb'); + $cipher->setKeyLength(256); + $cipher->setIV(str_repeat(pack('x'), 16)); + break; + case 10: + $cipher = new Crypt_Twofish('cfb'); + $cipher->setIV(str_repeat(pack('x'), 16)); + $key_bytes = 32; + break; + } + if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher + + + if(!isset($key_bytes)) $key_bytes = $cipher->getKeyLength() >> 3; + if(!isset($key_block_bytes)) $key_block_bytes = $cipher->getBlockLengthInBytes(); + return array($cipher, $key_bytes, $key_block_bytes); + } + + public static function getEncryptedData($m) { + foreach($m as $p) { + if($p instanceof OpenPGP_EncryptedDataPacket) return $p; + } + throw new Exception("Can only decrypt EncryptedDataPacket"); + } + + public static function checksum($s) { + $mkChk = 0; + for($i = 0; $i < strlen($s); $i++) { + $mkChk = ($mkChk + ord($s[$i])) % 65536; + } + return $mkChk; + } +} diff --git a/lib/openpgp_mcrypt_wrapper.php b/lib/openpgp_mcrypt_wrapper.php new file mode 100644 index 0000000..65fc145 --- /dev/null +++ b/lib/openpgp_mcrypt_wrapper.php @@ -0,0 +1,40 @@ +cipher = $cipher; + $this->key_size = mcrypt_module_get_algo_key_size($cipher); + $this->block_size = mcrypt_module_get_algo_block_size($cipher); + $this->iv = str_repeat("\0", mcrypt_get_iv_size($cipher, 'ncfb')); + } + + function getBlockLengthInBytes() + { + return $this->block_size; + } + + function getKeyLength() { + return $this->key_size << 3; + } + + function setKey($key) { + $this->key = $key; + } + + function setIV($iv) { + $this->iv = $iv; + } + + function encrypt($data) { + return mcrypt_encrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv); + } + + function decrypt($data) { + return mcrypt_decrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv); + } + } +} diff --git a/lib/openpgp_openssl_wrapper.php b/lib/openpgp_openssl_wrapper.php new file mode 100644 index 0000000..7ebbb78 --- /dev/null +++ b/lib/openpgp_openssl_wrapper.php @@ -0,0 +1,42 @@ +cipher = $cipher; + $this->key_size = 16; + $this->block_size = 8; + $this->iv = str_repeat("\0", 8); + } + + function getBlockLengthInBytes() + { + return $this->block_size; + } + + function getKeyLength() { + return $this->key_size << 3; + } + + function setKey($key) { + $this->key = $key; + } + + function setIV($iv) { + $this->iv = $iv; + } + + function encrypt($data) { + return openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv); + } + + function decrypt($data) { + return openssl_decrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv); + } + } +} diff --git a/lib/openpgp_sodium.php b/lib/openpgp_sodium.php new file mode 100644 index 0000000..08a95db --- /dev/null +++ b/lib/openpgp_sodium.php @@ -0,0 +1,24 @@ +fingerprint, strlen($s->issuer())*-1) == $s->issuer()) { + $pk = $p; + break; + } + } + } + } + + if ($pk->algorithm != 22) throw new Exception("Only EdDSA supported"); + if (bin2hex($pk->key['oid']) != '2b06010401da470f01') throw new Exception("Only ed25519 supported"); + return sodium_crypto_sign_verify_detached( + implode($s->data), + hash($s->hash_algorithm_name(), $m, true), + substr($pk->key['p'], 1) + ); + }; +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..bb86520 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,35 @@ + + + + tests/suite.php + + + + tests/suite.php + + + + tests/suite.php + + + + tests/phpseclib_suite.php + + + + tests/phpseclib_suite.php + + + + tests/phpseclib_suite.php + + + + tests/phpseclib_suite.php + + + + tests/sodium_suite.php + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..9635a9c --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/tests/data/000003-002.sig b/tests/data/000003-002.sig new file mode 100644 index 0000000..1e0656d Binary files /dev/null and b/tests/data/000003-002.sig differ diff --git a/tests/data/000004-012.ring_trust b/tests/data/000004-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000004-012.ring_trust differ diff --git a/tests/data/000005-002.sig b/tests/data/000005-002.sig new file mode 100644 index 0000000..108b998 Binary files /dev/null and b/tests/data/000005-002.sig differ diff --git a/tests/data/000006-012.ring_trust b/tests/data/000006-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000006-012.ring_trust differ diff --git a/tests/data/000007-002.sig b/tests/data/000007-002.sig new file mode 100644 index 0000000..14276d0 Binary files /dev/null and b/tests/data/000007-002.sig differ diff --git a/tests/data/000008-012.ring_trust b/tests/data/000008-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000008-012.ring_trust differ diff --git a/tests/data/000009-002.sig b/tests/data/000009-002.sig new file mode 100644 index 0000000..4a282dd Binary files /dev/null and b/tests/data/000009-002.sig differ diff --git a/tests/data/000010-012.ring_trust b/tests/data/000010-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000010-012.ring_trust differ diff --git a/tests/data/000011-002.sig b/tests/data/000011-002.sig new file mode 100644 index 0000000..cae1b73 Binary files /dev/null and b/tests/data/000011-002.sig differ diff --git a/tests/data/000012-012.ring_trust b/tests/data/000012-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000012-012.ring_trust differ diff --git a/tests/data/000013-014.public_subkey b/tests/data/000013-014.public_subkey new file mode 100644 index 0000000..08676d0 Binary files /dev/null and b/tests/data/000013-014.public_subkey differ diff --git a/tests/data/000014-002.sig b/tests/data/000014-002.sig new file mode 100644 index 0000000..dd60180 Binary files /dev/null and b/tests/data/000014-002.sig differ diff --git a/tests/data/000015-012.ring_trust b/tests/data/000015-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000015-012.ring_trust differ diff --git a/tests/data/000016-006.public_key b/tests/data/000016-006.public_key new file mode 100644 index 0000000..c9dccbf Binary files /dev/null and b/tests/data/000016-006.public_key differ diff --git a/tests/data/000017-002.sig b/tests/data/000017-002.sig new file mode 100644 index 0000000..e734505 Binary files /dev/null and b/tests/data/000017-002.sig differ diff --git a/tests/data/000018-012.ring_trust b/tests/data/000018-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000018-012.ring_trust differ diff --git a/tests/data/000019-013.user_id b/tests/data/000019-013.user_id new file mode 100644 index 0000000..ab3f51d --- /dev/null +++ b/tests/data/000019-013.user_id @@ -0,0 +1 @@ +$Test Key (DSA) \ No newline at end of file diff --git a/tests/data/000020-002.sig b/tests/data/000020-002.sig new file mode 100644 index 0000000..8588489 Binary files /dev/null and b/tests/data/000020-002.sig differ diff --git a/tests/data/000021-012.ring_trust b/tests/data/000021-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000021-012.ring_trust differ diff --git a/tests/data/000022-002.sig b/tests/data/000022-002.sig new file mode 100644 index 0000000..fefcb5f Binary files /dev/null and b/tests/data/000022-002.sig differ diff --git a/tests/data/000023-012.ring_trust b/tests/data/000023-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000023-012.ring_trust differ diff --git a/tests/data/000024-014.public_subkey b/tests/data/000024-014.public_subkey new file mode 100644 index 0000000..2e8deea Binary files /dev/null and b/tests/data/000024-014.public_subkey differ diff --git a/tests/data/000025-002.sig b/tests/data/000025-002.sig new file mode 100644 index 0000000..a3eea0a Binary files /dev/null and b/tests/data/000025-002.sig differ diff --git a/tests/data/000026-012.ring_trust b/tests/data/000026-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000026-012.ring_trust differ diff --git a/tests/data/000027-006.public_key b/tests/data/000027-006.public_key new file mode 100644 index 0000000..5817e00 Binary files /dev/null and b/tests/data/000027-006.public_key differ diff --git a/tests/data/000028-002.sig b/tests/data/000028-002.sig new file mode 100644 index 0000000..5194b78 Binary files /dev/null and b/tests/data/000028-002.sig differ diff --git a/tests/data/000029-012.ring_trust b/tests/data/000029-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000029-012.ring_trust differ diff --git a/tests/data/000030-013.user_id b/tests/data/000030-013.user_id new file mode 100644 index 0000000..fb3f49e --- /dev/null +++ b/tests/data/000030-013.user_id @@ -0,0 +1 @@ ++Test Key (DSA sign-only) \ No newline at end of file diff --git a/tests/data/000031-002.sig b/tests/data/000031-002.sig new file mode 100644 index 0000000..f69f687 Binary files /dev/null and b/tests/data/000031-002.sig differ diff --git a/tests/data/000032-012.ring_trust b/tests/data/000032-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000032-012.ring_trust differ diff --git a/tests/data/000033-002.sig b/tests/data/000033-002.sig new file mode 100644 index 0000000..2bb55d4 Binary files /dev/null and b/tests/data/000033-002.sig differ diff --git a/tests/data/000034-012.ring_trust b/tests/data/000034-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000034-012.ring_trust differ diff --git a/tests/data/000035-006.public_key b/tests/data/000035-006.public_key new file mode 100644 index 0000000..5980638 Binary files /dev/null and b/tests/data/000035-006.public_key differ diff --git a/tests/data/000036-013.user_id b/tests/data/000036-013.user_id new file mode 100644 index 0000000..5d0d46e --- /dev/null +++ b/tests/data/000036-013.user_id @@ -0,0 +1 @@ +.Test Key (RSA sign-only) \ No newline at end of file diff --git a/tests/data/000037-002.sig b/tests/data/000037-002.sig new file mode 100644 index 0000000..833b563 Binary files /dev/null and b/tests/data/000037-002.sig differ diff --git a/tests/data/000038-012.ring_trust b/tests/data/000038-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000038-012.ring_trust differ diff --git a/tests/data/000039-002.sig b/tests/data/000039-002.sig new file mode 100644 index 0000000..89c34fa Binary files /dev/null and b/tests/data/000039-002.sig differ diff --git a/tests/data/000040-012.ring_trust b/tests/data/000040-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000040-012.ring_trust differ diff --git a/tests/data/000041-017.attribute b/tests/data/000041-017.attribute new file mode 100644 index 0000000..a21a82f Binary files /dev/null and b/tests/data/000041-017.attribute differ diff --git a/tests/data/000042-002.sig b/tests/data/000042-002.sig new file mode 100644 index 0000000..fc6267f Binary files /dev/null and b/tests/data/000042-002.sig differ diff --git a/tests/data/000043-012.ring_trust b/tests/data/000043-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000043-012.ring_trust differ diff --git a/tests/data/000044-014.public_subkey b/tests/data/000044-014.public_subkey new file mode 100644 index 0000000..06bf50e Binary files /dev/null and b/tests/data/000044-014.public_subkey differ diff --git a/tests/data/000045-002.sig b/tests/data/000045-002.sig new file mode 100644 index 0000000..336eb0f Binary files /dev/null and b/tests/data/000045-002.sig differ diff --git a/tests/data/000046-012.ring_trust b/tests/data/000046-012.ring_trust new file mode 100644 index 0000000..ffa57e5 Binary files /dev/null and b/tests/data/000046-012.ring_trust differ diff --git a/tests/data/000047-005.secret_key b/tests/data/000047-005.secret_key new file mode 100644 index 0000000..77b5d42 Binary files /dev/null and b/tests/data/000047-005.secret_key differ diff --git a/tests/data/000048-013.user_id b/tests/data/000048-013.user_id new file mode 100644 index 0000000..759449b --- /dev/null +++ b/tests/data/000048-013.user_id @@ -0,0 +1 @@ +$Test Key (RSA) \ No newline at end of file diff --git a/tests/data/000049-002.sig b/tests/data/000049-002.sig new file mode 100644 index 0000000..14276d0 Binary files /dev/null and b/tests/data/000049-002.sig differ diff --git a/tests/data/000050-012.ring_trust b/tests/data/000050-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000050-012.ring_trust differ diff --git a/tests/data/000051-007.secret_subkey b/tests/data/000051-007.secret_subkey new file mode 100644 index 0000000..b4e65c9 Binary files /dev/null and b/tests/data/000051-007.secret_subkey differ diff --git a/tests/data/000052-002.sig b/tests/data/000052-002.sig new file mode 100644 index 0000000..dd60180 Binary files /dev/null and b/tests/data/000052-002.sig differ diff --git a/tests/data/000053-012.ring_trust b/tests/data/000053-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000053-012.ring_trust differ diff --git a/tests/data/000054-005.secret_key b/tests/data/000054-005.secret_key new file mode 100644 index 0000000..f153e59 Binary files /dev/null and b/tests/data/000054-005.secret_key differ diff --git a/tests/data/000055-002.sig b/tests/data/000055-002.sig new file mode 100644 index 0000000..e734505 Binary files /dev/null and b/tests/data/000055-002.sig differ diff --git a/tests/data/000056-012.ring_trust b/tests/data/000056-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000056-012.ring_trust differ diff --git a/tests/data/000057-013.user_id b/tests/data/000057-013.user_id new file mode 100644 index 0000000..ab3f51d --- /dev/null +++ b/tests/data/000057-013.user_id @@ -0,0 +1 @@ +$Test Key (DSA) \ No newline at end of file diff --git a/tests/data/000058-002.sig b/tests/data/000058-002.sig new file mode 100644 index 0000000..8588489 Binary files /dev/null and b/tests/data/000058-002.sig differ diff --git a/tests/data/000059-012.ring_trust b/tests/data/000059-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000059-012.ring_trust differ diff --git a/tests/data/000060-007.secret_subkey b/tests/data/000060-007.secret_subkey new file mode 100644 index 0000000..9df45f3 Binary files /dev/null and b/tests/data/000060-007.secret_subkey differ diff --git a/tests/data/000061-002.sig b/tests/data/000061-002.sig new file mode 100644 index 0000000..6394942 Binary files /dev/null and b/tests/data/000061-002.sig differ diff --git a/tests/data/000062-012.ring_trust b/tests/data/000062-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000062-012.ring_trust differ diff --git a/tests/data/000063-005.secret_key b/tests/data/000063-005.secret_key new file mode 100644 index 0000000..2f4268e Binary files /dev/null and b/tests/data/000063-005.secret_key differ diff --git a/tests/data/000064-002.sig b/tests/data/000064-002.sig new file mode 100644 index 0000000..5194b78 Binary files /dev/null and b/tests/data/000064-002.sig differ diff --git a/tests/data/000065-012.ring_trust b/tests/data/000065-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000065-012.ring_trust differ diff --git a/tests/data/000066-013.user_id b/tests/data/000066-013.user_id new file mode 100644 index 0000000..fb3f49e --- /dev/null +++ b/tests/data/000066-013.user_id @@ -0,0 +1 @@ ++Test Key (DSA sign-only) \ No newline at end of file diff --git a/tests/data/000067-002.sig b/tests/data/000067-002.sig new file mode 100644 index 0000000..d354e79 Binary files /dev/null and b/tests/data/000067-002.sig differ diff --git a/tests/data/000068-012.ring_trust b/tests/data/000068-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000068-012.ring_trust differ diff --git a/tests/data/000069-005.secret_key b/tests/data/000069-005.secret_key new file mode 100644 index 0000000..17a2c35 Binary files /dev/null and b/tests/data/000069-005.secret_key differ diff --git a/tests/data/000070-013.user_id b/tests/data/000070-013.user_id new file mode 100644 index 0000000..5d0d46e --- /dev/null +++ b/tests/data/000070-013.user_id @@ -0,0 +1 @@ +.Test Key (RSA sign-only) \ No newline at end of file diff --git a/tests/data/000071-002.sig b/tests/data/000071-002.sig new file mode 100644 index 0000000..833b563 Binary files /dev/null and b/tests/data/000071-002.sig differ diff --git a/tests/data/000072-012.ring_trust b/tests/data/000072-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000072-012.ring_trust differ diff --git a/tests/data/000073-017.attribute b/tests/data/000073-017.attribute new file mode 100644 index 0000000..a21a82f Binary files /dev/null and b/tests/data/000073-017.attribute differ diff --git a/tests/data/000074-002.sig b/tests/data/000074-002.sig new file mode 100644 index 0000000..fc6267f Binary files /dev/null and b/tests/data/000074-002.sig differ diff --git a/tests/data/000075-012.ring_trust b/tests/data/000075-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000075-012.ring_trust differ diff --git a/tests/data/000076-007.secret_subkey b/tests/data/000076-007.secret_subkey new file mode 100644 index 0000000..b380339 Binary files /dev/null and b/tests/data/000076-007.secret_subkey differ diff --git a/tests/data/000077-002.sig b/tests/data/000077-002.sig new file mode 100644 index 0000000..336eb0f Binary files /dev/null and b/tests/data/000077-002.sig differ diff --git a/tests/data/000078-012.ring_trust b/tests/data/000078-012.ring_trust new file mode 100644 index 0000000..b1eeabb Binary files /dev/null and b/tests/data/000078-012.ring_trust differ diff --git a/tests/data/000079-002.sig b/tests/data/000079-002.sig new file mode 100644 index 0000000..d5a51b9 Binary files /dev/null and b/tests/data/000079-002.sig differ diff --git a/tests/data/000080-006.public_key b/tests/data/000080-006.public_key new file mode 100644 index 0000000..dce135c Binary files /dev/null and b/tests/data/000080-006.public_key differ diff --git a/tests/data/000081-002.sig b/tests/data/000081-002.sig new file mode 100644 index 0000000..e428a87 Binary files /dev/null and b/tests/data/000081-002.sig differ diff --git a/tests/data/000082-006.public_key b/tests/data/000082-006.public_key new file mode 100644 index 0000000..f521feb Binary files /dev/null and b/tests/data/000082-006.public_key differ diff --git a/tests/data/000083-002.sig b/tests/data/000083-002.sig new file mode 100644 index 0000000..ecff87f Binary files /dev/null and b/tests/data/000083-002.sig differ diff --git a/tests/data/002182-002.sig b/tests/data/002182-002.sig new file mode 100644 index 0000000..2bc6679 Binary files /dev/null and b/tests/data/002182-002.sig differ diff --git a/tests/data/compressedsig-bzip2.gpg b/tests/data/compressedsig-bzip2.gpg new file mode 100644 index 0000000..87539db Binary files /dev/null and b/tests/data/compressedsig-bzip2.gpg differ diff --git a/tests/data/compressedsig-zlib.gpg b/tests/data/compressedsig-zlib.gpg new file mode 100644 index 0000000..4da4dfa Binary files /dev/null and b/tests/data/compressedsig-zlib.gpg differ diff --git a/tests/data/compressedsig.gpg b/tests/data/compressedsig.gpg new file mode 100644 index 0000000..dd617de Binary files /dev/null and b/tests/data/compressedsig.gpg differ diff --git a/tests/data/ed25519.public_key b/tests/data/ed25519.public_key new file mode 100644 index 0000000..eda21e4 Binary files /dev/null and b/tests/data/ed25519.public_key differ diff --git a/tests/data/ed25519.secret_key b/tests/data/ed25519.secret_key new file mode 100644 index 0000000..acfbdf5 Binary files /dev/null and b/tests/data/ed25519.secret_key differ diff --git a/tests/data/ed25519.sig b/tests/data/ed25519.sig new file mode 100644 index 0000000..7c585a8 Binary files /dev/null and b/tests/data/ed25519.sig differ diff --git a/tests/data/encryptedSecretKey.gpg b/tests/data/encryptedSecretKey.gpg new file mode 100644 index 0000000..af700e8 Binary files /dev/null and b/tests/data/encryptedSecretKey.gpg differ diff --git a/tests/data/hello.gpg b/tests/data/hello.gpg new file mode 100644 index 0000000..986de95 Binary files /dev/null and b/tests/data/hello.gpg differ diff --git a/tests/data/helloKey.gpg b/tests/data/helloKey.gpg new file mode 100644 index 0000000..b1dd078 Binary files /dev/null and b/tests/data/helloKey.gpg differ diff --git a/tests/data/onepass_sig b/tests/data/onepass_sig new file mode 100644 index 0000000..87b2895 Binary files /dev/null and b/tests/data/onepass_sig differ diff --git a/tests/data/pubring.gpg b/tests/data/pubring.gpg new file mode 100644 index 0000000..a1519ee Binary files /dev/null and b/tests/data/pubring.gpg differ diff --git a/tests/data/secring.gpg b/tests/data/secring.gpg new file mode 100644 index 0000000..9e43912 Binary files /dev/null and b/tests/data/secring.gpg differ diff --git a/tests/data/symmetric-3des.gpg b/tests/data/symmetric-3des.gpg new file mode 100644 index 0000000..0029cae Binary files /dev/null and b/tests/data/symmetric-3des.gpg differ diff --git a/tests/data/symmetric-aes.gpg b/tests/data/symmetric-aes.gpg new file mode 100644 index 0000000..f148ada --- /dev/null +++ b/tests/data/symmetric-aes.gpg @@ -0,0 +1 @@ +  FyD`>~iXaMM7*WK1I&U]-axsn֎jUǕ%ԩe \ No newline at end of file diff --git a/tests/data/symmetric-blowfish.gpg b/tests/data/symmetric-blowfish.gpg new file mode 100644 index 0000000..0dda30c --- /dev/null +++ b/tests/data/symmetric-blowfish.gpg @@ -0,0 +1 @@ + hϳfuhs_VF4 \ No newline at end of file diff --git a/tests/data/symmetric-cast5.gpg b/tests/data/symmetric-cast5.gpg new file mode 100644 index 0000000..950b791 --- /dev/null +++ b/tests/data/symmetric-cast5.gpg @@ -0,0 +1 @@ + 9F-`2萗wD0q,z}Zϲ֣#־ aٛ!! \ No newline at end of file diff --git a/tests/data/symmetric-no-mdc.gpg b/tests/data/symmetric-no-mdc.gpg new file mode 100644 index 0000000..40d31b0 --- /dev/null +++ b/tests/data/symmetric-no-mdc.gpg @@ -0,0 +1,2 @@ + [JHB`#{%|6buC g+ +H \ No newline at end of file diff --git a/tests/data/symmetric-twofish.gpg b/tests/data/symmetric-twofish.gpg new file mode 100644 index 0000000..14255d8 --- /dev/null +++ b/tests/data/symmetric-twofish.gpg @@ -0,0 +1,3 @@ +  +cІ 9=]TfA cvekʲn}%.lu?\I +[bl \ No newline at end of file diff --git a/tests/data/symmetric-with-session-key.gpg b/tests/data/symmetric-with-session-key.gpg new file mode 100644 index 0000000..0b623f1 Binary files /dev/null and b/tests/data/symmetric-with-session-key.gpg differ diff --git a/tests/data/symmetrically_encrypted b/tests/data/symmetrically_encrypted new file mode 100644 index 0000000..129155a Binary files /dev/null and b/tests/data/symmetrically_encrypted differ diff --git a/tests/data/uncompressed-ops-dsa-sha384.txt.gpg b/tests/data/uncompressed-ops-dsa-sha384.txt.gpg new file mode 100644 index 0000000..39828fc Binary files /dev/null and b/tests/data/uncompressed-ops-dsa-sha384.txt.gpg differ diff --git a/tests/data/uncompressed-ops-dsa.gpg b/tests/data/uncompressed-ops-dsa.gpg new file mode 100644 index 0000000..97e7a26 Binary files /dev/null and b/tests/data/uncompressed-ops-dsa.gpg differ diff --git a/tests/data/uncompressed-ops-rsa.gpg b/tests/data/uncompressed-ops-rsa.gpg new file mode 100644 index 0000000..7ae453d Binary files /dev/null and b/tests/data/uncompressed-ops-rsa.gpg differ diff --git a/tests/phpseclib_suite.php b/tests/phpseclib_suite.php new file mode 100644 index 0000000..742ac43 --- /dev/null +++ b/tests/phpseclib_suite.php @@ -0,0 +1,228 @@ +assertSame($verify->verify($m), $m->signatures()); + } + + public function testUncompressedOpsRSA() { + $this->oneMessageRSA('pubring.gpg', 'uncompressed-ops-rsa.gpg'); + } + + public function testCompressedSig() { + $this->oneMessageRSA('pubring.gpg', 'compressedsig.gpg'); + } + + public function testCompressedSigZLIB() { + $this->oneMessageRSA('pubring.gpg', 'compressedsig-zlib.gpg'); + } + + public function testCompressedSigBzip2() { + $this->oneMessageRSA('pubring.gpg', 'compressedsig-bzip2.gpg'); + } + + public function testSigningMessages() { + $wkey = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + if (function_exists('uopz_set_return')) uopz_set_return('time', 0); + $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); + $sign = new OpenPGP_Crypt_RSA($wkey); + $m = $sign->sign($data)->to_bytes(); + $reparsedM = OpenPGP_Message::parse($m); + if (function_exists('uopz_unset_return')) { + uopz_unset_return('time'); + $this->assertSame(4871, $reparsedM[0]->hash_head); + } + $this->assertSame($sign->verify($reparsedM), $reparsedM->signatures()); + } + +/* + public function testUncompressedOpsDSA() { + $this->oneMessageDSA('pubring.gpg', 'uncompressed-ops-dsa.gpg'); + } + + public function testUncompressedOpsDSAsha384() { + $this->oneMessageDSA('pubring.gpg', 'uncompressed-ops-dsa-sha384.gpg'); + } +*/ +} + + +class KeyVerification extends TestCase { + public function oneKeyRSA($path) { + $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); + $verify = new OpenPGP_Crypt_RSA($m); + $this->assertSame($verify->verify($m), $m->signatures()); + } + + public function testHelloKey() { + $this->oneKeyRSA("helloKey.gpg"); + } +} + +abstract class LibTestCase extends TestCase { + public function assertCast5Support() { + if(in_array('mcrypt', get_loaded_extensions())) { + return; + } + if(in_array('cast5-cfb', openssl_get_cipher_methods()) || in_array('CAST5-CFB', openssl_get_cipher_methods())) { + return; + } + $this->markTestSkipped('Not supported'); + } +} + +class Decryption extends LibTestCase { + public function oneSymmetric($pass, $cnt, $path) { + $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); + $m2 = OpenPGP_Crypt_Symmetric::decryptSymmetric($pass, $m); + while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data; + foreach($m2 as $p) { + if($p instanceof OpenPGP_LiteralDataPacket) { + $this->assertEquals($p->data, $cnt); + } + } + } + + public function testDecrypt3DES() { + $this->oneSymmetric("hello", "PGP\n", "symmetric-3des.gpg"); + } + + public function testDecryptCAST5() { // Requires mcrypt or openssl + $this->assertCast5Support(); + $this->oneSymmetric("hello", "PGP\n", "symmetric-cast5.gpg"); + } + + public function testDecryptBlowfish() { + $this->oneSymmetric("hello", "PGP\n", "symmetric-blowfish.gpg"); + } + + public function testDecryptAES() { + $this->oneSymmetric("hello", "PGP\n", "symmetric-aes.gpg"); + } + + public function testDecryptTwofish() { + if(OpenPGP_Crypt_Symmetric::getCipher(10)[0]) { + $this->oneSymmetric("hello", "PGP\n", "symmetric-twofish.gpg"); + } + } + + public function testDecryptSessionKey() { + $this->oneSymmetric("hello", "PGP\n", "symmetric-with-session-key.gpg"); + } + + public function testDecryptNoMDC() { + $this->oneSymmetric("hello", "PGP\n", "symmetric-no-mdc.gpg"); + } + + public function testDecryptAsymmetric() { + $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/hello.gpg')); + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + $decryptor = new OpenPGP_Crypt_RSA($key); + $m2 = $decryptor->decrypt($m); + while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data; + foreach($m2 as $p) { + if($p instanceof OpenPGP_LiteralDataPacket) { + $this->assertEquals($p->data, "hello\n"); + } + } + } + + public function testDecryptRoundtrip() { + $m = new OpenPGP_Message(array(new OpenPGP_LiteralDataPacket("hello\n"))); + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + $em = OpenPGP_Crypt_Symmetric::encrypt($key, $m); + + foreach($key as $packet) { + if(!($packet instanceof OpenPGP_SecretKeyPacket)) continue; + $decryptor = new OpenPGP_Crypt_RSA($packet); + $m2 = $decryptor->decrypt($em); + + foreach($m2 as $p) { + if($p instanceof OpenPGP_LiteralDataPacket) { + $this->assertEquals($p->data, "hello\n"); + } + } + } + } + + public function testDecryptSecretKey() { + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/encryptedSecretKey.gpg')); + $skey = OpenPGP_Crypt_Symmetric::decryptSecretKey("hello", $key[0]); + $this->assertSame(!!$skey, true); + } + + public function testEncryptSecretKeyRoundtrip() { + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + $enkey = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $key[0]); + $skey = OpenPGP_Crypt_Symmetric::decryptSecretKey("password", $enkey); + $this->assertEquals($key[0], $skey); + } + + public function testAlreadyDecryptedSecretKey() { + $this->expectException(Exception::class); + $this->expectExceptionMessage("Data is already unencrypted"); + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + OpenPGP_Crypt_Symmetric::decryptSecretKey("hello", $key[0]); + } +} + +class Encryption extends LibTestCase { + public function oneSymmetric($algorithm) { + $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); + $encrypted = OpenPGP_Crypt_Symmetric::encrypt('secret', new OpenPGP_Message(array($data)), $algorithm); + $encrypted = OpenPGP_Message::parse($encrypted->to_bytes()); + $decrypted = OpenPGP_Crypt_Symmetric::decryptSymmetric('secret', $encrypted); + $this->assertEquals($decrypted[0]->data, 'This is text.'); + } + + public function testEncryptSymmetric3DES() { + $this->oneSymmetric(2); + } + + public function testEncryptSymmetricCAST5() { + $this->assertCast5Support(); + $this->oneSymmetric(3); + } + + public function testEncryptSymmetricBlowfish() { + $this->oneSymmetric(4); + } + + public function testEncryptSymmetricAES128() { + $this->oneSymmetric(7); + } + + public function testEncryptSymmetricAES192() { + $this->oneSymmetric(8); + } + + public function testEncryptSymmetricAES256() { + $this->oneSymmetric(9); + } + + public function testEncryptSymmetricTwofish() { + if(OpenPGP_Crypt_Symmetric::getCipher(10)[0]) { + $this->oneSymmetric(10); + } + } + + public function testEncryptAsymmetric() { + $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); + $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); + $encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data))); + $encrypted = OpenPGP_Message::parse($encrypted->to_bytes()); + $decryptor = new OpenPGP_Crypt_RSA($key); + $decrypted = $decryptor->decrypt($encrypted); + $this->assertEquals($decrypted[0]->data, 'This is text.'); + } +} diff --git a/tests/sodium_suite.php b/tests/sodium_suite.php new file mode 100644 index 0000000..22f5d55 --- /dev/null +++ b/tests/sodium_suite.php @@ -0,0 +1,20 @@ +assertSame($m->verified_signatures(array('EdDSA' => $verify)), $m->signatures()); + } + + public function tested25519() { + $this->oneMessageEdDSA('ed25519.public_key', 'ed25519.sig'); + } +} diff --git a/tests/suite.php b/tests/suite.php new file mode 100644 index 0000000..fbd6f3b --- /dev/null +++ b/tests/suite.php @@ -0,0 +1,451 @@ +to_bytes(); + $out = OpenPGP_Message::parse($mid); + $this->assertEquals($in, $out); + } + + public function test000001006public_key() { + $this->oneSerialization("000001-006.public_key"); + } + + public function test000002013user_id() { + $this->oneSerialization("000002-013.user_id"); + } + + public function test000003002sig() { + $this->oneSerialization("000003-002.sig"); + } + + public function test000004012ring_trust() { + $this->oneSerialization("000004-012.ring_trust"); + } + + public function test000005002sig() { + $this->oneSerialization("000005-002.sig"); + } + + public function test000006012ring_trust() { + $this->oneSerialization("000006-012.ring_trust"); + } + + public function test000007002sig() { + $this->oneSerialization("000007-002.sig"); + } + + public function test000008012ring_trust() { + $this->oneSerialization("000008-012.ring_trust"); + } + + public function test000009002sig() { + $this->oneSerialization("000009-002.sig"); + } + + public function test000010012ring_trust() { + $this->oneSerialization("000010-012.ring_trust"); + } + + public function test000011002sig() { + $this->oneSerialization("000011-002.sig"); + } + + public function test000012012ring_trust() { + $this->oneSerialization("000012-012.ring_trust"); + } + + public function test000013014public_subkey() { + $this->oneSerialization("000013-014.public_subkey"); + } + + public function test000014002sig() { + $this->oneSerialization("000014-002.sig"); + } + + public function test000015012ring_trust() { + $this->oneSerialization("000015-012.ring_trust"); + } + + public function test000016006public_key() { + $this->oneSerialization("000016-006.public_key"); + } + + public function test000017002sig() { + $this->oneSerialization("000017-002.sig"); + } + + public function test000018012ring_trust() { + $this->oneSerialization("000018-012.ring_trust"); + } + + public function test000019013user_id() { + $this->oneSerialization("000019-013.user_id"); + } + + public function test000020002sig() { + $this->oneSerialization("000020-002.sig"); + } + + public function test000021012ring_trust() { + $this->oneSerialization("000021-012.ring_trust"); + } + + public function test000022002sig() { + $this->oneSerialization("000022-002.sig"); + } + + public function test000023012ring_trust() { + $this->oneSerialization("000023-012.ring_trust"); + } + + public function test000024014public_subkey() { + $this->oneSerialization("000024-014.public_subkey"); + } + + public function test000025002sig() { + $this->oneSerialization("000025-002.sig"); + } + + public function test000026012ring_trust() { + $this->oneSerialization("000026-012.ring_trust"); + } + + public function test000027006public_key() { + $this->oneSerialization("000027-006.public_key"); + } + + public function test000028002sig() { + $this->oneSerialization("000028-002.sig"); + } + + public function test000029012ring_trust() { + $this->oneSerialization("000029-012.ring_trust"); + } + + public function test000030013user_id() { + $this->oneSerialization("000030-013.user_id"); + } + + public function test000031002sig() { + $this->oneSerialization("000031-002.sig"); + } + + public function test000032012ring_trust() { + $this->oneSerialization("000032-012.ring_trust"); + } + + public function test000033002sig() { + $this->oneSerialization("000033-002.sig"); + } + + public function test000034012ring_trust() { + $this->oneSerialization("000034-012.ring_trust"); + } + + public function test000035006public_key() { + $this->oneSerialization("000035-006.public_key"); + } + + public function test000036013user_id() { + $this->oneSerialization("000036-013.user_id"); + } + + public function test000037002sig() { + $this->oneSerialization("000037-002.sig"); + } + + public function test000038012ring_trust() { + $this->oneSerialization("000038-012.ring_trust"); + } + + public function test000039002sig() { + $this->oneSerialization("000039-002.sig"); + } + + public function test000040012ring_trust() { + $this->oneSerialization("000040-012.ring_trust"); + } + + public function test000041017attribute() { + $this->oneSerialization("000041-017.attribute"); + } + + public function test000042002sig() { + $this->oneSerialization("000042-002.sig"); + } + + public function test000043012ring_trust() { + $this->oneSerialization("000043-012.ring_trust"); + } + + public function test000044014public_subkey() { + $this->oneSerialization("000044-014.public_subkey"); + } + + public function test000045002sig() { + $this->oneSerialization("000045-002.sig"); + } + + public function test000046012ring_trust() { + $this->oneSerialization("000046-012.ring_trust"); + } + + public function test000047005secret_key() { + $this->oneSerialization("000047-005.secret_key"); + } + + public function test000048013user_id() { + $this->oneSerialization("000048-013.user_id"); + } + + public function test000049002sig() { + $this->oneSerialization("000049-002.sig"); + } + + public function test000050012ring_trust() { + $this->oneSerialization("000050-012.ring_trust"); + } + + public function test000051007secret_subkey() { + $this->oneSerialization("000051-007.secret_subkey"); + } + + public function test000052002sig() { + $this->oneSerialization("000052-002.sig"); + } + + public function test000053012ring_trust() { + $this->oneSerialization("000053-012.ring_trust"); + } + + public function test000054005secret_key() { + $this->oneSerialization("000054-005.secret_key"); + } + + public function test000055002sig() { + $this->oneSerialization("000055-002.sig"); + } + + public function test000056012ring_trust() { + $this->oneSerialization("000056-012.ring_trust"); + } + + public function test000057013user_id() { + $this->oneSerialization("000057-013.user_id"); + } + + public function test000058002sig() { + $this->oneSerialization("000058-002.sig"); + } + + public function test000059012ring_trust() { + $this->oneSerialization("000059-012.ring_trust"); + } + + public function test000060007secret_subkey() { + $this->oneSerialization("000060-007.secret_subkey"); + } + + public function test000061002sig() { + $this->oneSerialization("000061-002.sig"); + } + + public function test000062012ring_trust() { + $this->oneSerialization("000062-012.ring_trust"); + } + + public function test000063005secret_key() { + $this->oneSerialization("000063-005.secret_key"); + } + + public function test000064002sig() { + $this->oneSerialization("000064-002.sig"); + } + + public function test000065012ring_trust() { + $this->oneSerialization("000065-012.ring_trust"); + } + + public function test000066013user_id() { + $this->oneSerialization("000066-013.user_id"); + } + + public function test000067002sig() { + $this->oneSerialization("000067-002.sig"); + } + + public function test000068012ring_trust() { + $this->oneSerialization("000068-012.ring_trust"); + } + + public function test000069005secret_key() { + $this->oneSerialization("000069-005.secret_key"); + } + + public function test000070013user_id() { + $this->oneSerialization("000070-013.user_id"); + } + + public function test000071002sig() { + $this->oneSerialization("000071-002.sig"); + } + + public function test000072012ring_trust() { + $this->oneSerialization("000072-012.ring_trust"); + } + + public function test000073017attribute() { + $this->oneSerialization("000073-017.attribute"); + } + + public function test000074002sig() { + $this->oneSerialization("000074-002.sig"); + } + + public function test000075012ring_trust() { + $this->oneSerialization("000075-012.ring_trust"); + } + + public function test000076007secret_subkey() { + $this->oneSerialization("000076-007.secret_subkey"); + } + + public function test000077002sig() { + $this->oneSerialization("000077-002.sig"); + } + + public function test000078012ring_trust() { + $this->oneSerialization("000078-012.ring_trust"); + } + + public function test002182002sig() { + $this->oneSerialization("002182-002.sig"); + } + + public function testpubringgpg() { + $this->oneSerialization("pubring.gpg"); + } + + public function testsecringgpg() { + $this->oneSerialization("secring.gpg"); + } + + public function testcompressedsiggpg() { + $this->oneSerialization("compressedsig.gpg"); + } + + public function testcompressedsigzlibgpg() { + $this->oneSerialization("compressedsig-zlib.gpg"); + } + + public function testcompressedsigbzip2gpg() { + $this->oneSerialization("compressedsig-bzip2.gpg"); + } + + public function testonepass_sig() { + $this->oneSerialization("onepass_sig"); + } + + public function testsymmetrically_encrypted() { + $this->oneSerialization("symmetrically_encrypted"); + } + + public function testuncompressedopsdsagpg() { + $this->oneSerialization("uncompressed-ops-dsa.gpg"); + } + + public function testuncompressedopsdsasha384txtgpg() { + $this->oneSerialization("uncompressed-ops-dsa-sha384.txt.gpg"); + } + + public function testuncompressedopsrsagpg() { + $this->oneSerialization("uncompressed-ops-rsa.gpg"); + } + + public function testSymmetricAES() { + $this->oneSerialization("symmetric-aes.gpg"); + } + + public function testSymmetricNoMDC() { + $this->oneSerialization("symmetric-no-mdc.gpg"); + } + + public function tested25519_public() { + $this->oneSerialization("ed25519.public_key"); + } + + public function tested25519_secret() { + $this->oneSerialization("ed25519.secret_key"); + } +} + +class Fingerprint extends TestCase { + public function oneFingerprint($path, $kf) { + $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); + $this->assertEquals($m[0]->fingerprint(), $kf); + } + + public function test000001006public_key() { + $this->oneFingerprint("000001-006.public_key", "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"); + } + + public function test000016006public_key() { + $this->oneFingerprint("000016-006.public_key", "AF95E4D7BAC521EE9740BED75E9F1523413262DC"); + } + + public function test000027006public_key() { + $this->oneFingerprint("000027-006.public_key", "1EB20B2F5A5CC3BEAFD6E5CB7732CF988A63EA86"); + } + + public function test000035006public_key() { + $this->oneFingerprint("000035-006.public_key", "CB7933459F59C70DF1C3FBEEDEDC3ECF689AF56D"); + } + + public function test000080006public_key() { + $this->oneFingerprint("000080-006.public_key", "AEDA0C4468AE265E8B7CCA1C3047D4A7B15467AB"); + } + + public function test000082006public_key() { + $this->oneFingerprint("000082-006.public_key", "589D7E6884A9235BBE821D35BD7BA7BC5547FD09"); + } + + public function tested25519() { + $this->oneFingerprint("ed25519.public_key", "88771946427EC2E24569C1D86208BE2B78C27E94"); + } +} + +class Signature extends TestCase { + public function oneIssuer($path, $kf) { + $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); + $this->assertEquals($m[0]->issuer(), $kf); + } + + public function test000079002sig() { + $this->oneIssuer("000079-002.sig", "C25059FA8730BC38"); + } + + public function test000081002sig() { + $this->oneIssuer("000081-002.sig", "6B799484725130FE"); + } + + public function test000083002sig() { + $this->oneIssuer("000083-002.sig", "BD7BA7BC5547FD09"); + } +} + +class Armor extends TestCase { + public function testRoundTrip() { + $bytes = "abcd\0\xff"; + $this->assertEquals($bytes, OpenPGP::unarmor(OpenPGP::enarmor($bytes), 'MESSAGE')); + } + + public function testInvalidBase64() { + $input = OpenPGP::header('MESSAGE') . "\n\nY~WJjZAD/\n=PE3Q\n" . OpenPGP::footer('MESSAGE'); + $this->assertEquals(false, OpenPGP::unarmor($input, 'MESSAGE')); + } +} \ No newline at end of file