diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 4328340..0000000 --- a/AUTHORS +++ /dev/null @@ -1,2 +0,0 @@ -* Arto Bendiken -* Stephen Paul Weber diff --git a/Doxyfile b/Doxyfile deleted file mode 100644 index a3f7542..0000000 --- a/Doxyfile +++ /dev/null @@ -1,1890 +0,0 @@ -# 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 b/README deleted file mode 120000 index 42061c0..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -README.md \ No newline at end of file diff --git a/VERSION b/VERSION deleted file mode 100644 index 1d0ba9e..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.4.0 diff --git a/lib/OpenPGP.php b/lib/OpenPGP.php new file mode 100644 index 0000000..755b528 --- /dev/null +++ b/lib/OpenPGP.php @@ -0,0 +1,133 @@ + + * @author Stephen Paul Weber + * @author Deon George + * @see http://github.com/bendiken/openpgp-php + */ + +namespace Leenooks; + +use Leenooks\OpenPGP\Exceptions\PacketTagException; + +/** + * @see http://tools.ietf.org/html/rfc4880 + */ +class OpenPGP +{ + const VERSION = [0,5,0]; + + /** + * @see http://tools.ietf.org/html/rfc4880#section-6 + * @see http://tools.ietf.org/html/rfc4880#section-6.2 + * @see http://tools.ietf.org/html/rfc2045 + */ + static function enarmor($data,$marker='MESSAGE',array $headers=[]) + { + $text = self::header($marker)."\n"; + + foreach ($headers as $key => $value) { + $text .= $key.': '.(string)$value."\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; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-6 + * @see http://tools.ietf.org/html/rfc2045 + */ + static function unarmor($text,$header='PGP PUBLIC KEY BLOCK') + { + $header = self::header($header); + + $text = str_replace(["\r\n","\r"],["\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)); + } + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-6.2 + */ + static protected function header($marker): string + { + return '-----BEGIN '.strtoupper((string)$marker).'-----'; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-6.2 + */ + static protected function footer($marker): string + { + return'-----END '.strtoupper((string)$marker).'-----'; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-6 + * @see http://tools.ietf.org/html/rfc4880#section-6.1 + */ + static function crc24($data): int + { + $crc = 0x00b704ce; + + for ($i = 0; $i < strlen($data); $i++) { + $crc ^= (ord($data[$i]) & 255) << 16; + + for ($j = 0; $j < 8; $j++) { + $crc <<= 1; + if ($crc & 0x01000000) { + $crc ^= 0x01864cfb; + } + } + } + + return $crc & 0x00ffffff; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-12.2 + */ + 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; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/AsymmetricSessionKeyPacket.php b/lib/OpenPgP/AsymmetricSessionKeyPacket.php new file mode 100644 index 0000000..a2e27cb --- /dev/null +++ b/lib/OpenPgP/AsymmetricSessionKeyPacket.php @@ -0,0 +1,60 @@ +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 = ''; + + // Store KeyID in Hex + for ($i=0;$ikeyid .= 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;$ikeyid);$i+= 2) { + $bytes .= chr(hexdec($this->keyid{$i}.$this->keyid{$i+1})); + } + + $bytes .= chr($this->key_algorithm); + $bytes .= $this->encrypted_data; + + return $bytes; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/CompressedDataPacket.php b/lib/OpenPgP/CompressedDataPacket.php new file mode 100644 index 0000000..4ca0496 --- /dev/null +++ b/lib/OpenPgP/CompressedDataPacket.php @@ -0,0 +1,102 @@ + 'Uncompressed', + 1 => 'ZIP', + 2 => 'ZLIB', + 3 => 'BZip2', + ]; + + // IteratorAggregate interface + + function getIterator() + { + return new \ArrayIterator($this->data->packets); + } + + // ArrayAccess interface + + function offsetExists($offset) { + return isset($this->data[$offset]); + } + + function offsetGet($offset) { + return $this->data[$offset]; + } + + function offsetSet($offset, $value) { + return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value; + } + + function offsetUnset($offset) { + unset($this->data[$offset]); + } + + function body() + { + $body = chr($this->algorithm); + + switch($this->algorithm) { + case 0: + $body .= $this->data->to_bytes(); + break; + + case 1: + $body .= gzdeflate($this->data->to_bytes()); + break; + + case 2: + $body .= gzcompress($this->data->to_bytes()); + break; + + case 3: + $body .= bzcompress($this->data->to_bytes()); + break; + + default: + /* @todo error? */ + } + + return $body; + } + + function read() + { + $this->algorithm = ord($this->read_byte()); + $this->data = $this->read_bytes($this->length); + + switch($this->algorithm) { + case 0: + $this->data = OpenPGP\Message::parse($this->data); + break; + + case 1: + $this->data = OpenPGP\Message::parse(gzinflate($this->data)); + break; + + case 2: + $this->data = OpenPGP\Message::parse(gzuncompress($this->data)); + break; + + case 3: + $this->data = OpenPGP\Message::parse(bzdecompress($this->data)); + break; + + default: + /* TODO error? */ + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/Crypt/RSA.php b/lib/OpenPgP/Crypt/RSA.php new file mode 100644 index 0000000..db0ec8e --- /dev/null +++ b/lib/OpenPgP/Crypt/RSA.php @@ -0,0 +1,356 @@ +key = $packet; + } else { + $this->message = $packet; + } + } + + function key($keyid=NULL) + { + // No key + if (! $this->key) + return NULL; + + 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) + { + if (static::$DEBUG) + dump(['In METHOD: '=>__METHOD__,'packet'=>$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->setHash(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->setHash(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; + } + + // Missing some data + if (! $key || !$message) + return NULL; + + 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->setHash(strtolower($hash)); + + $sig = new SignaturePacket($message,'RSA',strtoupper($hash)); + $sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid); + $sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($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); + + // Missing some data + if (! $key || ! $packet) + return NULL; + + if (! $keyid) + $keyid = substr($this->key->fingerprint,-16); + $key->setHash(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(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($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; + } + + // Missing some data + if (! $keys || ! $message) + return NULL; + + if (! ($keys instanceof Crypt_RSA)) { + $keys = new self($keys); + } + + if (static::$DEBUG) + dump([__METHOD__=>['keys'=>$keys,'message'=>$message]]); + + foreach ($message as $p) { + if (static::$DEBUG) + dump(['p'=>$p,'test'=> ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) ]); + + if ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) { + if (static::$DEBUG) + dump(['keys'=>$keys,'test'=>($keys instanceof Crypt_RSA)]); + + if($keys instanceof Crypt_RSA) { + $sk = self::try_decrypt_session($keys,substr($p->encrypted_data,2)); + + } elseif (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 (static::$DEBUG) + dump(['c'=>$p->keyid,'key'=>$key,'sk'=>$sk]); + } + + if (! $sk) + continue; + + $r = Symmetric::decryptPacket(Symmetric::getEncryptedData($message),$sk[0],$sk[1]); + + if ($r) + return $r; + } + } + + return NULL; /* Failed */ + } + + static function try_decrypt_session($key,$edata) + { + $key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); + $data = @$key->decrypt($edata); + 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;$isetSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); + $rsa->setHash(strtolower($hash)); + $rsa->modulus = new Math_BigInteger($mod,256); + $rsa->k = strlen($rsa->modulus->toBytes()); + $rsa->exponent = new Math_BigInteger($exp,256); + $rsa->setPublicKey(); + + return $rsa; + } + + static function convert_key($packet,$private=false) + { + if (! is_object($packet)) + $packet = OpenPGP\Message::parse($packet); + + if ($packet instanceof OpenPGP\Message) + $packet = $packet[0]; + + $mod = $packet->key['n']; + $exp = $packet->key['e']; + + if ($private) + $exp = $packet->key['d']; + + // Packet doesn't have needed data + if (! $exp) + return NULL; + + $rsa = self::crypt_rsa_key($mod,$exp); + + if ($private) { + /** + * @see https://github.com/phpseclib/phpseclib/issues/1113 + * Primes and coefficients now use BigIntegers. + **/ + //set the primes + if ($packet->key['p'] && $packet->key['q']) + $rsa->primes = [ + 1 => new Math_BigInteger($packet->key['p'], 256), + 2 => new Math_BigInteger($packet->key['q'], 256) + ]; + // set the coefficients + if ($packet->key['u']) + $rsa->coefficients = [2=>new Math_BigInteger($packet->key['u'],256)]; + } + + return $rsa; + } + + static function convert_public_key($packet) + { + return self::convert_key($packet, false); + } + + static function convert_private_key($packet) + { + return self::convert_key($packet, true); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/Crypt/Symmetric.php b/lib/OpenPgP/Crypt/Symmetric.php new file mode 100644 index 0000000..8f28619 --- /dev/null +++ b/lib/OpenPgP/Crypt/Symmetric.php @@ -0,0 +1,328 @@ +__METHOD__,'passphrases_and_keys'=>$passphrases_and_keys,'symmetric_algorithm'=>$symmetric_algorithm]); + + list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($symmetric_algorithm); + if (static::$DEBUG) + dump(['cipher'=>$cipher,'key_bytes'=>$key_bytes,'key_block_bytes'=>$key_block_bytes]); + + if (! $cipher) + throw new Exception("Unsupported cipher"); + + $prefix = Random::string($key_block_bytes); + $prefix .= substr($prefix, -2); + + $key = Random::string($key_bytes); + $cipher->setKey($key); + + $to_encrypt = $prefix.$message->to_bytes(); + + $mdc = new OpenPGP\ModificationDetectionCodePacket(hash('sha1',$to_encrypt."\xD3\x14",true)); + $to_encrypt .= $mdc->to_bytes(); + + if (static::$DEBUG) + dump(['to_encrypt'=>$to_encrypt]); + + $encrypted = [new OpenPGP\IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt))]; + + if (static::$DEBUG) + dump(['encrypted'=>$encrypted]); + + if (! is_array($passphrases_and_keys) && ! ($passphrases_and_keys instanceof \IteratorAggregate)) { + $passphrases_and_keys = (array)$passphrases_and_keys; + } + + if (static::$DEBUG) + dump(['pk'=>$passphrases_and_keys]); + + foreach ($passphrases_and_keys as $pass) { + if ($pass instanceof OpenPGP\PublicKeyPacket) { + if (static::$DEBUG) + dump(['pass'=>$pass,'instanceof'=>'Leenooks\OpenPGP\PublicKeyPacket']); + + if (! in_array($pass->algorithm,[1,2,3])) + throw new Exception("Only RSA keys are supported."); + + $crypt_rsa = new RSA($pass); + $rsa = $crypt_rsa->public_key(); + + if (static::$DEBUG) + dump(['public_key'=>$rsa]); + + $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_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)); + + } elseif (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)); + } + } + + if (static::$DEBUG) + dump(['Out METHOD: '=>__METHOD__,'encrypted'=>$encrypted,'message'=>(new OpenPGP\Message($encrypted))]); + + 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); + dump(['data'=>$data,'msg'=>$msg]); + + } 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; + + switch($algo) { + case NULL: + case 0: + throw new Exception("Data is already unencrypted"); + + case 2: + $cipher = new Crypt_TripleDES(Crypt_TripleDES::MODE_CFB); + $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(Crypt_Blowfish::MODE_CFB); + $key_bytes = 16; + $key_block_bytes = 8; + break; + + case 7: + $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); + $cipher->setKeyLength(128); + break; + + case 8: + $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); + $cipher->setKeyLength(192); + break; + + case 9: + $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); + $cipher->setKeyLength(256); + break; + + case 10: + $cipher = new Crypt_Twofish(Crypt_Twofish::MODE_CFB); + if (method_exists($cipher, 'setKeyLength')) { + $cipher->setKeyLength(256); + } else { + $cipher = NULL; + } + break; + } + + // Unsupported cipher + if (! $cipher) + return [NULL,NULL,NULL]; + + if (! isset($key_bytes)) + $key_bytes = isset($cipher->key_size)?$cipher->key_size:$cipher->key_length; + + if (! isset($key_block_bytes)) + $key_block_bytes = $cipher->block_size; + + return [$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; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/EncryptedDataPacket.php b/lib/OpenPgP/EncryptedDataPacket.php new file mode 100644 index 0000000..897526d --- /dev/null +++ b/lib/OpenPgP/EncryptedDataPacket.php @@ -0,0 +1,18 @@ +data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/Exceptions/PacketTagException.php b/lib/OpenPgP/Exceptions/PacketTagException.php new file mode 100644 index 0000000..7c94e9a --- /dev/null +++ b/lib/OpenPgP/Exceptions/PacketTagException.php @@ -0,0 +1,14 @@ +message = $message; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/ExperimentalPacket.php b/lib/OpenPgP/ExperimentalPacket.php new file mode 100644 index 0000000..de85419 --- /dev/null +++ b/lib/OpenPgP/ExperimentalPacket.php @@ -0,0 +1,11 @@ +version = $version; + } + + function body() + { + return chr($this->version).$this->data; + } + + function read() + { + $this->version = ord($this->read_byte()); + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/LiteralDataPacket.php b/lib/OpenPgP/LiteralDataPacket.php new file mode 100644 index 0000000..e9b8ff8 --- /dev/null +++ b/lib/OpenPgP/LiteralDataPacket.php @@ -0,0 +1,55 @@ +format = isset($opt['format']) ? $opt['format'] : 'b'; + $this->filename = isset($opt['filename']) ? $opt['filename'] : 'data'; + $this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time(); + } + + function body() + { + return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data; + } + + 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(); + $filename_length = ord($this->read_byte()); + $this->size -= $filename_length; + $this->filename = $this->read_bytes($filename_length); + $this->timestamp = $this->read_timestamp(); + $this->data = $this->read_bytes($this->size); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/MarkerPacket.php b/lib/OpenPgP/MarkerPacket.php new file mode 100644 index 0000000..03dc8a7 --- /dev/null +++ b/lib/OpenPgP/MarkerPacket.php @@ -0,0 +1,14 @@ +packets); + } + + // ArrayAccess interface + + function offsetExists($offset) + { + return isset($this->packets[$offset]); + } + + function offsetGet($offset) + { + return $this->packets[$offset]; + } + + function offsetSet($offset,$value) + { + return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; + } + + function offsetUnset($offset) + { + unset($this->packets[$offset]); + } + + // Class + + function __construct(array $packets=[]) + { + $this->packets = $packets; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-4.1 + * @see http://tools.ietf.org/html/rfc4880#section-4.2 + */ + static function parse($input): self + { + if (is_resource($input)) { + return self::parse_stream($input); + } + + if (is_string($input)) { + return self::parse_string($input); + } + } + + static function parse_file($path): self + { + if (($msg=self::parse(file_get_contents($path)))) { + $msg->uri = preg_match('!^[\w\d]+://!',$path) ? $path : 'file://'.realpath($path); + + return $msg; + } + } + + static function parse_stream($input): self + { + return self::parse_string(stream_get_contents($input)); + } + + static function parse_string($input): self + { + $msg = new self; + + while (($length=strlen($input)) > 0) { + if (($packet=Packet::parse($input))) { + $msg[] = $packet; + } + + // is parsing stuck? + if ($length == strlen($input)) { + break; + } + } + + return $msg; + } + + /** + * Extract signed objects from a well-formatted message + * + * Recurses into CompressedDataPacket + * + * @see http://tools.ietf.org/html/rfc4880#section-11 + */ + public function signatures(): array + { + $msg = $this; + + $key = NULL; + $userid = NULL; + $subkey = NULL; + $sigs = []; + $final_sigs = []; + + while ($msg[0] instanceof CompressedDataPacket) + $msg = $msg[0]->data; + + foreach ($msg as $idx => $p) { + if ($p instanceof LiteralDataPacket) { + return [ + [ + $p, + array_values(array_filter($msg->packets,function($p) + { + return $p instanceof SignaturePacket; + })) + ] + ]; + + } elseif ($p instanceof PublicSubkeyPacket || $p instanceof SecretSubkeyPacket) { + if ($userid) { + array_push($final_sigs,[$key,$userid,$sigs]); + $userid = NULL; + + } elseif ($subkey) { + array_push($final_sigs,[$key,$subkey,$sigs]); + $key = NULL; + } + + $sigs = []; + $subkey = $p; + + } elseif ($p instanceof PublicKeyPacket) { + if ($userid) { + array_push($final_sigs,[$key,$userid,$sigs]); + $userid = NULL; + + } elseif ($subkey) { + array_push($final_sigs,[$key,$subkey,$sigs]); + $subkey = NULL; + + } elseif ($key) { + array_push($final_sigs,[$key,$sigs]); + $key = NULL; + } + + $sigs = []; + $key = $p; + + } elseif ($p instanceof UserIDPacket) { + if ($userid) { + array_push($final_sigs,[$key,$userid,$sigs]); + $userid = NULL; + + } elseif ($key) { + array_push($final_sigs,[$key,$sigs]); + } + + $sigs = []; + $userid = $p; + + } elseif ($p instanceof SignaturePacket) { + $sigs[] = $p; + } + } + + if ($userid) { + array_push($final_sigs,[$key,$userid,$sigs]); + + } elseif ($subkey) { + array_push($final_sigs,[$key,$subkey,$sigs]); + + } elseif ($key) { + array_push($final_sigs,[$key,$sigs]); + } + + return $final_sigs; + } + + public function to_bytes(): string + { + $bytes = ''; + + foreach ($this as $p) { + $bytes .= $p->to_bytes(); + } + + return $bytes; + } + + /** + * Function to extract verified signatures + * + * $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet + */ + function verified_signatures($verifiers): array + { + $signed = $this->signatures(); + $vsigned = []; + + foreach ($signed as $sign) { + $signatures = array_pop($sign); + $vsigs = []; + + foreach ($signatures as $sig) { + $verifier = $verifiers[$sig->key_algorithm_name()][$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 LiteralDataPacket) { + $sign[0]->normalize(); + $raw = $sign[0]->data; + + } elseif (isset($sign[1]) && $sign[1] instanceof UserIDPacket) { + $raw = implode( + '', + array_merge( + $sign[0]->fingerprint_material(), + array(chr(0xB4),pack('N',strlen($sign[1]->body())),$sign[1]->body()) + )); + + } elseif (isset($sign[1]) && ($sign[1] instanceof PublicSubkeyPacket || $sign[1] instanceof SecretSubkeyPacket)) { + $raw = implode('',array_merge($sign[0]->fingerprint_material(),$sign[1]->fingerprint_material())); + + } elseif ($sign[0] instanceof PublicKeyPacket) { + $raw = implode('',$sign[0]->fingerprint_material()); + + } else { + return NULL; + } + + return call_user_func($verifier,$raw.$sig->trailer,$sig); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/ModificationDetectionCodePacket.php b/lib/OpenPgP/ModificationDetectionCodePacket.php new file mode 100644 index 0000000..8bf5119 --- /dev/null +++ b/lib/OpenPgP/ModificationDetectionCodePacket.php @@ -0,0 +1,32 @@ +body(); + + if (strlen($body) != 20) + throw new Exception("Bad ModificationDetectionCodePacket"); + + return ['header'=>"\xD3\x14",'body'=>$body]; + } + + function read() + { + $this->data = $this->input; + + if (strlen($this->input) != 20) + throw new Exception("Bad ModificationDetectionCodePacket"); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/OnePassSignaturePacket.php b/lib/OpenPgP/OnePassSignaturePacket.php new file mode 100644 index 0000000..087c0da --- /dev/null +++ b/lib/OpenPgP/OnePassSignaturePacket.php @@ -0,0 +1,39 @@ +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((int)$this->nested); + return $body; + } + + 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()); + + // Store KeyID in Hex + for ($i=0;$i<8;$i++) { + $this->key_id .= sprintf('%02X',ord($this->read_byte())); + } + + $this->nested = ord($this->read_byte()); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/Packet.php b/lib/OpenPgP/Packet.php new file mode 100644 index 0000000..b3f67d2 --- /dev/null +++ b/lib/OpenPgP/Packet.php @@ -0,0 +1,271 @@ + 'AsymmetricSessionKey', // Public-Key Encrypted Session Key + 2 => 'Signature', // Signature Packet + 3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet + 4 => 'OnePassSignature', // One-Pass Signature Packet + 5 => 'SecretKey', // Secret-Key Packet + 6 => 'PublicKey', // Public-Key Packet + 7 => 'SecretSubkey', // Secret-Subkey Packet + 8 => 'CompressedData', // Compressed Data Packet + 9 => 'EncryptedData', // Symmetrically Encrypted Data Packet + 10 => 'Marker', // Marker Packet + 11 => 'LiteralData', // Literal Data Packet + 12 => 'Trust', // Trust Packet + 13 => 'UserID', // User ID Packet + 14 => 'PublicSubkey', // Public-Subkey Packet + 17 => 'UserAttribute', // User Attribute Packet + 18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet + 19 => 'ModificationDetectionCode', // Modification Detection Code Packet + 60 => 'Experimental', // Private or Experimental Values + 61 => 'Experimental', // Private or Experimental Values + 62 => 'Experimental', // Private or Experimental Values + 63 => 'Experimental', // Private or Experimental Values + ]; + + static function class_for($tag) + { + return (isset(self::$tags[$tag]) AND class_exists($class='Leenooks\OpenPGP\\'.self::$tags[$tag].'Packet')) + ? $class + : __CLASS__; + } + + /** + * 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) + { + if (static::$DEBUG) + dump(['In METHOD: '=>__METHOD__,'input'=>$input]); + + $packet = NULL; + + if (strlen($input) > 0) { + $parser = (ord($input[0]) & 64) ? 'parse_new_format' : 'parse_old_format'; + + $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 (static::$DEBUG) + dump(['parser'=>$parser,'tag'=>$tag,'class'=>($class=self::class_for($tag)),'c'=>$class]); + + if ($tag && ($class=self::class_for($tag))) { + $packet = new $class; + $packet->tag = $tag; + $packet->input = $packet_data; + $packet->length = strlen($packet_data); + $packet->read(); + + unset($packet->input); + unset($packet->length); + } + + $input = substr($input,$consumed); + } + + if (static::$DEBUG) + dump(['Out METHOD: '=>__METHOD__,'packet'=>$packet]); + + return $packet; + } + + /** + * Parses a new-format (RFC 4880) OpenPGP packet. + * + * @see http://tools.ietf.org/html/rfc4880#section-4.2.2 + */ + static function parse_new_format($input,$header_start=0): array + { + $tag = ord($input[0]) & 63; + $len = ord($input[$header_start+1]); + + // One octet length + if ($len < 192) { + return [$tag,2,$len,FALSE]; + } + + // Two octet length + if ($len > 191 && $len < 224) { + return [$tag,3,(($len-192)<<8)+ord($input[$header_start+2])+192,FALSE]; + } + + // Five octet length + if ($len == 255) { + $unpacked = unpack('N',substr($input,$header_start+2,4)); + + return [$tag,6,reset($unpacked),FALSE]; + } + + // Partial body lengths + return [$tag,2,1<<($len & 0x1f),TRUE]; + } + + /** + * Parses an old-format (PGP 2.6.x) OpenPGP packet. + * + * @see http://tools.ietf.org/html/rfc4880#section-4.2.1 + */ + static function parse_old_format($input): array + { + $len = ($tag=ord($input[0]))&3; + $tag = ($tag>>2)&15; + + switch ($len) { + // The packet has a one-octet length. The header is 2 octets long. + case 0: + $head_length = 2; + $data_length = ord($input[1]); + break; + + // The packet has a two-octet length. The header is 3 octets long. + case 1: + $head_length = 3; + $data_length = unpack('n', substr($input, 1, 2)); + $data_length = $data_length[1]; + break; + + // The packet has a four-octet length. The header is 5 octets long. + case 2: + $head_length = 5; + $data_length = unpack('N', substr($input, 1, 4)); + $data_length = $data_length[1]; + break; + + // The packet is of indeterminate length. The header is 1 octet long. + case 3: + $head_length = 1; + $data_length = strlen($input) - $head_length; + break; + } + + return [$tag, $head_length, $data_length, FALSE]; + } + + public function __construct($data=NULL) + { + // Make sure our tag is set in our packet class. + try { + if (is_null($this->tag)) + throw new PacketTagException('Missing tag in '.get_class($this)); + } catch (\Exception $e) { + dd($e->getMessage()); + } + + if (static::$DEBUG) + dump(['CREATE: '=>get_class($this),'data'=>$data]); + + if (static::$DEBUG) + dump([ + 'substr1'=>substr(get_class($this),strlen("Leenooks\OpenPGP")+1), + 'substr2'=>substr(substr(get_class($this),strlen("Leenooks\OpenPGP")+1),0,-6), + 'tags: '=>serialize(self::$tags)]); + + $this->tag = array_search(substr(substr(get_class($this),strlen("Leenooks\OpenPGP")+1),0,-6),self::$tags); + $this->data = $data; + } + + // Will normally be overridden by subclasses + public function body() + { + return $this->data; + } + + public function read() + { + } + + function header_and_body(): array + { + $body = $this->body(); // Get body first, we will need it's length + $size = chr(255).pack('N',strlen($body)); // Use 5-octet lengths + $tag = chr($this->tag|0xC0); // First two bits are 1 for new packet format + + return ['header'=>$tag.$size,'body'=>$body]; + } + + function to_bytes() + { + $data = $this->header_and_body(); + + return $data['header'].$data['body']; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-3.5 + */ + function read_timestamp() + { + return $this->read_unpacked(4,'N'); + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-3.2 + */ + function read_mpi() + { + $length = $this->read_unpacked(2,'n'); // length in bits + $length = (int)floor(($length+7)/8); // length in bytes + + return $this->read_bytes($length); + } + + /** + * @see http://php.net/manual/en/function.unpack.php + */ + protected function read_unpacked($count,$format) + { + $unpacked = unpack($format,$this->read_bytes($count)); + + return reset($unpacked); + } + + protected function read_byte() + { + return ($bytes=$this->read_bytes()) ? $bytes[0] : NULL; + } + + protected function read_bytes($count=1) + { + $bytes = substr($this->input,0,$count); + $this->input = substr($this->input,$count); + + return $bytes; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/PublicKeyPacket.php b/lib/OpenPgP/PublicKeyPacket.php new file mode 100644 index 0000000..18ba5a3 --- /dev/null +++ b/lib/OpenPgP/PublicKeyPacket.php @@ -0,0 +1,210 @@ + array('n', 'e'), // RSA + 16 => array('p', 'g', 'y'), // ELG-E + 17 => array('p', 'q', 'g', 'y'), // DSA + ); + + static $algorithms = [ + 1 => 'RSA', + 2 => 'RSA', + 3 => 'RSA', + 16 => 'ELGAMAL', + 17 => 'DSA', + 18 => 'ECC', + 19 => 'ECDSA', + 21 => 'DH' + ]; + + function __construct($key=[],$algorithm='RSA',$timestamp=NULL,$version=4) + { + parent::__construct(); + + if ($key instanceof 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); + } + } + } + + 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()) + ); + + case 4: + return implode('', array_slice($this->fingerprint_material(), 2)); + } + } + + // 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 SignaturePacket\KeyExpirationTimePacket) { + return $this->timestamp + $s->data; + } + } + } + + // Never expires + return NULL; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-12.2 + * @see http://tools.ietf.org/html/rfc4880#section-3.3 + */ + function fingerprint() + { + switch ($this->version) { + case 2: + case 3: + return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material()))); + + case 4: + return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material()))); + } + } + + 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 = []; + foreach (self::$key_fields[$this->algorithm] as $i) { + $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); + $material[] = $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-5.5.2 + */ + function read() + { + switch ($this->version = ord($this->read_byte())) { + case 3: + $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(); + break; + } + } + /** + * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 + */ + function read_key_material() + { + foreach (self::$key_fields[$this->algorithm] as $field) { + $this->key[$field] = $this->read_mpi(); + } + + $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 = []; + $keyid16 = strtoupper(substr($this->fingerprint,-16)); + + foreach ($message as $p) { + if ($p instanceof OpenPGP\SignaturePacket) { + if (strtoupper($p->issuer()) == $keyid16) { + $sigs[] = $p; + + } else { + foreach (array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) { + if ($s instanceof SignaturePacket\EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) { + $sigs[] = $p; + break; + } + } + } + + // After we've seen a self sig, the next non-sig stop all self-sigs + } elseif (count($sigs)) + break; + } + + return $sigs; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/PublicSubkeyPacket.php b/lib/OpenPgP/PublicSubkeyPacket.php new file mode 100644 index 0000000..6c9b1af --- /dev/null +++ b/lib/OpenPgP/PublicSubkeyPacket.php @@ -0,0 +1,17 @@ +type = $type; + $this->hash_algorithm = $hash_algorithm; + $this->salt = $salt; + $this->count = $count; + } + + static function parse(&$input) + { + $s2k = new self; + + 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); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SecretKeyPacket.php b/lib/OpenPgP/SecretKeyPacket.php new file mode 100644 index 0000000..bff303e --- /dev/null +++ b/lib/OpenPgP/SecretKeyPacket.php @@ -0,0 +1,80 @@ + 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 + ); + + 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', \Leenooks\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; + } + + function key_from_input() + { + foreach(self::$secret_key_fields[$this->algorithm] as $field) { + $this->key[$field] = $this->read_mpi(); + } + } + + 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? + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SecretSubkeyPacket.php b/lib/OpenPgP/SecretSubkeyPacket.php new file mode 100644 index 0000000..4354de5 --- /dev/null +++ b/lib/OpenPgP/SecretSubkeyPacket.php @@ -0,0 +1,17 @@ + 'MD5', + 2 => 'SHA1', + 3 => 'RIPEMD160', + 8 => 'SHA256', + 9 => 'SHA384', + 10 => 'SHA512', + 11 => 'SHA224' + ]; + + static $subpacket_types = [ + //0 => 'Reserved', + //1 => 'Reserved', + 2 => 'SignatureCreationTime', + 3 => 'SignatureExpirationTime', + 4 => 'ExportableCertification', + 5 => 'TrustSignature', + 6 => 'RegularExpression', + 7 => 'Revocable', + //8 => 'Reserved', + 9 => 'KeyExpirationTime', + //10 => 'Placeholder for backward compatibility', + 11 => 'PreferredSymmetricAlgorithms', + 12 => 'RevocationKey', + //13 => 'Reserved', + //14 => 'Reserved', + //15 => 'Reserved', + 16 => 'Issuer', + //17 => 'Reserved', + //18 => 'Reserved', + //19 => 'Reserved', + 20 => 'NotationData', + 21 => 'PreferredHashAlgorithms', + 22 => 'PreferredCompressionAlgorithms', + 23 => 'KeyServerPreferences', + 24 => 'PreferredKeyServer', + 25 => 'PrimaryUserID', + 26 => 'PolicyURI', + 27 => 'KeyFlags', + 28 => 'SignersUserID', + 29 => 'ReasonforRevocation', + 30 => 'Features', + 31 => 'SignatureTarget', + 32 => 'EmbeddedSignature', + ]; + + function __construct($data=NULL,$key_algorithm=NULL,$hash_algorithm=NULL) + { + parent::__construct(); + + // Default to version 4 sigs + $this->version = 4; + + 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,PublicKeyPacket::$algorithms); + } + + // If we have any data, set up the creation time + if ($data) { + $this->hashed_subpackets = [new SignaturePacket\SignatureCreationTimePacket(time())]; + } + + if ($data instanceof LiteralDataPacket) { + $this->signature_type = ($data->format == 'b') ? 0x00 : 0x01; + $data->normalize(); + $data = $data->data; + + } elseif ($data instanceof Message && $data[0] instanceof 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; + } + + // Store to-be-signed data in here until the signing happens + $this->data = $data; + } + + function body() + { + switch($this->version) { + case 2: + case 3: + $body = chr($this->version).chr(5).chr($this->signature_type); + + foreach ((array)$this->unhashed_subpackets as $p) { + if ($p instanceof SignaturePacket\SignatureCreationTimePacket) { + $body .= pack('N',$p->data); + + break; + } + } + + foreach ((array)$this->unhashed_subpackets as $p) { + if ($p instanceof 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 body_start() + { + $body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); + + $hashed_subpackets = ''; + foreach((array)$this->hashed_subpackets as $p) { + $hashed_subpackets .= $p->to_bytes(); + } + + $body .= pack('n',strlen($hashed_subpackets)).$hashed_subpackets; + + return $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)); + } + + static function class_for($tag) + { + return (isset(self::$subpacket_types[$tag]) AND class_exists($class='Leenooks\OpenPGP\SignaturePacket\\'.self::$subpacket_types[$tag].'Packet')) + ? $class + : 'Leenooks\OpenPGP\SignaturePacket\Subpacket'; + } + + static function get_subpacket(&$input) + { + $len = ord($input[0]); + $length_of_length = 1; + + // if($len < 192) One octet length, no furthur processing + if ($len > 190 && $len < 255) { // Two octet length + $length_of_length = 2; + $len = (($len - 192) << 8) + ord($input[1]) + 192; + } + + // Five octet length + if ($len == 255) { + $length_of_length = 5; + $unpacked = unpack('N', substr($input, 1, 4)); + $len = reset($unpacked); + } + + $input = substr($input, $length_of_length); // Chop off length header + $tag = ord($input[0]); + + $class = self::class_for($tag); + if (self::$DEBUG) + dump(['class'=>$class,'tag'=>$tag]); + + if ($class) { + $packet = new $class; + //$packet->tag = $tag; // @todo Tag should already be set. + $packet->input = substr($input, 1, $len-1); + $packet->length = $len-1; + $packet->read(); + unset($packet->input); + unset($packet->length); + } + + // Chop off the data from this packet + $input = substr($input,$len); + + return $packet; + } + + /** + * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1 + */ + static function get_subpackets($input) + { + $subpackets = array(); + + while(($length = strlen($input)) > 0) { + $subpackets[] = self::get_subpacket($input); + + // Parsing stuck? + if ($length == strlen($input)) { + break; + } + } + + return $subpackets; + } + + function hash_algorithm_name() + { + return self::$hash_algorithms[$this->hash_algorithm]; + } + + function issuer() + { + foreach ($this->hashed_subpackets as $p) { + if ($p instanceof SignaturePacket\IssuerPacket) + return $p->data; + } + + foreach($this->unhashed_subpackets as $p) { + if ($p instanceof SignaturePacket\IssuerPacket) + return $p->data; + } + + return NULL; + } + + function key_algorithm_name() + { + return PublicKeyPacket::$algorithms[$this->key_algorithm]; + } + + function read() + { + switch($this->version = ord($this->read_byte())) { + case 2: + case 3: + 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 = ''; + + // Store KeyID in Hex + for ($i=0;$ihashed_subpackets = []; + $this->unhashed_subpackets = [ + new SignaturePacket\SignatureCreationTimePacket($creation_time), + new 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()); + $this->key_algorithm = ord($this->read_byte()); + $this->hash_algorithm = ord($this->read_byte()); + $this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); + + $hashed_size = $this->read_unpacked(2, 'n'); + $hashed_subpackets = $this->read_bytes($hashed_size); + $this->trailer .= pack('n', $hashed_size).$hashed_subpackets; + $this->hashed_subpackets = self::get_subpackets($hashed_subpackets); + + $this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size); + + $unhashed_size = $this->read_unpacked(2, 'n'); + $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size)); + + $this->hash_head = $this->read_unpacked(2, 'n'); + + $this->data = array(); + + while(strlen($this->input) > 0) { + $this->data[] = $this->read_mpi(); + } + + break; + } + } + + /** + * $this->data must be set to the data to sign (done by constructor) + * $signers in the same format as $verifiers for Message. + */ + public function sign_data($signers) + { + $this->trailer = $this->calculate_trailer(); + $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()]; + $this->data = call_user_func($signer,$this->data.$this->trailer); + $unpacked = unpack('n', substr(implode('',$this->data),0,2)); + $this->hash_head = reset($unpacked); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/EmbeddedSignaturePacket.php b/lib/OpenPgP/SignaturePacket/EmbeddedSignaturePacket.php new file mode 100644 index 0000000..f827afc --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/EmbeddedSignaturePacket.php @@ -0,0 +1,22 @@ +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 ['header'=>$size.$tag,'body'=>$body]; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/ExportableCertificationPacket.php b/lib/OpenPgP/SignaturePacket/ExportableCertificationPacket.php new file mode 100644 index 0000000..a95dcd1 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/ExportableCertificationPacket.php @@ -0,0 +1,21 @@ +data ? 1 : 0); + } + + function read() + { + $this->data = (ord($this->input) != 0); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/FeaturesPacket.php b/lib/OpenPgP/SignaturePacket/FeaturesPacket.php new file mode 100644 index 0000000..2019998 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/FeaturesPacket.php @@ -0,0 +1,12 @@ +data); $i += 2) { + $bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1})); + } + return $bytes; + } + + function read() + { + for($i = 0; $i < 8; $i++) { // Store KeyID in Hex + $this->data .= sprintf('%02X',ord($this->read_byte())); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/KeyExpirationTimePacket.php b/lib/OpenPgP/SignaturePacket/KeyExpirationTimePacket.php new file mode 100644 index 0000000..c934c70 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/KeyExpirationTimePacket.php @@ -0,0 +1,21 @@ +data); + } + + function read() + { + $this->data = $this->read_timestamp(); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/KeyFlagsPacket.php b/lib/OpenPgP/SignaturePacket/KeyFlagsPacket.php new file mode 100644 index 0000000..13c193e --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/KeyFlagsPacket.php @@ -0,0 +1,37 @@ +flags = $flags; + } + + function body() + { + $bytes = ''; + + foreach($this->flags as $f) { + $bytes .= chr($f); + } + return $bytes; + } + + function read() + { + $this->flags = array(); + + while($this->input) { + $this->flags[] = ord($this->read_byte()); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/KeyServerPreferencesPacket.php b/lib/OpenPgP/SignaturePacket/KeyServerPreferencesPacket.php new file mode 100644 index 0000000..787340f --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/KeyServerPreferencesPacket.php @@ -0,0 +1,23 @@ +no_modify ? 0x80 : 0x00); + } + + function read() + { + $flags = ord($this->input); + $this->no_modify = $flags & 0x80 == 0x80; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/NotationDataPacket.php b/lib/OpenPgP/SignaturePacket/NotationDataPacket.php new file mode 100644 index 0000000..8149207 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/NotationDataPacket.php @@ -0,0 +1,29 @@ +human_readable ? 0x80 : 0x00) . "\0\0\0" . + pack('n', strlen($this->name)) . pack('n', strlen($this->data)) . + $this->name . $this->data; + } + + 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); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PolicyURIPacket.php b/lib/OpenPgP/SignaturePacket/PolicyURIPacket.php new file mode 100644 index 0000000..eb20fb8 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PolicyURIPacket.php @@ -0,0 +1,21 @@ +data; + } + + function read() + { + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PreferredCompressionAlgorithmsPacket.php b/lib/OpenPgP/SignaturePacket/PreferredCompressionAlgorithmsPacket.php new file mode 100644 index 0000000..0dc7041 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PreferredCompressionAlgorithmsPacket.php @@ -0,0 +1,30 @@ +data as $algo) { + $bytes .= chr($algo); + } + return $bytes; + } + + function read() + { + $this->data = array(); + + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PreferredHashAlgorithmsPacket.php b/lib/OpenPgP/SignaturePacket/PreferredHashAlgorithmsPacket.php new file mode 100644 index 0000000..e17f003 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PreferredHashAlgorithmsPacket.php @@ -0,0 +1,29 @@ +data as $algo) { + $bytes .= chr($algo); + } + return $bytes; + } + + function read() + { + $this->data = array(); + + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PreferredKeyServerPacket.php b/lib/OpenPgP/SignaturePacket/PreferredKeyServerPacket.php new file mode 100644 index 0000000..50ee8d5 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PreferredKeyServerPacket.php @@ -0,0 +1,21 @@ +data; + } + + function read() + { + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PreferredSymmetricAlgorithmsPacket.php b/lib/OpenPgP/SignaturePacket/PreferredSymmetricAlgorithmsPacket.php new file mode 100644 index 0000000..7596fa6 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PreferredSymmetricAlgorithmsPacket.php @@ -0,0 +1,31 @@ +data as $algo) { + $bytes .= chr($algo); + } + + return $bytes; + } + + function read() + { + $this->data = array(); + + while(strlen($this->input) > 0) { + $this->data[] = ord($this->read_byte()); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/PrimaryUserIDPacket.php b/lib/OpenPgP/SignaturePacket/PrimaryUserIDPacket.php new file mode 100644 index 0000000..cfaff07 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/PrimaryUserIDPacket.php @@ -0,0 +1,21 @@ +data ? 1 : 0); + } + + function read() + { + $this->data = (ord($this->input) != 0); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/ReasonforRevocationPacket.php b/lib/OpenPgP/SignaturePacket/ReasonforRevocationPacket.php new file mode 100644 index 0000000..344b89b --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/ReasonforRevocationPacket.php @@ -0,0 +1,23 @@ +code) . $this->data; + } + + function read() + { + $this->code = ord($this->read_byte()); + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/RegularExpressionPacket.php b/lib/OpenPgP/SignaturePacket/RegularExpressionPacket.php new file mode 100644 index 0000000..701afc4 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/RegularExpressionPacket.php @@ -0,0 +1,21 @@ +data . chr(0); + } + + function read() + { + $this->data = substr($this->input, 0, -1); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/RevocablePacket.php b/lib/OpenPgP/SignaturePacket/RevocablePacket.php new file mode 100644 index 0000000..1222111 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/RevocablePacket.php @@ -0,0 +1,21 @@ +data ? 1 : 0); + } + + function read() + { + $this->data = (ord($this->input) != 0); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/RevocationKeyPacket.php b/lib/OpenPgP/SignaturePacket/RevocationKeyPacket.php new file mode 100644 index 0000000..589fb6d --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/RevocationKeyPacket.php @@ -0,0 +1,38 @@ +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; + } + + 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())); + } + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/SignatureCreationTimePacket.php b/lib/OpenPgP/SignaturePacket/SignatureCreationTimePacket.php new file mode 100644 index 0000000..9d6cc1a --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/SignatureCreationTimePacket.php @@ -0,0 +1,21 @@ +data); + } + + function read() + { + $this->data = $this->read_timestamp(); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/SignatureExpirationTimePacket.php b/lib/OpenPgP/SignaturePacket/SignatureExpirationTimePacket.php new file mode 100644 index 0000000..b637254 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/SignatureExpirationTimePacket.php @@ -0,0 +1,21 @@ +data); + } + + function read() + { + $this->data = $this->read_timestamp(); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/SignatureTargetPacket.php b/lib/OpenPgP/SignaturePacket/SignatureTargetPacket.php new file mode 100644 index 0000000..60eddcb --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/SignatureTargetPacket.php @@ -0,0 +1,24 @@ +key_algorithm) . chr($this->hash_algorithm) . $this->data; + } + + function read() + { + $this->key_algorithm = ord($this->read_byte()); + $this->hash_algorithm = ord($this->read_byte()); + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/SignersUserIDPacket.php b/lib/OpenPgP/SignaturePacket/SignersUserIDPacket.php new file mode 100644 index 0000000..269ba16 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/SignersUserIDPacket.php @@ -0,0 +1,21 @@ +data; + } + + function read() + { + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/Subpacket.php b/lib/OpenPgP/SignaturePacket/Subpacket.php new file mode 100644 index 0000000..077dded --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/Subpacket.php @@ -0,0 +1,30 @@ +data; + } + + function header_and_body(): array + { + $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 ['header'=>$size.$tag,'body'=>$body]; + } + + /* Defaults for unsupported packets */ + function read() + { + $this->data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SignaturePacket/TrustSignaturePacket.php b/lib/OpenPgP/SignaturePacket/TrustSignaturePacket.php new file mode 100644 index 0000000..5b023b0 --- /dev/null +++ b/lib/OpenPgP/SignaturePacket/TrustSignaturePacket.php @@ -0,0 +1,22 @@ +depth) . chr($this->trust); + } + + function read() + { + $this->depth = ord($this->input{0}); + $this->trust = ord($this->input{1}); + } +} \ No newline at end of file diff --git a/lib/OpenPgP/SymmetricSessionKeyPacket.php b/lib/OpenPgP/SymmetricSessionKeyPacket.php new file mode 100644 index 0000000..21110d5 --- /dev/null +++ b/lib/OpenPgP/SymmetricSessionKeyPacket.php @@ -0,0 +1,37 @@ +version = $version; + $this->symmetric_algorithm = $symmetric_algorithm; + $this->s2k = $s2k; + $this->encrypted_data = $encrypted_data; + } + + function body() + { + return chr($this->version) . chr($this->symmetric_algorithm) . + $this->s2k->to_bytes() . $this->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; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/TrustPacket.php b/lib/OpenPgP/TrustPacket.php new file mode 100644 index 0000000..3b9b48f --- /dev/null +++ b/lib/OpenPgP/TrustPacket.php @@ -0,0 +1,17 @@ +data = $this->input; + } +} \ No newline at end of file diff --git a/lib/OpenPgP/UserAttributePacket.php b/lib/OpenPgP/UserAttributePacket.php new file mode 100644 index 0000000..b619470 --- /dev/null +++ b/lib/OpenPgP/UserAttributePacket.php @@ -0,0 +1,16 @@ +input = $name; + $this->read(); + + } else { + $this->name = $name; + $this->comment = $comment; + $this->email = $email; + } + } + + function __toString() + { + $text = []; + + if ($this->name) { $text[] = $this->name; } + if ($this->comment) { $text[] = "({$this->comment})"; } + if ($this->email) { $text[] = "<{$this->email}>"; } + + return implode(' ', $text); + } + + function body() + { + return ''.$this; // Convert to string is the body + } + + function read() + { + $this->data = $this->input; + // User IDs of the form: "name (comment) " + 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->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->data, $matches)) { + $this->name = trim($matches[1]); + $this->comment = NULL; + $this->email = NULL; + } + + // User IDs of the form: "" + else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) { + $this->name = NULL; + $this->comment = NULL; + $this->email = trim($matches[2]); + } + } +} \ No newline at end of file diff --git a/lib/openpgp.php b/lib/openpgp.php deleted file mode 100644 index 29b1e0a..0000000 --- a/lib/openpgp.php +++ /dev/null @@ -1,1886 +0,0 @@ - - * @author Stephen Paul Weber - * @see http://github.com/bendiken/openpgp-php - */ - -////////////////////////////////////////////////////////////////////////////// -// OpenPGP utilities - -/** - * @see http://tools.ietf.org/html/rfc4880 - */ -class OpenPGP { - const VERSION = array(0, 4, 0); - - /** - * @see http://tools.ietf.org/html/rfc4880#section-6 - * @see http://tools.ietf.org/html/rfc4880#section-6.2 - * @see http://tools.ietf.org/html/rfc2045 - */ - static function enarmor($data, $marker = 'MESSAGE', array $headers = array()) { - $text = self::header($marker) . "\n"; - foreach ($headers as $key => $value) { - $text .= $key . ': ' . (string)$value . "\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; - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-6 - * @see http://tools.ietf.org/html/rfc2045 - */ - 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)); - } - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-6.2 - */ - static function header($marker) { - return '-----BEGIN ' . strtoupper((string)$marker) . '-----'; - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-6.2 - */ - static function footer($marker) { - return '-----END ' . strtoupper((string)$marker) . '-----'; - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-6 - * @see http://tools.ietf.org/html/rfc4880#section-6.1 - */ - static function crc24($data) { - $crc = 0x00b704ce; - for ($i = 0; $i < strlen($data); $i++) { - $crc ^= (ord($data[$i]) & 255) << 16; - for ($j = 0; $j < 8; $j++) { - $crc <<= 1; - if ($crc & 0x01000000) { - $crc ^= 0x01864cfb; - } - } - } - return $crc & 0x00ffffff; - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-12.2 - */ - 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); - } - } -} - -////////////////////////////////////////////////////////////////////////////// -// OpenPGP messages - -/** - * @see http://tools.ietf.org/html/rfc4880#section-4.1 - * @see http://tools.ietf.org/html/rfc4880#section-11 - * @see http://tools.ietf.org/html/rfc4880#section-11.3 - */ -class OpenPGP_Message implements IteratorAggregate, ArrayAccess { - public $uri = NULL; - public $packets = array(); - - static function parse_file($path) { - if (($msg = self::parse(file_get_contents($path)))) { - $msg->uri = preg_match('!^[\w\d]+://!', $path) ? $path : 'file://' . realpath($path); - return $msg; - } - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-4.1 - * @see http://tools.ietf.org/html/rfc4880#section-4.2 - */ - static function parse($input) { - if (is_resource($input)) { - return self::parse_stream($input); - } - if (is_string($input)) { - return self::parse_string($input); - } - } - - static function parse_stream($input) { - return self::parse_string(stream_get_contents($input)); - } - - static function parse_string($input) { - $msg = new self; - while (($length = strlen($input)) > 0) { - if (($packet = OpenPGP_Packet::parse($input))) { - $msg[] = $packet; - } - if ($length == strlen($input)) { // is parsing stuck? - break; - } - } - return $msg; - } - - function __construct(array $packets = array()) { - $this->packets = $packets; - } - - function to_bytes() { - $bytes = ''; - foreach($this as $p) { - $bytes .= $p->to_bytes(); - } - return $bytes; - } - - /** - * Extract signed objects from a well-formatted message - * - * Recurses into CompressedDataPacket - * - * @see http://tools.ietf.org/html/rfc4880#section-11 - */ - function signatures() { - $msg = $this; - 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($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)); - } - - return $final_sigs; - } - - /** - * Function to extract verified signatures - * $verifiers is an array of callbacks formatted like 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()][$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() { - return new ArrayIterator($this->packets); - } - - // ArrayAccess interface - - function offsetExists($offset) { - return isset($this->packets[$offset]); - } - - function offsetGet($offset) { - return $this->packets[$offset]; - } - - function offsetSet($offset, $value) { - return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; - } - - function offsetUnset($offset) { - unset($this->packets[$offset]); - } -} - -////////////////////////////////////////////////////////////////////////////// -// OpenPGP packets - -/** - * OpenPGP packet. - * - * @see http://tools.ietf.org/html/rfc4880#section-4.1 - * @see http://tools.ietf.org/html/rfc4880#section-4.3 - */ -class OpenPGP_Packet { - public $tag, $size, $data; - - static function class_for($tag) { - return isset(self::$tags[$tag]) && class_exists( - $class = 'OpenPGP_' . self::$tags[$tag] . 'Packet') ? $class : __CLASS__; - } - - /** - * 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'; - - $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 = $packet_data; - $packet->length = strlen($packet_data); - $packet->read(); - unset($packet->input); - unset($packet->length); - } - $input = substr($input, $consumed); - } - return $packet; - } - - /** - * Parses a new-format (RFC 4880) OpenPGP packet. - * - * @see http://tools.ietf.org/html/rfc4880#section-4.2.2 - */ - static function parse_new_format($input, $header_start = 0) { - $tag = ord($input[0]) & 63; - $len = ord($input[$header_start + 1]); - if($len < 192) { // One octet length - return array($tag, 2, $len, false); - } - if($len > 191 && $len < 224) { // Two octet length - return array($tag, 3, (($len - 192) << 8) + ord($input[$header_start + 2]) + 192, false); - } - if($len == 255) { // Five octet length - $unpacked = unpack('N', substr($input, $header_start + 2, 4)); - return array($tag, 6, reset($unpacked), false); - } - // Partial body lengths - return array($tag, 2, 1 << ($len & 0x1f), true); - } - - /** - * Parses an old-format (PGP 2.6.x) OpenPGP packet. - * - * @see http://tools.ietf.org/html/rfc4880#section-4.2.1 - */ - static function parse_old_format($input) { - $len = ($tag = ord($input[0])) & 3; - $tag = ($tag >> 2) & 15; - switch ($len) { - case 0: // The packet has a one-octet length. The header is 2 octets long. - $head_length = 2; - $data_length = ord($input[1]); - break; - case 1: // The packet has a two-octet length. The header is 3 octets long. - $head_length = 3; - $data_length = unpack('n', substr($input, 1, 2)); - $data_length = $data_length[1]; - break; - case 2: // The packet has a four-octet length. The header is 5 octets long. - $head_length = 5; - $data_length = unpack('N', substr($input, 1, 4)); - $data_length = $data_length[1]; - break; - case 3: // The packet is of indeterminate length. The header is 1 octet long. - $head_length = 1; - $data_length = strlen($input) - $head_length; - break; - } - return array($tag, $head_length, $data_length, false); - } - - function __construct($data=NULL) { - $this->tag = array_search(substr(substr(get_class($this), 8), 0, -6), self::$tags); - $this->data = $data; - } - - function read() { - } - - function body() { - return $this->data; // Will normally be overridden by subclasses - } - - function header_and_body() { - $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); - } - - function to_bytes() { - $data = $this->header_and_body(); - return $data['header'].$data['body']; - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-3.5 - */ - function read_timestamp() { - return $this->read_unpacked(4, 'N'); - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-3.2 - */ - function read_mpi() { - $length = $this->read_unpacked(2, 'n'); // length in bits - $length = (int)floor(($length + 7) / 8); // length in bytes - return $this->read_bytes($length); - } - - /** - * @see http://php.net/manual/en/function.unpack.php - */ - function read_unpacked($count, $format) { - $unpacked = unpack($format, $this->read_bytes($count)); - return reset($unpacked); - } - - function read_byte() { - return ($bytes = $this->read_bytes()) ? $bytes[0] : NULL; - } - - function read_bytes($count = 1) { - $bytes = substr($this->input, 0, $count); - $this->input = substr($this->input, $count); - return $bytes; - } - - static $tags = array( - 1 => 'AsymmetricSessionKey', // Public-Key Encrypted Session Key - 2 => 'Signature', // Signature Packet - 3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet - 4 => 'OnePassSignature', // One-Pass Signature Packet - 5 => 'SecretKey', // Secret-Key Packet - 6 => 'PublicKey', // Public-Key Packet - 7 => 'SecretSubkey', // Secret-Subkey Packet - 8 => 'CompressedData', // Compressed Data Packet - 9 => 'EncryptedData', // Symmetrically Encrypted Data Packet - 10 => 'Marker', // Marker Packet - 11 => 'LiteralData', // Literal Data Packet - 12 => 'Trust', // Trust Packet - 13 => 'UserID', // User ID Packet - 14 => 'PublicSubkey', // Public-Subkey Packet - 17 => 'UserAttribute', // User Attribute Packet - 18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet - 19 => 'ModificationDetectionCode', // Modification Detection Code Packet - 60 => 'Experimental', // Private or Experimental Values - 61 => 'Experimental', // Private or Experimental Values - 62 => 'Experimental', // Private or Experimental Values - 63 => 'Experimental', // Private or Experimental Values - ); -} - -/** - * OpenPGP Public-Key Encrypted Session Key packet (tag 1). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.1 - */ -class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet { - public $version, $keyid, $key_algorithm, $encrypted_data; - - 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 - - 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()]; - $this->data = call_user_func($signer, $this->data.$this->trailer); - $unpacked = unpack('n', substr(implode('',$this->data), 0, 2)); - $this->hash_head = reset($unpacked); - } - - function read() { - switch($this->version = ord($this->read_byte())) { - case 2: - case 3: - 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()); - $this->key_algorithm = ord($this->read_byte()); - $this->hash_algorithm = ord($this->read_byte()); - $this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); - - $hashed_size = $this->read_unpacked(2, 'n'); - $hashed_subpackets = $this->read_bytes($hashed_size); - $this->trailer .= pack('n', $hashed_size).$hashed_subpackets; - $this->hashed_subpackets = self::get_subpackets($hashed_subpackets); - - $this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size); - - $unhashed_size = $this->read_unpacked(2, 'n'); - $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size)); - - $this->hash_head = $this->read_unpacked(2, 'n'); - - $this->data = array(); - while(strlen($this->input) > 0) { - $this->data[] = $this->read_mpi(); - } - break; - } - } - - 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((array)$this->hashed_subpackets as $p) { - $hashed_subpackets .= $p->to_bytes(); - } - $body .= pack('n', strlen($hashed_subpackets)).$hashed_subpackets; - - return $body; - } - - function body() { - switch($this->version) { - case 2: - case 3: - $body = chr($this->version) . chr(5) . chr($this->signature_type); - - foreach((array)$this->unhashed_subpackets as $p) { - if($p instanceof OpenPGP_SignaturePacket_SignatureCreationTimePacket) { - $body .= pack('N', $p->data); - break; - } - } - - 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() { - return OpenPGP_PublicKeyPacket::$algorithms[$this->key_algorithm]; - } - - 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 - */ - static function get_subpackets($input) { - $subpackets = array(); - while(($length = strlen($input)) > 0) { - $subpackets[] = self::get_subpacket($input); - if($length == strlen($input)) { // Parsing stuck? - break; - } - } - return $subpackets; - } - - static function get_subpacket(&$input) { - $len = ord($input[0]); - $length_of_length = 1; - // if($len < 192) One octet length, no furthur processing - if($len > 190 && $len < 255) { // Two octet length - $length_of_length = 2; - $len = (($len - 192) << 8) + ord($input[1]) + 192; - } - if($len == 255) { // Five octet length - $length_of_length = 5; - $unpacked = unpack('N', substr($input, 1, 4)); - $len = reset($unpacked); - } - $input = substr($input, $length_of_length); // Chop off length header - $tag = ord($input[0]); - $class = self::class_for($tag); - if($class) { - $packet = new $class(); - $packet->tag = $tag; - $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; - } - - static $hash_algorithms = array( - 1 => 'MD5', - 2 => 'SHA1', - 3 => 'RIPEMD160', - 8 => 'SHA256', - 9 => 'SHA384', - 10 => 'SHA512', - 11 => 'SHA224' - ); - - static $subpacket_types = array( - //0 => 'Reserved', - //1 => 'Reserved', - 2 => 'SignatureCreationTime', - 3 => 'SignatureExpirationTime', - 4 => 'ExportableCertification', - 5 => 'TrustSignature', - 6 => 'RegularExpression', - 7 => 'Revocable', - //8 => 'Reserved', - 9 => 'KeyExpirationTime', - //10 => 'Placeholder for backward compatibility', - 11 => 'PreferredSymmetricAlgorithms', - 12 => 'RevocationKey', - //13 => 'Reserved', - //14 => 'Reserved', - //15 => 'Reserved', - 16 => 'Issuer', - //17 => 'Reserved', - //18 => 'Reserved', - //19 => 'Reserved', - 20 => 'NotationData', - 21 => 'PreferredHashAlgorithms', - 22 => 'PreferredCompressionAlgorithms', - 23 => 'KeyServerPreferences', - 24 => 'PreferredKeyServer', - 25 => 'PrimaryUserID', - 26 => 'PolicyURI', - 27 => 'KeyFlags', - 28 => 'SignersUserID', - 29 => 'ReasonforRevocation', - 30 => 'Features', - 31 => 'SignatureTarget', - 32 => 'EmbeddedSignature', - ); - - static function class_for($tag) { - 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 { - 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; - } -} - -/** - * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4 - */ -class OpenPGP_SignaturePacket_SignatureCreationTimePacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = $this->read_timestamp(); - } - - function body() { - return pack('N', $this->data); - } -} - -class OpenPGP_SignaturePacket_SignatureExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = $this->read_timestamp(); - } - - function body() { - return pack('N', $this->data); - } -} - -class OpenPGP_SignaturePacket_ExportableCertificationPacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = (ord($this->input) != 0); - } - - function body() { - return chr($this->data ? 1 : 0); - } -} - -class OpenPGP_SignaturePacket_TrustSignaturePacket extends OpenPGP_SignaturePacket_Subpacket { - 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 { - function read() { - $this->data = substr($this->input, 0, -1); - } - - function body() { - return $this->data . chr(0); - } -} - -class OpenPGP_SignaturePacket_RevocablePacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = (ord($this->input) != 0); - } - - function body() { - return chr($this->data ? 1 : 0); - } -} - -class OpenPGP_SignaturePacket_KeyExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = $this->read_timestamp(); - } - - function body() { - return pack('N', $this->data); - } -} - -class OpenPGP_SignaturePacket_PreferredSymmetricAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { - 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 { - 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; - } -} - -/** - * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5 - */ -class OpenPGP_SignaturePacket_IssuerPacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - for($i = 0; $i < 8; $i++) { // Store KeyID in Hex - $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})); - } - return $bytes; - } -} - -class OpenPGP_SignaturePacket_NotationDataPacket extends OpenPGP_SignaturePacket_Subpacket { - 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 { - 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 { - 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 { - 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 { - function read() { - $this->data = $this->input; - } - - function body() { - return $this->data; - } -} - -class OpenPGP_SignaturePacket_PrimaryUserIDPacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = (ord($this->input) != 0); - } - - function body() { - return chr($this->data ? 1 : 0); - } - -} - -class OpenPGP_SignaturePacket_PolicyURIPacket extends OpenPGP_SignaturePacket_Subpacket { - function read() { - $this->data = $this->input; - } - - function body() { - return $this->data; - } -} - -class OpenPGP_SignaturePacket_KeyFlagsPacket extends OpenPGP_SignaturePacket_Subpacket { - 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 { - function read() { - $this->data = $this->input; - } - - function body() { - return $this->data; - } -} - -class OpenPGP_SignaturePacket_ReasonforRevocationPacket extends OpenPGP_SignaturePacket_Subpacket { - 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_KeyFlagsPacket { - // Identical functionality to parent -} - -class OpenPGP_SignaturePacket_SignatureTargetPacket extends OpenPGP_SignaturePacket_Subpacket { - 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 { - // 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); - } -} - -/** - * OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.3 - */ -class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet { - public $version, $symmetric_algorithm, $s2k, $encrypted_data; - - 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; - } -} - -/** - * OpenPGP One-Pass Signature packet (tag 4). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.4 - */ -class OpenPGP_OnePassSignaturePacket extends OpenPGP_Packet { - public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested; - 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 .= sprintf('%02X',ord($this->read_byte())); - } - $this->nested = ord($this->read_byte()); - } - - 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((int)$this->nested); - return $body; - } -} - -/** - * OpenPGP Public-Key packet (tag 6). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1 - * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 - * @see http://tools.ietf.org/html/rfc4880#section-11.1 - * @see http://tools.ietf.org/html/rfc4880#section-12 - */ -class OpenPGP_PublicKeyPacket extends OpenPGP_Packet { - public $version, $timestamp, $algorithm; - public $key, $key_id, $fingerprint; - public $v3_days_of_validity; - - 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 { - 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 3: - $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(); - } - } - - /** - * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 - */ - function read_key_material() { - foreach (self::$key_fields[$this->algorithm] as $field) { - $this->key[$field] = $this->read_mpi(); - } - $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) { - $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); - $material[] = $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 - */ - function fingerprint() { - switch ($this->version) { - case 2: - case 3: - return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material()))); - case 4: - 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()) - ); - case 4: - return implode('', array_slice($this->fingerprint_material(), 2)); - } - } - - static $key_fields = array( - 1 => array('n', 'e'), // RSA - 16 => array('p', 'g', 'y'), // ELG-E - 17 => array('p', 'q', 'g', 'y'), // DSA - ); - - static $algorithms = array( - 1 => 'RSA', - 2 => 'RSA', - 3 => 'RSA', - 16 => 'ELGAMAL', - 17 => 'DSA', - 18 => 'ECC', - 19 => 'ECDSA', - 21 => 'DH' - ); - -} - -/** - * OpenPGP Public-Subkey packet (tag 14). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2 - * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 - * @see http://tools.ietf.org/html/rfc4880#section-11.1 - * @see http://tools.ietf.org/html/rfc4880#section-12 - */ -class OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket { - // TODO -} - -/** - * OpenPGP Secret-Key packet (tag 5). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3 - * @see http://tools.ietf.org/html/rfc4880#section-5.5.3 - * @see http://tools.ietf.org/html/rfc4880#section-11.2 - * @see http://tools.ietf.org/html/rfc4880#section-12 - */ -class OpenPGP_SecretKeyPacket extends OpenPGP_PublicKeyPacket { - 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 - ); - - 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; - } -} - -/** - * OpenPGP Secret-Subkey packet (tag 7). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4 - * @see http://tools.ietf.org/html/rfc4880#section-5.5.3 - * @see http://tools.ietf.org/html/rfc4880#section-11.2 - * @see http://tools.ietf.org/html/rfc4880#section-12 - */ -class OpenPGP_SecretSubkeyPacket extends OpenPGP_SecretKeyPacket { - // TODO -} - -/** - * OpenPGP Compressed Data packet (tag 8). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.6 - */ -class OpenPGP_CompressedDataPacket extends OpenPGP_Packet implements IteratorAggregate, ArrayAccess { - public $algorithm; - /* see http://tools.ietf.org/html/rfc4880#section-9.3 */ - static $algorithms = array(0 => 'Uncompressed', 1 => 'ZIP', 2 => 'ZLIB', 3 => 'BZip2'); - function read() { - $this->algorithm = ord($this->read_byte()); - $this->data = $this->read_bytes($this->length); - switch($this->algorithm) { - case 0: - $this->data = OpenPGP_Message::parse($this->data); - break; - case 1: - $this->data = OpenPGP_Message::parse(gzinflate($this->data)); - break; - case 2: - $this->data = OpenPGP_Message::parse(gzuncompress($this->data)); - break; - case 3: - $this->data = OpenPGP_Message::parse(bzdecompress($this->data)); - break; - default: - /* TODO error? */ - } - } - - function body() { - $body = chr($this->algorithm); - switch($this->algorithm) { - case 0: - $body .= $this->data->to_bytes(); - break; - case 1: - $body .= gzdeflate($this->data->to_bytes()); - break; - case 2: - $body .= gzcompress($this->data->to_bytes()); - break; - case 3: - $body .= bzcompress($this->data->to_bytes()); - break; - default: - /* TODO error? */ - } - return $body; - } - - // IteratorAggregate interface - - function getIterator() { - return new ArrayIterator($this->data->packets); - } - - // ArrayAccess interface - - function offsetExists($offset) { - return isset($this->data[$offset]); - } - - function offsetGet($offset) { - return $this->data[$offset]; - } - - function offsetSet($offset, $value) { - return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value; - } - - function offsetUnset($offset) { - unset($this->data[$offset]); - } - -} - -/** - * OpenPGP Symmetrically Encrypted Data packet (tag 9). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.7 - */ -class OpenPGP_EncryptedDataPacket extends OpenPGP_Packet { - function read() { - $this->data = $this->input; - } - - function body() { - return $this->data; - } -} - -/** - * OpenPGP Marker packet (tag 10). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.8 - */ -class OpenPGP_MarkerPacket extends OpenPGP_Packet { - // TODO -} - -/** - * OpenPGP Literal Data packet (tag 11). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.9 - */ -class OpenPGP_LiteralDataPacket extends OpenPGP_Packet { - public $format, $filename, $timestamp; - - 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(); - $filename_length = ord($this->read_byte()); - $this->size -= $filename_length; - $this->filename = $this->read_bytes($filename_length); - $this->timestamp = $this->read_timestamp(); - $this->data = $this->read_bytes($this->size); - } - - function body() { - return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data; - } -} - -/** - * OpenPGP Trust packet (tag 12). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.10 - */ -class OpenPGP_TrustPacket extends OpenPGP_Packet { - function read() { - $this->data = $this->input; - } - - function body() { - return $this->data; - } -} - -/** - * OpenPGP User ID packet (tag 13). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.11 - * @see http://tools.ietf.org/html/rfc2822 - */ -class OpenPGP_UserIDPacket extends OpenPGP_Packet { - public $name, $comment, $email; - - 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->data = $this->input; - // User IDs of the form: "name (comment) " - 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->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->data, $matches)) { - $this->name = trim($matches[1]); - $this->comment = NULL; - $this->email = NULL; - } - // User IDs of the form: "" - else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) { - $this->name = NULL; - $this->comment = NULL; - $this->email = trim($matches[2]); - } - } - - function __toString() { - $text = array(); - if ($this->name) { $text[] = $this->name; } - if ($this->comment) { $text[] = "({$this->comment})"; } - if ($this->email) { $text[] = "<{$this->email}>"; } - return implode(' ', $text); - } - - function body() { - return ''.$this; // Convert to string is the body - } -} - -/** - * OpenPGP User Attribute packet (tag 17). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.12 - * @see http://tools.ietf.org/html/rfc4880#section-11.1 - */ -class OpenPGP_UserAttributePacket extends OpenPGP_Packet { - public $packets; - - // TODO -} - -/** - * OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.13 - */ -class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_EncryptedDataPacket { - public $version; - - 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; - } -} - -/** - * OpenPGP Modification Detection Code packet (tag 19). - * - * @see http://tools.ietf.org/html/rfc4880#section-5.14 - */ -class OpenPGP_ModificationDetectionCodePacket extends OpenPGP_Packet { - 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; - } -} - -/** - * OpenPGP Private or Experimental packet (tags 60..63). - * - * @see http://tools.ietf.org/html/rfc4880#section-4.3 - */ -class OpenPGP_ExperimentalPacket extends OpenPGP_Packet {} diff --git a/lib/openpgp_crypt_rsa.php b/lib/openpgp_crypt_rsa.php deleted file mode 100644 index 70925ef..0000000 --- a/lib/openpgp_crypt_rsa.php +++ /dev/null @@ -1,276 +0,0 @@ -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->setHash(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->setHash(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->setHash(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 array($key->sign($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->setHash(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 array($key->sign($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); - } - - foreach($message as $p) { - if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) { - 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; - } - } - - return NULL; /* Failed */ - } - - static function try_decrypt_session($key, $edata) { - $key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); - $data = @$key->decrypt($edata); - 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') { - $rsa = new Crypt_RSA(); - $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); - $rsa->setHash(strtolower($hash)); - $rsa->modulus = new Math_BigInteger($mod, 256); - $rsa->k = strlen($rsa->modulus->toBytes()); - $rsa->exponent = new Math_BigInteger($exp, 256); - $rsa->setPublicKey(); - return $rsa; - } - - static function convert_key($packet, $private=false) { - if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); - if($packet instanceof OpenPGP_Message) $packet = $packet[0]; - - $mod = $packet->key['n']; - $exp = $packet->key['e']; - if($private) $exp = $packet->key['d']; - if(!$exp) return NULL; // Packet doesn't have needed data - - $rsa = self::crypt_rsa_key($mod, $exp); - - if($private) { - /** - * @see https://github.com/phpseclib/phpseclib/issues/1113 - * Primes and coefficients now use BigIntegers. - **/ - //set the primes - if($packet->key['p'] && $packet->key['q']) - $rsa->primes = array( - 1 => new Math_BigInteger($packet->key['p'], 256), - 2 => new Math_BigInteger($packet->key['q'], 256) - ); - // set the coefficients - if($packet->key['u']) $rsa->coefficients = array(2 => new Math_BigInteger($packet->key['u'], 256)); - } - - return $rsa; - } - - 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 deleted file mode 100644 index a69c37a..0000000 --- a/lib/openpgp_crypt_symmetric.php +++ /dev/null @@ -1,236 +0,0 @@ -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(); - $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_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; - switch($algo) { - case NULL: - case 0: - throw new Exception("Data is already unencrypted"); - case 2: - $cipher = new Crypt_TripleDES(Crypt_TripleDES::MODE_CFB); - $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(Crypt_Blowfish::MODE_CFB); - $key_bytes = 16; - $key_block_bytes = 8; - break; - case 7: - $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); - $cipher->setKeyLength(128); - break; - case 8: - $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); - $cipher->setKeyLength(192); - break; - case 9: - $cipher = new Crypt_AES(Crypt_AES::MODE_CFB); - $cipher->setKeyLength(256); - break; - case 10: - $cipher = new Crypt_Twofish(Crypt_Twofish::MODE_CFB); - if(method_exists($cipher, 'setKeyLength')) { - $cipher->setKeyLength(256); - } else { - $cipher = NULL; - } - break; - } - if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher - if(!isset($key_bytes)) $key_bytes = isset($cipher->key_size)?$cipher->key_size:$cipher->key_length; - if(!isset($key_block_bytes)) $key_block_bytes = $cipher->block_size; - 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; - } -}