diff -Nru ndncert-0.0.5-1-gfae76c4dc/.github/workflows/ci.yml ndncert-0.0.6-1-g3e4818421/.github/workflows/ci.yml --- ndncert-0.0.5-1-gfae76c4dc/.github/workflows/ci.yml 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.github/workflows/ci.yml 2023-11-17 18:39:05.000000000 +0000 @@ -1,68 +1,12 @@ name: CI on: push: - paths-ignore: - - 'docs/**' - - '*.md' - - '.mailmap' workflow_dispatch: -permissions: - contents: read +permissions: {} jobs: - linux: - name: ${{ matrix.compiler }} on ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - compiler: [g++-8, g++-9, g++-10, g++-11, - clang++-7, clang++-8, clang++-9, clang++-10, clang++-11, clang++-12] - os: [ubuntu-20.04] - include: - - compiler: g++-7 - os: ubuntu-18.04 - - compiler: clang++-5.0 - os: ubuntu-18.04 - - compiler: clang++-6.0 - os: ubuntu-18.04 - runs-on: ${{ matrix.os }} - env: - CXX: ${{ matrix.compiler }} - NODE_LABELS: Linux Ubuntu - WAF_JOBS: 2 - steps: - - name: Install C++ compiler - run: | - sudo apt-get -qy install ${CXX/clang++/clang} - ${CXX} --version - - name: Checkout - uses: actions/checkout@v2 - - name: Build and test - run: ./.jenkins - - macos: - name: Xcode ${{ matrix.xcode }} on ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - xcode: ['11.3', '11.7', '12.4'] - os: [macos-10.15] - include: - - xcode: '12.5' - os: macos-11 - - xcode: '13.2' - os: macos-11 - runs-on: ${{ matrix.os }} - env: - NODE_LABELS: OSX - WAF_JOBS: 3 - steps: - - name: Set up Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: ${{ matrix.xcode }} - - name: Checkout - uses: actions/checkout@v2 - - name: Build and test - run: ./.jenkins + Ubuntu: + uses: named-data/actions/.github/workflows/jenkins-script-ubuntu.yml@v1 + macOS: + uses: named-data/actions/.github/workflows/jenkins-script-macos.yml@v1 diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins ndncert-0.0.6-1-g3e4818421/.jenkins --- ndncert-0.0.5-1-gfae76c4dc/.jenkins 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins 2023-11-17 18:39:05.000000000 +0000 @@ -1,13 +1,39 @@ #!/usr/bin/env bash -set -e -source .jenkins.d/util.sh +set -eo pipefail + +case $(uname) in + Linux) + if [[ -e /etc/os-release ]]; then + source /etc/os-release + else + source /usr/lib/os-release + fi + export ID VERSION_ID + export ID_LIKE="${ID} ${ID_LIKE} linux" + export PATH="${HOME}/.local/bin${PATH:+:}${PATH}" + ;; + Darwin) + # Emulate a subset of os-release(5) + export ID=macos + export VERSION_ID=$(sw_vers -productVersion) + export PATH="/usr/local/bin${PATH:+:}${PATH}" + if [[ -x /opt/homebrew/bin/brew ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -x /usr/local/bin/brew ]]; then + eval "$(/usr/local/bin/brew shellenv)" + fi + ;; +esac -if has Linux $NODE_LABELS; then - export PATH="${HOME}/.local/bin${PATH:+:}${PATH}" -fi export CACHE_DIR=${CACHE_DIR:-/tmp} -export WAF_JOBS=${WAF_JOBS:-1} -[[ $JOB_NAME == *"code-coverage" ]] && export DISABLE_ASAN=yes + +if [[ $JOB_NAME == *"code-coverage" ]]; then + export DISABLE_ASAN=yes + export DISABLE_HEADERS_CHECK=yes +fi + +# https://reproducible-builds.org/docs/source-date-epoch/ +export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) for file in .jenkins.d/*; do [[ -f $file && -x $file ]] || continue diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/00-deps.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/00-deps.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/00-deps.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/00-deps.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,40 +1,37 @@ #!/usr/bin/env bash -set -ex +set -eo pipefail -if has OSX $NODE_LABELS; then - FORMULAE=(boost openssl pkg-config) - if [[ $JOB_NAME == *"Docs" ]]; then +APT_PKGS=(build-essential pkg-config python3-minimal + libboost-all-dev libssl-dev libsqlite3-dev) +FORMULAE=(boost openssl pkg-config) +PIP_PKGS=() +case $JOB_NAME in + *code-coverage) + APT_PKGS+=(lcov python3-pip) + PIP_PKGS+=('gcovr~=5.2') + ;; + *Docs) + APT_PKGS+=(doxygen graphviz) FORMULAE+=(doxygen graphviz) - fi + ;; +esac + +set -x +if [[ $ID == macos ]]; then if [[ -n $GITHUB_ACTIONS ]]; then - # GitHub Actions runners have a large number of pre-installed - # Homebrew packages. Don't waste time upgrading all of them. - brew list --versions "${FORMULAE[@]}" || brew update - for FORMULA in "${FORMULAE[@]}"; do - brew list --versions "$FORMULA" || brew install "$FORMULA" - done - # Ensure /usr/local/opt/openssl exists - brew reinstall openssl - else - brew update - brew upgrade - brew install "${FORMULAE[@]}" - brew cleanup + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 fi - -elif has Ubuntu $NODE_LABELS; then + brew update + brew install --formula "${FORMULAE[@]}" +elif [[ $ID_LIKE == *debian* ]]; then sudo apt-get -qq update - sudo apt-get -qy install build-essential pkg-config python3-minimal \ - libboost-all-dev libssl-dev libsqlite3-dev + sudo apt-get -qy install "${APT_PKGS[@]}" +elif [[ $ID_LIKE == *fedora* ]]; then + sudo dnf -y install gcc-c++ libasan lld pkgconf-pkg-config python3 \ + boost-devel openssl-devel sqlite-devel +fi - case $JOB_NAME in - *code-coverage) - sudo apt-get -qy install lcov python3-pip - pip3 install --user --upgrade --upgrade-strategy=eager 'gcovr~=5.0' - ;; - *Docs) - sudo apt-get -qy install doxygen graphviz - ;; - esac +if (( ${#PIP_PKGS[@]} )); then + pip3 install --user --upgrade --upgrade-strategy=eager "${PIP_PKGS[@]}" fi diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/01-ndn-cxx.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/01-ndn-cxx.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/01-ndn-cxx.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/01-ndn-cxx.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,11 +1,11 @@ #!/usr/bin/env bash -set -ex +set -exo pipefail pushd "$CACHE_DIR" >/dev/null INSTALLED_VERSION= -if has OSX $NODE_LABELS; then - BOOST=$(brew ls --versions boost) +if [[ $ID == macos ]]; then + BOOST=$(brew list --formula --versions boost) OLD_BOOST=$(cat boost.txt || :) if [[ $OLD_BOOST != $BOOST ]]; then echo "$BOOST" > boost.txt @@ -35,21 +35,16 @@ pushd ndn-cxx >/dev/null -if has CentOS-8 $NODE_LABELS; then - # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 - PCH="--without-pch" -fi - -./waf --color=yes configure --disable-static --enable-shared --without-osx-keychain $PCH -./waf --color=yes build -j$WAF_JOBS -sudo_preserve_env PATH -- ./waf --color=yes install +./waf --color=yes configure --without-osx-keychain +./waf --color=yes build +sudo ./waf --color=yes install popd >/dev/null popd >/dev/null -if has CentOS-8 $NODE_LABELS; then +if [[ $ID_LIKE == *fedora* ]]; then sudo tee /etc/ld.so.conf.d/ndn.conf >/dev/null <<< /usr/local/lib64 fi -if has Linux $NODE_LABELS; then +if [[ $ID_LIKE == *linux* ]]; then sudo ldconfig fi diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/09-cleanup.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/09-cleanup.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/09-cleanup.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/09-cleanup.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -ex +set -exo pipefail PROJ=ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/10-build.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/10-build.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/10-build.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/10-build.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,8 +1,5 @@ #!/usr/bin/env bash -set -ex - -git submodule sync -git submodule update --init +set -eo pipefail if [[ -z $DISABLE_ASAN ]]; then ASAN="--with-sanitizer=address" @@ -11,17 +8,19 @@ COVERAGE="--with-coverage" fi +set -x + if [[ $JOB_NAME != *"code-coverage" && $JOB_NAME != *"limited-build" ]]; then # Build in release mode with tests ./waf --color=yes configure --with-tests - ./waf --color=yes build -j$WAF_JOBS + ./waf --color=yes build # Cleanup ./waf --color=yes distclean # Build in release mode without tests ./waf --color=yes configure - ./waf --color=yes build -j$WAF_JOBS + ./waf --color=yes build # Cleanup ./waf --color=yes distclean @@ -29,13 +28,16 @@ # Build in debug mode with tests ./waf --color=yes configure --debug --with-tests $ASAN $COVERAGE -./waf --color=yes build -j$WAF_JOBS +./waf --color=yes build # (tests will be run against the debug version) # Install -sudo_preserve_env PATH -- ./waf --color=yes install +sudo ./waf --color=yes install -if has Linux $NODE_LABELS; then +if [[ $ID_LIKE == *fedora* ]]; then + sudo tee /etc/ld.so.conf.d/ndn.conf >/dev/null <<< /usr/local/lib64 +fi +if [[ $ID_LIKE == *linux* ]]; then sudo ldconfig fi diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/20-tests.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/20-tests.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/20-tests.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/20-tests.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,9 +1,5 @@ #!/usr/bin/env bash -set -ex - -# Prepare environment -rm -rf ~/.ndn -ndnsec key-gen "/tmp/jenkins/$NODE_NAME" | ndnsec cert-install - +set -eo pipefail # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags ASAN_OPTIONS="color=always" @@ -15,10 +11,16 @@ ASAN_OPTIONS+=":strip_path_prefix=${PWD}/" export ASAN_OPTIONS +# https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/runtime_config/summary.html export BOOST_TEST_BUILD_INFO=1 export BOOST_TEST_COLOR_OUTPUT=1 export BOOST_TEST_DETECT_MEMORY_LEAK=0 export BOOST_TEST_LOGGER=HRF,test_suite,stdout:XML,all,build/xunit-log.xml +set -x + +# Prepare environment +rm -rf ~/.ndn + # Run unit tests ./build/unit-tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/30-coverage.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/30-coverage.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/30-coverage.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/30-coverage.sh 2023-11-17 18:39:05.000000000 +0000 @@ -1,17 +1,16 @@ #!/usr/bin/env bash -set -ex +set -exo pipefail if [[ $JOB_NAME == *"code-coverage" ]]; then # Generate an XML report (Cobertura format) and a detailed HTML report using gcovr # Note: trailing slashes are important in the paths below. Do not remove them! - gcovr -j$WAF_JOBS \ - --object-directory build \ + gcovr --object-directory build \ --filter src/ \ --exclude-throw-branches \ --exclude-unreachable-branches \ - --print-summary \ + --cobertura build/coverage.xml \ --html-details build/gcovr/ \ - --xml build/coverage.xml + --print-summary # Generate a detailed HTML report using lcov lcov --quiet \ diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/README.md ndncert-0.0.6-1-g3e4818421/.jenkins.d/README.md --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/README.md 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/README.md 2023-11-17 18:39:05.000000000 +0000 @@ -1,28 +1,36 @@ -# CONTINUOUS INTEGRATION SCRIPTS +# Continuous Integration Scripts -## Environment Variables Used in Build Scripts +## Environment Variables -- `NODE_LABELS`: space-separated list of platform properties. The included values are used by - the build scripts to select the proper behavior for different operating systems and versions. +- `ID`: lower-case string that identifies the operating system, for example: `ID=ubuntu`, + `ID=centos`. See [os-release(5)] for more information. On macOS, where `os-release` is + not available, we emulate it by setting `ID=macos`. - The list should normally contain `[OS_TYPE]`, `[DISTRO_TYPE]`, and `[DISTRO_VERSION]`. +- `ID_LIKE`: space-separated list of operating system identifiers that are closely related + to the running OS. See [os-release(5)] for more information. The listed values are used + by the CI scripts to select the proper behavior for different platforms and OS flavors. - Example values: + Examples: - - `[OS_TYPE]`: `Linux`, `OSX` - - `[DISTRO_TYPE]`: `Ubuntu`, `CentOS` - - `[DISTRO_VERSION]`: `Ubuntu-16.04`, `Ubuntu-18.04`, `CentOS-8`, `OSX-10.14`, `OSX-10.15` + - On CentOS, `ID_LIKE="centos rhel fedora linux"` + - On Ubuntu, `ID_LIKE="ubuntu debian linux"` -- `JOB_NAME`: optional variable that defines the type of build job. Depending on the job type, - the build scripts can perform different tasks. +- `VERSION_ID`: identifies the operating system version, excluding any release code names. + See [os-release(5)] for more information. Examples: `VERSION_ID=42`, `VERSION_ID=22.04`. - Possible values: +- `JOB_NAME`: defines the type of the current CI job. Depending on the job type, the CI + scripts can perform different tasks. + + Supported values: - empty: default build task - - `code-coverage`: debug build with tests and code coverage analysis (Ubuntu Linux is assumed) + - `code-coverage`: debug build with tests and code coverage analysis - `limited-build`: only a single debug build with tests -- `CACHE_DIR`: directory containing cached files from previous builds, e.g., a compiled version - of ndn-cxx. If not set, `/tmp` is used. +- `CACHE_DIR`: directory containing cached files from previous builds, e.g., a compiled + version of ndn-cxx. If not set, `/tmp` is used. + +- `DISABLE_ASAN`: disable building with AddressSanitizer. This is automatically set for + the `code-coverage` job type. -- `WAF_JOBS`: number of parallel build threads used by waf, defaults to 1. +[os-release(5)]: https://www.freedesktop.org/software/systemd/man/os-release.html diff -Nru ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/util.sh ndncert-0.0.6-1-g3e4818421/.jenkins.d/util.sh --- ndncert-0.0.5-1-gfae76c4dc/.jenkins.d/util.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.jenkins.d/util.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -has() { - local saved_xtrace - [[ $- == *x* ]] && saved_xtrace=-x || saved_xtrace=+x - set +x - - local p=$1 - shift - local i ret=1 - for i in "$@"; do - if [[ "${i}" == "${p}" ]]; then - ret=0 - break - fi - done - - set ${saved_xtrace} - return ${ret} -} -export -f has - -sudo_preserve_env() { - local saved_xtrace - [[ $- == *x* ]] && saved_xtrace=-x || saved_xtrace=+x - set +x - - local vars=() - while [[ $# -gt 0 ]]; do - local arg=$1 - shift - case ${arg} in - --) break ;; - *) vars+=("${arg}=${!arg}") ;; - esac - done - - set ${saved_xtrace} - sudo env "${vars[@]}" "$@" -} -export -f sudo_preserve_env diff -Nru ndncert-0.0.5-1-gfae76c4dc/.waf-tools/boost.py ndncert-0.0.6-1-g3e4818421/.waf-tools/boost.py --- ndncert-0.0.5-1-gfae76c4dc/.waf-tools/boost.py 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.waf-tools/boost.py 2023-11-17 18:39:05.000000000 +0000 @@ -54,8 +54,8 @@ from waflib.Configure import conf from waflib.TaskGen import feature, after_method -BOOST_LIBS = ['/usr/lib', '/usr/local/lib', '/opt/local/lib', '/sw/lib', '/lib'] -BOOST_INCLUDES = ['/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include'] +BOOST_LIBS = ['/usr/lib', '/usr/local/lib', '/opt/homebrew/lib', '/opt/local/lib', '/sw/lib', '/lib'] +BOOST_INCLUDES = ['/usr/include', '/usr/local/include', '/opt/homebrew/include', '/opt/local/include', '/sw/include'] BOOST_VERSION_FILE = 'boost/version.hpp' BOOST_VERSION_CODE = ''' diff -Nru ndncert-0.0.5-1-gfae76c4dc/.waf-tools/default-compiler-flags.py ndncert-0.0.6-1-g3e4818421/.waf-tools/default-compiler-flags.py --- ndncert-0.0.5-1-gfae76c4dc/.waf-tools/default-compiler-flags.py 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.waf-tools/default-compiler-flags.py 2023-11-17 18:39:05.000000000 +0000 @@ -3,10 +3,12 @@ import platform from waflib import Configure, Logs, Utils + def options(opt): opt.add_option('--debug', '--with-debug', action='store_true', default=False, help='Compile in debugging mode with minimal optimizations (-Og)') + def configure(conf): conf.start_msg('Checking C++ compiler version') @@ -16,23 +18,27 @@ errmsg = '' warnmsg = '' if cxx == 'gcc': - if ccver < (5, 3, 0): + if ccver < (7, 4, 0): errmsg = ('The version of gcc you are using is too old.\n' - 'The minimum supported gcc version is 7.4.0.') - elif ccver < (7, 4, 0): - warnmsg = ('Using a version of gcc older than 7.4.0 is not ' + 'The minimum supported gcc version is 9.3.') + elif ccver < (9, 3, 0): + warnmsg = ('Using a version of gcc older than 9.3 is not ' 'officially supported and may result in build failures.') conf.flags = GccFlags() elif cxx == 'clang': - if Utils.unversioned_sys_platform() == 'darwin' and ccver < (9, 0, 0): - errmsg = ('The version of Xcode you are using is too old.\n' - 'The minimum supported Xcode version is 9.0.') - elif ccver < (4, 0, 0): + if Utils.unversioned_sys_platform() == 'darwin': + if ccver < (10, 0, 0): + errmsg = ('The version of Xcode you are using is too old.\n' + 'The minimum supported Xcode version is 12.4.') + elif ccver < (12, 0, 0): + warnmsg = ('Using a version of Xcode older than 12.4 is not ' + 'officially supported and may result in build failures.') + elif ccver < (7, 0, 0): errmsg = ('The version of clang you are using is too old.\n' - 'The minimum supported clang version is 4.0.') + 'The minimum supported clang version is 7.0.') conf.flags = ClangFlags() else: - warnmsg = '%s compiler is unsupported' % cxx + warnmsg = f'{cxx} compiler is unsupported' conf.flags = CompilerFlags() if errmsg: @@ -44,7 +50,7 @@ else: conf.end_msg(ccverstr) - conf.areCustomCxxflagsPresent = (len(conf.env.CXXFLAGS) > 0) + conf.areCustomCxxflagsPresent = len(conf.env.CXXFLAGS) > 0 # General flags are always applied (e.g., selecting C++ language standard) generalFlags = conf.flags.getGeneralFlags(conf) @@ -52,6 +58,7 @@ conf.add_supported_linkflags(generalFlags['LINKFLAGS']) conf.env.DEFINES += generalFlags['DEFINES'] + @Configure.conf def check_compiler_flags(conf): # Debug or optimized CXXFLAGS and LINKFLAGS are applied only if the @@ -74,10 +81,11 @@ conf.env.DEFINES += extraFlags['DEFINES'] + @Configure.conf def add_supported_cxxflags(self, cxxflags): """ - Check which cxxflags are supported by compiler and add them to env.CXXFLAGS variable + Check which cxxflags are supported by the active compiler and add them to env.CXXFLAGS variable. """ if len(cxxflags) == 0: return @@ -93,10 +101,11 @@ self.end_msg(' '.join(supportedFlags)) self.env.prepend_value('CXXFLAGS', supportedFlags) + @Configure.conf def add_supported_linkflags(self, linkflags): """ - Check which linkflags are supported by compiler and add them to env.LINKFLAGS variable + Check which linkflags are supported by the active compiler and add them to env.LINKFLAGS variable. """ if len(linkflags) == 0: return @@ -113,13 +122,17 @@ self.env.prepend_value('LINKFLAGS', supportedFlags) -class CompilerFlags(object): +class CompilerFlags: def getCompilerVersion(self, conf): return tuple(int(i) for i in conf.env.CC_VERSION) def getGeneralFlags(self, conf): """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are always needed""" - return {'CXXFLAGS': [], 'LINKFLAGS': [], 'DEFINES': []} + return { + 'CXXFLAGS': [], + 'LINKFLAGS': [], + 'DEFINES': ['BOOST_ASIO_NO_DEPRECATED', 'BOOST_FILESYSTEM_NO_DEPRECATED'], + } def getDebugFlags(self, conf): """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are needed only in debug mode""" @@ -129,99 +142,100 @@ """Get dict of CXXFLAGS, LINKFLAGS, and DEFINES that are needed only in optimized mode""" return {'CXXFLAGS': [], 'LINKFLAGS': [], 'DEFINES': ['NDEBUG']} -class GccBasicFlags(CompilerFlags): + +class GccClangCommonFlags(CompilerFlags): """ - This class defines basic flags that work for both gcc and clang compilers + This class defines common flags that work for both gcc and clang compilers. """ + def getGeneralFlags(self, conf): - flags = super(GccBasicFlags, self).getGeneralFlags(conf) - flags['CXXFLAGS'] += ['-std=c++14'] - if Utils.unversioned_sys_platform() == 'linux': - flags['LINKFLAGS'] += ['-fuse-ld=gold'] - elif Utils.unversioned_sys_platform() == 'freebsd': + flags = super().getGeneralFlags(conf) + flags['CXXFLAGS'] += ['-std=c++17'] + if Utils.unversioned_sys_platform() != 'darwin': flags['LINKFLAGS'] += ['-fuse-ld=lld'] return flags + __cxxFlags = [ + '-fdiagnostics-color', + '-Wall', + '-Wextra', + '-Wpedantic', + '-Wenum-conversion', + '-Wextra-semi', + '-Wnon-virtual-dtor', + '-Wno-unused-parameter', + ] + __linkFlags = ['-Wl,-O1'] + def getDebugFlags(self, conf): - flags = super(GccBasicFlags, self).getDebugFlags(conf) - flags['CXXFLAGS'] += ['-Og', - '-g3', - '-pedantic', - '-Wall', - '-Wextra', - '-Werror', - '-Wcatch-value=2', - '-Wextra-semi', - '-Wnon-virtual-dtor', - '-Wno-error=deprecated-declarations', # Bug #3795 - '-Wno-error=maybe-uninitialized', # Bug #1615 - '-Wno-unused-parameter', - ] - flags['LINKFLAGS'] += ['-Wl,-O1'] + flags = super().getDebugFlags(conf) + flags['CXXFLAGS'] += ['-Og', '-g'] + self.__cxxFlags + [ + '-Werror', + '-Wno-error=deprecated-declarations', # Bug #3795 + '-Wno-error=maybe-uninitialized', # Bug #1615 + ] + flags['LINKFLAGS'] += self.__linkFlags return flags def getOptimizedFlags(self, conf): - flags = super(GccBasicFlags, self).getOptimizedFlags(conf) - flags['CXXFLAGS'] += ['-O2', - '-g', - '-pedantic', - '-Wall', - '-Wextra', - '-Wcatch-value=2', - '-Wextra-semi', - '-Wnon-virtual-dtor', - '-Wno-unused-parameter', - ] - flags['LINKFLAGS'] += ['-Wl,-O1'] + flags = super().getOptimizedFlags(conf) + flags['CXXFLAGS'] += ['-O2', '-g1'] + self.__cxxFlags + flags['LINKFLAGS'] += self.__linkFlags return flags -class GccFlags(GccBasicFlags): + +class GccFlags(GccClangCommonFlags): + __cxxFlags = [ + '-Wcatch-value=2', + '-Wcomma-subscript', # enabled by default in C++20 + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Wlogical-op', + '-Wredundant-tags', + '-Wvolatile', # enabled by default in C++20 + ] + def getDebugFlags(self, conf): - flags = super(GccFlags, self).getDebugFlags(conf) - flags['CXXFLAGS'] += ['-fdiagnostics-color', - '-Wredundant-tags', - ] - if platform.machine() == 'armv7l' and self.getCompilerVersion(conf) >= (7, 1, 0): + flags = super().getDebugFlags(conf) + flags['CXXFLAGS'] += self.__cxxFlags + if platform.machine() == 'armv7l': flags['CXXFLAGS'] += ['-Wno-psabi'] # Bug #5106 return flags def getOptimizedFlags(self, conf): - flags = super(GccFlags, self).getOptimizedFlags(conf) - flags['CXXFLAGS'] += ['-fdiagnostics-color', - '-Wredundant-tags', - ] - if platform.machine() == 'armv7l' and self.getCompilerVersion(conf) >= (7, 1, 0): + flags = super().getOptimizedFlags(conf) + flags['CXXFLAGS'] += self.__cxxFlags + if platform.machine() == 'armv7l': flags['CXXFLAGS'] += ['-Wno-psabi'] # Bug #5106 return flags -class ClangFlags(GccBasicFlags): + +class ClangFlags(GccClangCommonFlags): def getGeneralFlags(self, conf): - flags = super(ClangFlags, self).getGeneralFlags(conf) + flags = super().getGeneralFlags(conf) if Utils.unversioned_sys_platform() == 'darwin': # Bug #4296 - flags['CXXFLAGS'] += [['-isystem', '/usr/local/include'], # for Homebrew - ['-isystem', '/opt/local/include']] # for MacPorts + brewdir = '/opt/homebrew' if platform.machine() == 'arm64' else '/usr/local' + flags['CXXFLAGS'] += [ + ['-isystem', f'{brewdir}/include'], # for Homebrew + ['-isystem', '/opt/local/include'], # for MacPorts + ] elif Utils.unversioned_sys_platform() == 'freebsd': # Bug #4790 flags['CXXFLAGS'] += [['-isystem', '/usr/local/include']] return flags + __cxxFlags = [ + '-Wundefined-func-template', + '-Wno-unused-local-typedef', # Bugs #2657 and #3209 + ] + def getDebugFlags(self, conf): - flags = super(ClangFlags, self).getDebugFlags(conf) - flags['CXXFLAGS'] += ['-fcolor-diagnostics', - '-Wundefined-func-template', - '-Wno-unused-local-typedef', # Bugs #2657 and #3209 - ] - if self.getCompilerVersion(conf) < (6, 0, 0): - flags['CXXFLAGS'] += ['-Wno-missing-braces'] # Bug #4721 + flags = super().getDebugFlags(conf) + flags['CXXFLAGS'] += self.__cxxFlags return flags def getOptimizedFlags(self, conf): - flags = super(ClangFlags, self).getOptimizedFlags(conf) - flags['CXXFLAGS'] += ['-fcolor-diagnostics', - '-Wundefined-func-template', - '-Wno-unused-local-typedef', # Bugs #2657 and #3209 - ] - if self.getCompilerVersion(conf) < (6, 0, 0): - flags['CXXFLAGS'] += ['-Wno-missing-braces'] # Bug #4721 + flags = super().getOptimizedFlags(conf) + flags['CXXFLAGS'] += self.__cxxFlags return flags diff -Nru ndncert-0.0.5-1-gfae76c4dc/.waf-tools/openssl.py ndncert-0.0.6-1-g3e4818421/.waf-tools/openssl.py --- ndncert-0.0.5-1-gfae76c4dc/.waf-tools/openssl.py 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/.waf-tools/openssl.py 2023-11-17 18:39:05.000000000 +0000 @@ -19,8 +19,11 @@ from waflib import Utils from waflib.Configure import conf -OPENSSL_DIR_OSX = ['/usr/local', '/opt/local', '/usr/local/opt/openssl'] OPENSSL_DIR = ['/usr', '/usr/local', '/opt/local', '/sw'] +OPENSSL_DIR_MACOS = ['/usr/local', + '/opt/homebrew/opt/openssl', # Homebrew on arm64 + '/usr/local/opt/openssl', # Homebrew on x86_64 + '/opt/local'] # MacPorts def options(opt): opt.add_option('--with-openssl', type='string', default=None, dest='openssl_dir', @@ -43,7 +46,7 @@ openssl_dirs = OPENSSL_DIR if Utils.unversioned_sys_platform() == 'darwin': - openssl_dirs = OPENSSL_DIR_OSX + openssl_dirs = OPENSSL_DIR_MACOS for dir in openssl_dirs: file = self.__openssl_get_version_file(dir) diff -Nru ndncert-0.0.5-1-gfae76c4dc/AUTHORS.md ndncert-0.0.6-1-g3e4818421/AUTHORS.md --- ndncert-0.0.5-1-gfae76c4dc/AUTHORS.md 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/AUTHORS.md 2023-11-17 18:39:05.000000000 +0000 @@ -1,8 +1,8 @@ -# ndncert Authors +# NDNCERT Authors -The following lists maintainers, primary developers, and all much-appreciated contributors to ndncert in alphabetic order. -The specific contributions of individual authors can be obtained from the git history of the [official ndncert repository](https://github.com/named-data/ndncert). -If you would like to become a contributor to the official repository, please follow the recommendations in https://github.com/named-data/.github/blob/master/CONTRIBUTING.md. +The following lists maintainers, primary developers, and all much-appreciated contributors to NDNCERT in alphabetical order. +The specific contributions of individual authors can be obtained from the git history of the [official NDNCERT repository](https://github.com/named-data/ndncert). +If you would like to become a contributor to the official repository, please follow the recommendations in . * Alexander Afanasyev * Ashlesh Gawande @@ -11,9 +11,10 @@ * Davide Pesavento * Md Ashiqur Rahman * Junxiao Shi +* Tianyuan Yu * Yufeng Zhang * Zhiyi Zhang -# Technical Advisors +## Technical Advisors * Lixia Zhang diff -Nru ndncert-0.0.5-1-gfae76c4dc/COPYING.md ndncert-0.0.6-1-g3e4818421/COPYING.md --- ndncert-0.0.5-1-gfae76c4dc/COPYING.md 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/COPYING.md 2023-11-17 18:39:05.000000000 +0000 @@ -14,11 +14,10 @@ - The waf build system is licensed under the terms of the [BSD license](https://github.com/named-data/ndncert/blob/master/waf) - The GPL license is provided below in this file. For more information about -these licenses, see https://www.gnu.org/licenses/ +these licenses, see ----------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- ### GNU GENERAL PUBLIC LICENSE diff -Nru ndncert-0.0.5-1-gfae76c4dc/README.md ndncert-0.0.6-1-g3e4818421/README.md --- ndncert-0.0.5-1-gfae76c4dc/README.md 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/README.md 2023-11-17 18:39:05.000000000 +0000 @@ -1,7 +1,7 @@ # NDNCERT: NDN Certificate Management Protocol [![CI](https://github.com/named-data/ndncert/actions/workflows/ci.yml/badge.svg)](https://github.com/named-data/ndncert/actions/workflows/ci.yml) -![Language](https://img.shields.io/badge/C%2B%2B-14-blue) +![Language](https://img.shields.io/badge/C%2B%2B-17-blue) The NDN certificate management protocol (**NDNCERT**) enables automatic certificate management in NDN. In Named Data Networking (NDN), every entity should have a corresponding identity @@ -23,12 +23,11 @@ ## Contributing We greatly appreciate contributions to the NDNCERT code base, provided that they are -licensed under the GPL 3.0+ or a compatible license (see below). -If you are new to the NDN software community, please read the -[Contributor's Guide](https://github.com/named-data/.github/blob/master/CONTRIBUTING.md) -to get started. +licensed under the GNU GPL version 3 or a compatible license. +If you are new to the NDN software community, please read our [Contributor's Guide]( +https://github.com/named-data/.github/blob/main/CONTRIBUTING.md) to get started. ## License -NDNCERT is an open source project licensed under the GPL version 3. -See [`COPYING.md`](COPYING.md) for more information. +NDNCERT is free software distributed under the GNU General Public License version 3. +See [`COPYING.md`](COPYING.md) for details. diff -Nru ndncert-0.0.5-1-gfae76c4dc/ca.conf.sample ndncert-0.0.6-1-g3e4818421/ca.conf.sample --- ndncert-0.0.5-1-gfae76c4dc/ca.conf.sample 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/ca.conf.sample 2023-11-17 18:39:05.000000000 +0000 @@ -14,9 +14,15 @@ ], "redirect-to": [ - { - "ca-prefix": "/example/site1", - "certificate": "Bv0CvQcwCAdleGFtcGxlCAVzaXRlMQgDS0VZCAh6af3szF4QZwgEc2VsZggJ/QAAAXT6+NCKFAkYAQIZBAA27oAV/QEmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA43hjZT0HUFFJcqwj8lZnd/vg0NrzqvZ4jhsq1c+nv6J3Huc9Uq5jRZwhFQ8nBWT3CeFScO5FUQfNXDIDncZ4vYPFEnockOFVtvmKQ/ELwReUvjH80d1NPGrIVrD0lMRpv2sFr6NW2p7aw6bCSj3OJq7H/+QHDkAryssMZyHwTbPzMZHyYKmxR68CyCCpvLlgp8tYFT+cCrOc3lz3nROK3VFR+apgwubpvl8nbKD10QLcgMHSkLoLEy/Ksq8OH7MQhUEZDjLk/zL9baZ7MiKXtdUZCNTZk13y5z+4aT4TqumLB+obiDXmv6JAi+CkYIMf2ck2IvMV6JgxxIlv3+Ke2wIDAQABFlAbAQEcIQcfCAdleGFtcGxlCAVzaXRlMQgDS0VZCAh6af3szF4QZ/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwNDAwOTMwVDIyNTQwNBf9AQCCSXOqUX40mAIdKCa+nnfJCGZbNowQPJp5kDnyolj5/Ek9x8czyLcX58xTsgYtiPmL5DxMgkRujRJu9INm0pUJIJRlsqhDOwsrxIjlSgwy5AeexYe7SM3rSwljLxTR4MfBw26pym9iYt8ovHXotCDE+etyKwHzXoOgzxORoPXqBGwobNOPnhDfpzHQBFOrPd8qqLAGioNNk/k2U/uyvBbLoZS4ScNVJpfbcvcmzu/A8H/VyT4234LrlISL9WpWlO8J18yzhrXchFR0ZwCoYge5rLZ4vsQhY1WqXHCsYnRa3la6Txz44EWYEBpmk12qnkPt06KAPvQ82N1CICxFb9NY" - } + { + "ca-prefix": "/ndn/edu/ucla", + "certificate": "Bv0BNQcwCANuZG4IA2VkdQgEdWNsYQgDS0VZCAgAdGt6D7S2VAgEc2VsZggJ/QAAAX5lZMOiFAkYAQIZBAA27oAVWzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOKmvwHmK5t+MhMPgft4qmKC7YF9I6UM/o7GFa4BjZQknsqLvxdW2zIAF+iPPHJV0eVAijX6bYrQobuomiWZAY0WUBsBAxwhBx8IA25kbggDZWR1CAR1Y2xhCANLRVkICAB0a3oPtLZU/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjAxMTJUMDAxNjQ5F0cwRQIgBF/HS0j1DMo/dIILv/6IMUmMAhVtS3m97YgS8tsBhC0CIQCgEm0e6KoBCyV6PiueN9YW9zSSkdg8MLCxsyduP8tRsQ==" + }, + { + "ca-prefix": "/ndn/edu/ucla/cs", + "certificate": "Bv0BPgc0CANuZG4IA2VkdQgEdWNsYQgCY3MIA0tFWQgI27kFrpVyxUAIBHNlbGYICf0AAAF+ZZ/79xQJGAECGQQANu6AFVswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASOLtEWMpMk8tPqPe0VY9SAYA0e969NNy5t0QeseNvr6AbYWQHBR4oa6Ymv3TRlQnyy+IzvKPte5suX/Qhtnjn2FlQbAQMcJQcjCANuZG4IA2VkdQgEdWNsYQgCY3MIA0tFWQgI27kFrpVyxUD9AP0m/QD+DzE5NzAwMTAxVDAwMDAwMP0A/w8yMDQyMDExMlQwMTIxMzAXSDBGAiEAm+aJbcmI0n37Qhear5fo//S02ZlDkmao8a7olSsElx8CIQDD8dZkYfD8xcvYl3vXm7G/NSXFrnrRqxC7NR/4r4swbw==" + "policy-type": "email", + "policy-param": "cs.ucla.edu" + } ] } \ No newline at end of file diff -Nru ndncert-0.0.5-1-gfae76c4dc/client.conf.sample ndncert-0.0.6-1-g3e4818421/client.conf.sample --- ndncert-0.0.5-1-gfae76c4dc/client.conf.sample 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/client.conf.sample 2023-11-17 18:39:05.000000000 +0000 @@ -2,15 +2,8 @@ "ca-list": [ { - "ca-prefix": "/example", - "ca-info": "An example NDNCERT CA", - "max-validity-period": "1296000", - "max-suffix-length": "2", - "probe-parameters": - [ - {"probe-parameter-key": "email"} - ], - "certificate": "Bv0CrwcpCAdleGFtcGxlCANLRVkICJev38tR696CCARzZWxmCAn9AAABdPr4U3cUCRgBAhkEADbugBX9ASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLjjKYDiimVlQD2vOAOYvu3iCsfS5mzQYOjw1cVS7+EDzC0T841FrmLEtrYe1USBGTSYUKNePspBTDMeAh2WlG+UmnzHyTJEcpv3Dtp/37pYIFlR8vW6TvBkBmCKVR7IDOW0DmBEY2iHbWOCCTyTd9NVyH4XiHqFx+cL9uGRyhVeXcZ5pl335SrnW58Q6tvZeOHS8YXuiAZ8E10Mdhzdmponl2+yRlc1kzkcAX1DQZt7eYXjlSsa5FSphLzw5LNqJuESLcrPRctN+4vs8xTIvWGex8n3+6wAhfXD9DLsO01mJorUxms+mwIKnV7KNLXtwDNsAZYT2BPPnO0H5Zk+tRAgMBAAEWSRsBARwaBxgIB2V4YW1wbGUIA0tFWQgIl6/fy1Hr3oL9AP0m/QD+DzE5NzAwMTAxVDAwMDAwMP0A/w8yMDQwMDkzMFQyMjUzMzIX/QEAZLgyhRsOERTgy5Q2X4FLXQV+r6emud472za0CVT20XHLXofkgybvLvY0UJ80CtuLcNRt/WKTDIKd01SoCnonx1VydKjXsO23RV95pBt/BqZVWakYEJEgbO5KzekpBHbKJmTOWBa/Tmgd9Pd5KFhQm9Ny2ZK1nlyyV37EKqR9jADkDVgRs+Sgr+Z4v14+WePROk7LR11P4NxYfgy2L0CHjpIxiFxnU+CpQL+BoGRQDTFgGq4gxUR45rpt/Y/vl5ImMdmIFDOI5W34AjVIhSLrYH3EDhWgjo9cDqvS+KT45i+t87SBQjtcQbrLl8osQcG+HgRldBv7HEEDVoPL8qL0yg==" + "ca-prefix": "/ndn", + "certificate": "Bv0BSwcjCANuZG4IA0tFWQgIJ8SyKp97gScIA25kbjYIAAABgHX6c7QUCRgBAhkEADbugBVbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPuDnW4oq0mULLT8PDXh0zuBg+0SJ1yPC85jylUU+hgxX9fDNyjlykLrvb1D6IQRJWJHMKWe6TJKPUhGgOT658hZyGwEDHBYHFAgDbmRuCANLRVkICCfEsiqfe4En/QD9Jv0A/g8yMDIyMDQyOVQxNTM5NTD9AP8PMjAyNjEyMzFUMjM1OTU5/QECKf0CACX9AgEIZnVsbG5hbWX9AgIVTkROIFRlc3RiZWQgUm9vdCAyMjA0F0gwRgIhAPYUOjNakdfDGh5j9dcCGOz+Ie1MqoAEsjM9PEUEWbnqAiEApu0rg9GAK1LNExjLYAF6qVgpWQgU+atPn63Gtuubqyg=" } ] } diff -Nru ndncert-0.0.5-1-gfae76c4dc/debian/changelog ndncert-0.0.6-1-g3e4818421/debian/changelog --- ndncert-0.0.5-1-gfae76c4dc/debian/changelog 2022-02-17 21:24:23.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/debian/changelog 2023-11-17 18:39:06.000000000 +0000 @@ -1,9 +1,16 @@ +ndncert (0.0.6-1-g3e4818421-ppa1~focal) focal; urgency=low + + * New version based on 3e4818421a606c46614e25e66296c6e1a0761a75 + (https://github.com/named-data/ndncert) + + -- Alex Afanasyev Fri, 17 Nov 2023 18:39:06 +0000 + ndncert (0.0.5-1-gfae76c4dc-ppa1~focal) focal; urgency=low - * New version based on fae76c4dc736b0252e4503ef76c8738afce1d571 + * Version based on fae76c4dc736b0252e4503ef76c8738afce1d571 (https://github.com/named-data/ndncert) - -- Alex Afanasyev Thu, 17 Feb 2022 21:24:23 +0000 + -- Alex Afanasyev Thu, 17 Feb 2022 21:24:51 +0000 ndncert (0.0.4-1-g8579001-ppa1~focal) focal; urgency=low diff -Nru ndncert-0.0.5-1-gfae76c4dc/debian/control ndncert-0.0.6-1-g3e4818421/debian/control --- ndncert-0.0.5-1-gfae76c4dc/debian/control 2022-02-17 21:24:23.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/debian/control 2023-11-17 18:39:06.000000000 +0000 @@ -1,28 +1,36 @@ Source: ndncert -Priority: extra +Section: net +Priority: optional Maintainer: Zhiyi Zhang Build-Depends: debhelper (>= 8.0.0), - python (>= 2.7.0), - pkg-config (>= 0.26), - libndn-cxx-dev (>= 0.6.5) + python3 (>= 3.6.0), + pkg-config (>= 0.29), + libboost-all-dev (>= 1.65.1), + libndn-cxx-dev (>= 0.8.1), + libsqlite3-dev (>= 3.22.0), + libssl-dev (>= 1.1.1) Standards-Version: 3.9.2 -Section: net -Homepage: http://github.com/named-data/ndncert -Vcs-Git: git://github.com/named-data/ndncert -Vcs-Browser: http://github.com/named-data/ndncert +Homepage: https://github.com/named-data/ndncert +Vcs-Browser: https://github.com/named-data/ndncert +Vcs-Git: https://github.com/named-data/ndncert.git Package: libndncert -Section: libs Architecture: i386 amd64 arm64 armel armhf ppc64el +Section: libs Depends: ${shlibs:Depends}, ${misc:Depends} Description: NDN Certificate Management System The library to support NDNCERT protocol and help NDN entities to manage certificates. Package: libndncert-dev -Section: libdevel -Depends: libndncert (= ${binary:Version}), ${misc:Depends}, libndn-cxx-dev Architecture: i386 amd64 arm64 armel armhf ppc64el +Section: libdevel +Depends: libndncert (= ${binary:Version}), ${misc:Depends}, + libboost-dev (>= 1.65.1), + libboost-filesystem-dev (>= 1.65.1), + libndn-cxx-dev (>= 0.8.0), + libsqlite3-dev (>= 3.22.0), + libssl-dev (>= 1.1.1) Description: NDN Certificate Management System The library to support NDNCERT protocol and help NDN entities to manage certificates. @@ -30,27 +38,28 @@ This package contains the development files (headers and libraries) Package: libndncert-dbg +Architecture: any Section: debug -Architecture: i386 amd64 arm64 armel armhf ppc64el -Priority: extra Depends: libndncert (= ${binary:Version}), ${misc:Depends} -Multi-Arch: foreign -Description: NDN Certificate Management System +Description: NDN Certificate Management System (debugging symbols) The library to support NDNCERT protocol and help NDN entities to manage certificates. . - This package contains the debugging symbols for the libraries. + This package contains the debugging symbols for the library. Package: ndncert Architecture: i386 amd64 arm64 armel armhf ppc64el -Depends: ${shlibs:Depends}, ${misc:Depends}, libndn-cxx, ndnsec +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: ndnsec Description: NDN Certificate Management System Tools to support NDNCERT protocol and help NDN entities do certificate management. Package: ndncert-server -Depends: ${shlibs:Depends}, ${misc:Depends}, ndncert, libndn-cxx, ndnsec, nfd Architecture: i386 amd64 arm64 armel armhf ppc64el +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: ndnsec +Suggests: nfd Description: NDN Certificate Management System Daemon to support NDNCERT protocol and help NDN entities do certificate management. diff -Nru ndncert-0.0.5-1-gfae76c4dc/debian/rules ndncert-0.0.6-1-g3e4818421/debian/rules --- ndncert-0.0.5-1-gfae76c4dc/debian/rules 2022-02-17 21:24:23.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/debian/rules 2023-11-17 18:39:06.000000000 +0000 @@ -11,7 +11,6 @@ LDFLAGS := $(shell dpkg-buildflags --get LDFLAGS) CXXFLAGS := $(shell dpkg-buildflags --get CXXFLAGS) -CXXFLAGS += -std=c++14 LIBDIR := /usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: diff -Nru ndncert-0.0.5-1-gfae76c4dc/deployment/deploy-over-testbed.md ndncert-0.0.6-1-g3e4818421/deployment/deploy-over-testbed.md --- ndncert-0.0.5-1-gfae76c4dc/deployment/deploy-over-testbed.md 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/deployment/deploy-over-testbed.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Deploy NDNCERT v0.3 over testbed - -Three steps: - -* Deploy root CA `/ndn` by setting up the NDNCERT CA configuration and run NDNCERT service -* At each site server, run NDNCERT client command line tools to get certificate issued by `/ndn` using the PIN code challenge, set up the CA configuration and run NDNCERT CA service. -* Update the `/ndn`'s configuration file and restart the service. - -## Step 1 - -```bash -sudo ./deploy.sh -``` - -## Step 2 - -On each site: - -```bash -sudo ./deploy.sh -``` - -```bash -ndnsec-dump-certificate XXX -``` - -## Step 3 -Stop NDNCERT CA -```bash -sudo systemctl stop ndncert-ca -``` - -Update CA configuation file ``ca.conf`` with the output certificate just get: -Inside ``ca.conf``, site CAs are configured by sections below: - -``` - "redirect-to": - [ - { - "ca-prefix": "/example/site1", - "certificate": "Bv0CvQcwCAdleGFtcGxlCAVzaXRlMQgDS0VZCAh6af3szF4QZwgEc2VsZggJ/QAAAXT6+NCKFAkYAQIZBAA27oAV/QEmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA43hjZT0HUFFJcqwj8lZnd/vg0NrzqvZ4jhsq1c+nv6J3Huc9Uq5jRZwhFQ8nBWT3CeFScO5FUQfNXDIDncZ4vYPFEnockOFVtvmKQ/ELwReUvjH80d1NPGrIVrD0lMRpv2sFr6NW2p7aw6bCSj3OJq7H/+QHDkAryssMZyHwTbPzMZHyYKmxR68CyCCpvLlgp8tYFT+cCrOc3lz3nROK3VFR+apgwubpvl8nbKD10QLcgMHSkLoLEy/Ksq8OH7MQhUEZDjLk/zL9baZ7MiKXtdUZCNTZk13y5z+4aT4TqumLB+obiDXmv6JAi+CkYIMf2ck2IvMV6JgxxIlv3+Ke2wIDAQABFlAbAQEcIQcfCAdleGFtcGxlCAVzaXRlMQgDS0VZCAh6af3szF4QZ/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwNDAwOTMwVDIyNTQwNBf9AQCCSXOqUX40mAIdKCa+nnfJCGZbNowQPJp5kDnyolj5/Ek9x8czyLcX58xTsgYtiPmL5DxMgkRujRJu9INm0pUJIJRlsqhDOwsrxIjlSgwy5AeexYe7SM3rSwljLxTR4MfBw26pym9iYt8ovHXotCDE+etyKwHzXoOgzxORoPXqBGwobNOPnhDfpzHQBFOrPd8qqLAGioNNk/k2U/uyvBbLoZS4ScNVJpfbcvcmzu/A8H/VyT4234LrlISL9WpWlO8J18yzhrXchFR0ZwCoYge5rLZ4vsQhY1WqXHCsYnRa3la6Txz44EWYEBpmk12qnkPt06KAPvQ82N1CICxFb9NY" - } - ] -``` - -Replace the ``ca-prefix`` and ``certificate`` in this example section with the ones in your case. - -Start NDNCERT CA -```bash -sudo systemctl start ndncert-ca -``` diff -Nru ndncert-0.0.5-1-gfae76c4dc/deployment/deploy.sh ndncert-0.0.6-1-g3e4818421/deployment/deploy.sh --- ndncert-0.0.5-1-gfae76c4dc/deployment/deploy.sh 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/deployment/deploy.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,280 +0,0 @@ -#!/usr/bin/env bash - -function generate_client_config() { -echo -echo "What is the parent CA's prefix?" -read -r parent_ca_prefix -echo "what is the parent certificate? (use Ctrl-D to end input)" -root_cert=$(cat | tr -d '\n') - -cat > ndncert-site-client.conf << ~EOF -{ - "ca-list": - [ - { - "ca-prefix": "$parent_ca_prefix", - "ca-info": "NDN Testbed Root Trust Anchor", - "max-validity-period": "1296000", - "max-suffix-length": "3", - "probe-parameters": - [ - {"probe-parameter-key": "pin"} - ], - "certificate": "$root_cert" - } - ] -} -~EOF -echo "config file generated at ndncert-site-client.conf" -echo -} - -function generate_ca_config() { -echo -echo "Load the new configuration file for the CA" -echo "Would you like to allow email challenge for this CA? [Y/N]" -read -r allow_email_challenge -# prepare CA configuration file -cat > ndncert-deploy-ca.conf << ~EOF -{ - "ca-prefix": "$1", - "ca-info": "NDN Trust Anchor: $1", - "max-validity-period": "1296000", - "max-suffix-length": "2", - "probe-parameters": - [ - {"probe-parameter-key": "email"} - ], - "supported-challenges": - [ -~EOF -if [ "$allow_email_challenge" = 'y' ]; then - echo ' { "challenge": "email" },' >> ndncert-deploy-ca.conf -elif [ "$allow_email_challenge" = 'Y' ]; then - echo ' { "challenge": "email" },' >> ndncert-deploy-ca.conf -fi -cat >> ndncert-deploy-ca.conf << ~EOF - { "challenge": "pin" } - ], - "name-assignment": - { - "param": "/email" - } -} -~EOF - -sudo touch /usr/local/etc/ndncert/ca.conf -sudo mv ndncert-deploy-ca.conf /usr/local/etc/ndncert/ca.conf - -echo "" -} - -deployment_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -ndncert_dir="$(dirname "$deployment_dir")" -current_path="$(pwd)" -cd "$ndncert_dir" - -echo "Do you want to (re) compile and build NDNCERT? [Y/N]" -read -r NDNCERT_COMPILE -echo "" - -case $NDNCERT_COMPILE in - N|n) - echo "Okay, we'll skip compilation and build." - ;; - Y|y) - CXXFLAGS="-O2" "./waf" configure - "./waf" - ;; - *) - echo "Unknown option, build and install is cancelled" - cd "$current_path" - exit 1 - ;; -esac -echo "Need sudo to install NDNCERT CLI tools" -sudo "./waf" install -echo "" - -echo "===================================================================" -echo "==" -echo "== deploying NDNCERT" -echo "==" -echo "===================================================================" -echo "" -echo "Are you sure [Y/n] ?" -read -r deploy - -case $deploy in - N|n) - echo "deployment cancelled" - cd "$current_path" - exit 1 - ;; - Y|y) - ;; - *) - echo "Unknown option, deployment cancelled" - cd "$current_path" - exit 1 - ;; -esac - -echo "" -echo "===================================================================" -echo "==" -echo "== deployment started" -echo "==" -echo "===================================================================" - -echo "What is the CA Prefix (eg. /example) you want to deploy?" -read -r ca_prefix -echo "" - -echo "" -echo "===================================================================" -echo "==" -echo "== systemd config" -echo "==" -echo "===================================================================" - -echo "Do you want to install ndncert CA for systemd on this machine? [Y/N]" -read -r systemd_install -echo "" - -case $systemd_install in - N|n) - echo "We will not install systemd CA on this machine" - echo "Successfully finish the deployment of NDNCERT. To run NDNCERT, please use CLI ndncert-ca-server" - cd "$current_path" - exit 0 - ;; - Y|y) - echo "Copying NDNCERT-CA systemd service on this machine" - sudo cp "$ndncert_dir/build/systemd/ndncert-ca.service" /etc/systemd/system - sudo chmod 644 /etc/systemd/system/ndncert-ca.service - ;; - *) - echo "Unknown option, deployment cancelled" - cd "$current_path" - exit 1 - ;; -esac - -echo "" -echo "ndncert-ca service requires user ndn. Will check it now :D" -if id ndn &>/dev/null; then - echo 'ndn user account found, GOOD!' -else - echo 'ndn user not found; adding ndn user as root' - sudo useradd ndn -fi - -echo "" -echo "ndncert-ca service requires /var/lib/ndncert-ca. Will check or create the keychain in /var/lib/ndncert-ca" -sudo mkdir -p /var/lib/ndncert-ca -sudo chown ndn /var/lib/ndncert-ca -echo '/var/lib/ndncert-ca is ready, GOOD!' - -echo "" -echo "===================================================================" -echo "==" -echo "== anchor certificate generation" -echo "==" -echo "===================================================================" - -echo "" -echo "Do you want to import an existing safebag for $ca_prefix ? [Y/N]" -read -r use_safe_bag - -case $use_safe_bag in - N|n) - if [ "$(HOME=/var/lib/ndncert-ca ndnsec list | grep " $ca_prefix$" > /dev/null 2>&1; echo $?)" -ne 0 ]; then - echo "Generating new NDN identity for $ca_prefix" - sudo HOME=/var/lib/ndncert-ca -u ndn ndnsec-keygen "$ca_prefix" - else - echo "Key detected for $ca_prefix" - echo "Continue..." - fi - ;; - Y|y) - echo "Reading the safebag." - echo "What is the safebag file name?" - read -r safe_bag_path - echo "" - - echo "What is the password of the safebag?" - read -r safe_bafg_pwd - echo "" - - sudo HOME=/var/lib/ndncert-ca -u ndn ndnsec-import -i "$safe_bag_path" -P "$safe_bafg_pwd" - ;; - *) - echo "Unknown option, deployment cancelled" - cd "$current_path" - exit 1 - ;; -esac - -echo "" -echo "Do you want to request a certificate from a parent CA? [Y/N]" -read -r run_client -case $run_client in - Y|y) - echo "Running ndncert client" - generate_client_config - ndncert-client -c ndncert-site-client.conf - rm ndncert-site-client.conf - - echo "What is the new certificate name?" - read -r new_cert_name - ndnsec set-default -c "$new_cert_name" - ;; - *) - echo "Will not request a certificate. " - ;; -esac - -echo "" -echo "===================================================================" -echo "==" -echo "== configuration generation" -echo "==" -echo "===================================================================" - -generate_ca_config "$ca_prefix" - -echo "" -echo "===================================================================" -echo "==" -echo "== done" -echo "==" -echo "===================================================================" - -echo "Do you want to start the service now? [Y/N]" -read -r start_now -case $start_now in - N|n) - echo "Successfully finish the deployment of NDNCERT. You can run sudo systemctl start ndncert-ca when you want to start the service" - cd "$current_path" - exit 0 - ;; - Y|y) - echo "Starting the service ndncert-ca" - sudo systemctl daemon-reload - sudo systemctl start ndncert-ca - sleep 2 - echo "Reading the status of service ndncert-ca" - sudo systemctl status ndncert-ca - echo "Successfully finish the deployment of NDNCERT. You can run sudo systemctl status ndncert-ca when you want to check the status of the service" - cd "$current_path" - exit 0 - ;; - *) - echo "Unknown option, deployment cancelled" - cd "$current_path" - exit 1 - ;; -esac - -cd "$current_path" \ No newline at end of file diff -Nru ndncert-0.0.5-1-gfae76c4dc/ndncert-send-email-challenge.py ndncert-0.0.6-1-g3e4818421/ndncert-send-email-challenge.py --- ndncert-0.0.5-1-gfae76c4dc/ndncert-send-email-challenge.py 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/ndncert-send-email-challenge.py 2023-11-17 18:39:05.000000000 +0000 @@ -1,12 +1,9 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import smtplib import argparse import socket -try: # python3 - from configparser import ConfigParser -except ImportError: # python2 - from ConfigParser import SafeConfigParser as ConfigParser +from configparser import ConfigParser from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/ca-module.cpp ndncert-0.0.6-1-g3e4818421/src/ca-module.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/ca-module.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/ca-module.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -35,11 +35,10 @@ #include #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { -static const time::seconds DEFAULT_DATA_FRESHNESS_PERIOD = 1_s; -static const time::seconds REQUEST_VALIDITY_PERIOD_NOT_BEFORE_GRACE_PERIOD = 120_s; +const time::seconds DEFAULT_DATA_FRESHNESS_PERIOD = 1_s; +const time::seconds REQUEST_VALIDITY_PERIOD_NOT_BEFORE_GRACE_PERIOD = 120_s; NDN_LOG_INIT(ndncert.ca); @@ -51,10 +50,13 @@ // load the config and create storage m_config.load(configPath); m_storage = CaStorage::createCaStorage(storageType, m_config.caProfile.caPrefix, ""); - ndn::random::generateSecureBytes(m_requestIdGenKey, 32); - if (m_config.nameAssignmentFuncs.size() == 0) { + + ndn::random::generateSecureBytes(m_requestIdGenKey); + + if (m_config.nameAssignmentFuncs.empty()) { m_config.nameAssignmentFuncs.push_back(NameAssignmentFunc::createNameAssignmentFunc("random")); } + registerPrefix(); } @@ -75,11 +77,10 @@ Name prefix = m_config.caProfile.caPrefix; prefix.append("CA"); - auto prefixId = m_face.registerPrefix( - prefix, + auto prefixId = m_face.registerPrefix(prefix, [&] (const Name& name) { // register INFO RDR metadata prefix - ndn::name::Component metaDataComp(32, reinterpret_cast("metadata"), std::strlen("metadata")); + const auto& metaDataComp = ndn::MetadataObject::getKeywordComponent(); auto filterId = m_face.setInterestFilter(Name(name).append("INFO").append(metaDataComp), [this] (auto&&, const auto& i) { onCaProfileDiscovery(i); }); m_interestFilterHandles.push_back(filterId); @@ -120,10 +121,8 @@ CaModule::getCaProfileData() { if (m_profileData == nullptr) { - const auto& pib = m_keyChain.getPib(); - const auto& identity = pib.getIdentity(m_config.caProfile.caPrefix); - const auto& cert = identity.getDefaultKey().getDefaultCertificate(); - Block contentTLV = infotlv::encodeDataContent(m_config.caProfile, cert); + auto key = m_keyChain.getPib().getIdentity(m_config.caProfile.caPrefix).getDefaultKey(); + Block contentTLV = infotlv::encodeDataContent(m_config.caProfile, key.getDefaultCertificate()); Name infoPacketName(m_config.caProfile.caPrefix); auto segmentComp = ndn::name::Component::fromSegment(0); @@ -147,8 +146,7 @@ ndn::MetadataObject metadata; metadata.setVersionedName(m_profileData->getName().getPrefix(-1)); Name discoveryInterestName(m_profileData->getName().getPrefix(-2)); - ndn::name::Component metadataComponent(32, reinterpret_cast("metadata"), std::strlen("metadata")); - discoveryInterestName.append(metadataComponent); + discoveryInterestName.append(ndn::MetadataObject::getKeywordComponent()); m_face.put(metadata.makeData(discoveryInterestName, m_keyChain, signingByIdentity(m_config.caProfile.caPrefix))); } @@ -159,19 +157,37 @@ NDN_LOG_TRACE("Received PROBE request"); // process PROBE requests: collect probe parameters - auto parameters = probetlv::decodeApplicationParameters(request.getApplicationParameters()); + std::vector redirectionNames; std::vector availableComponents; - for (auto& item : m_config.nameAssignmentFuncs) { - auto names = item->assignName(parameters); - availableComponents.insert(availableComponents.end(), names.begin(), names.end()); + try { + auto parameters = probetlv::decodeApplicationParameters(request.getApplicationParameters()); + + // collect redirections + for (auto &item : m_config.redirection) { + if (item.second->isRedirecting(parameters)) { + redirectionNames.push_back(item.first->getFullName()); + } + } + + // collect name assignments + for (auto &item : m_config.nameAssignmentFuncs) { + auto names = item->assignName(parameters); + availableComponents.insert(availableComponents.end(), names.begin(), names.end()); + } } - if (availableComponents.size() == 0) { + catch (const std::exception& e) { + NDN_LOG_ERROR("[CaModule::onProbe]Error in decoding TLV: " << e.what()); + return; + } + + if (availableComponents.empty() && redirectionNames.empty()) { m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::INVALID_PARAMETER, "Cannot generate available names from parameters provided.")); return; } - std::vector availableNames; - for (const auto& component : availableComponents) { + + std::vector availableNames; + for (const auto &component : availableComponents) { Name newIdentityName = m_config.caProfile.caPrefix; newIdentityName.append(component); availableNames.push_back(newIdentityName); @@ -179,8 +195,8 @@ Data result; result.setName(request.getName()); - result.setContent( - probetlv::encodeDataContent(availableNames, m_config.caProfile.maxSuffixLength, m_config.redirection)); + result.setContent(probetlv::encodeDataContent(availableNames, m_config.caProfile.maxSuffixLength, + redirectionNames)); result.setFreshnessPeriod(DEFAULT_DATA_FRESHNESS_PERIOD); m_keyChain.sign(result, signingByIdentity(m_config.caProfile.caPrefix)); m_face.put(result); @@ -190,12 +206,11 @@ void CaModule::onNewRenewRevoke(const Interest& request, RequestType requestType) { - - //verify ca cert validity - const auto& caCert = m_keyChain.getPib() - .getIdentity(m_config.caProfile.caPrefix) - .getDefaultKey() - .getDefaultCertificate(); + // verify ca cert validity + auto caCert = m_keyChain.getPib() + .getIdentity(m_config.caProfile.caPrefix) + .getDefaultKey() + .getDefaultCertificate(); if (!caCert.isValid()) { NDN_LOG_ERROR("Server certificate invalid/expired"); m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::BAD_VALIDITY_PERIOD, @@ -266,11 +281,11 @@ if (requestType == RequestType::NEW) { // check the validity period - auto expectedPeriod = clientCert->getValidityPeriod().getPeriod(); + auto [notBefore, notAfter] = clientCert->getValidityPeriod().getPeriod(); auto currentTime = time::system_clock::now(); - if (expectedPeriod.first < currentTime - REQUEST_VALIDITY_PERIOD_NOT_BEFORE_GRACE_PERIOD || - expectedPeriod.second > currentTime + m_config.caProfile.maxValidityPeriod || - expectedPeriod.second <= expectedPeriod.first) { + if (notBefore < currentTime - REQUEST_VALIDITY_PERIOD_NOT_BEFORE_GRACE_PERIOD || + notAfter > currentTime + m_config.caProfile.maxValidityPeriod || + notAfter <= notBefore) { NDN_LOG_ERROR("An invalid validity period is being requested."); m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::BAD_VALIDITY_PERIOD, "An invalid validity period is being requested.")); @@ -305,10 +320,10 @@ uint8_t requestIdData[32]; Block certNameTlv = clientCert->getName().wireEncode(); try { - hmacSha256(certNameTlv.wire(), certNameTlv.size(), m_requestIdGenKey, 32, requestIdData); + hmacSha256(certNameTlv.data(), certNameTlv.size(), m_requestIdGenKey, 32, requestIdData); } catch (const std::runtime_error& e) { - NDN_LOG_ERROR("Error computing the request ID: " << std::string(e.what())); + NDN_LOG_ERROR("Error computing the request ID: " << e.what()); m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::INVALID_PARAMETER, "Error computing the request ID.")); return; @@ -323,7 +338,7 @@ requestState.cert = *clientCert; // generate salt for HKDF std::array salt; - ndn::random::generateSecureBytes(salt.data(), salt.size()); + ndn::random::generateSecureBytes(salt); // hkdf std::array aesKey; hkdf(sharedSecret.data(), sharedSecret.size(), salt.data(), salt.size(), @@ -338,6 +353,7 @@ "Duplicate Request ID: The same request has been seen before.")); return; } + Data result; result.setName(request.getName()); result.setFreshnessPeriod(DEFAULT_DATA_FRESHNESS_PERIOD); @@ -362,6 +378,7 @@ "No certificate request state can be found.")); return; } + // verify signature if (!ndn::security::verifySignature(request, requestState->cert)) { NDN_LOG_ERROR("Invalid Signature in the Interest packet."); @@ -369,6 +386,7 @@ "Invalid Signature in the Interest packet.")); return; } + // decrypt the parameters ndn::Buffer paramTLVPayload; try { @@ -383,14 +401,15 @@ "Interest paramaters decryption failed.")); return; } - if (paramTLVPayload.size() == 0) { + if (paramTLVPayload.empty()) { NDN_LOG_ERROR("No parameters are found after decryption."); m_storage->deleteRequest(requestState->requestId); m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::INVALID_PARAMETER, "No parameters are found after decryption.")); return; } - auto paramTLV = ndn::makeBinaryBlock(tlv::EncryptedPayload, paramTLVPayload.data(), paramTLVPayload.size()); + + auto paramTLV = ndn::makeBinaryBlock(tlv::EncryptedPayload, paramTLVPayload); paramTLV.parse(); // load the corresponding challenge module @@ -452,20 +471,20 @@ Certificate CaModule::issueCertificate(const RequestState& requestState) { - auto expectedPeriod = requestState.cert.getValidityPeriod().getPeriod(); - ndn::security::ValidityPeriod period(expectedPeriod.first, expectedPeriod.second); + auto period = requestState.cert.getValidityPeriod(); Certificate newCert; Name certName = requestState.cert.getKeyName(); - certName.append("NDNCERT").append(ndn::to_string(ndn::random::generateSecureWord64())); + certName.append("NDNCERT").appendVersion(); newCert.setName(certName); newCert.setContent(requestState.cert.getContent()); + newCert.setFreshnessPeriod(1_h); NDN_LOG_TRACE("cert request content " << requestState.cert); SignatureInfo signatureInfo; signatureInfo.setValidityPeriod(period); ndn::security::SigningInfo signingInfo(ndn::security::SigningInfo::SIGNER_TYPE_ID, m_config.caProfile.caPrefix, signatureInfo); - + // Note: we should use KeyChain::makeCertificate() in future. m_keyChain.sign(newCert, signingInfo); NDN_LOG_TRACE("new cert got signed" << newCert); return newCert; @@ -484,7 +503,7 @@ return nullptr; } try { - NDN_LOG_TRACE("Request Id to query the database " << ndn::toHex(requestId.data(), requestId.size())); + NDN_LOG_TRACE("Request Id to query the database " << ndn::toHex(requestId)); return std::make_unique(m_storage->getRequest(requestId)); } catch (const std::exception& e) { @@ -510,5 +529,4 @@ return result; } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/ca-module.hpp ndncert-0.0.6-1-g3e4818421/src/ca-module.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/ca-module.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/ca-module.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -28,8 +28,7 @@ #include #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { /** * @brief The function would be invoked whenever the certificate request status is updated. @@ -56,7 +55,7 @@ } const std::unique_ptr& - getCaStorage() + getCaStorage() const { return m_storage; } @@ -111,7 +110,6 @@ std::list m_interestFilterHandles; }; -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca #endif // NDNCERT_CA_MODULE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-email.cpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-email.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-email.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-email.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -29,7 +29,6 @@ const std::string ChallengeEmail::NEED_CODE = "need-code"; const std::string ChallengeEmail::WRONG_CODE = "wrong-code"; -const std::string ChallengeEmail::INVALID_EMAIL = "invalid-email"; const std::string ChallengeEmail::PARAMETER_KEY_EMAIL = "email"; const std::string ChallengeEmail::PARAMETER_KEY_CODE = "code"; @@ -50,22 +49,17 @@ if (request.status == Status::BEFORE_CHALLENGE) { // for the first time, init the challenge std::string emailAddress = readString(params.get(tlv::ParameterValue)); - if (!isValidEmailAddress(emailAddress)) { - return returnWithNewChallengeStatus(request, INVALID_EMAIL, JsonSection(), m_maxAttemptTimes - 1, - m_secretLifetime); - } auto lastComponentRequested = readString(request.cert.getIdentity().get(-1)); if (lastComponentRequested != emailAddress) { - NDN_LOG_TRACE("Email and requested name do not match. Email " << emailAddress << "requested last component " - << lastComponentRequested); + NDN_LOG_TRACE("Email and requested name do not match. Email " << emailAddress + << " - requested last component " << lastComponentRequested); } std::string emailCode = generateSecretCode(); JsonSection secretJson; secretJson.add(PARAMETER_KEY_CODE, emailCode); // send out the email sendEmail(emailAddress, emailCode, request); - NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId.data(), request.requestId.size()) - << " : " << emailCode); + NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId) << " : " << emailCode); return returnWithNewChallengeStatus(request, NEED_CODE, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime); } @@ -96,8 +90,8 @@ } else { // run out times - NDN_LOG_TRACE("Wrong secret code provided. Ran out tires. Challenge failed."); - return returnWithError(request, ErrorCode::OUT_OF_TRIES, "Ran out tires."); + NDN_LOG_TRACE("Wrong secret code provided. Ran out of tries. Challenge failed."); + return returnWithError(request, ErrorCode::OUT_OF_TRIES, "Ran out of tries."); } } } @@ -109,12 +103,9 @@ ChallengeEmail::getRequestedParameterList(Status status, const std::string& challengeStatus) { std::multimap result; - if (status == Status::BEFORE_CHALLENGE && challengeStatus == "") { + if (status == Status::BEFORE_CHALLENGE && challengeStatus.empty()) { result.emplace(PARAMETER_KEY_EMAIL, "Please input your email address"); } - else if (status == Status::CHALLENGE && challengeStatus == INVALID_EMAIL) { - result.emplace(PARAMETER_KEY_EMAIL, "Invalid email, please try again"); - } else if (status == Status::CHALLENGE && challengeStatus == NEED_CODE) { result.emplace(PARAMETER_KEY_CODE, "Please input your verification code"); } @@ -176,8 +167,10 @@ if (child.exit_code() != 0) { NDN_LOG_TRACE("EmailSending Script " + m_sendEmailScript + " fails."); } - NDN_LOG_TRACE("EmailSending Script " + m_sendEmailScript + - " was executed successfully with return value 0."); + else { + NDN_LOG_TRACE("EmailSending Script " + m_sendEmailScript + + " was executed successfully with return value 0."); + } } } // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-email.hpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-email.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-email.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-email.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -26,12 +26,7 @@ namespace ndncert { /** - * @brief Provide Email based challenge - * - * For challenge design - * @sa https://github.com/named-data/ndncert/wiki/NDN-Certificate-Management-Protocol - * For deployment instructions: - * @sa https://github.com/named-data/ndncert/wiki/Deploy-Email-Challenge + * @brief Provide email-based challenge. * * The main process of this challenge module is: * 1. Requester provides its email address. @@ -44,8 +39,9 @@ * * Failure info when application fails: * FAILURE_MAXRETRY: When run out retry times. - * FAILURE_INVALID_EMAIL: When the email is invalid. * FAILURE_TIMEOUT: When the secret lifetime expires. + * + * @sa https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3-Challenges */ class ChallengeEmail : public ChallengeModule { @@ -69,7 +65,6 @@ // challenge status static const std::string NEED_CODE; static const std::string WRONG_CODE; - static const std::string INVALID_EMAIL; // challenge parameters static const std::string PARAMETER_KEY_EMAIL; static const std::string PARAMETER_KEY_CODE; diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-module.cpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-module.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-module.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-module.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,6 +19,7 @@ */ #include "challenge/challenge-module.hpp" + #include namespace ndncert { @@ -35,15 +36,15 @@ bool ChallengeModule::isChallengeSupported(const std::string& challengeType) { - ChallengeFactory& factory = getFactory(); + auto& factory = getFactory(); auto i = factory.find(challengeType); - return i == factory.end() ? false : true; + return i != factory.end(); } std::unique_ptr ChallengeModule::createChallengeModule(const std::string& challengeType) { - ChallengeFactory& factory = getFactory(); + auto& factory = getFactory(); auto i = factory.find(challengeType); return i == factory.end() ? nullptr : i->second(); } @@ -51,7 +52,7 @@ ChallengeModule::ChallengeFactory& ChallengeModule::getFactory() { - static ChallengeModule::ChallengeFactory factory; + static ChallengeFactory factory; return factory; } @@ -61,10 +62,9 @@ uint32_t securityCode = 0; do { securityCode = ndn::random::generateSecureWord32(); - } - while (securityCode >= 4294000000); + } while (securityCode >= 4294000000); securityCode /= 4294; - std::string result = ndn::to_string(securityCode); + std::string result = std::to_string(securityCode); while (result.length() < 6) { result = "0" + result; } @@ -76,8 +76,8 @@ { request.status = Status::FAILURE; request.challengeType = ""; - request.challengeState = nullopt; - return std::make_tuple(errorCode, std::move(errorInfo)); + request.challengeState = std::nullopt; + return {errorCode, std::move(errorInfo)}; } std::tuple @@ -89,7 +89,7 @@ request.challengeType = CHALLENGE_TYPE; request.challengeState = ca::ChallengeState(challengeStatus, time::system_clock::now(), remainingTries, remainingTime, std::move(challengeSecret)); - return std::make_tuple(ErrorCode::NO_ERROR, ""); + return {ErrorCode::NO_ERROR, ""}; } std::tuple @@ -97,8 +97,8 @@ { request.status = Status::PENDING; request.challengeType = CHALLENGE_TYPE; - request.challengeState = nullopt; - return std::make_tuple(ErrorCode::NO_ERROR, ""); + request.challengeState = std::nullopt; + return {ErrorCode::NO_ERROR, ""}; } } // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-module.hpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-module.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-module.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-module.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,32 +23,18 @@ #include "detail/ca-request-state.hpp" +#include + namespace ndncert { class ChallengeModule : boost::noncopyable { public: - explicit ChallengeModule(const std::string& challengeType, size_t maxAttemptTimes, time::seconds secretLifetime); virtual ~ChallengeModule() = default; - template - static void - registerChallengeModule(const std::string& typeName) - { - ChallengeFactory& factory = getFactory(); - BOOST_ASSERT(factory.count(typeName) == 0); - factory[typeName] = [] { return std::make_unique(); }; - } - - static bool - isChallengeSupported(const std::string& challengeType); - - static std::unique_ptr - createChallengeModule(const std::string& challengeType); - // For CA virtual std::tuple handleChallengeRequest(const Block& params, ca::RequestState& request) = 0; @@ -61,13 +47,27 @@ genChallengeRequestTLV(Status status, const std::string& challengeStatus, const std::multimap& params) = 0; - // helpers +public: // factory + template + static void + registerChallengeModule(const std::string& type) + { + auto& factory = getFactory(); + BOOST_ASSERT(factory.count(type) == 0); + factory[type] = [] { return std::make_unique(); }; + } + + static bool + isChallengeSupported(const std::string& challengeType); + + static std::unique_ptr + createChallengeModule(const std::string& challengeType); + +protected: // helpers used by concrete challenge modules static std::string generateSecretCode(); -protected: - // used by challenge modules - std::tuple + static std::tuple returnWithError(ca::RequestState& request, ErrorCode errorCode, std::string errorInfo); std::tuple @@ -79,27 +79,29 @@ public: const std::string CHALLENGE_TYPE; + +protected: const size_t m_maxAttemptTimes; const time::seconds m_secretLifetime; private: - typedef std::function()> ChallengeCreateFunc; - typedef std::map ChallengeFactory; + using CreateFunc = std::function()>; + using ChallengeFactory = std::map; static ChallengeFactory& getFactory(); }; -#define NDNCERT_REGISTER_CHALLENGE(C, T) \ - static class NdnCert##C##ChallengeRegistrationClass \ - { \ - public: \ - NdnCert##C##ChallengeRegistrationClass() \ - { \ - ::ndncert::ChallengeModule::registerChallengeModule(T); \ - } \ - } g_NdnCert##C##ChallengeRegistrationVariable - } // namespace ndncert +#define NDNCERT_REGISTER_CHALLENGE(C, T) \ +static class NdnCert##C##ChallengeRegistrationClass \ +{ \ +public: \ + NdnCert##C##ChallengeRegistrationClass() \ + { \ + ::ndncert::ChallengeModule::registerChallengeModule(T); \ + } \ +} g_NdnCert##C##ChallengeRegistrationVariable + #endif // NDNCERT_CHALLENGE_MODULE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-pin.cpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-pin.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-pin.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-pin.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -48,7 +48,7 @@ std::string secretCode = generateSecretCode(); JsonSection secretJson; secretJson.add(PARAMETER_KEY_CODE, secretCode); - NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId.data(), request.requestId.size()) + NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId) << " : " << secretCode); return returnWithNewChallengeStatus(request, NEED_CODE, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime); diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-pin.hpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-pin.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-pin.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-pin.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -26,9 +26,7 @@ namespace ndncert { /** - * @brief Provide PIN code based challenge - * - * @sa https://github.com/named-data/ndncert/wiki/NDN-Certificate-Management-Protocol + * @brief Provide PIN code based challenge. * * The main process of this challenge module is: * 1. End entity provides empty string. The first POLL is only for selection. @@ -42,6 +40,8 @@ * Failure info when application fails: * FAILURE_TIMEOUT: When secret is out-dated. * FAILURE_MAXRETRY: When requester tries too many times. + * + * @sa https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3-Challenges */ class ChallengePin : public ChallengeModule { diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-possession.cpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-possession.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-possession.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-possession.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -38,7 +38,7 @@ const std::string ChallengePossession::NEED_PROOF = "need-proof"; ChallengePossession::ChallengePossession(const std::string& configPath) - : ChallengeModule("Possession", 1, time::seconds(60)) + : ChallengeModule("Possession", 1, time::seconds(60)) { if (configPath.empty()) { m_configFile = std::string(NDNCERT_SYSCONFDIR) + "/ndncert/challenge-credential.conf"; @@ -57,7 +57,7 @@ } catch (const boost::property_tree::file_parser_error& error) { NDN_THROW(std::runtime_error("Failed to parse configuration file " + m_configFile + ": " + - error.message() + " on line " + ndn::to_string(error.line()))); + error.message() + " on line " + std::to_string(error.line()))); } if (config.begin() == config.end()) { @@ -112,39 +112,29 @@ // verify the credential and the self-signed cert if (request.status == Status::BEFORE_CHALLENGE) { NDN_LOG_TRACE("Challenge Interest arrives. Check certificate and init the challenge"); - using ndn::toHex; // check the certificate - bool checkOK = false; - if (credential.hasContent() && signatureLen == 0) { - Name signingKeyName = credential.getSignatureInfo().getKeyLocator().getName(); - ndn::security::transform::PublicKey key; - const auto& pubKeyBuffer = credential.getPublicKey(); - key.loadPkcs8(pubKeyBuffer.data(), pubKeyBuffer.size()); - for (auto anchor : m_trustAnchors) { - if (anchor.getKeyName() == signingKeyName) { - if (ndn::security::verifySignature(credential, anchor)) { - checkOK = true; - } - } - } - } - else { + if (!credential.hasContent() || signatureLen != 0) { return returnWithError(request, ErrorCode::BAD_INTEREST_FORMAT, "Cannot find certificate"); } + auto keyLocator = credential.getSignatureInfo().getKeyLocator().getName(); + ndn::security::transform::PublicKey key; + key.loadPkcs8(credential.getPublicKey()); + bool checkOK = std::any_of(m_trustAnchors.begin(), m_trustAnchors.end(), [&] (const auto& anchor) { + return (anchor.getKeyName() == keyLocator || anchor.getName() == keyLocator) && + ndn::security::verifySignature(credential, anchor); + }); if (!checkOK) { return returnWithError(request, ErrorCode::INVALID_PARAMETER, "Certificate cannot be verified"); } // for the first time, init the challenge std::array secretCode{}; - ndn::random::generateSecureBytes(secretCode.data(), 16); + ndn::random::generateSecureBytes(secretCode); JsonSection secretJson; - secretJson.add(PARAMETER_KEY_NONCE, toHex(secretCode.data(), 16)); - auto credential_block = credential.wireEncode(); - secretJson.add(PARAMETER_KEY_CREDENTIAL_CERT, toHex(credential_block.wire(), credential_block.size())); - NDN_LOG_TRACE("Secret for request " << toHex(request.requestId.data(), request.requestId.size()) - << " : " << toHex(secretCode.data(), 16)); + secretJson.add(PARAMETER_KEY_NONCE, ndn::toHex(secretCode)); + secretJson.add(PARAMETER_KEY_CREDENTIAL_CERT, ndn::toHex(credential.wireEncode())); + NDN_LOG_TRACE("Secret for request " << ndn::toHex(request.requestId) << " : " << ndn::toHex(secretCode)); return returnWithNewChallengeStatus(request, NEED_PROOF, std::move(secretJson), m_maxAttemptTimes, m_secretLifetime); } else if (request.challengeState && request.challengeState->challengeStatus == NEED_PROOF) { @@ -158,9 +148,8 @@ //check the proof ndn::security::transform::PublicKey key; - const auto& pubKeyBuffer = credential.getPublicKey(); - key.loadPkcs8(pubKeyBuffer.data(), pubKeyBuffer.size()); - if (ndn::security::verifySignature({{secretCode.data(), secretCode.size()}}, signature, signatureLen, key)) { + key.loadPkcs8(credential.getPublicKey()); + if (ndn::security::verifySignature({secretCode}, {signature, signatureLen}, key)) { return returnWithSuccess(request); } return returnWithError(request, ErrorCode::INVALID_PARAMETER, @@ -203,7 +192,8 @@ request.push_back(ndn::makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_CREDENTIAL_CERT)); Block valueBlock(tlv::ParameterValue); auto& certTlvStr = std::get<1>(item); - valueBlock.push_back(Block(reinterpret_cast(certTlvStr.data()), certTlvStr.size())); + valueBlock.push_back(Block(ndn::make_span(reinterpret_cast(certTlvStr.data()), + certTlvStr.size()))); request.push_back(valueBlock); } else { @@ -218,11 +208,7 @@ for (const auto& item : params) { if (std::get<0>(item) == PARAMETER_KEY_PROOF) { request.push_back(ndn::makeStringBlock(tlv::ParameterKey, PARAMETER_KEY_PROOF)); - auto& sigTlvStr = std::get<1>(item); - auto valueBlock = ndn::makeBinaryBlock(tlv::ParameterValue, - reinterpret_cast(sigTlvStr.data()), - sigTlvStr.size()); - request.push_back(valueBlock); + request.push_back(ndn::makeStringBlock(tlv::ParameterValue, std::get<1>(item))); } else { NDN_THROW(std::runtime_error("Wrong parameter provided.")); @@ -239,20 +225,20 @@ void ChallengePossession::fulfillParameters(std::multimap& params, ndn::KeyChain& keyChain, const Name& issuedCertName, - const std::array& nonce) + ndn::span nonce) { auto keyName = ndn::security::extractKeyNameFromCertName(issuedCertName); auto id = keyChain.getPib().getIdentity(ndn::security::extractIdentityFromCertName(issuedCertName)); auto issuedCert = id.getKey(keyName).getCertificate(issuedCertName); - auto issuedCertTlv = issuedCert.wireEncode(); - auto signature = keyChain.getTpm().sign({{nonce.data(), nonce.size()}}, keyName, ndn::DigestAlgorithm::SHA256); + const auto& issuedCertTlv = issuedCert.wireEncode(); + auto signature = keyChain.getTpm().sign({nonce}, keyName, ndn::DigestAlgorithm::SHA256); - for (auto& item : params) { - if (item.first == PARAMETER_KEY_CREDENTIAL_CERT) { - item.second = std::string(reinterpret_cast(issuedCertTlv.wire()), issuedCertTlv.size()); + for (auto& [key, val] : params) { + if (key == PARAMETER_KEY_CREDENTIAL_CERT) { + val = std::string(reinterpret_cast(issuedCertTlv.data()), issuedCertTlv.size()); } - else if (item.first == PARAMETER_KEY_PROOF) { - item.second = std::string(signature->get(), signature->size()); + else if (key == PARAMETER_KEY_PROOF) { + val = std::string(signature->get(), signature->size()); } } } diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-possession.hpp ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-possession.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/challenge/challenge-possession.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/challenge/challenge-possession.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -51,6 +51,7 @@ class ChallengePossession : public ChallengeModule { public: + explicit ChallengePossession(const std::string& configPath = ""); // For CA @@ -68,7 +69,7 @@ static void fulfillParameters(std::multimap& params, ndn::KeyChain& keyChain, const Name& issuedCertName, - const std::array& nonce); + ndn::span nonce); // challenge parameters static const std::string PARAMETER_KEY_CREDENTIAL_CERT; diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-configuration.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-configuration.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-configuration.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-configuration.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -25,8 +25,7 @@ #include #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { void CaConfig::load(const std::string& fileName) @@ -38,34 +37,50 @@ catch (const std::exception& error) { NDN_THROW(std::runtime_error("Failed to parse configuration file " + fileName + ", " + error.what())); } + if (configJson.begin() == configJson.end()) { NDN_THROW(std::runtime_error("No JSON configuration found in file: " + fileName)); } caProfile = CaProfile::fromJson(configJson); - if (caProfile.supportedChallenges.size() == 0) { + if (caProfile.supportedChallenges.empty()) { NDN_THROW(std::runtime_error("At least one challenge should be specified.")); } - // parse redirection section if appears + + // parse redirection section if present redirection.clear(); auto redirectionItems = configJson.get_child_optional(CONFIG_REDIRECTION); if (redirectionItems) { for (const auto& item : *redirectionItems) { auto caPrefixStr = item.second.get(CONFIG_CA_PREFIX, ""); auto caCertStr = item.second.get(CONFIG_CERTIFICATE, ""); - if (caCertStr == "") { - NDN_THROW(std::runtime_error("Redirect-to item's ca-prefix or certificate cannot be empty.")); + if (caCertStr.empty()) { + NDN_THROW(std::runtime_error("Redirect-to item's certificate cannot be empty.")); } std::istringstream ss(caCertStr); auto caCert = ndn::io::load(ss); - redirection.push_back(caCert); + if (!caPrefixStr.empty() && Name(caPrefixStr) != caCert->getIdentity()) { + NDN_THROW(std::runtime_error("Redirect-to item's prefix and certificate does not match.")); + } + + auto policyType = item.second.get(CONFIG_REDIRECTION_POLICY_TYPE, ""); + auto policyParam = item.second.get(CONFIG_REDIRECTION_POLICY_PARAM, ""); + if (policyType.empty()) { + NDN_THROW(std::runtime_error("Redirect-to policy type expected but not provided.")); + } + auto policy = RedirectionPolicy::createPolicyFunc(policyType, policyParam); + if (policy == nullptr) { + NDN_THROW(std::runtime_error("Error on creating redirection policy")); + } + redirection.emplace_back(caCert, std::move(policy)); } } - // parse name assignment if appears + + // parse name assignment if present nameAssignmentFuncs.clear(); auto nameAssignmentItems = configJson.get_child_optional(CONFIG_NAME_ASSIGNMENT); if (nameAssignmentItems) { - for (const auto& item : *nameAssignmentItems) { - auto func = NameAssignmentFunc::createNameAssignmentFunc(item.first, item.second.data()); + for (const auto& [key, val] : *nameAssignmentItems) { + auto func = NameAssignmentFunc::createNameAssignmentFunc(key, val.data()); if (func == nullptr) { NDN_THROW(std::runtime_error("Error on creating name assignment function")); } @@ -74,5 +89,4 @@ } } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-configuration.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-configuration.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-configuration.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-configuration.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -21,11 +21,11 @@ #ifndef NDNCERT_DETAIL_CA_CONFIGURATION_HPP #define NDNCERT_DETAIL_CA_CONFIGURATION_HPP -#include "detail/ca-profile.hpp" +#include "ca-profile.hpp" #include "name-assignment/assignment-func.hpp" +#include "redirection/redirection-policy.hpp" -namespace ndncert { -namespace ca { +namespace ndncert::ca { /** * @brief CA's configuration on NDNCERT. @@ -66,14 +66,13 @@ /** * @brief Used for CA redirection */ - std::vector> redirection; + std::vector, std::unique_ptr>> redirection; /** * @brief Name Assignment Functions */ std::vector> nameAssignmentFuncs; }; -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca #endif // NDNCERT_DETAIL_CA_CONFIGURATION_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-memory.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-memory.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-memory.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-memory.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,13 +20,12 @@ #include "detail/ca-memory.hpp" -namespace ndncert { -namespace ca { +namespace ndncert::ca { const std::string CaMemory::STORAGE_TYPE = "ca-storage-memory"; NDNCERT_REGISTER_CA_STORAGE(CaMemory); -CaMemory::CaMemory(const Name& caName, const std::string& path) +CaMemory::CaMemory(const Name&, const std::string&) : CaStorage() { } @@ -34,47 +33,32 @@ RequestState CaMemory::getRequest(const RequestId& requestId) { - auto search = m_requests.find(requestId); - if (search == m_requests.end()) { - NDN_THROW(std::runtime_error("Request " + ndn::toHex(requestId.data(), requestId.size()) + - " does not exists")); + auto it = m_requests.find(requestId); + if (it == m_requests.end()) { + NDN_THROW(std::runtime_error("Request " + ndn::toHex(requestId) + " does not exist")); } - return search->second; + return it->second; } void CaMemory::addRequest(const RequestState& request) { - auto search = m_requests.find(request.requestId); - if (search == m_requests.end()) { - m_requests.insert(std::make_pair(request.requestId, request)); - } - else { - NDN_THROW(std::runtime_error("Request " + ndn::toHex(request.requestId.data(), request.requestId.size()) + - " already exists")); + auto result = m_requests.insert({request.requestId, request}); + if (!result.second) { + NDN_THROW(std::runtime_error("Request " + ndn::toHex(request.requestId) + " already exists")); } } void CaMemory::updateRequest(const RequestState& request) { - auto search = m_requests.find(request.requestId); - if (search == m_requests.end()) { - m_requests.insert(std::make_pair(request.requestId, request)); - } - else { - search->second = request; - } + m_requests.insert_or_assign(request.requestId, request); } void CaMemory::deleteRequest(const RequestId& requestId) { - auto search = m_requests.find(requestId); - auto keyName = search->second.cert.getKeyName(); - if (search != m_requests.end()) { - m_requests.erase(search); - } + m_requests.erase(requestId); } std::list @@ -99,5 +83,4 @@ return result; } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-memory.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-memory.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-memory.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-memory.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,25 +23,20 @@ #include "detail/ca-storage.hpp" -namespace ndncert { -namespace ca { +namespace ndncert::ca { class CaMemory : public CaStorage { public: - CaMemory(const Name& caName = Name(), const std::string& path = ""); - const static std::string STORAGE_TYPE; + static const std::string STORAGE_TYPE; + + explicit + CaMemory(const Name& caName = "", const std::string& path = ""); public: - /** - * @throw if request cannot be fetched from underlying data storage - */ RequestState getRequest(const RequestId& requestId) override; - /** - * @throw if there is an existing request with the same request ID - */ void addRequest(const RequestState& request) override; @@ -61,7 +56,6 @@ std::map m_requests; }; -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca #endif // NDNCERT_DETAIL_CA_MEMORY_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-profile.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-profile.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-profile.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-profile.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -41,7 +41,7 @@ // CA max validity period profile.maxValidityPeriod = time::seconds(json.get(CONFIG_MAX_VALIDITY_PERIOD, 86400)); // CA max suffix length - profile.maxSuffixLength = nullopt; + profile.maxSuffixLength = std::nullopt; auto maxSuffixLength = json.get_optional(CONFIG_MAX_SUFFIX_LENGTH); if (maxSuffixLength) { profile.maxSuffixLength = *maxSuffixLength; @@ -78,7 +78,7 @@ // anchor certificate profile.cert = nullptr; auto certificateStr = json.get(CONFIG_CERTIFICATE, ""); - if (certificateStr != "") { + if (!certificateStr.empty()) { std::istringstream ss(certificateStr); profile.cert = ndn::io::load(ss); } @@ -100,7 +100,7 @@ for (const auto& key : probeParameterKeys) { JsonSection keyJson; keyJson.put(CONFIG_PROBE_PARAMETER, key); - probeParametersJson.push_back(std::make_pair("", keyJson)); + probeParametersJson.push_back({"", keyJson}); } caItem.add_child("", probeParametersJson); } @@ -109,7 +109,7 @@ for (const auto& challenge : supportedChallenges) { JsonSection challengeJson; challengeJson.put(CONFIG_CHALLENGE, challenge); - challengeListJson.push_back(std::make_pair("", challengeJson)); + challengeListJson.push_back({"", challengeJson}); } caItem.add_child("", challengeListJson); } diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-profile.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-profile.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-profile.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-profile.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -37,6 +37,8 @@ const std::string CONFIG_CERTIFICATE = "certificate"; const std::string CONFIG_REDIRECTION = "redirect-to"; const std::string CONFIG_NAME_ASSIGNMENT = "name-assignment"; +const std::string CONFIG_REDIRECTION_POLICY_TYPE = "policy-type"; +const std::string CONFIG_REDIRECTION_POLICY_PARAM = "policy-param"; class CaProfile { @@ -81,7 +83,7 @@ * E.g., When its value is 2, at most 2 name components can be assigned after m_caPrefix. * Default: none. */ - optional maxSuffixLength = nullopt; + std::optional maxSuffixLength; /** * @brief A list of supported challenges. Only CA side will have m_supportedChallenges. */ diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-request-state.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-request-state.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-request-state.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-request-state.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -56,7 +56,7 @@ namespace ca { ChallengeState::ChallengeState(const std::string& challengeStatus, - const time::system_clock::TimePoint& challengeTp, + const time::system_clock::time_point& challengeTp, size_t remainingTries, time::seconds remainingTime, JsonSection&& challengeSecrets) : challengeStatus(challengeStatus) @@ -71,7 +71,7 @@ operator<<(std::ostream& os, const RequestState& request) { os << "Request's CA name: " << request.caPrefix << "\n"; - os << "Request's request ID: " << ndn::toHex(request.requestId.data(), request.requestId.size()) << "\n"; + os << "Request's request ID: " << ndn::toHex(request.requestId) << "\n"; os << "Request's status: " << statusToString(request.status) << "\n"; os << "Request's challenge type: " << request.challengeType << "\n"; if (request.challengeState) { diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-request-state.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-request-state.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-request-state.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-request-state.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -27,14 +27,14 @@ namespace ndncert { -typedef std::array RequestId; +using RequestId = std::array; enum class Status : uint16_t { BEFORE_CHALLENGE = 0, CHALLENGE = 1, PENDING = 2, SUCCESS = 3, - FAILURE = 4 + FAILURE = 4, }; /** @@ -56,9 +56,10 @@ */ struct ChallengeState { - ChallengeState(const std::string& challengeStatus, const time::system_clock::TimePoint& challengeTp, + ChallengeState(const std::string& challengeStatus, const time::system_clock::time_point& challengeTp, size_t remainingTries, time::seconds remainingTime, JsonSection&& challengeSecrets); + /** * @brief The status of the challenge. */ @@ -66,7 +67,7 @@ /** * @brief The timestamp of the last update of the challenge state. */ - time::system_clock::TimePoint timestamp; + time::system_clock::time_point timestamp; /** * @brief Remaining tries of the challenge. */ @@ -127,7 +128,7 @@ /** * @brief The challenge state. */ - optional challengeState; + std::optional challengeState; }; std::ostream& diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-sqlite.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-sqlite.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-sqlite.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-sqlite.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -28,8 +28,7 @@ #include #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { using ndn::util::Sqlite3Statement; @@ -148,7 +147,7 @@ std::memcpy(state.encryptionKey.data(), statement.getBlob(11), statement.getSize(11)); state.encryptionIv = std::vector(statement.getBlob(12), statement.getBlob(12) + statement.getSize(12)); state.decryptionIv = std::vector(statement.getBlob(13), statement.getBlob(13) + statement.getSize(13)); - if (state.challengeType != "") { + if (!state.challengeType.empty()) { ChallengeState challengeState(statement.getString(3), time::fromIsoString(statement.getString(7)), statement.getInt(8), time::seconds(statement.getInt(9)), convertString2Json(statement.getString(6))); @@ -157,8 +156,7 @@ return state; } else { - NDN_THROW(std::runtime_error("Request " + ndn::toHex(requestId.data(), requestId.size()) + - " cannot be fetched from database")); + NDN_THROW(std::runtime_error("Request " + ndn::toHex(requestId) + " cannot be fetched from database")); } } @@ -188,8 +186,8 @@ statement.bind(11, request.challengeState->remainingTime.count()); } if (statement.step() != SQLITE_DONE) { - NDN_THROW(std::runtime_error("Request " + ndn::toHex(request.requestId.data(), request.requestId.size()) + - " cannot be added to database")); + NDN_THROW(std::runtime_error("Request " + ndn::toHex(request.requestId) + + " cannot be added to the database")); } } @@ -280,7 +278,7 @@ std::memcpy(state.encryptionKey.data(), statement.getBlob(12), statement.getSize(12)); state.encryptionIv = std::vector(statement.getBlob(13), statement.getBlob(13) + statement.getSize(13)); state.decryptionIv = std::vector(statement.getBlob(14), statement.getBlob(14) + statement.getSize(14)); - if (state.challengeType != "") { + if (!state.challengeType.empty()) { ChallengeState challengeState(statement.getString(4), time::fromIsoString(statement.getString(8)), statement.getInt(9), time::seconds(statement.getInt(10)), convertString2Json(statement.getString(7))); @@ -300,5 +298,4 @@ statement.step(); } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-sqlite.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-sqlite.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-sqlite.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-sqlite.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -25,13 +25,12 @@ struct sqlite3; -namespace ndncert { -namespace ca { +namespace ndncert::ca { class CaSqlite : public CaStorage { public: - const static std::string STORAGE_TYPE; + static const std::string STORAGE_TYPE; explicit CaSqlite(const Name& caName, const std::string& path = ""); @@ -39,15 +38,9 @@ ~CaSqlite() override; public: - /** - * @throw if request cannot be fetched from underlying data storage - */ RequestState getRequest(const RequestId& requestId) override; - /** - * @throw if there is an existing request with the same request ID - */ void addRequest(const RequestState& request) override; @@ -67,7 +60,6 @@ sqlite3* m_database; }; -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca #endif // NDNCERT_DETAIL_CA_SQLITE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-storage.cpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-storage.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-storage.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-storage.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,13 +20,12 @@ #include "detail/ca-storage.hpp" -namespace ndncert { -namespace ca { +namespace ndncert::ca { std::unique_ptr CaStorage::createCaStorage(const std::string& caStorageType, const Name& caName, const std::string& path) { - CaStorageFactory& factory = getFactory(); + auto& factory = getFactory(); auto i = factory.find(caStorageType); return i == factory.end() ? nullptr : i->second(caName, path); } @@ -34,9 +33,8 @@ CaStorage::CaStorageFactory& CaStorage::getFactory() { - static CaStorage::CaStorageFactory factory; + static CaStorageFactory factory; return factory; } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-storage.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ca-storage.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ca-storage.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ca-storage.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,20 +23,24 @@ #include "detail/ca-request-state.hpp" -namespace ndncert { -namespace ca { +#include + +namespace ndncert::ca { class CaStorage : boost::noncopyable { -public: // request related +public: + virtual + ~CaStorage() = default; + /** - * @throw if request cannot be fetched from underlying data storage + * @throw std::runtime_error The request cannot be fetched from underlying data storage */ virtual RequestState getRequest(const RequestId& requestId) = 0; /** - * @throw if there is an existing request with the same request ID + * @throw std::runtime_error There is an existing request with the same request ID */ virtual void addRequest(const RequestState& request) = 0; @@ -56,11 +60,11 @@ public: // factory template static void - registerCaStorage(const std::string& caStorageType = CaStorageType::STORAGE_TYPE) + registerCaStorage(const std::string& type = CaStorageType::STORAGE_TYPE) { - CaStorageFactory& factory = getFactory(); - BOOST_ASSERT(factory.count(caStorageType) == 0); - factory[caStorageType] = [] (const Name& caName, const std::string& path) { + auto& factory = getFactory(); + BOOST_ASSERT(factory.count(type) == 0); + factory[type] = [] (const Name& caName, const std::string& path) { return std::make_unique(caName, path); }; } @@ -68,28 +72,24 @@ static std::unique_ptr createCaStorage(const std::string& caStorageType, const Name& caName, const std::string& path); - virtual - ~CaStorage() = default; - private: - using CaStorageCreateFunc = std::function (const Name&, const std::string&)>; - using CaStorageFactory = std::map; + using CreateFunc = std::function(const Name&, const std::string&)>; + using CaStorageFactory = std::map; static CaStorageFactory& getFactory(); }; -#define NDNCERT_REGISTER_CA_STORAGE(C) \ -static class NdnCert ## C ## CaStorageRegistrationClass \ -{ \ -public: \ - NdnCert ## C ## CaStorageRegistrationClass() \ - { \ - ::ndncert::ca::CaStorage::registerCaStorage(); \ - } \ -} g_NdnCert ## C ## CaStorageRegistrationVariable +} // namespace ndncert::ca -} // namespace ca -} // namespace ndncert +#define NDNCERT_REGISTER_CA_STORAGE(C) \ +static class NdnCert##C##CaStorageRegistrationClass \ +{ \ +public: \ + NdnCert##C##CaStorageRegistrationClass() \ + { \ + ::ndncert::ca::CaStorage::registerCaStorage(); \ + } \ +} g_NdnCert##C##CaStorageRegistrationVariable #endif // NDNCERT_DETAIL_CA_STORAGE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/challenge-encoder.cpp ndncert-0.0.6-1-g3e4818421/src/detail/challenge-encoder.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/challenge-encoder.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/challenge-encoder.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,10 +20,10 @@ #include "detail/challenge-encoder.hpp" -namespace ndncert { +namespace ndncert::challengetlv { Block -challengetlv::encodeDataContent(ca::RequestState& request, const Name& issuedCertName) +encodeDataContent(ca::RequestState& request, const Name& issuedCertName) { Block response(tlv::EncryptedPayload); response.push_back(ndn::makeNonNegativeIntegerBlock(tlv::Status, static_cast(request.status))); @@ -36,11 +36,12 @@ if (request.challengeState->challengeStatus == "need-proof") { response.push_back(ndn::makeStringBlock(tlv::ParameterKey, "nonce")); auto nonce = ndn::fromHex(request.challengeState->secrets.get("nonce", "")); - response.push_back(ndn::makeBinaryBlock(tlv::ParameterValue, nonce->data(), 16)); + response.push_back(ndn::makeBinaryBlock(tlv::ParameterValue, *nonce)); } } if (!issuedCertName.empty()) { response.push_back(makeNestedBlock(tlv::IssuedCertName, issuedCertName)); + response.push_back(makeNestedBlock(ndn::tlv::ForwardingHint, Name(request.caPrefix).append("CA"))); } response.encode(); @@ -51,40 +52,74 @@ } void -challengetlv::decodeDataContent(const Block& contentBlock, requester::Request& state) +decodeDataContent(const Block& contentBlock, requester::Request& state) { auto result = decodeBlockWithAesGcm128(contentBlock, state.m_aesKey.data(), state.m_requestId.data(), state.m_requestId.size(), state.m_decryptionIv, state.m_encryptionIv); - auto data = ndn::makeBinaryBlock(tlv::EncryptedPayload, result.data(), result.size()); + auto data = ndn::makeBinaryBlock(tlv::EncryptedPayload, result); data.parse(); - state.m_status = statusFromBlock(data.get(tlv::Status)); - if (data.find(tlv::ChallengeStatus) != data.elements_end()) { - state.m_challengeStatus = readString(data.get(tlv::ChallengeStatus)); - } - if (data.find(tlv::RemainingTries) != data.elements_end()) { - state.m_remainingTries = readNonNegativeInteger(data.get(tlv::RemainingTries)); - } - if (data.find(tlv::RemainingTime) != data.elements_end()) { - state.m_freshBefore = time::system_clock::now() + - time::seconds(readNonNegativeInteger(data.get(tlv::RemainingTime))); - } - if (data.find(tlv::IssuedCertName) != data.elements_end()) { - Block issuedCertNameBlock = data.get(tlv::IssuedCertName); - state.m_issuedCertName = Name(issuedCertNameBlock.blockFromValue()); - } - if (data.find(tlv::ParameterKey) != data.elements_end() && - readString(data.get(tlv::ParameterKey)) == "nonce") { - if (data.find(tlv::ParameterKey) == data.elements_end()) { - NDN_THROW(std::runtime_error("Parameter Key found, but no value found")); + int numStatus = 0; + bool lookingForNonce = false; + for (const auto &item : data.elements()) { + if (!lookingForNonce) { + switch (item.type()) { + case tlv::Status: + state.m_status = statusFromBlock(data.get(tlv::Status)); + numStatus++; + break; + case tlv::ChallengeStatus: + state.m_challengeStatus = readString(item); + break; + case tlv::RemainingTries: + state.m_remainingTries = readNonNegativeInteger(item); + break; + case tlv::RemainingTime: + state.m_freshBefore = time::system_clock::now() + + time::seconds(readNonNegativeInteger(item)); + break; + case tlv::IssuedCertName: + state.m_issuedCertName = Name(item.blockFromValue()); + break; + case ndn::tlv::ForwardingHint: + state.m_forwardingHint = Name(item.blockFromValue()); + break; + case tlv::ParameterKey: + if (readString(item) == "nonce") { + lookingForNonce = true; + } + else { + NDN_THROW(std::runtime_error("Unknown Parameter: " + readString(item))); + } + break; + default: + if (ndn::tlv::isCriticalType(item.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(item.type()))); + } + else { + //ignore + } + break; + } } - Block nonceBlock = data.get(tlv::ParameterValue); - if (nonceBlock.value_size() != 16) { - NDN_THROW(std::runtime_error("Wrong nonce length")); + else { + if (item.type() == tlv::ParameterValue) { + lookingForNonce = false; + if (item.value_size() != 16) { + NDN_THROW(std::runtime_error("Wrong nonce length")); + } + memcpy(state.m_nonce.data(), item.value(), 16); + } + else { + NDN_THROW(std::runtime_error("Parameter Key found, but no value found")); + } } - memcpy(state.m_nonce.data(), nonceBlock.value(), 16); + } + if (numStatus != 1) { + NDN_THROW(std::runtime_error("number of status block is not equal to 1; there are " + + std::to_string(numStatus) + " status blocks")); } } -} // namespace ndncert +} // namespace ndncert::challengetlv diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/challenge-encoder.hpp ndncert-0.0.6-1-g3e4818421/src/detail/challenge-encoder.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/challenge-encoder.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/challenge-encoder.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -24,8 +24,7 @@ #include "detail/ca-request-state.hpp" #include "requester-request.hpp" -namespace ndncert { -namespace challengetlv { +namespace ndncert::challengetlv { Block encodeDataContent(ca::RequestState& request, const Name& issuedCertName = Name()); @@ -33,7 +32,6 @@ void decodeDataContent(const Block& contentBlock, requester::Request& state); -} // namespace challengetlv -} // namespace ndncert +} // namespace ndncert::challengetlv #endif // NDNCERT_DETAIL_CHALLENGE_ENCODER_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/crypto-helpers.cpp ndncert-0.0.6-1-g3e4818421/src/detail/crypto-helpers.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/crypto-helpers.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/crypto-helpers.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -362,33 +362,10 @@ // } } -#ifndef NDNCERT_HAVE_TESTS -static -#endif -uint32_t +static uint32_t loadBigU32(const uint8_t* src) noexcept { -#if BOOST_VERSION >= 107100 return boost::endian::endian_load(src); -#else - uint32_t dest; - std::memcpy(reinterpret_cast(&dest), src, sizeof(dest)); - return boost::endian::big_to_native(dest); -#endif -} - -#ifndef NDNCERT_HAVE_TESTS -static -#endif -void -storeBigU32(uint8_t* dest, uint32_t src) noexcept -{ -#if BOOST_VERSION >= 107100 - boost::endian::endian_store(dest, src); -#else - boost::endian::native_to_big_inplace(src); - std::memcpy(dest, reinterpret_cast(&src), sizeof(src)); -#endif } static void @@ -404,7 +381,7 @@ else { counter += increment; } - storeBigU32(&iv[8], counter); + boost::endian::endian_store(&iv[8], counter); } Block @@ -416,17 +393,20 @@ // The spec of AES encrypted payload TLV used in NDNCERT: // https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3#242-aes-gcm-encryption ndn::Buffer encryptedPayload(payloadSize); - uint8_t tag[16]; if (encryptionIv.empty()) { encryptionIv.resize(12, 0); - ndn::random::generateSecureBytes(encryptionIv.data(), 8); + ndn::random::generateSecureBytes(ndn::make_span(encryptionIv).first(8)); } + + uint8_t tag[16]; size_t encryptedPayloadLen = aesGcm128Encrypt(payload, payloadSize, associatedData, associatedDataSize, key, encryptionIv.data(), encryptedPayload.data(), tag); + Block content(tlvType); - content.push_back(ndn::makeBinaryBlock(tlv::InitializationVector, encryptionIv.data(), encryptionIv.size())); - content.push_back(ndn::makeBinaryBlock(tlv::AuthenticationTag, tag, 16)); - content.push_back(ndn::makeBinaryBlock(tlv::EncryptedPayload, encryptedPayload.data(), encryptedPayloadLen)); + content.push_back(ndn::makeBinaryBlock(tlv::InitializationVector, encryptionIv)); + content.push_back(ndn::makeBinaryBlock(tlv::AuthenticationTag, tag)); + content.push_back(ndn::makeBinaryBlock(tlv::EncryptedPayload, + ndn::make_span(encryptedPayload).first(encryptedPayloadLen))); content.encode(); // update IV's counter updateIv(encryptionIv, payloadSize); diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/crypto-helpers.hpp ndncert-0.0.6-1-g3e4818421/src/detail/crypto-helpers.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/crypto-helpers.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/crypto-helpers.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -139,7 +139,7 @@ * * The TLV spec: https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3#242-aes-gcm-encryption. * - * @param tlv_type The TLV TYPE of the encoded block, either ApplicationParameters or Content. + * @param tlvType The TLV TYPE of the encoded block, either ApplicationParameters or Content. * @param key The AES key used for encryption. * @param payload The plaintext payload. * @param payloadSize The size of the plaintext payload. @@ -173,14 +173,6 @@ const uint8_t* associatedData, size_t associatedDataSize, std::vector& decryptionIv, const std::vector& encryptionIv); -#ifdef NDNCERT_HAVE_TESTS -uint32_t -loadBigU32(const uint8_t* src) noexcept; - -void -storeBigU32(uint8_t* dest, uint32_t src) noexcept; -#endif - } // namespace ndncert #endif // NDNCERT_DETAIL_CRYPTO_HELPERS_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/error-encoder.cpp ndncert-0.0.6-1-g3e4818421/src/detail/error-encoder.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/error-encoder.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/error-encoder.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,10 +20,12 @@ #include "detail/error-encoder.hpp" -namespace ndncert { +NDN_LOG_INIT(ndncert.encode.error); + +namespace ndncert::errortlv { Block -errortlv::encodeDataContent(ErrorCode errorCode, const std::string& description) +encodeDataContent(ErrorCode errorCode, const std::string& description) { Block response(ndn::tlv::Content); response.push_back(ndn::makeNonNegativeIntegerBlock(tlv::ErrorCode, static_cast(errorCode))); @@ -33,14 +35,49 @@ } std::tuple -errortlv::decodefromDataContent(const Block& block) +decodefromDataContent(const Block& block) { - block.parse(); - if (block.find(tlv::ErrorCode) == block.elements_end()) { - return std::make_tuple(ErrorCode::NO_ERROR, ""); + try { + block.parse(); + + int codeCount = 0; + int infoCount = 0; + int otherCriticalCount = 0; + ErrorCode error = ErrorCode::NO_ERROR; + std::string errorInfo; + for (const auto& item : block.elements()) { + if (item.type() == tlv::ErrorCode) { + error = ndn::readNonNegativeIntegerAs(block.get(tlv::ErrorCode)); + codeCount++; + } + else if (item.type() == tlv::ErrorInfo) { + errorInfo = readString(block.get(tlv::ErrorInfo)); + infoCount++; + } + else if (ndn::tlv::isCriticalType(item.type())) { + otherCriticalCount++; + } + else { + // ignore + } + } + + if (codeCount == 0 && infoCount == 0) { + return {ErrorCode::NO_ERROR, ""}; + } + if (codeCount != 1 || infoCount != 1) { + NDN_THROW(std::runtime_error("Error TLV contains " + std::to_string(codeCount) + " error code(s) and " + + std::to_string(infoCount) + " error info(s), instead of expected 1 time each.")); + } + if (otherCriticalCount > 0) { + NDN_THROW(std::runtime_error("Unknown critical TLV type in error packet")); + } + return {error, errorInfo}; + } + catch (const std::exception& e) { + NDN_LOG_ERROR("Exception in error message decoding: " << e.what()); + return {ErrorCode::NO_ERROR, ""}; } - ErrorCode error = static_cast(readNonNegativeInteger(block.get(tlv::ErrorCode))); - return std::make_tuple(error, readString(block.get(tlv::ErrorInfo))); } -} // namespace ndncert +} // namespace ndncert::errortlv diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/error-encoder.hpp ndncert-0.0.6-1-g3e4818421/src/detail/error-encoder.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/error-encoder.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/error-encoder.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,8 +23,7 @@ #include "detail/ca-profile.hpp" -namespace ndncert { -namespace errortlv { +namespace ndncert::errortlv { /** * Encode error information into a Data content TLV @@ -38,7 +37,6 @@ std::tuple decodefromDataContent(const Block& block); -} // namespace errortlv -} // namespace ndncert +} // namespace ndncert::errortlv #endif // NDNCERT_DETAIL_ERROR_ENCODER_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/info-encoder.cpp ndncert-0.0.6-1-g3e4818421/src/detail/info-encoder.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/info-encoder.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/info-encoder.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,15 +20,17 @@ #include "detail/info-encoder.hpp" -namespace ndncert { +NDN_LOG_INIT(ndncert.encode.info); + +namespace ndncert::infotlv { Block -infotlv::encodeDataContent(const CaProfile& caConfig, const Certificate& certificate) +encodeDataContent(const CaProfile& caConfig, const Certificate& certificate) { Block content(ndn::tlv::Content); content.push_back(makeNestedBlock(tlv::CaPrefix, caConfig.caPrefix)); - std::string caInfo = ""; - if (caConfig.caInfo == "") { + std::string caInfo; + if (caConfig.caInfo.empty()) { caInfo = "Issued by " + certificate.getSignatureInfo().getKeyLocator().getName().toUri(); } else { @@ -41,39 +43,42 @@ content.push_back(ndn::makeNonNegativeIntegerBlock(tlv::MaxValidityPeriod, caConfig.maxValidityPeriod.count())); content.push_back(makeNestedBlock(tlv::CaCertificate, certificate)); content.encode(); + NDN_LOG_TRACE("Encoding INFO packet with certificate " << certificate.getFullName()); return content; } CaProfile -infotlv::decodeDataContent(const Block& block) +decodeDataContent(const Block& block) { CaProfile result; block.parse(); - for (auto const& item : block.elements()) { + for (auto const &item : block.elements()) { switch (item.type()) { - case tlv::CaPrefix: - item.parse(); - result.caPrefix.wireDecode(item.get(ndn::tlv::Name)); - break; - case tlv::CaInfo: - result.caInfo = readString(item); - break; - case tlv::ParameterKey: - result.probeParameterKeys.push_back(readString(item)); - break; - case tlv::MaxValidityPeriod: - result.maxValidityPeriod = time::seconds(readNonNegativeInteger(item)); - break; - case tlv::CaCertificate: - item.parse(); - result.cert = std::make_shared(item.get(ndn::tlv::Data)); - break; - default: - continue; - break; + case tlv::CaPrefix: + item.parse(); + result.caPrefix.wireDecode(item.get(ndn::tlv::Name)); + break; + case tlv::CaInfo: + result.caInfo = readString(item); + break; + case tlv::ParameterKey: + result.probeParameterKeys.push_back(readString(item)); + break; + case tlv::MaxValidityPeriod: + result.maxValidityPeriod = time::seconds(readNonNegativeInteger(item)); + break; + case tlv::CaCertificate: + item.parse(); + result.cert = std::make_shared(item.get(ndn::tlv::Data)); + break; + default: + if (ndn::tlv::isCriticalType(item.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(item.type()))); + } + break; } } return result; } -} // namespace ndncert +} // namespace ndncert::infotlv diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/info-encoder.hpp ndncert-0.0.6-1-g3e4818421/src/detail/info-encoder.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/info-encoder.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/info-encoder.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,8 +23,7 @@ #include "detail/ca-profile.hpp" -namespace ndncert { -namespace infotlv { +namespace ndncert::infotlv { /** * Encode CA configuration and its certificate into a TLV block as INFO Data packet content. @@ -38,7 +37,6 @@ CaProfile decodeDataContent(const Block& block); -} // namespace infotlv -} // namespace ndncert +} // namespace ndncert::infotlv #endif // NDNCERT_DETAIL_INFO_ENCODER_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/ndncert-common.hpp ndncert-0.0.6-1-g3e4818421/src/detail/ndncert-common.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/ndncert-common.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/ndncert-common.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -37,8 +37,11 @@ #include #include +#include +#include #include #include +#include #include #include @@ -48,7 +51,6 @@ #include #include #include -#include #include #include @@ -65,9 +67,6 @@ using ndn::SignatureInfo; using ndn::security::Certificate; -using ndn::optional; -using ndn::nullopt; - namespace time = ndn::time; using namespace ndn::time_literals; using namespace std::string_literals; diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/probe-encoder.cpp ndncert-0.0.6-1-g3e4818421/src/detail/probe-encoder.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/probe-encoder.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/probe-encoder.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,10 +20,10 @@ #include "detail/probe-encoder.hpp" -namespace ndncert { +namespace ndncert::probetlv { Block -probetlv::encodeApplicationParameters(const std::multimap& parameters) +encodeApplicationParameters(const std::multimap& parameters) { Block content(ndn::tlv::ApplicationParameters); for (const auto& items : parameters) { @@ -35,22 +35,30 @@ } std::multimap -probetlv::decodeApplicationParameters(const Block& block) +decodeApplicationParameters(const Block& block) { std::multimap result; block.parse(); - for (size_t i = 0; i < block.elements().size() - 1; i++) { - if (block.elements()[i].type() == tlv::ParameterKey && block.elements()[i + 1].type() == tlv::ParameterValue) { - result.emplace(readString(block.elements().at(i)), readString(block.elements().at(i + 1))); - i ++; + const auto& elements = block.elements(); + for (size_t i = 0; i < elements.size(); i++) { + if (i + 1 < elements.size() && elements[i].type() == tlv::ParameterKey && + elements[i + 1].type() == tlv::ParameterValue) { + result.emplace(readString(elements.at(i)), readString(elements.at(i + 1))); + i++; + } + else if (ndn::tlv::isCriticalType(elements[i].type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(elements[i].type()))); + } + else { + //ignore } } return result; } Block -probetlv::encodeDataContent(const std::vector& identifiers, optional maxSuffixLength, - std::vector> redirectionItems) +encodeDataContent(const std::vector& identifiers, std::optional maxSuffixLength, + const std::vector& redirectionItems) { Block content(ndn::tlv::Content); for (const auto& name : identifiers) { @@ -61,17 +69,18 @@ } content.push_back(item); } + for (const auto& item : redirectionItems) { - content.push_back(makeNestedBlock(tlv::ProbeRedirect, item->getFullName())); + content.push_back(makeNestedBlock(tlv::ProbeRedirect, item)); } + content.encode(); return content; } void -probetlv::decodeDataContent(const Block& block, - std::vector>& availableNames, - std::vector& availableRedirection) +decodeDataContent(const Block& block, std::vector>& availableNames, + std::vector& availableRedirection) { block.parse(); for (const auto& item : block.elements()) { @@ -89,16 +98,28 @@ else if (subBlock.type() == tlv::MaxSuffixLength) { maxSuffixLength = readNonNegativeInteger(subBlock); } + else if (ndn::tlv::isCriticalType(subBlock.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type in probe name item: " + std::to_string(subBlock.type()))); + } + else { + //ignore + } } if (elementName.empty()) { NDN_THROW(std::runtime_error("Invalid probe format")); } availableNames.emplace_back(elementName, maxSuffixLength); } - if (item.type() == tlv::ProbeRedirect) { + else if (item.type() == tlv::ProbeRedirect) { availableRedirection.emplace_back(Name(item.blockFromValue())); } + else if (ndn::tlv::isCriticalType(item.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(item.type()))); + } + else { + //ignore + } } } -} // namespace ndncert +} // namespace ndncert::probetlv diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/probe-encoder.hpp ndncert-0.0.6-1-g3e4818421/src/detail/probe-encoder.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/probe-encoder.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/probe-encoder.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,28 +23,26 @@ #include "detail/ndncert-common.hpp" -namespace ndncert { -namespace probetlv { +namespace ndncert::probetlv { // For Client use Block encodeApplicationParameters(const std::multimap& parameters); void -decodeDataContent(const Block& block, std::vector>& availableNames, +decodeDataContent(const Block& block, + std::vector>& availableNames, std::vector& availableRedirection); // For CA use Block encodeDataContent(const std::vector& identifiers, - optional maxSuffixLength = nullopt, - std::vector> redirectionItems = - std::vector>()); + std::optional maxSuffixLength = std::nullopt, + const std::vector& redirectionItems = {}); std::multimap decodeApplicationParameters(const Block& block); -} // namespace probetlv -} // namespace ndncert +} // namespace ndncert::probetlv #endif // NDNCERT_DETAIL_PROBE_ENCODER_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/profile-storage.cpp ndncert-0.0.6-1-g3e4818421/src/detail/profile-storage.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/profile-storage.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/profile-storage.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,11 +20,9 @@ #include "detail/profile-storage.hpp" -#include #include -namespace ndncert { -namespace requester { +namespace ndncert::requester { void ProfileStorage::load(const std::string& fileName) @@ -47,13 +45,12 @@ { m_caProfiles.clear(); auto caList = json.get_child("ca-list"); - for (auto item : caList) { - CaProfile caItem; - caItem = CaProfile::fromJson(item.second); - if (caItem.cert == nullptr) { + for (const auto& item : caList) { + auto profile = CaProfile::fromJson(item.second); + if (profile.cert == nullptr) { NDN_THROW(std::runtime_error("No CA certificate is loaded from JSON configuration.")); } - m_caProfiles.push_back(std::move(caItem)); + m_caProfiles.push_back(std::move(profile)); } } @@ -62,14 +59,12 @@ { JsonSection configJson; for (const auto& caItem : m_caProfiles) { - configJson.push_back(std::make_pair("", caItem.toJson())); + configJson.push_back({"", caItem.toJson()}); } std::stringstream ss; boost::property_tree::write_json(ss, configJson); - std::ofstream configFile; - configFile.open(fileName); + std::ofstream configFile(fileName); configFile << ss.str(); - configFile.close(); } void @@ -96,5 +91,4 @@ return m_caProfiles; } -} // namespace requester -} // namespace ndncert +} // namespace ndncert::requester diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/profile-storage.hpp ndncert-0.0.6-1-g3e4818421/src/detail/profile-storage.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/profile-storage.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/profile-storage.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,8 +23,7 @@ #include "detail/ca-profile.hpp" -namespace ndncert { -namespace requester { +namespace ndncert::requester { /** * @brief CA profiles kept by a requester. @@ -53,8 +52,7 @@ /** * @brief Add a new CA profile - * - * Be cautious. This will add a new trust anchor for requesters. + * @warning This will add a new trust anchor for requesters. */ void addCaProfile(const CaProfile& profile); @@ -66,7 +64,6 @@ std::list m_caProfiles; }; -} // namespace requester -} // namespace ndncert +} // namespace ndncert::requester #endif // NDNCERT_DETAIL_PROFILE_STORAGE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/request-encoder.cpp ndncert-0.0.6-1-g3e4818421/src/detail/request-encoder.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/request-encoder.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/request-encoder.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -31,9 +31,8 @@ const std::vector& ecdhPub, const Certificate& certRequest) { - Block - request(ndn::tlv::ApplicationParameters); - request.push_back(ndn::makeBinaryBlock(tlv::EcdhPub, ecdhPub.data(), ecdhPub.size())); + Block request(ndn::tlv::ApplicationParameters); + request.push_back(ndn::makeBinaryBlock(tlv::EcdhPub, ecdhPub)); if (requestType == RequestType::NEW || requestType == RequestType::RENEW) { request.push_back(makeNestedBlock(tlv::CertRequest, certRequest)); } @@ -51,31 +50,47 @@ { payload.parse(); - const auto& ecdhBlock = payload.get(tlv::EcdhPub); - ecdhPub.resize(ecdhBlock.value_size()); - std::memcpy(ecdhPub.data(), ecdhBlock.value(), ecdhBlock.value_size()); - + int ecdhPubCount = 0; Block requestPayload; - if (requestType == RequestType::NEW) { - requestPayload = payload.get(tlv::CertRequest); - } - else if (requestType == RequestType::REVOKE) { - requestPayload = payload.get(tlv::CertToRevoke); + int requestPayloadCount = 0; + for (const auto &item : payload.elements()) { + if (item.type() == tlv::EcdhPub) { + ecdhPub.resize(item.value_size()); + std::memcpy(ecdhPub.data(), item.value(), item.value_size()); + ecdhPubCount++; + } + else if ((requestType == RequestType::NEW && item.type() == tlv::CertRequest) || + (requestType == RequestType::REVOKE && item.type() == tlv::CertToRevoke)) { + requestPayload = item; + requestPayloadCount++; + requestPayload.parse(); + clientCert = std::make_shared(requestPayload.get(ndn::tlv::Data)); + } + else if (ndn::tlv::isCriticalType(item.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(item.type()))); + } + else { + //ignore + } } - requestPayload.parse(); - clientCert = std::make_shared(requestPayload.get(ndn::tlv::Data)); + if (ecdhPubCount != 1 || requestPayloadCount != 1) { + NDN_THROW(std::runtime_error("Error TLV contains " + std::to_string(ecdhPubCount) + " ecdh public param(s) and " + + std::to_string(requestPayloadCount) + + "request payload(s), instead of expected 1 times each.")); + } } Block -requesttlv::encodeDataContent(const std::vector & ecdhKey, const std::array& salt, +requesttlv::encodeDataContent(const std::vector& ecdhKey, + const std::array& salt, const RequestId& requestId, - const std::vector & challenges) + const std::vector& challenges) { Block response(ndn::tlv::Content); - response.push_back(ndn::makeBinaryBlock(tlv::EcdhPub, ecdhKey.data(), ecdhKey.size())); - response.push_back(ndn::makeBinaryBlock(tlv::Salt, salt.data(), salt.size())); - response.push_back(ndn::makeBinaryBlock(tlv::RequestId, requestId.data(), requestId.size())); + response.push_back(ndn::makeBinaryBlock(tlv::EcdhPub, ecdhKey)); + response.push_back(ndn::makeBinaryBlock(tlv::Salt, salt)); + response.push_back(ndn::makeBinaryBlock(tlv::RequestId, requestId)); for (const auto& entry: challenges) { response.push_back(ndn::makeStringBlock(tlv::Challenge, entry)); } @@ -85,25 +100,38 @@ std::list requesttlv::decodeDataContent(const Block& content, std::vector & ecdhKey, - std::array& salt, RequestId& requestId) -{ + std::array& salt, RequestId& requestId) { + std::list challenges; content.parse(); - - const auto& ecdhBlock = content.get(tlv::EcdhPub); - ecdhKey.resize(ecdhBlock.value_size()); - std::memcpy(ecdhKey.data(), ecdhBlock.value(), ecdhBlock.value_size()); - - const auto& saltBlock = content.get(tlv::Salt); - std::memcpy(salt.data(), saltBlock.value(), saltBlock.value_size()); - - const auto& requestIdBlock = content.get(tlv::RequestId); - std::memcpy(requestId.data(), requestIdBlock.value(), requestIdBlock.value_size()); - - std::list challenges; - for (auto const& element : content.elements()) { + int ecdhPubCount = 0, saltCount = 0, requestIdCount = 0; + for (auto const &element : content.elements()) { if (element.type() == tlv::Challenge) { challenges.push_back(readString(element)); } + else if (element.type() == tlv::EcdhPub) { + ecdhKey.resize(element.value_size()); + std::memcpy(ecdhKey.data(), element.value(), element.value_size()); + ecdhPubCount++; + } + else if (element.type() == tlv::Salt) { + std::memcpy(salt.data(), element.value(), element.value_size()); + saltCount++; + } + else if (element.type() == tlv::RequestId) { + std::memcpy(requestId.data(), element.value(), element.value_size()); + requestIdCount++; + } + else if (ndn::tlv::isCriticalType(element.type())) { + NDN_THROW(std::runtime_error("Unrecognized TLV Type: " + std::to_string(element.type()))); + } + else { + //ignore + } + } + if (ecdhPubCount != 1 || saltCount != 1 || requestIdCount != 1) { + NDN_THROW(std::runtime_error("Error TLV contains " + std::to_string(ecdhPubCount) + " ecdh public param(s), " + + std::to_string(saltCount) + " salt(s) and " + std::to_string(requestIdCount) + + "request id(s), instead of expected 1 times each.")); } return challenges; } diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/detail/request-encoder.hpp ndncert-0.0.6-1-g3e4818421/src/detail/request-encoder.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/detail/request-encoder.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/detail/request-encoder.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -23,8 +23,7 @@ #include "detail/ca-request-state.hpp" -namespace ndncert { -namespace requesttlv { +namespace ndncert::requesttlv { Block encodeApplicationParameters(RequestType requestType, const std::vector& ecdhPub, @@ -42,7 +41,6 @@ decodeDataContent(const Block& content, std::vector& ecdhKey, std::array& salt, RequestId& requestId); -} // namespace requesttlv -} // namespace ndncert +} // namespace ndncert::requesttlv #endif // NDNCERT_DETAIL_REQUEST_ENCODER_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-email.cpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-email.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-email.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-email.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,62 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#include "assignment-email.hpp" + +namespace ndncert { + +NDNCERT_REGISTER_NAME_ASSIGNMENT_FUNC(AssignmentEmail, "email"); + +AssignmentEmail::AssignmentEmail(const std::string& format) + : NameAssignmentFunc(format) +{ +} + +std::vector +AssignmentEmail::assignName(const std::multimap& params) +{ + std::vector resultList; + Name result; + if (!m_nameFormat.empty() && params.count("email") > 0) { + const std::string& email = params.begin()->second; + auto formatIter = m_nameFormat.begin(); + size_t emailSplit = email.rfind("@"); + std::string domain = "." + email.substr(emailSplit + 1); + + if (emailSplit != std::string::npos && emailSplit > 0) { + size_t domainSplit = domain.rfind("."); + while (domainSplit != std::string::npos) { + if (formatIter != m_nameFormat.end() && domain.substr(domainSplit + 1) == *formatIter) { + formatIter++; + } + else { + result.push_back(domain.substr(domainSplit + 1).c_str()); + } + domain = domain.substr(0, domainSplit); + domainSplit = domain.rfind("."); + } + result.push_back(email.substr(0, emailSplit).c_str()); + resultList.push_back(std::move(result)); + } + } + return resultList; +} + +} // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-email.hpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-email.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-email.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-email.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,43 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#ifndef NDNCERT_ASSIGNMENT_EMAIL_HPP +#define NDNCERT_ASSIGNMENT_EMAIL_HPP + +#include "assignment-func.hpp" + +namespace ndncert { + +/** + * @brief Assign names based on requester's email address + */ +class AssignmentEmail : public NameAssignmentFunc +{ +public: + explicit + AssignmentEmail(const std::string& format = ""); + + std::vector + assignName(const std::multimap& params) override; +}; + +} // namespace ndncert + +#endif // NDNCERT_ASSIGNMENT_EMAIL_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-func.cpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-func.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-func.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-func.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -40,15 +40,15 @@ std::unique_ptr NameAssignmentFunc::createNameAssignmentFunc(const std::string& challengeType, const std::string& format) { - CurriedFuncFactory& factory = getFactory(); + auto& factory = getFactory(); auto i = factory.find(challengeType); return i == factory.end() ? nullptr : i->second(format); } -NameAssignmentFunc::CurriedFuncFactory& +NameAssignmentFunc::FuncFactory& NameAssignmentFunc::getFactory() { - static NameAssignmentFunc::CurriedFuncFactory factory; + static FuncFactory factory; return factory; } diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-func.hpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-func.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-func.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-func.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -30,10 +30,12 @@ class NameAssignmentFunc : boost::noncopyable { protected: - explicit NameAssignmentFunc(const std::string& format = ""); + explicit + NameAssignmentFunc(const std::string& format = ""); public: - virtual ~NameAssignmentFunc() = default; + virtual + ~NameAssignmentFunc() = default; /** * @brief The name assignment function provided by the CA operator to generate available @@ -48,40 +50,40 @@ virtual std::vector assignName(const std::multimap& params) = 0; -public: - template +public: // factory + template static void - registerNameAssignmentFunc(const std::string& typeName) + registerNameAssignmentFunc(const std::string& type) { - CurriedFuncFactory& factory = getFactory(); - BOOST_ASSERT(factory.count(typeName) == 0); - factory[typeName] = [](const std::string& format) { return std::make_unique(format); }; + auto& factory = getFactory(); + BOOST_ASSERT(factory.count(type) == 0); + factory[type] = [] (const std::string& format) { return std::make_unique(format); }; } static std::unique_ptr - createNameAssignmentFunc(const std::string& challengeType, const std::string& format = ""); + createNameAssignmentFunc(const std::string& type, const std::string& format = ""); NDNCERT_PUBLIC_WITH_TESTS_ELSE_PROTECTED: std::vector m_nameFormat; private: - typedef std::function(const std::string&)> FactoryCreateFunc; - typedef std::map CurriedFuncFactory; + using CreateFunc = std::function(const std::string &)>; + using FuncFactory = std::map; - static CurriedFuncFactory& + static FuncFactory& getFactory(); }; -#define NDNCERT_REGISTER_FUNCFACTORY(C, T) \ - static class NdnCert##C##FuncFactoryRegistrationClass \ - { \ - public: \ - NdnCert##C##FuncFactoryRegistrationClass() \ - { \ - ::ndncert::NameAssignmentFunc::registerNameAssignmentFunc(T); \ - } \ - } g_NdnCert##C##ChallengeRegistrationVariable - } // namespace ndncert +#define NDNCERT_REGISTER_NAME_ASSIGNMENT_FUNC(C, T) \ +static class NdnCert##C##NameAssignmentRegistrationClass \ +{ \ +public: \ + NdnCert##C##NameAssignmentRegistrationClass() \ + { \ + ::ndncert::NameAssignmentFunc::registerNameAssignmentFunc(T); \ + } \ +} g_NdnCert##C##NameAssignmentRegistrationVariable + #endif // NDNCERT_ASSIGNMENT_FUNC_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-hash.cpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-hash.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-hash.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-hash.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -24,7 +24,7 @@ namespace ndncert { -NDNCERT_REGISTER_FUNCFACTORY(AssignmentHash, "hash"); +NDNCERT_REGISTER_NAME_ASSIGNMENT_FUNC(AssignmentHash, "hash"); AssignmentHash::AssignmentHash(const std::string& format) : NameAssignmentFunc(format) diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-param.cpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-param.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-param.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-param.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -22,7 +22,7 @@ namespace ndncert { -NDNCERT_REGISTER_FUNCFACTORY(AssignmentParam, "param"); +NDNCERT_REGISTER_NAME_ASSIGNMENT_FUNC(AssignmentParam, "param"); AssignmentParam::AssignmentParam(const std::string& format) : NameAssignmentFunc(format) @@ -35,13 +35,17 @@ std::vector resultList; Name result; for (const auto& item : m_nameFormat) { - auto it = std::find_if(params.begin(), params.end(), - [&](const std::tuple& e) { return std::get<0>(e) == item; }); - if (it != params.end() && !it->second.empty()) { - result.append(it->second); + if (item.size() >= 2 && item[0] == '"' && item[item.size() - 1] == '"') { + result.append(item.substr(1, item.size() - 2)); } else { - return resultList; + auto it = params.find(item); + if (it != params.end() && !it->second.empty()) { + result.append(it->second); + } + else { + return resultList; // empty + } } } resultList.push_back(std::move(result)); diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-random.cpp ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-random.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/name-assignment/assignment-random.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/name-assignment/assignment-random.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -24,7 +24,7 @@ namespace ndncert { -NDNCERT_REGISTER_FUNCFACTORY(AssignmentRandom, "random"); +NDNCERT_REGISTER_NAME_ASSIGNMENT_FUNC(AssignmentRandom, "random"); AssignmentRandom::AssignmentRandom(const std::string& format) : NameAssignmentFunc(format) @@ -34,7 +34,7 @@ std::vector AssignmentRandom::assignName(const std::multimap&) { - return {ndn::PartialName(ndn::to_string(ndn::random::generateSecureWord64()))}; + return {ndn::PartialName(std::to_string(ndn::random::generateSecureWord64()))}; } } // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-email.cpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-email.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-email.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-email.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,46 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#include "redirection-email.hpp" + +#include + +namespace ndncert { + +NDNCERT_REGISTER_REDIRECTION_POLICY(RedirectionEmail, "email"); + +RedirectionEmail::RedirectionEmail(const std::string& format) + : m_domain(format) +{ +} + +bool +RedirectionEmail::isRedirecting(const std::multimap& params) +{ + for (auto it = params.find("email"); it != params.end() && it->first == "email"; it++) { + auto i = it->second.rfind('@'); + if (i != std::string::npos && it->second.substr(i + 1) == m_domain) { + return true; + } + } + return false; +} + +} // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-email.hpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-email.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-email.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-email.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,43 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#ifndef NDNCERT_REDIRECTION_EMAIL_HPP +#define NDNCERT_REDIRECTION_EMAIL_HPP + +#include "redirection-policy.hpp" + +namespace ndncert { + +class RedirectionEmail : public RedirectionPolicy +{ +public: + explicit + RedirectionEmail(const std::string& format = ""); + + bool + isRedirecting(const std::multimap& params) override; + +private: + std::string m_domain; +}; + +} // namespace ndncert + +#endif // NDNCERT_REDIRECTION_EMAIL_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-param.cpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-param.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-param.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-param.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,64 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#include "redirection-param.hpp" + +#include + +namespace ndncert { + +NDNCERT_REGISTER_REDIRECTION_POLICY(RedirectionParam, "param"); + +RedirectionParam::RedirectionParam(const std::string& format) +{ + if (format.empty()) { + return; + } + + std::vector strs; + boost::split(strs,format,boost::is_any_of("&")); + for (const auto& s : strs) { + auto i = s.find('='); + if (i == std::string::npos) { + NDN_THROW(std::runtime_error("Redirection param format: no '=' in format piece")); + } + m_format.emplace(s.substr(0, i), s.substr(i + 1)); + } +} + +bool +RedirectionParam::isRedirecting(const std::multimap& params) +{ + for (const auto& p : m_format) { + bool found = false; + for (auto it = params.find(p.first); it != params.end() && it->first == p.first; ++it) { + if (it->second == p.second) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +} // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-param.hpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-param.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-param.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-param.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,43 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#ifndef NDNCERT_REDIRECTION_PARAM_HPP +#define NDNCERT_REDIRECTION_PARAM_HPP + +#include "redirection-policy.hpp" + +namespace ndncert { + +class RedirectionParam : public RedirectionPolicy +{ +public: + explicit + RedirectionParam(const std::string& format = ""); + + bool + isRedirecting(const std::multimap& params) override; + +private: + std::map m_format; +}; + +} // namespace ndncert + +#endif // NDNCERT_REDIRECTION_PARAM_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-policy.cpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-policy.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-policy.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-policy.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,40 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#include "redirection-policy.hpp" + +namespace ndncert { + +std::unique_ptr +RedirectionPolicy::createPolicyFunc(const std::string& policyType, const std::string& format) +{ + auto& factory = getFactory(); + auto i = factory.find(policyType); + return i == factory.end() ? nullptr : i->second(format); +} + +RedirectionPolicy::PolicyFactory& +RedirectionPolicy::getFactory() +{ + static PolicyFactory factory; + return factory; +} + +} // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-policy.hpp ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-policy.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/redirection/redirection-policy.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/redirection/redirection-policy.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,79 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#ifndef NDNCERT_REDIRECTION_POLICY_HPP +#define NDNCERT_REDIRECTION_POLICY_HPP + +#include "detail/ca-request-state.hpp" + +#include + +namespace ndncert { + +class RedirectionPolicy : boost::noncopyable +{ +public: + virtual + ~RedirectionPolicy() = default; + + /** + * @brief The Redirection Policy provided by the CA operator to decide if redirection is suitable. + * + * + * @param vector A list of parameter key-value pair from probe. + * @return a boolean that is true if the provided params conform to the configured redirection policy. + */ + virtual bool + isRedirecting(const std::multimap& params) = 0; + +public: // factory + template + static void + registerRedirectionPolicy(const std::string& type) + { + PolicyFactory& factory = getFactory(); + BOOST_ASSERT(factory.count(type) == 0); + factory[type] = [] (const std::string& format) { return std::make_unique(format); }; + } + + static std::unique_ptr + createPolicyFunc(const std::string& policyType, const std::string& format = ""); + +private: + using CreateFunc = std::function(const std::string &)>; + using PolicyFactory = std::map; + + static PolicyFactory& + getFactory(); +}; + +} // namespace ndncert + +#define NDNCERT_REGISTER_REDIRECTION_POLICY(C, T) \ +static class NdnCert##C##RedirectionPolicyRegistrationClass \ +{ \ +public: \ + NdnCert##C##RedirectionPolicyRegistrationClass() \ + { \ + ::ndncert::RedirectionPolicy::registerRedirectionPolicy(T); \ + } \ +} g_NdnCert##C##RedirectionPolicyRegistrationVariable + +#endif // NDNCERT_REDIRECTION_POLICY_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/requester-request.cpp ndncert-0.0.6-1-g3e4818421/src/requester-request.cpp --- ndncert-0.0.5-1-gfae76c4dc/src/requester-request.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/requester-request.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -39,8 +39,7 @@ #include -namespace ndncert { -namespace requester { +namespace ndncert::requester { NDN_LOG_INIT(ndncert.client); @@ -58,14 +57,12 @@ Request::genCaProfileInterestFromDiscoveryResponse(const Data& reply) { auto metaData = ndn::MetadataObject(reply); - auto interestName= metaData.getVersionedName(); + auto interestName = metaData.getVersionedName(); interestName.appendSegment(0); - auto interest = std::make_shared(interestName); - interest->setCanBePrefix(false); - return interest; + return std::make_shared(interestName); } -optional +std::optional Request::onCaProfileResponse(const Data& reply) { auto caItem = infotlv::decodeDataContent(reply.getContent()); @@ -76,7 +73,7 @@ return caItem; } -optional +std::optional Request::onCaProfileResponseAfterRedirection(const Data& reply, const Name& caCertFullName) { auto caItem = infotlv::decodeDataContent(reply.getContent()); @@ -96,8 +93,7 @@ interestName.append("CA").append("PROBE"); auto interest = std::make_shared(interestName); interest->setMustBeFresh(true); - interest->setCanBePrefix(false); - interest->setApplicationParameters(probetlv::encodeApplicationParameters(std::move(probeInfo))); + interest->setApplicationParameters(probetlv::encodeApplicationParameters(probeInfo)); return interest; } @@ -108,7 +104,6 @@ if (!ndn::security::verifySignature(reply, *ca.cert)) { NDN_LOG_ERROR("Cannot verify replied Data packet signature."); NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature.")); - return; } processIfError(reply); probetlv::decodeDataContent(reply.getContent(), identityNames, otherCas); @@ -122,47 +117,29 @@ } std::shared_ptr -Request::genNewInterest(const Name& newIdentityName, - const time::system_clock::TimePoint& notBefore, - const time::system_clock::TimePoint& notAfter) +Request::genNewInterest(const Name& keyName, + const time::system_clock::time_point& notBefore, + const time::system_clock::time_point& notAfter) { - if (!m_caProfile.caPrefix.isPrefixOf(newIdentityName)) { + if (!m_caProfile.caPrefix.isPrefixOf(keyName)) { return nullptr; } - if (newIdentityName.empty()) { - NDN_LOG_TRACE("Randomly create a new name because newIdentityName is empty and the param is empty."); - m_identityName = m_caProfile.caPrefix; - m_identityName.append(ndn::to_string(ndn::random::generateSecureWord64())); + if (keyName.empty()) { + return nullptr; } else { - m_identityName = newIdentityName; - } - - // generate a newly key pair or use an existing key - const auto& pib = m_keyChain.getPib(); - ndn::security::pib::Identity identity; - try { + const auto& pib = m_keyChain.getPib(); + ndn::security::pib::Identity identity; + m_identityName = ndn::security::extractIdentityFromKeyName(keyName); identity = pib.getIdentity(m_identityName); + m_keyPair = identity.getKey(keyName); } - catch (const ndn::security::Pib::Error&) { - identity = m_keyChain.createIdentity(m_identityName); - m_isNewlyCreatedIdentity = true; - m_isNewlyCreatedKey = true; - } - try { - m_keyPair = identity.getDefaultKey(); - } - catch (const ndn::security::Pib::Error&) { - m_keyPair = m_keyChain.createKey(identity); - m_isNewlyCreatedKey = true; - } - auto& keyName = m_keyPair.getName(); // generate certificate request Certificate certRequest; certRequest.setName(Name(keyName).append("cert-request").appendVersion()); certRequest.setContentType(ndn::tlv::ContentType_Key); - certRequest.setContent(m_keyPair.getPublicKey().data(), m_keyPair.getPublicKey().size()); + certRequest.setContent(m_keyPair.getPublicKey()); SignatureInfo signatureInfo; signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(notBefore, notAfter)); m_keyChain.sign(certRequest, signingByKey(keyName).setSignatureInfo(signatureInfo)); @@ -170,11 +147,10 @@ // generate Interest packet Name interestName = m_caProfile.caPrefix; interestName.append("CA").append("NEW"); - auto interest =std::make_shared(interestName); + auto interest = std::make_shared(interestName); interest->setMustBeFresh(true); - interest->setCanBePrefix(false); interest->setApplicationParameters( - requesttlv::encodeApplicationParameters(RequestType::NEW, m_ecdh.getSelfPubKey(), certRequest)); + requesttlv::encodeApplicationParameters(RequestType::NEW, m_ecdh.getSelfPubKey(), certRequest)); // sign the Interest packet m_keyChain.sign(*interest, signingByKey(keyName)); @@ -187,14 +163,14 @@ if (!m_caProfile.caPrefix.isPrefixOf(certificate.getName())) { return nullptr; } + // generate Interest packet Name interestName = m_caProfile.caPrefix; interestName.append("CA").append("REVOKE"); - auto interest =std::make_shared(interestName); + auto interest = std::make_shared(interestName); interest->setMustBeFresh(true); - interest->setCanBePrefix(false); interest->setApplicationParameters( - requesttlv::encodeApplicationParameters(RequestType::REVOKE, m_ecdh.getSelfPubKey(), certificate)); + requesttlv::encodeApplicationParameters(RequestType::REVOKE, m_ecdh.getSelfPubKey(), certificate)); return interest; } @@ -236,20 +212,19 @@ std::shared_ptr Request::genChallengeInterest(std::multimap&& parameters) { - if (m_challengeType == "") { + if (m_challengeType.empty()) { NDN_THROW(std::runtime_error("The challenge has not been selected.")); } auto challenge = ChallengeModule::createChallengeModule(m_challengeType); if (challenge == nullptr) { NDN_THROW(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT.")); } - auto challengeParams = challenge->genChallengeRequestTLV(m_status, m_challengeStatus, std::move(parameters)); + auto challengeParams = challenge->genChallengeRequestTLV(m_status, m_challengeStatus, parameters); Name interestName = m_caProfile.caPrefix; - interestName.append("CA").append("CHALLENGE").append(m_requestId.data(), m_requestId.size()); - auto interest =std::make_shared(interestName); + interestName.append("CA").append("CHALLENGE").append(Name::Component(m_requestId)); + auto interest = std::make_shared(interestName); interest->setMustBeFresh(true); - interest->setCanBePrefix(false); // encrypt the Interest parameters auto paramBlock = encodeBlockWithAesGcm128(ndn::tlv::ApplicationParameters, m_aesKey.data(), @@ -275,10 +250,10 @@ std::shared_ptr Request::genCertFetchInterest() const { - Name interestName = m_issuedCertName; - auto interest = std::make_shared(interestName); - interest->setMustBeFresh(false); - interest->setCanBePrefix(false); + auto interest = std::make_shared(m_issuedCertName); + if (!m_forwardingHint.empty()) { + interest->setForwardingHint({m_forwardingHint}); + } return interest; } @@ -289,27 +264,8 @@ return std::make_shared(reply); } catch (const std::exception&) { - NDN_LOG_ERROR("Cannot parse replied certificate "); - NDN_THROW(std::runtime_error("Cannot parse replied certificate ")); - return nullptr; - } -} - -void -Request::endSession() -{ - if (m_status == Status::SUCCESS) { - return; - } - if (m_isNewlyCreatedIdentity) { - // put the identity into the if scope is because it may cause an error - // outside since when endSession is called, identity may not have been created yet. - auto identity = m_keyChain.getPib().getIdentity(m_identityName); - m_keyChain.deleteIdentity(identity); - } - else if (m_isNewlyCreatedKey) { - auto identity = m_keyChain.getPib().getIdentity(m_identityName); - m_keyChain.deleteKey(identity, m_keyPair); + NDN_LOG_ERROR("Cannot parse replied certificate"); + NDN_THROW(std::runtime_error("Cannot parse replied certificate")); } } @@ -327,5 +283,4 @@ " and Error Info: " + std::get<1>(errorInfo))); } -} // namespace requester -} // namespace ndncert +} // namespace ndncert::requester diff -Nru ndncert-0.0.5-1-gfae76c4dc/src/requester-request.hpp ndncert-0.0.6-1-g3e4818421/src/requester-request.hpp --- ndncert-0.0.5-1-gfae76c4dc/src/requester-request.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/src/requester-request.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -27,8 +27,7 @@ #include -namespace ndncert { -namespace requester { +namespace ndncert::requester { class Request : boost::noncopyable { @@ -61,7 +60,7 @@ * @return the CaProfile if decoding is successful * @throw std::runtime_error if the decoding fails or receiving an error packet. */ - static optional + static std::optional onCaProfileResponse(const Data& reply); /** @@ -76,7 +75,7 @@ * @return the CaProfile if decoding is successful * @throw std::runtime_error if the decoding fails or receiving an error packet. */ - static optional + static std::optional onCaProfileResponseAfterRedirection(const Data& reply, const Name& caCertFullName); /** @@ -112,20 +111,19 @@ * @brief Generates a NEW interest to the CA. * * @param state The current requester state for this request. Will be modified in the function. - * @param newIdentityName The identity name to be requested. + * @param keyName The key name to be requested. * @param notBefore The expected notBefore field for the certificate (starting time) * @param notAfter The expected notAfter field for the certificate (expiration time) * @return The shared pointer to the encoded interest. */ std::shared_ptr - genNewInterest(const Name& newIdentityName, - const time::system_clock::TimePoint& notBefore, - const time::system_clock::TimePoint& notAfter); + genNewInterest(const Name& keyName, + const time::system_clock::time_point& notBefore, + const time::system_clock::time_point& notAfter); /** * @brief Generates a REVOKE interest to the CA. * - * @param state The current requester state for this request. Will be modified in the function. * @param certificate The certificate to the revoked. * @return The shared pointer to the encoded interest. */ @@ -135,7 +133,6 @@ /** * @brief Decodes the replied data of NEW, RENEW, or REVOKE interest from the CA. * - * @param state The current requester state for the request. Will be updated in the function. * @param reply The replied data from the network * @return the list of challenge accepted by the CA, for CHALLENGE step. * @throw std::runtime_error if the decoding fails or receiving an error packet. @@ -147,9 +144,8 @@ /** * @brief Generates the required parameter for the selected challenge for the request * - * @param state, The requester state of the request.Will be updated in the function. - * @param challengeSelected, The selected challenge for the request. - * Can use state.m_challengeType to continue. + * @param challengeSelected The selected challenge for the request. + * Can use state.m_challengeType to continue. * @return The requirement list for the current stage of the challenge, in name, prompt mapping. * @throw std::runtime_error if the challenge is not supported. */ @@ -159,8 +155,7 @@ /** * @brief Generates the CHALLENGE interest for the request. * - * @param state, The requester state of the request. - * @param parameters, The requirement list, in name, value mapping. + * @param parameters The requirement list, in name, value mapping. * @return The shared pointer to the encoded interest * @throw std::runtime_error if the challenge is not selected or is not supported. */ @@ -170,8 +165,7 @@ /** * @brief Decodes the responded data from the CHALLENGE interest. * - * @param state, the corresponding requester state of the request. Will be modified. - * @param reply, the response data. + * @param reply The response data. * @throw std::runtime_error if the decoding fails or receiving an error packet. */ void @@ -180,7 +174,6 @@ /** * @brief Generate the interest to fetch the issued certificate * - * @param state, the state of the request. * @return The shared pointer to the encoded interest */ std::shared_ptr @@ -189,20 +182,12 @@ /** * @brief Decoded and installs the response certificate from the certificate fetch. * - * @param reply, the data replied from the certificate fetch interest. + * @param reply The data replied from the certificate fetch interest. * @return The shared pointer to the certificate being fetched. */ static std::shared_ptr onCertFetchResponse(const Data& reply); - /** - * @brief End the current request session and performs cleanup if necessary. - * - * @param state, the requester state for the request. - */ - void - endSession(); - private: static void processIfError(const Data& data); @@ -243,12 +228,16 @@ /** * @brief The time this challenge will remain fresh */ - time::system_clock::TimePoint m_freshBefore; + time::system_clock::time_point m_freshBefore; /** * @brief the name of the certificate being issued. */ Name m_issuedCertName; /** + * @brief The optional forwarding hint. + */ + Name m_forwardingHint; + /** * @brief ecdh state. */ ECDHState m_ecdh; @@ -275,17 +264,11 @@ */ ndn::KeyChain& m_keyChain; /** - * @brief State about how identity/key is generated. - */ - bool m_isNewlyCreatedIdentity = false; - bool m_isNewlyCreatedKey = false; - /** * @brief The keypair for the request. */ ndn::security::Key m_keyPair; }; -} // namespace requester -} // namespace ndncert +} // namespace ndncert::requester #endif // NDNCERT_REQUESTER_REQUEST_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/systemd/ndncert-ca.service.in ndncert-0.0.6-1-g3e4818421/systemd/ndncert-ca.service.in --- ndncert-0.0.5-1-gfae76c4dc/systemd/ndncert-ca.service.in 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/systemd/ndncert-ca.service.in 2023-11-17 18:39:05.000000000 +0000 @@ -1,14 +1,41 @@ [Unit] Description=Certificate Management Identity Management Service for NDN +BindsTo=nfd.service +After=nfd.service [Service] Environment=HOME=%S/ndncert-ca ExecStart=@BINDIR@/ndncert-ca-server Restart=on-failure RestartPreventExitStatus=2 -RestartSec=5 User=ndn +CapabilityBoundingSet= +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +# systemd older than v232 doesn't support a value of "strict" for ProtectSystem, +# so it will ignore that line and use ProtectSystem=full; with newer systemd, +# the latter assignment is recognized and takes precedence, resulting in an +# effective setting of ProtectSystem=strict +ProtectSystem=full +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 AF_PACKET +RestrictNamespaces=yes +RestrictRealtime=yes +StateDirectory=ndncert-ca +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=~@aio @chown @clock @cpu-emulation @debug @keyring @module @mount @obsolete @privileged @raw-io @reboot @resources @setuid @swap + +# Dependency [Install] WantedBy=multi-user.target -Alias=ndncert.service \ No newline at end of file +WantedBy=nfd.service \ No newline at end of file diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/clock-fixture.cpp ndncert-0.0.6-1-g3e4818421/tests/clock-fixture.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/clock-fixture.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/clock-fixture.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,54 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2022 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#include "tests/clock-fixture.hpp" + +namespace ndncert::tests { + +ClockFixture::ClockFixture() + : m_steadyClock(std::make_shared()) + , m_systemClock(std::make_shared()) +{ + time::setCustomClocks(m_steadyClock, m_systemClock); +} + +ClockFixture::~ClockFixture() +{ + time::setCustomClocks(nullptr, nullptr); +} + +void +ClockFixture::advanceClocks(time::nanoseconds tick, time::nanoseconds total) +{ + BOOST_ASSERT(tick > time::nanoseconds::zero()); + BOOST_ASSERT(total >= time::nanoseconds::zero()); + + while (total > time::nanoseconds::zero()) { + auto t = std::min(tick, total); + m_steadyClock->advance(t); + m_systemClock->advance(t); + total -= t; + + afterTick(); + } +} + +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/clock-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/clock-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/clock-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/clock-fixture.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,85 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2022 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#ifndef NDNCERT_TESTS_CLOCK_FIXTURE_HPP +#define NDNCERT_TESTS_CLOCK_FIXTURE_HPP + +#include + +namespace ndncert::tests { + +namespace time = ndn::time; + +/** \brief A test fixture that overrides steady clock and system clock. + */ +class ClockFixture +{ +public: + virtual + ~ClockFixture(); + + /** \brief Advance steady and system clocks. + * + * Clocks are advanced in increments of \p tick for \p nTicks ticks. + * afterTick() is called after each tick. + * + * Exceptions thrown during I/O events are propagated to the caller. + * Clock advancement will stop in the event of an exception. + */ + void + advanceClocks(time::nanoseconds tick, size_t nTicks = 1) + { + advanceClocks(tick, tick * nTicks); + } + + /** \brief Advance steady and system clocks. + * + * Clocks are advanced in increments of \p tick for \p total time. + * The last increment might be shorter than \p tick. + * afterTick() is called after each tick. + * + * Exceptions thrown during I/O events are propagated to the caller. + * Clock advancement will stop in the event of an exception. + */ + void + advanceClocks(time::nanoseconds tick, time::nanoseconds total); + +protected: + ClockFixture(); + +private: + /** \brief Called by advanceClocks() after each clock advancement (tick). + * + * The base class implementation is a no-op. + */ + virtual void + afterTick() + { + } + +protected: + std::shared_ptr m_steadyClock; + std::shared_ptr m_systemClock; +}; + +} // namespace ndncert::tests + +#endif // NDNCERT_TESTS_CLOCK_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/database-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/database-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/database-fixture.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/database-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2013-2021 Regents of the University of California. - * - * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). - * - * ndn-cxx library is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received copies of the GNU General Public License and GNU Lesser - * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see - * . - * - * See AUTHORS.md for complete list of ndn-cxx authors and contributors. - */ - -#ifndef NDNCERT_TESTS_DATABASE_FIXTURE_HPP -#define NDNCERT_TESTS_DATABASE_FIXTURE_HPP - -#include "identity-management-fixture.hpp" -#include "unit-test-time-fixture.hpp" - -#include - -namespace ndncert { -namespace tests { - -class IdentityManagementTimeFixture : public UnitTestTimeFixture - , public IdentityManagementFixture -{ -}; - -class DatabaseFixture : public IdentityManagementTimeFixture -{ -public: - DatabaseFixture() - { - boost::filesystem::path parentDir{TMP_TESTS_PATH}; - dbDir = parentDir / "test-home" / ".ndncert"; - if (!boost::filesystem::exists(dbDir)) { - boost::filesystem::create_directory(dbDir); - } - } - - ~DatabaseFixture() - { - boost::filesystem::remove_all(dbDir); - } - -protected: - boost::filesystem::path dbDir; -}; - -} // namespace tests -} // namespace ndncert - -#endif // NDNCERT_TESTS_DATABASE_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/global-configuration.cpp ndncert-0.0.6-1-g3e4818421/tests/global-configuration.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/global-configuration.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/global-configuration.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,14 +20,13 @@ #include "detail/ndncert-common.hpp" -#include "boost-test.hpp" +#include "tests/boost-test.hpp" #include #include #include -namespace ndncert { -namespace tests { +namespace ndncert::tests { class GlobalConfiguration { @@ -38,7 +37,7 @@ if (envHome) m_home = envHome; - auto testHome = boost::filesystem::path(TMP_TESTS_PATH) / "test-home"; + auto testHome = boost::filesystem::path(UNIT_TESTS_TMPDIR) / "test-home"; if (::setenv("HOME", testHome.c_str(), 1) != 0) NDN_THROW(std::runtime_error("setenv() failed")); @@ -61,13 +60,6 @@ std::string m_home; }; -#if BOOST_VERSION >= 106500 BOOST_TEST_GLOBAL_CONFIGURATION(GlobalConfiguration); -#elif BOOST_VERSION >= 105900 -BOOST_GLOBAL_FIXTURE(GlobalConfiguration); -#else -BOOST_GLOBAL_FIXTURE(GlobalConfiguration) -#endif -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/identity-management-fixture.cpp ndncert-0.0.6-1-g3e4818421/tests/identity-management-fixture.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/identity-management-fixture.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/identity-management-fixture.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,130 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2013-2021 Regents of the University of California. - * - * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). - * - * ndn-cxx library is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received copies of the GNU General Public License and GNU Lesser - * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see - * . - * - * See AUTHORS.md for complete list of ndn-cxx authors and contributors. - */ - -#include "identity-management-fixture.hpp" - -#include -#include - -#include - -namespace ndncert { -namespace tests { - -using namespace ndn::security; - -IdentityManagementBaseFixture::~IdentityManagementBaseFixture() -{ - boost::system::error_code ec; - for (const auto& certFile : m_certFiles) { - boost::filesystem::remove(certFile, ec); // ignore error - } -} - -bool -IdentityManagementBaseFixture::saveCertToFile(const Data& obj, const std::string& filename) -{ - m_certFiles.insert(filename); - try { - ndn::io::save(obj, filename); - return true; - } - catch (const ndn::io::Error&) { - return false; - } -} - -IdentityManagementFixture::IdentityManagementFixture() - : m_keyChain("pib-memory:", "tpm-memory:") -{ -} - -Identity -IdentityManagementFixture::addIdentity(const Name& identityName, const ndn::KeyParams& params) -{ - auto identity = m_keyChain.createIdentity(identityName, params); - m_identities.insert(identityName); - return identity; -} - -bool -IdentityManagementFixture::saveCertificate(const Identity& identity, const std::string& filename) -{ - try { - auto cert = identity.getDefaultKey().getDefaultCertificate(); - return saveCertToFile(cert, filename); - } - catch (const Pib::Error&) { - return false; - } -} - -Identity -IdentityManagementFixture::addSubCertificate(const Name& subIdentityName, - const Identity& issuer, const ndn::KeyParams& params) -{ - auto subIdentity = addIdentity(subIdentityName, params); - - Certificate request = subIdentity.getDefaultKey().getDefaultCertificate(); - request.setName(request.getKeyName().append("parent").appendVersion()); - - SignatureInfo info; - auto now = time::system_clock::now(); - info.setValidityPeriod(ValidityPeriod(now, now + 7300_days)); - - AdditionalDescription description; - description.set("type", "sub-certificate"); - info.addCustomTlv(description.wireEncode()); - - m_keyChain.sign(request, signingByIdentity(issuer).setSignatureInfo(info)); - m_keyChain.setDefaultCertificate(subIdentity.getDefaultKey(), request); - - return subIdentity; -} - -Certificate -IdentityManagementFixture::addCertificate(const Key& key, const std::string& issuer) -{ - Name certificateName = key.getName(); - certificateName - .append(issuer) - .appendVersion(); - Certificate certificate; - certificate.setName(certificateName); - - // set metainfo - certificate.setContentType(ndn::tlv::ContentType_Key); - certificate.setFreshnessPeriod(1_h); - - // set content - certificate.setContent(key.getPublicKey().data(), key.getPublicKey().size()); - - // set signature-info - SignatureInfo info; - auto now = time::system_clock::now(); - info.setValidityPeriod(ValidityPeriod(now, now + 10_days)); - - m_keyChain.sign(certificate, signingByKey(key).setSignatureInfo(info)); - return certificate; -} - -} // namespace tests -} // namespace ndncert diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/identity-management-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/identity-management-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/identity-management-fixture.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/identity-management-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2013-2021 Regents of the University of California. - * - * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). - * - * ndn-cxx library is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received copies of the GNU General Public License and GNU Lesser - * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see - * . - * - * See AUTHORS.md for complete list of ndn-cxx authors and contributors. - */ - -#ifndef NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP -#define NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP - -#include "detail/ndncert-common.hpp" - -#include -#include - -namespace ndncert { -namespace tests { - -class IdentityManagementBaseFixture -{ -public: - ~IdentityManagementBaseFixture(); - - bool - saveCertToFile(const Data& obj, const std::string& filename); - -protected: - std::set m_identities; - std::set m_certFiles; -}; - -/** - * @brief A test suite level fixture to help with identity management - * - * Test cases in the suite can use this fixture to create identities. Identities, - * certificates, and saved certificates are automatically removed during test teardown. - */ -class IdentityManagementFixture : public IdentityManagementBaseFixture -{ -protected: - using Identity = ndn::security::Identity; - using Key = ndn::security::Key; - -public: - IdentityManagementFixture(); - - /** - * @brief Add identity @p identityName - * @return name of the created self-signed certificate - */ - Identity - addIdentity(const Name& identityName, - const ndn::KeyParams& params = ndn::KeyChain::getDefaultKeyParams()); - - /** - * @brief Save identity certificate to a file - * @param identity identity - * @param filename file name, should be writable - * @return whether successful - */ - bool - saveCertificate(const Identity& identity, const std::string& filename); - - /** - * @brief Issue a certificate for \p subIdentityName signed by \p issuer - * - * If identity does not exist, it is created. - * A new key is generated as the default key for identity. - * A default certificate for the key is signed by the issuer using its default certificate. - * - * @return the sub identity - */ - Identity - addSubCertificate(const Name& subIdentityName, const Identity& issuer, - const ndn::KeyParams& params = ndn::KeyChain::getDefaultKeyParams()); - - /** - * @brief Add a self-signed certificate to @p key with issuer ID @p issuer - */ - Certificate - addCertificate(const Key& key, const std::string& issuer); - -protected: - ndn::KeyChain m_keyChain; -}; - -} // namespace tests -} // namespace ndncert - -#endif // NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/io-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/io-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/io-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/io-fixture.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,49 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2023 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#ifndef NDNCERT_TESTS_IO_FIXTURE_HPP +#define NDNCERT_TESTS_IO_FIXTURE_HPP + +#include "tests/clock-fixture.hpp" + +#include + +namespace ndncert::tests { + +class IoFixture : public ClockFixture +{ +private: + void + afterTick() final + { + if (m_io.stopped()) { + m_io.restart(); + } + m_io.poll(); + } + +protected: + boost::asio::io_context m_io; +}; + +} // namespace ndncert::tests + +#endif // NDNCERT_TESTS_IO_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/io-key-chain-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/io-key-chain-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/io-key-chain-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/io-key-chain-fixture.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,36 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2022 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#ifndef NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP +#define NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP + +#include "tests/io-fixture.hpp" +#include "tests/key-chain-fixture.hpp" + +namespace ndncert::tests { + +class IoKeyChainFixture : public IoFixture, public KeyChainFixture +{ +}; + +} // namespace ndncert::tests + +#endif // NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/key-chain-fixture.cpp ndncert-0.0.6-1-g3e4818421/tests/key-chain-fixture.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/key-chain-fixture.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/key-chain-fixture.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,93 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2022 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#include "tests/key-chain-fixture.hpp" + +#include + +#include + +namespace ndncert::tests { + +using namespace ndn::security; + +KeyChainFixture::KeyChainFixture() + : m_keyChain("pib-memory:", "tpm-memory:") +{ +} + +KeyChainFixture::~KeyChainFixture() +{ + boost::system::error_code ec; + for (const auto& certFile : m_certFiles) { + boost::filesystem::remove(certFile, ec); // ignore error + } +} + +bool +KeyChainFixture::saveCert(const Data& cert, const std::string& filename) +{ + m_certFiles.push_back(filename); + try { + ndn::io::save(cert, filename); + return true; + } + catch (const ndn::io::Error&) { + return false; + } +} + +bool +KeyChainFixture::saveIdentityCert(const Identity& identity, const std::string& filename) +{ + Certificate cert; + try { + cert = identity.getDefaultKey().getDefaultCertificate(); + } + catch (const Pib::Error&) { + return false; + } + + return saveCert(cert, filename); +} + +bool +KeyChainFixture::saveIdentityCert(const Name& identityName, const std::string& filename, + bool allowCreate) +{ + Identity id; + try { + id = m_keyChain.getPib().getIdentity(identityName); + } + catch (const Pib::Error&) { + if (allowCreate) { + id = m_keyChain.createIdentity(identityName); + } + } + + if (!id) { + return false; + } + + return saveIdentityCert(id, filename); +} + +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/key-chain-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/key-chain-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/key-chain-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/key-chain-fixture.hpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,85 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2013-2022 Regents of the University of California. + * + * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). + * + * ndn-cxx library is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received copies of the GNU General Public License and GNU Lesser + * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see + * . + * + * See AUTHORS.md for complete list of ndn-cxx authors and contributors. + */ + +#ifndef NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP +#define NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP + +#include "detail/ndncert-common.hpp" + +#include +#include + +namespace ndncert::tests { + +/** + * @brief A fixture providing an in-memory KeyChain. + * + * Test cases can use this fixture to create identities. Identities, certificates, and + * saved certificates are automatically removed during test teardown. + */ +class KeyChainFixture +{ +protected: + using Certificate = ndn::security::Certificate; + using Identity = ndn::security::Identity; + using Key = ndn::security::Key; + +public: + /** + * @brief Saves an NDN certificate to a file + * @return true if successful, false otherwise + */ + bool + saveCert(const Data& cert, const std::string& filename); + + /** + * @brief Saves the default certificate of @p identity to a file + * @return true if successful, false otherwise + */ + bool + saveIdentityCert(const Identity& identity, const std::string& filename); + + /** + * @brief Saves the default certificate of the identity named @p identityName to a file + * @param identityName Name of the identity + * @param filename File name, must be writable + * @param allowCreate If true, create the identity if it does not exist + * @return true if successful, false otherwise + */ + bool + saveIdentityCert(const Name& identityName, const std::string& filename, + bool allowCreate = false); + +protected: + KeyChainFixture(); + + ~KeyChainFixture(); + +protected: + ndn::KeyChain m_keyChain; + +private: + std::vector m_certFiles; +}; + +} // namespace ndncert::tests + +#endif // NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/main.cpp ndncert-0.0.6-1-g3e4818421/tests/main.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/main.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/main.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2014-2020, Regents of the University of California, - * Arizona Board of Regents, - * Colorado State University, - * University Pierre & Marie Curie, Sorbonne University, - * Washington University in St. Louis, - * Beijing Institute of Technology, - * The University of Memphis. - * - * This file, originally written as part of NFD (Named Data Networking Forwarding Daemon), - * is a part of ndncert, a certificate management system based on NDN. - * - * ndncert is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received copies of the GNU General Public License along with - * ndncert, e.g., in COPYING.md file. If not, see . - * - * See AUTHORS.md for complete list of ndncert authors and contributors. - */ - -#define BOOST_TEST_MODULE ndncert -#include "boost-test.hpp" \ No newline at end of file diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/test-common.hpp ndncert-0.0.6-1-g3e4818421/tests/test-common.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/test-common.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/test-common.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2013-2020 Regents of the University of California. - * - * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). - * - * ndn-cxx library is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received copies of the GNU General Public License and GNU Lesser - * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see - * . - * - * See AUTHORS.md for complete list of ndn-cxx authors and contributors. - */ - -#ifndef NDNCERT_TESTS_TEST_COMMON_HPP -#define NDNCERT_TESTS_TEST_COMMON_HPP - -#include "boost-test.hpp" -#include "database-fixture.hpp" -#include "identity-management-fixture.hpp" -#include "unit-test-time-fixture.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -#endif // NDNCERT_TESTS_TEST_COMMON_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-test-time-fixture.hpp ndncert-0.0.6-1-g3e4818421/tests/unit-test-time-fixture.hpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-test-time-fixture.hpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-test-time-fixture.hpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2013-2021 Regents of the University of California. - * - * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). - * - * ndn-cxx library is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later version. - * - * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - * - * You should have received copies of the GNU General Public License and GNU Lesser - * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see - * . - * - * See AUTHORS.md for complete list of ndn-cxx authors and contributors. - */ - -#ifndef NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP -#define NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP - -#include "detail/ndncert-common.hpp" - -#include - -#include - -namespace ndncert { -namespace tests { - -/** \brief a test fixture that overrides steady clock and system clock - */ -class UnitTestTimeFixture -{ -public: - UnitTestTimeFixture() - : steadyClock(std::make_shared()) - , systemClock(std::make_shared()) - { - time::setCustomClocks(steadyClock, systemClock); - } - - ~UnitTestTimeFixture() - { - time::setCustomClocks(nullptr, nullptr); - } - - /** \brief advance steady and system clocks - * - * Clocks are advanced in increments of \p tick for \p nTicks ticks. - * After each tick, io_service is polled to process pending I/O events. - * - * Exceptions thrown during I/O events are propagated to the caller. - * Clock advancing would stop in case of an exception. - */ - void - advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1) - { - this->advanceClocks(tick, tick * nTicks); - } - - /** \brief advance steady and system clocks - * - * Clocks are advanced in increments of \p tick for \p total time. - * The last increment might be shorter than \p tick. - * After each tick, io_service is polled to process pending I/O events. - * - * Exceptions thrown during I/O events are propagated to the caller. - * Clock advancing would stop in case of an exception. - */ - void - advanceClocks(const time::nanoseconds& tick, const time::nanoseconds& total) - { - BOOST_ASSERT(tick > time::nanoseconds::zero()); - BOOST_ASSERT(total >= time::nanoseconds::zero()); - - time::nanoseconds remaining = total; - while (remaining > time::nanoseconds::zero()) { - if (remaining >= tick) { - steadyClock->advance(tick); - systemClock->advance(tick); - remaining -= tick; - } - else { - steadyClock->advance(remaining); - systemClock->advance(remaining); - remaining = time::nanoseconds::zero(); - } - - if (io.stopped()) - io.reset(); - io.poll(); - } - } - -public: - std::shared_ptr steadyClock; - std::shared_ptr systemClock; - boost::asio::io_service io; -}; - -} // namespace tests -} // namespace ndncert - -#endif // NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/bench.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/bench.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/bench.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/bench.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -22,20 +22,25 @@ #include "challenge/challenge-pin.hpp" #include "detail/info-encoder.hpp" #include "requester-request.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/io-key-chain-fixture.hpp" -BOOST_FIXTURE_TEST_SUITE(Benchmark, IdentityManagementTimeFixture) +#include +#include +#include + +namespace ndncert::tests { + +BOOST_FIXTURE_TEST_SUITE(Benchmark, IoKeyChainFixture) BOOST_AUTO_TEST_CASE(PacketSize0) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - ndn::util::DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); auto profileData = ca.getCaProfileData(); @@ -62,8 +67,7 @@ // std::cout << "CA Config MetaData Size: " << response.wireEncode().size() << std::endl; auto block = response.getContent(); block.parse(); - infoInterest =std::make_shared(Name(block.get(ndn::tlv::Name)).appendSegment(0)); - infoInterest->setCanBePrefix(false); + infoInterest = std::make_shared(Name(block.get(ndn::tlv::Name)).appendSegment(0)); // std::cout << "CA Config fetch Interest Size: " << infoInterest->wireEncode().size() << std::endl; } else { @@ -90,11 +94,11 @@ BOOST_AUTO_TEST_CASE(PacketSize1) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - ndn::util::DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -103,10 +107,9 @@ item.caPrefix = Name("/ndn"); item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); - auto newInterest = state.genNewInterest(Name("/ndn/alice"), + auto newInterest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/alice")).getDefaultKey().getName(), time::system_clock::now(), time::system_clock::now() + time::days(1)); - // std::cout << "New Interest Size: " << newInterest->wireEncode().size() << std::endl; // generate CHALLENGE Interest @@ -169,5 +172,4 @@ BOOST_AUTO_TEST_SUITE_END() // Benchmark -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-memory.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-memory.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-memory.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-memory.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,21 +19,21 @@ */ #include "detail/ca-memory.hpp" -#include "detail/ca-sqlite.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/key-chain-fixture.hpp" + +namespace ndncert::tests { using namespace ca; -BOOST_FIXTURE_TEST_SUITE(TestCaMemory, IdentityManagementFixture) +BOOST_FIXTURE_TEST_SUITE(TestCaMemory, KeyChainFixture) BOOST_AUTO_TEST_CASE(RequestOperations) { CaMemory storage; - auto identity1 = addIdentity(Name("/ndn/site1")); + auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1")); auto key1 = identity1.getDefaultKey(); auto cert1 = key1.getDefaultCertificate(); @@ -72,7 +72,7 @@ BOOST_CHECK_EQUAL(request2.caPrefix, result.caPrefix); // another add operation - auto identity2 = addIdentity(Name("/ndn/site2")); + auto identity2 = m_keyChain.createIdentity(Name("/ndn/site2")); auto key2 = identity2.getDefaultKey(); auto cert2 = key2.getDefaultCertificate(); RequestId requestId2 = {{102}}; @@ -94,5 +94,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestCaMemory -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-module.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-module.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-module.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-module.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -24,20 +24,24 @@ #include "challenge/challenge-pin.hpp" #include "detail/info-encoder.hpp" #include "requester-request.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/io-key-chain-fixture.hpp" + +#include +#include +#include + +namespace ndncert::tests { using namespace ca; -using ndn::util::DummyClientFace; using ndn::security::verifySignature; -BOOST_FIXTURE_TEST_SUITE(TestCaModule, DatabaseFixture) +BOOST_FIXTURE_TEST_SUITE(TestCaModule, IoKeyChainFixture) BOOST_AUTO_TEST_CASE(Initialization) { - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); BOOST_CHECK_EQUAL(ca.getCaConf().caProfile.caPrefix, "/ndn"); @@ -48,11 +52,11 @@ BOOST_AUTO_TEST_CASE(HandleProfileFetching) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); auto profileData = ca.getCaProfileData(); @@ -77,7 +81,6 @@ auto block = response.getContent(); block.parse(); infoInterest = std::make_shared(Name(block.get(ndn::tlv::Name)).appendSegment(0)); - infoInterest->setCanBePrefix(false); } else { count++; @@ -102,22 +105,19 @@ BOOST_AUTO_TEST_CASE(HandleProbe) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); Interest interest("/ndn/CA/PROBE"); - interest.setCanBePrefix(false); - Block paramTLV = ndn::makeEmptyBlock(ndn::tlv::ApplicationParameters); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterKey, "name")); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterValue, "zhiyi")); paramTLV.encode(); - interest.setApplicationParameters(paramTLV); int count = 0; @@ -140,22 +140,19 @@ BOOST_AUTO_TEST_CASE(HandleProbeUsingDefaultHandler) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); Interest interest("/ndn/CA/PROBE"); - interest.setCanBePrefix(false); - Block paramTLV = ndn::makeEmptyBlock(ndn::tlv::ApplicationParameters); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterKey, "name")); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterValue, "zhiyi")); paramTLV.encode(); - interest.setApplicationParameters(paramTLV); int count = 0; @@ -178,22 +175,19 @@ BOOST_AUTO_TEST_CASE(HandleProbeRedirection) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-5", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); Interest interest("/ndn/CA/PROBE"); - interest.setCanBePrefix(false); - Block paramTLV = ndn::makeEmptyBlock(ndn::tlv::ApplicationParameters); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterKey, "name")); paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterValue, "zhiyi")); paramTLV.encode(); - interest.setApplicationParameters(paramTLV); int count = 0; @@ -211,8 +205,8 @@ } } BOOST_CHECK_EQUAL(redirectionItems.size(), 2); - BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirectionItems[0].getPrefix(-1)), "/ndn/site1"); - BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirectionItems[1].getPrefix(-1)), "/ndn/site1"); + BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirectionItems[0].getPrefix(-1)), "/ndn/edu/ucla"); + BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirectionItems[1].getPrefix(-1)), "/ndn/edu/ucla/cs/irl"); }); face.receive(interest); advanceClocks(time::milliseconds(20), 60); @@ -221,11 +215,11 @@ BOOST_AUTO_TEST_CASE(HandleNew) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -233,7 +227,7 @@ item.caPrefix = Name("/ndn"); item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); - auto interest = state.genNewInterest(Name("/ndn/zhiyi"), + auto interest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(), time::system_clock::now(), time::system_clock::now() + time::days(1)); @@ -272,11 +266,11 @@ BOOST_AUTO_TEST_CASE(HandleNewWithInvalidValidityPeriod1) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1"); advanceClocks(time::milliseconds(20), 60); @@ -284,10 +278,12 @@ item.caPrefix = Name("/ndn"); item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); + auto client = m_keyChain.createIdentity(Name("/ndn/zhiyi")); auto current_tp = time::system_clock::now(); - auto interest1 = state.genNewInterest(Name("/ndn/zhiyi"), current_tp, current_tp - time::hours(1)); - auto interest2 = state.genNewInterest(Name("/ndn/zhiyi"), current_tp, current_tp + time::days(361)); - auto interest3 = state.genNewInterest(Name("/ndn/zhiyi"), current_tp - time::hours(1), current_tp + time::hours(2)); + auto interest1 = state.genNewInterest(client.getDefaultKey().getName(), current_tp, current_tp - time::hours(1)); + auto interest2 = state.genNewInterest(client.getDefaultKey().getName(), current_tp, current_tp + time::days(361)); + auto interest3 = state.genNewInterest(client.getDefaultKey().getName(), + current_tp - time::hours(1), current_tp + time::hours(2)); face.onSendData.connect([&](const Data& response) { auto contentTlv = response.getContent(); contentTlv.parse(); @@ -303,21 +299,20 @@ BOOST_AUTO_TEST_CASE(HandleNewWithServerBadValidity) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); //build expired cert Certificate cert; cert.setName(Name(key.getName()).append("self-sign").appendVersion()); cert.setContentType(ndn::tlv::ContentType_Key); - cert.setContent(key.getPublicKey().data(), key.getPublicKey().size()); + cert.setContent(key.getPublicKey()); SignatureInfo signatureInfo; - signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now() - time::days(1), - time::system_clock::now() - time::seconds(1))); + signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod::makeRelative(-1_days, -1_s)); m_keyChain.sign(cert, signingByKey(key.getName()).setSignatureInfo(signatureInfo)); m_keyChain.setDefaultCertificate(key, cert); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -325,7 +320,7 @@ item.caPrefix = Name("/ndn"); item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); - auto interest = state.genNewInterest(Name("/ndn/zhiyi"), + auto interest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(), time::system_clock::now(), time::system_clock::now() + time::days(1)); @@ -345,11 +340,11 @@ BOOST_AUTO_TEST_CASE(HandleNewWithLongSuffix) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -358,12 +353,15 @@ item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); - auto interest1 = state.genNewInterest(Name("/ndn/a"), time::system_clock::now(), - time::system_clock::now() + time::days(1)); - auto interest2 = state.genNewInterest(Name("/ndn/a/b"), time::system_clock::now(), - time::system_clock::now() + time::days(1)); - auto interest3 = state.genNewInterest(Name("/ndn/a/b/c/d"), time::system_clock::now(), - time::system_clock::now() + time::days(1)); + auto interest1 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a")).getDefaultKey().getName(), + time::system_clock::now(), + time::system_clock::now() + time::days(1)); + auto interest2 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b")).getDefaultKey().getName(), + time::system_clock::now(), + time::system_clock::now() + time::days(1)); + auto interest3 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(), + time::system_clock::now(), + time::system_clock::now() + time::days(1)); face.onSendData.connect([&](const Data& response) { auto contentTlv = response.getContent(); @@ -385,11 +383,11 @@ BOOST_AUTO_TEST_CASE(HandleNewWithInvalidLength1) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1"); advanceClocks(time::milliseconds(20), 60); @@ -399,8 +397,9 @@ requester::Request state(m_keyChain, item, RequestType::NEW); auto current_tp = time::system_clock::now(); - auto interest1 = state.genNewInterest(Name("/ndn"), current_tp, current_tp + time::days(1)); - auto interest2 = state.genNewInterest(Name("/ndn/a/b/c/d"), current_tp, current_tp + time::days(1)); + auto interest1 = state.genNewInterest(identity.getDefaultKey().getName(), current_tp, current_tp + time::days(1)); + auto interest2 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(), + current_tp, current_tp + time::days(1)); face.onSendData.connect([&](const Data& response) { auto contentTlv = response.getContent(); contentTlv.parse(); @@ -415,11 +414,11 @@ BOOST_AUTO_TEST_CASE(HandleChallenge) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -429,8 +428,9 @@ item.cert = std::make_shared(cert); requester::Request state(m_keyChain, item, RequestType::NEW); - auto newInterest = state.genNewInterest(Name("/ndn/zhiyi"), time::system_clock::now(), - time::system_clock::now() + time::days(1)); + auto newInterest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(), + time::system_clock::now(), + time::system_clock::now() + time::days(1)); // generate CHALLENGE Interest std::shared_ptr challengeInterest; @@ -475,6 +475,12 @@ BOOST_CHECK(state.m_status == Status::SUCCESS); } }); + ca.setStatusUpdateCallback([](const RequestState& request) { + if (request.status == Status::SUCCESS && request.requestType == RequestType::NEW) { + BOOST_REQUIRE_NO_THROW(Certificate{request.cert}); + BOOST_CHECK(Certificate(request.cert).isValid()); + } + }); face.receive(*newInterest); advanceClocks(time::milliseconds(20), 60); @@ -489,11 +495,11 @@ BOOST_AUTO_TEST_CASE(HandleRevoke) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, {true, true}); + ndn::DummyClientFace face(m_io, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -504,7 +510,7 @@ clientCert.setName(Name(clientKey.getName()).append("cert-request").appendVersion()); clientCert.setContentType(ndn::tlv::ContentType_Key); clientCert.setFreshnessPeriod(time::hours(24)); - clientCert.setContent(clientKey.getPublicKey().data(), clientKey.getPublicKey().size()); + clientCert.setContent(clientKey.getPublicKey()); SignatureInfo signatureInfo; signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now(), time::system_clock::now() + time::hours(10))); @@ -560,11 +566,11 @@ BOOST_AUTO_TEST_CASE(HandleRevokeWithBadCert) { - auto identity = addIdentity(Name("/ndn")); + auto identity = m_keyChain.createIdentity(Name("/ndn")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); - DummyClientFace face(io, {true, true}); + ndn::DummyClientFace face(m_io, {true, true}); CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory"); advanceClocks(time::milliseconds(20), 60); @@ -575,7 +581,7 @@ clientCert.setName(Name(clientKey.getName()).append("NDNCERT").append("1473283247810732701")); clientCert.setContentType(ndn::tlv::ContentType_Key); clientCert.setFreshnessPeriod(time::hours(24)); - clientCert.setContent(clientKey.getPublicKey().data(), clientKey.getPublicKey().size()); + clientCert.setContent(clientKey.getPublicKey()); SignatureInfo signatureInfo; signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now(), time::system_clock::now() + time::hours(10))); @@ -603,5 +609,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestCaModule -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-sqlite.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-sqlite.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/ca-sqlite.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/ca-sqlite.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,20 +19,45 @@ */ #include "detail/ca-sqlite.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/key-chain-fixture.hpp" + +#include +#include + +namespace ndncert::tests { using namespace ca; +class DatabaseFixture : public KeyChainFixture +{ +public: + DatabaseFixture() + { + boost::filesystem::path parentDir{UNIT_TESTS_TMPDIR}; + dbDir = parentDir / "test-home" / ".ndncert"; + if (!boost::filesystem::exists(dbDir)) { + boost::filesystem::create_directory(dbDir); + } + } + + ~DatabaseFixture() + { + boost::filesystem::remove_all(dbDir); + } + +protected: + boost::filesystem::path dbDir; +}; + BOOST_FIXTURE_TEST_SUITE(TestCaSqlite, DatabaseFixture) BOOST_AUTO_TEST_CASE(RequestOperations) { CaSqlite storage(Name(), dbDir.string() + "/TestCaSqlite_RequestOperations.db"); - auto identity1 = addIdentity(Name("/ndn/site1")); + auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1")); auto key1 = identity1.getDefaultKey(); auto cert1 = key1.getDefaultCertificate(); @@ -84,7 +109,7 @@ result.decryptionIv.begin(), result.decryptionIv.end()); // another add operation - auto identity2 = addIdentity(Name("/ndn/site2")); + auto identity2 = m_keyChain.createIdentity(Name("/ndn/site2")); auto key2 = identity2.getDefaultKey(); auto cert2 = key2.getDefaultCertificate(); RequestId requestId2 = {{102}}; @@ -112,7 +137,7 @@ { CaSqlite storage(Name(), dbDir.string() + "/TestCaSqlite_DuplicateAdd.db"); - auto identity1 = addIdentity(Name("/ndn/site1")); + auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1")); auto key1 = identity1.getDefaultKey(); auto cert1 = key1.getDefaultCertificate(); @@ -131,5 +156,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestCaSqlite -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-email.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-email.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-email.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-email.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,12 +19,15 @@ */ #include "challenge/challenge-email.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/key-chain-fixture.hpp" -BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, IdentityManagementFixture) +#include + +namespace ndncert::tests { + +BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, KeyChainFixture) BOOST_AUTO_TEST_CASE(ChallengeType) { @@ -41,7 +44,7 @@ BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmail) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); RequestId requestId = {{101}}; @@ -93,33 +96,9 @@ std::remove("tmp.txt"); } -BOOST_AUTO_TEST_CASE(OnChallengeRequestWithInvalidEmail) -{ - auto identity = addIdentity(Name("/ndn/site1")); - auto key = identity.getDefaultKey(); - auto cert = key.getDefaultCertificate(); - RequestId requestId = {{101}}; - ca::RequestState request; - request.caPrefix = Name("/ndn/site1"); - request.requestId = requestId; - request.requestType = RequestType::NEW; - request.cert = cert; - - Block paramTLV = ndn::makeEmptyBlock(tlv::EncryptedPayload); - paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterKey, ChallengeEmail::PARAMETER_KEY_EMAIL)); - paramTLV.push_back(ndn::makeStringBlock(tlv::ParameterValue, "zhiyi@cs")); - - ChallengeEmail challenge; - challenge.handleChallengeRequest(paramTLV, request); - - BOOST_CHECK_EQUAL(request.challengeType, "email"); - BOOST_CHECK_EQUAL(request.challengeState->challengeStatus, ChallengeEmail::INVALID_EMAIL); - BOOST_CHECK_EQUAL(request.challengeState->remainingTries, 2); -} - BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); JsonSection secret; @@ -148,7 +127,7 @@ BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithWrongCode) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); JsonSection secret; @@ -178,5 +157,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestChallengeEmail -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-pin.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-pin.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-pin.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-pin.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,12 +19,13 @@ */ #include "challenge/challenge-pin.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/key-chain-fixture.hpp" -BOOST_FIXTURE_TEST_SUITE(TestChallengePin, IdentityManagementFixture) +namespace ndncert::tests { + +BOOST_FIXTURE_TEST_SUITE(TestChallengePin, KeyChainFixture) BOOST_AUTO_TEST_CASE(ChallengeType) { @@ -34,7 +35,7 @@ BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmptyInfo) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); RequestId requestId = {{101}}; @@ -54,7 +55,7 @@ BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); JsonSection secret; @@ -83,7 +84,7 @@ BOOST_AUTO_TEST_CASE(OnChallengeRequestWithWrongCode) { - auto identity = addIdentity(Name("/ndn/site1")); + auto identity = m_keyChain.createIdentity(Name("/ndn/site1")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); JsonSection secret; @@ -113,5 +114,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestChallengePin -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-possession.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-possession.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/challenge-possession.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/challenge-possession.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,16 +20,75 @@ #include "challenge/challenge-possession.hpp" #include "detail/challenge-encoder.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/key-chain-fixture.hpp" -BOOST_FIXTURE_TEST_SUITE(TestChallengePossession, IdentityManagementFixture) +namespace ndncert::tests { + +class ChallengePossessionFixture : public KeyChainFixture +{ +public: + void + createTrustAnchor() + { + trustAnchor = m_keyChain.createIdentity("/trust").getDefaultKey().getDefaultCertificate(); + challenge.parseConfigFile(); + challenge.m_trustAnchors.front() = trustAnchor; + } + + void + createCertificateRequest() + { + state.caPrefix = "/example"; + state.requestId = RequestId{{101}}; + state.requestType = RequestType::NEW; + state.cert = m_keyChain.createIdentity("/example").getDefaultKey().getDefaultCertificate(); + } + + void + createRequesterCredential() + { + auto keyB = m_keyChain.createIdentity("/trust/cert").getDefaultKey(); + ndn::security::MakeCertificateOptions opts; + opts.issuerId = ndn::name::Component("Credential"); + opts.validity.emplace(ndn::security::ValidityPeriod::makeRelative(-1_s, 1_min)); + credential = m_keyChain.makeCertificate(keyB, signingByCertificate(trustAnchor), opts); + m_keyChain.addCertificate(keyB, credential); + } + + void + signCertRequest() + { + auto params = challenge.getRequestedParameterList(state.status, ""); + ChallengePossession::fulfillParameters(params, m_keyChain, credential.getName(), std::array{}); + Block paramsTlv = challenge.genChallengeRequestTLV(state.status, "", params); + challenge.handleChallengeRequest(paramsTlv, state); + BOOST_CHECK_EQUAL(statusToString(state.status), statusToString(Status::CHALLENGE)); + BOOST_REQUIRE(state.challengeState.has_value()); + BOOST_CHECK_EQUAL(state.challengeState->challengeStatus, "need-proof"); + } + + void + replyFromServer(ndn::span nonce) + { + auto params2 = challenge.getRequestedParameterList(state.status, state.challengeState->challengeStatus); + ChallengePossession::fulfillParameters(params2, m_keyChain, credential.getName(), nonce); + Block paramsTlv2 = challenge.genChallengeRequestTLV(state.status, state.challengeState->challengeStatus, params2); + challenge.handleChallengeRequest(paramsTlv2, state); + } + +public: + ChallengePossession challenge{"tests/unit-tests/config-files/config-challenge-possession"}; + Certificate trustAnchor; + ca::RequestState state; + Certificate credential; +}; + +BOOST_FIXTURE_TEST_SUITE(TestChallengePossession, ChallengePossessionFixture) BOOST_AUTO_TEST_CASE(LoadConfig) { - ChallengePossession challenge("./tests/unit-tests/config-files/config-challenge-possession"); BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "Possession"); challenge.parseConfigFile(); @@ -41,109 +100,30 @@ BOOST_AUTO_TEST_CASE(HandleChallengeRequest) { - // create trust anchor - ChallengePossession challenge("./tests/unit-tests/config-files/config-challenge-possession"); - auto identity = addIdentity(Name("/trust")); - auto key = identity.getDefaultKey(); - auto trustAnchor = key.getDefaultCertificate(); - challenge.parseConfigFile(); - challenge.m_trustAnchors.front() = trustAnchor; + createTrustAnchor(); + createCertificateRequest(); + createRequesterCredential(); + signCertRequest(); - // create certificate request - auto identityA = addIdentity(Name("/example")); - auto keyA = identityA.getDefaultKey(); - auto certA = key.getDefaultCertificate(); - RequestId requestId = {{101}}; - ca::RequestState state; - state.caPrefix = Name("/example"); - state.requestId = requestId; - state.requestType = RequestType::NEW; - state.cert = certA; - - // create requester's credential - auto identityB = addIdentity(Name("/trust/cert")); - auto keyB = identityB.getDefaultKey(); - auto credentialName = Name(keyB.getName()).append("Credential").appendVersion(); - Certificate credential; - credential.setName(credentialName); - credential.setContent(keyB.getPublicKey().data(), keyB.getPublicKey().size()); - SignatureInfo signatureInfo; - signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now(), - time::system_clock::now() + time::minutes(1))); - m_keyChain.sign(credential, signingByCertificate(trustAnchor).setSignatureInfo(signatureInfo)); - m_keyChain.addCertificate(keyB, credential); - - // using private key to sign cert request - auto params = challenge.getRequestedParameterList(state.status, ""); - ChallengePossession::fulfillParameters(params, m_keyChain, credential.getName(), std::array{}); - Block paramsTlv = challenge.genChallengeRequestTLV(state.status, "", params); - challenge.handleChallengeRequest(paramsTlv, state); - BOOST_CHECK_EQUAL(statusToString(state.status), statusToString(Status::CHALLENGE)); - BOOST_CHECK_EQUAL(state.challengeState->challengeStatus, "need-proof"); - - // reply from server auto nonceBuf = ndn::fromHex(state.challengeState->secrets.get("nonce", "")); std::array nonce{}; memcpy(nonce.data(), nonceBuf->data(), 16); - auto params2 = challenge.getRequestedParameterList(state.status, state.challengeState->challengeStatus); - ChallengePossession::fulfillParameters(params2, m_keyChain, credential.getName(), nonce); - Block paramsTlv2 = challenge.genChallengeRequestTLV(state.status, state.challengeState->challengeStatus, params2); - challenge.handleChallengeRequest(paramsTlv2, state); + replyFromServer(nonce); BOOST_CHECK_EQUAL(statusToString(state.status), statusToString(Status::PENDING)); } BOOST_AUTO_TEST_CASE(HandleChallengeRequestProofFail) { - // create trust anchor - ChallengePossession challenge("./tests/unit-tests/config-files/config-challenge-possession"); - auto identity = addIdentity(Name("/trust")); - auto key = identity.getDefaultKey(); - auto trustAnchor = key.getDefaultCertificate(); - challenge.parseConfigFile(); - challenge.m_trustAnchors.front() = trustAnchor; - - // create certificate request - auto identityA = addIdentity(Name("/example")); - auto keyA = identityA.getDefaultKey(); - auto certA = key.getDefaultCertificate(); - RequestId requestId = {{101}}; - ca::RequestState state; - state.caPrefix = Name("/example"); - state.requestId = requestId; - state.requestType = RequestType::NEW; - state.cert = certA; - - // create requester's credential - auto identityB = addIdentity(Name("/trust/cert")); - auto keyB = identityB.getDefaultKey(); - auto credentialName = Name(keyB.getName()).append("Credential").appendVersion(); - Certificate credential; - credential.setName(credentialName); - credential.setContent(keyB.getPublicKey().data(), keyB.getPublicKey().size()); - SignatureInfo signatureInfo; - signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now(), - time::system_clock::now() + time::minutes(1))); - m_keyChain.sign(credential, signingByCertificate(trustAnchor).setSignatureInfo(signatureInfo)); - m_keyChain.addCertificate(keyB, credential); - - // using private key to sign cert request - auto params = challenge.getRequestedParameterList(state.status, ""); - ChallengePossession::fulfillParameters(params, m_keyChain, credential.getName(), std::array{}); - Block paramsTlv = challenge.genChallengeRequestTLV(state.status, "", params); - challenge.handleChallengeRequest(paramsTlv, state); - BOOST_CHECK_EQUAL(statusToString(state.status), statusToString(Status::CHALLENGE)); - BOOST_CHECK_EQUAL(state.challengeState->challengeStatus, "need-proof"); + createTrustAnchor(); + createCertificateRequest(); + createRequesterCredential(); + signCertRequest(); - // reply from server std::array nonce{}; - auto params2 = challenge.getRequestedParameterList(state.status, state.challengeState->challengeStatus); - ChallengePossession::fulfillParameters(params2, m_keyChain, credential.getName(), nonce); - Block paramsTlv2 = challenge.genChallengeRequestTLV(state.status, state.challengeState->challengeStatus, params2); - challenge.handleChallengeRequest(paramsTlv2, state); + replyFromServer(nonce); BOOST_CHECK_EQUAL(statusToString(state.status), statusToString(Status::FAILURE)); } BOOST_AUTO_TEST_SUITE_END() // TestChallengePossession -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/config-files/config-ca-5 ndncert-0.0.6-1-g3e4818421/tests/unit-tests/config-files/config-ca-5 --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/config-files/config-ca-5 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/config-files/config-ca-5 2023-11-17 18:39:05.000000000 +0000 @@ -15,11 +15,15 @@ [ { "ca-prefix": "/ndn/edu/ucla", - "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F" + "certificate": "Bv0BNQcvCANuZG4IA2VkdQgEdWNsYQgDS0VZCAhtCJjCeE5aEwgEc2VsZjYIAAABf1ePw8kUCRgBAhkEADbugBVbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwrhu4gD0eba7jbVqg1qfwuRj2JSqOfnwdiOlholvhCGpdXHpqg5o68ajADQPL9S555uvfabbFnPhv86X/Diy5hZQGwEDHCEHHwgDbmRuCANlZHUIBHVjbGEIA0tFWQgIbQiYwnhOWhP9AP0m/QD+DzE5NzAwMTAxVDAwMDAwMP0A/w8yMDQyMDIyOFQwMDUxNTMXSDBGAiEA11i8sGwf83hd+IQ2vve+Ax1O7zZeV8cG6FAXvXFQ0kACIQDAvqq0CRAYYJ/RFLW21wNGJf1Rf3OgFyGEKpLjnRkxaw==", + "policy-type": "param", + "policy-param": "" }, { - "ca-prefix": "/ndn/edu/irl", - "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F" + "ca-prefix": "/ndn/edu/ucla/cs/irl", + "certificate": "Bv0BRQc4CANuZG4IA2VkdQgEdWNsYQgCY3MIA2lybAgDS0VZCAgWVGa5Tzd9WggEc2VsZjYIAAABf1eQ1LoUCRgBAhkEADbugBVbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7hBKT9GapKRII3LT9q0kY5RXEE9Cu9B2Pg/E4Mndbqr3nbnMmm+SUAeIcrnTQa4c9ri8oCLkTesXsW0Tr8oTuhZZGwEDHCoHKAgDbmRuCANlZHUIBHVjbGEIAmNzCANpcmwIA0tFWQgIFlRmuU83fVr9AP0m/QD+DzE5NzAwMTAxVDAwMDAwMP0A/w8yMDQyMDIyOFQwMDUzMDMXRjBEAiA9Q/FjffFLasMfr7MIQY/KBBQScNKYyrEyphz4wOcQjAIgLf14XL8LaqqUyfBkwQXeCv3pipsnZw5BFhv8c5UCLVE=", + "policy-type": "param", + "policy-param": "" } ], "name-assignment": diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/config-files/config-client-1 ndncert-0.0.6-1-g3e4818421/tests/unit-tests/config-files/config-client-1 --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/config-files/config-client-1 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/config-files/config-client-1 2023-11-17 18:39:05.000000000 +0000 @@ -10,11 +10,11 @@ [ { "probe-parameter-key": "email" } ], - "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F" + "certificate": "Bv0BKwcrCANuZG4IBXNpdGUxCANLRVkICEKyYEYHiBwyCARzZWxmNggAAAF/V5V9QhQJGAECGQQANu6AFVswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARRaZbFLRSnL1fmj8X3hInCCPy4qe17QujMMYq8qe+CKqV+OjexhrUvpwRDImRZOgXLCjOTyYnW3wxlxskTz8Y3FkwbAQMcHQcbCANuZG4IBXNpdGUxCANLRVkICEKyYEYHiBwy/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjAyMjhUMDA1ODA5F0YwRAIgFtFP0WocLQCtbwMTnqNtnCDmu62EJyC4uuCZ4Q/Wb8UCIGHb3e4St78378py81GjEZd/2L/aGbE3vbYQIiNxIYPN" }, { "ca-prefix": "/ndn/edu/ucla/zhiyi", - "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F" + "certificate": "Bv0BKwcrCANuZG4IBXNpdGUxCANLRVkICEKyYEYHiBwyCARzZWxmNggAAAF/V5V9QhQJGAECGQQANu6AFVswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARRaZbFLRSnL1fmj8X3hInCCPy4qe17QujMMYq8qe+CKqV+OjexhrUvpwRDImRZOgXLCjOTyYnW3wxlxskTz8Y3FkwbAQMcHQcbCANuZG4IBXNpdGUxCANLRVkICEKyYEYHiBwy/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjAyMjhUMDA1ODA5F0YwRAIgFtFP0WocLQCtbwMTnqNtnCDmu62EJyC4uuCZ4Q/Wb8UCIGHb3e4St78378py81GjEZd/2L/aGbE3vbYQIiNxIYPN" } ] } \ No newline at end of file diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/configuration.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/configuration.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/configuration.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/configuration.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -21,12 +21,12 @@ #include "detail/ca-configuration.hpp" #include "detail/profile-storage.hpp" #include "detail/info-encoder.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" -BOOST_FIXTURE_TEST_SUITE(TestConfig, IdentityManagementFixture) +namespace ndncert::tests { + +BOOST_AUTO_TEST_SUITE(TestConfig) BOOST_AUTO_TEST_CASE(CaConfigFile) { @@ -51,8 +51,8 @@ BOOST_CHECK_EQUAL(config.caProfile.supportedChallenges.front(), "pin"); config.load("tests/unit-tests/config-files/config-ca-5"); - BOOST_CHECK_EQUAL(config.redirection[0]->getName(), - "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5"); + BOOST_CHECK_EQUAL(config.redirection[0].first->getName(), + "/ndn/edu/ucla/KEY/m%08%98%C2xNZ%13/self/v=1646441513929"); BOOST_CHECK_EQUAL(config.nameAssignmentFuncs.size(), 3); BOOST_CHECK_EQUAL(config.nameAssignmentFuncs[0]->m_nameFormat[0], "group"); BOOST_CHECK_EQUAL(config.nameAssignmentFuncs[0]->m_nameFormat[1], "email"); @@ -99,7 +99,7 @@ BOOST_CHECK_EQUAL(profile1.probeParameterKeys.size(), 1); BOOST_CHECK_EQUAL(profile1.probeParameterKeys.front(), "email"); BOOST_CHECK_EQUAL(profile1.cert->getName(), - "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5"); + "/ndn/site1/KEY/B%B2%60F%07%88%1C2/self/v=1646441889090"); auto& profile2 = profileStorage.getKnownProfiles().back(); BOOST_CHECK_EQUAL(profile2.caPrefix, "/ndn/edu/ucla/zhiyi"); @@ -108,7 +108,7 @@ BOOST_CHECK(!profile2.maxSuffixLength); BOOST_CHECK_EQUAL(profile2.probeParameterKeys.size(), 0); BOOST_CHECK_EQUAL(profile2.cert->getName(), - "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5"); + "/ndn/site1/KEY/B%B2%60F%07%88%1C2/self/v=1646441889090"); } BOOST_AUTO_TEST_CASE(ProfileStorageWithErrors) @@ -144,5 +144,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestConfig -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/crypto-helpers.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/crypto-helpers.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/crypto-helpers.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/crypto-helpers.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,10 +19,12 @@ */ #include "detail/crypto-helpers.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" + +#include + +namespace ndncert::tests { BOOST_AUTO_TEST_SUITE(TestCryptoHelpers) @@ -271,6 +273,8 @@ BOOST_AUTO_TEST_CASE(AesIV) { + namespace be = boost::endian; + const uint8_t key[] = {0xbc, 0x22, 0xf3, 0xf0, 0x5c, 0xc4, 0x0d, 0xb9, 0x31, 0x1e, 0x41, 0x92, 0x96, 0x6f, 0xee, 0x92}; const std::string plaintext = "alongstringalongstringalongstringalongstringalongstringalongstringalongstringalongstring"; @@ -282,8 +286,8 @@ auto ivBlock = block.get(tlv::InitializationVector); ndn::Buffer ivBuf(ivBlock.value(), ivBlock.value_size()); BOOST_CHECK_EQUAL(ivBuf.size(), 12); - BOOST_CHECK_EQUAL(loadBigU32(&encryptionIv[8]), 6); - BOOST_CHECK_EQUAL(loadBigU32(&ivBuf[8]), 0); + BOOST_CHECK_EQUAL((be::endian_load(&encryptionIv[8])), 6); + BOOST_CHECK_EQUAL((be::endian_load(&ivBuf[8])), 0); block = encodeBlockWithAesGcm128(ndn::tlv::ApplicationParameters, key, (uint8_t*)plaintext.c_str(), plaintext.size(), (uint8_t*)associatedData.c_str(), associatedData.size(), encryptionIv); @@ -339,5 +343,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestCryptoHelpers -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/main.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/main.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/main.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/main.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,29 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2014-2022, Regents of the University of California, + * Arizona Board of Regents, + * Colorado State University, + * University Pierre & Marie Curie, Sorbonne University, + * Washington University in St. Louis, + * Beijing Institute of Technology, + * The University of Memphis. + * + * This file, originally written as part of NFD (Named Data Networking Forwarding Daemon), + * is a part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#define BOOST_TEST_MODULE ndncert +#include "tests/boost-test.hpp" diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/name-assignment.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/name-assignment.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/name-assignment.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/name-assignment.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -18,13 +18,14 @@ * See AUTHORS.md for complete list of ndncert authors and contributors. */ +#include "name-assignment/assignment-email.hpp" #include "name-assignment/assignment-random.hpp" #include "name-assignment/assignment-param.hpp" #include "name-assignment/assignment-hash.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" + +namespace ndncert::tests { BOOST_AUTO_TEST_SUITE(TestNameAssignment) @@ -49,6 +50,14 @@ BOOST_CHECK_EQUAL(*assignment.assignName(params).begin(), Name("/123/789")); params.find("xyz")->second = ""; BOOST_CHECK_EQUAL(assignment.assignName(params).size(), 0); + + AssignmentParam assignment2("/\"guest\"/email"); + params.emplace("email", "1@1.com"); + BOOST_CHECK_EQUAL(assignment2.assignName(params).size(), 1); + BOOST_CHECK_EQUAL(assignment2.assignName(params).begin()->toUri(), Name("/guest/1@1.com").toUri()); + + AssignmentParam assignment3("/\"/email"); + BOOST_CHECK_EQUAL(assignment3.assignName(params).size(), 0); } BOOST_AUTO_TEST_CASE(NameAssignmentHash) @@ -68,7 +77,20 @@ BOOST_CHECK_EQUAL(assignment.assignName(params).begin()->size(), 2); } +BOOST_AUTO_TEST_CASE(NameAssignmentEmail) +{ + AssignmentEmail assignment("/edu/ucla"); + std::multimap params; + BOOST_CHECK_EQUAL(assignment.assignName(params).size(), 0); + params.emplace("email", "das@math.ucla.edu"); + BOOST_CHECK_EQUAL(*assignment.assignName(params).begin(), Name("/math/das")); + + params.clear(); + params.emplace("email", "d/~.^as@6666=.9!"); + BOOST_CHECK_EQUAL(assignment.assignName(params).size(), 1); + BOOST_CHECK_EQUAL(*assignment.assignName(params).begin(), Name("/9!/6666%3D/d%2F~.%5Eas")); +} + BOOST_AUTO_TEST_SUITE_END() // TestNameAssignment -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/protocol-encoders.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/protocol-encoders.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/protocol-encoders.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/protocol-encoders.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -21,15 +21,17 @@ #include "detail/challenge-encoder.hpp" #include "detail/error-encoder.hpp" #include "detail/info-encoder.hpp" -#include "detail/request-encoder.hpp" #include "detail/probe-encoder.hpp" +#include "detail/request-encoder.hpp" #include "detail/ca-configuration.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/clock-fixture.hpp" +#include "tests/key-chain-fixture.hpp" + +namespace ndncert::tests { -BOOST_FIXTURE_TEST_SUITE(TestProtocolEncoding, IdentityManagementTimeFixture) +BOOST_AUTO_TEST_SUITE(TestProtocolEncoding) BOOST_AUTO_TEST_CASE(InfoEncoding) { @@ -79,7 +81,9 @@ std::vector names; names.emplace_back("/ndn/1"); names.emplace_back("/ndn/2"); - auto b = probetlv::encodeDataContent(names, 2, config.redirection); + std::vector redirectionNames; + for (const auto& i : config.redirection) redirectionNames.push_back(i.first->getFullName()); + auto b = probetlv::encodeDataContent(names, 2, redirectionNames); std::vector> retNames; std::vector redirection; probetlv::decodeDataContent(b, retNames, redirection); @@ -94,7 +98,7 @@ auto it3 = redirection.begin(); auto it4 = config.redirection.begin(); for (; it3 != redirection.end() && it4 != config.redirection.end(); it3++, it4++) { - BOOST_CHECK_EQUAL(*it3, (*it4)->getFullName()); + BOOST_CHECK_EQUAL(*it3, it4->first->getFullName()); } } @@ -109,11 +113,7 @@ std::shared_ptr returnedCert; requesttlv::decodeApplicationParameters(b, RequestType::REVOKE, returnedPub, returnedCert); - BOOST_CHECK_EQUAL(returnedPub.size(), pub.size()); - for (auto it1 = returnedPub.begin(), it2 = pub.begin(); - it1 != returnedPub.end() && it2 != pub.end(); it1++, it2++) { - BOOST_CHECK_EQUAL(*it1, *it2); - } + BOOST_TEST(returnedPub == pub, boost::test_tools::per_element()); BOOST_CHECK_EQUAL(*returnedCert, *certRequest); } @@ -135,7 +135,11 @@ BOOST_CHECK_EQUAL_COLLECTIONS(returnedId.begin(), returnedId.end(), id.begin(), id.end()); } -BOOST_AUTO_TEST_CASE(ChallengeEncoding) +class ChallengeEncodingFixture : public ClockFixture, public KeyChainFixture +{ +}; + +BOOST_FIXTURE_TEST_CASE(ChallengeEncoding, ChallengeEncodingFixture) { const uint8_t key[] = {0x23, 0x70, 0xe3, 0x20, 0xd4, 0x34, 0x42, 0x08, 0xe0, 0xff, 0x56, 0x83, 0xf2, 0x43, 0xb2, 0x13}; @@ -170,5 +174,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestProtocolEncoding -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/redirection-policy.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/redirection-policy.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/redirection-policy.t.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/redirection-policy.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -0,0 +1,79 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2017-2022, Regents of the University of California. + * + * This file is part of ndncert, a certificate management system based on NDN. + * + * ndncert is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received copies of the GNU General Public License along with + * ndncert, e.g., in COPYING.md file. If not, see . + * + * See AUTHORS.md for complete list of ndncert authors and contributors. + */ + +#include "redirection/redirection-policy.hpp" +#include "redirection/redirection-param.hpp" +#include "redirection/redirection-email.hpp" + +#include "tests/boost-test.hpp" + +namespace ndncert::tests { + +BOOST_AUTO_TEST_SUITE(TestRedirectionPolicy) + +BOOST_AUTO_TEST_CASE(RedirectionPolicyParam) +{ + RedirectionParam assignment(""); + std::multimap params; + BOOST_CHECK(assignment.isRedirecting(params)); + params.emplace("abc", "123"); + BOOST_CHECK(assignment.isRedirecting(params)); + + RedirectionParam assignment1("abc=123"); + params.clear(); + BOOST_CHECK(!assignment1.isRedirecting(params)); + params.emplace("abc", "124"); + BOOST_CHECK(!assignment1.isRedirecting(params)); + params.emplace("abc", "123"); + BOOST_CHECK(assignment1.isRedirecting(params)); + + RedirectionParam assignment2("abc=123&xyz=789"); + params.clear(); + BOOST_CHECK(!assignment2.isRedirecting(params)); + params.emplace("abc", "123"); + BOOST_CHECK(!assignment2.isRedirecting(params)); + params.emplace("xyz", "788"); + BOOST_CHECK(!assignment2.isRedirecting(params)); + params.emplace("xyz", "789"); + BOOST_CHECK(assignment2.isRedirecting(params)); + params.emplace("abz", "789"); + BOOST_CHECK(assignment2.isRedirecting(params)); +} + +BOOST_AUTO_TEST_CASE(RedirectionPolicyEmail) +{ + RedirectionEmail assignment("cs.ucla.edu"); + std::multimap params; + BOOST_CHECK(!assignment.isRedirecting(params)); + params.emplace("email", "das@math.ucla.edu"); + BOOST_CHECK(!assignment.isRedirecting(params)); + + params.clear(); + params.emplace("email", "das@cs.ucla.edu"); + BOOST_CHECK(assignment.isRedirecting(params)); + + params.clear(); + params.emplace("email", "das@ucla.edu"); + BOOST_CHECK(!assignment.isRedirecting(params)); +} + +BOOST_AUTO_TEST_SUITE_END() // TestNameAssignment + +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/requester.t.cpp ndncert-0.0.6-1-g3e4818421/tests/unit-tests/requester.t.cpp --- ndncert-0.0.5-1-gfae76c4dc/tests/unit-tests/requester.t.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/unit-tests/requester.t.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -19,22 +19,25 @@ */ #include "requester-request.hpp" +#include "ca-module.hpp" +#include "challenge/challenge-module.hpp" #include "detail/error-encoder.hpp" #include "detail/probe-encoder.hpp" -#include "challenge/challenge-module.hpp" -#include "ca-module.hpp" -#include "test-common.hpp" -namespace ndncert { -namespace tests { +#include "tests/boost-test.hpp" +#include "tests/io-key-chain-fixture.hpp" + +#include + +namespace ndncert::tests { using namespace requester; -BOOST_FIXTURE_TEST_SUITE(TestRequester, IdentityManagementTimeFixture) +BOOST_FIXTURE_TEST_SUITE(TestRequester, IoKeyChainFixture) BOOST_AUTO_TEST_CASE(GenProbeInterest) { - auto identity = addIdentity(Name("/site")); + auto identity = m_keyChain.createIdentity(Name("/site")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); @@ -57,8 +60,9 @@ BOOST_CHECK_EQUAL(readString(firstInterest->getApplicationParameters().get(tlv::ParameterValue)), "zhiyi@cs.ucla.edu"); } -BOOST_AUTO_TEST_CASE(OnProbeResponse){ - auto identity = addIdentity(Name("/site")); +BOOST_AUTO_TEST_CASE(OnProbeResponse) +{ + auto identity = m_keyChain.createIdentity(Name("/site")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); @@ -70,16 +74,20 @@ ca_profile.cert = std::make_shared(cert); std::vector availableNames; - availableNames.push_back(Name("/site1")); - availableNames.push_back(Name("/site2")); + availableNames.emplace_back("/site1"); + availableNames.emplace_back("/site2"); - ndn::util::DummyClientFace face(io, m_keyChain, {true, true}); + ndn::DummyClientFace face(m_io, m_keyChain, {true, true}); ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-5", "ca-storage-memory"); Data reply; reply.setName(Name("/site/CA/PROBE")); reply.setFreshnessPeriod(time::seconds(100)); - reply.setContent(probetlv::encodeDataContent(availableNames, 3, ca.m_config.redirection)); + { + std::vector redirectionNames; + for (const auto &i : ca.m_config.redirection) redirectionNames.push_back(i.first->getFullName()); + reply.setContent(probetlv::encodeDataContent(availableNames, 3, redirectionNames)); + } m_keyChain.sign(reply, ndn::signingByIdentity(identity)); std::vector> names; @@ -94,13 +102,13 @@ BOOST_CHECK_EQUAL(names[1].second, 3); BOOST_CHECK_EQUAL(redirects.size(), 2); - BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirects[0].getPrefix(-1)), "/ndn/site1"); - BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirects[1].getPrefix(-1)), "/ndn/site1"); + BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirects[0].getPrefix(-1)), "/ndn/edu/ucla"); + BOOST_CHECK_EQUAL(ndn::security::extractIdentityFromCertName(redirects[1].getPrefix(-1)), "/ndn/edu/ucla/cs/irl"); } BOOST_AUTO_TEST_CASE(ErrorHandling) { - auto identity = addIdentity(Name("/site")); + auto identity = m_keyChain.createIdentity(Name("/site")); auto key = identity.getDefaultKey(); auto cert = key.getDefaultCertificate(); @@ -124,5 +132,4 @@ BOOST_AUTO_TEST_SUITE_END() // TestRequester -} // namespace tests -} // namespace ndncert +} // namespace ndncert::tests diff -Nru ndncert-0.0.5-1-gfae76c4dc/tests/wscript ndncert-0.0.6-1-g3e4818421/tests/wscript --- ndncert-0.0.5-1-gfae76c4dc/tests/wscript 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tests/wscript 2023-11-17 18:39:05.000000000 +0000 @@ -1,16 +1,14 @@ # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + top = '..' def build(bld): - if not bld.env.WITH_TESTS: - return - - tmpdir = 'TMP_TESTS_PATH="%s"' % bld.bldnode.make_node('tmp-files') + tmpdir = 'UNIT_TESTS_TMPDIR="%s"' % bld.bldnode.make_node('tmp-files') bld.program( - target='../unit-tests', + target=f'{top}/unit-tests', name='unit-tests', source=bld.path.ant_glob(['*.cpp', 'unit-tests/**/*.cpp']), - use='ndn-cert', - includes='.', + use='BOOST_TESTS libndn-cert', defines=[tmpdir], + includes=top, install_path=None) diff -Nru ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-ca-server.cpp ndncert-0.0.6-1-g3e4818421/tools/ndncert-ca-server.cpp --- ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-ca-server.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tools/ndncert-ca-server.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -20,42 +20,39 @@ #include "ca-module.hpp" -#include #include +#include #include #include #include -#include + #include #include +#include #include #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { static ndn::Face face; static ndn::KeyChain keyChain; static std::string repoHost = "localhost"; static std::string repoPort = "7376"; -const size_t MAX_CACHED_CERT_NUM = 100; +constexpr size_t MAX_CACHED_CERT_NUM = 100; static bool -writeDataToRepo(const Data& data) { +writeDataToRepo(const Data& data) +{ boost::asio::ip::tcp::iostream requestStream; -#if BOOST_VERSION >= 106600 - requestStream.expires_after(std::chrono::seconds(3)); -#else - requestStream.expires_from_now(boost::posix_time::seconds(3)); -#endif //BOOST_VERSION >= 106600 + requestStream.expires_after(std::chrono::seconds(5)); requestStream.connect(repoHost, repoPort); if (!requestStream) { std::cerr << "ERROR: Cannot publish the certificate to repo-ng" << " (" << requestStream.error().message() << ")" << std::endl; return false; } - requestStream.write(reinterpret_cast(data.wireEncode().wire()), + requestStream.write(reinterpret_cast(data.wireEncode().data()), data.wireEncode().size()); return true; } @@ -75,14 +72,14 @@ std::cerr << signalName; } std::cerr << std::endl; - face.getIoService().stop(); + face.getIoContext().stop(); exit(1); } static int main(int argc, char* argv[]) { - boost::asio::signal_set terminateSignals(face.getIoService()); + boost::asio::signal_set terminateSignals(face.getIoContext()); terminateSignals.add(SIGINT); terminateSignals.add(SIGTERM); terminateSignals.async_wait(handleSignal); @@ -155,9 +152,6 @@ return; } } - }, - [](const Name&, const std::string& errorInfo) { - std::cerr << "ERROR: " << errorInfo << std::endl; }); } @@ -165,8 +159,7 @@ return 0; } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca int main(int argc, char* argv[]) diff -Nru ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-ca-status.cpp ndncert-0.0.6-1-g3e4818421/tools/ndncert-ca-status.cpp --- ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-ca-status.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tools/ndncert-ca-status.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2022, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -27,8 +27,7 @@ #include -namespace ndncert { -namespace ca { +namespace ndncert::ca { static int main(int argc, char* argv[]) @@ -74,8 +73,7 @@ return 0; } -} // namespace ca -} // namespace ndncert +} // namespace ndncert::ca int main(int argc, char* argv[]) diff -Nru ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-client.cpp ndncert-0.0.6-1-g3e4818421/tools/ndncert-client.cpp --- ndncert-0.0.5-1-gfae76c4dc/tools/ndncert-client.cpp 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tools/ndncert-client.cpp 2023-11-17 18:39:05.000000000 +0000 @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2017-2021, Regents of the University of California. + * Copyright (c) 2017-2023, Regents of the University of California. * * This file is part of ndncert, a certificate management system based on NDN. * @@ -31,11 +31,10 @@ #include -namespace ndncert { -namespace requester { +namespace ndncert::requester { static void -selectCaProfile(std::string configFilePath); +selectCaProfile(const std::string& configFilePath); static void runProbe(CaProfile profile); @@ -47,9 +46,14 @@ runChallenge(const std::string& challengeType); static size_t nStep = 1; +static const std::string defaultChallenge = "email"; static ndn::Face face; static ndn::KeyChain keyChain; static std::shared_ptr requesterState; +static std::shared_ptr> capturedProbeParams; +static std::shared_ptr trustedCert; +static Name newlyCreatedIdentityName; +static Name newlyCreatedKeyName; static void captureParams(std::multimap& requirement) @@ -79,15 +83,16 @@ } static int -captureValidityPeriod() +captureValidityPeriod(time::hours maxValidityPeriod) { std::cerr << "\n***************************************\n" << "Step " << nStep++ << ": Please type in your expected validity period of your certificate." << " Type the number of hours (168 for week, 730 for month, 8760 for year)." - << " The CA may reject your application if your expected period is too long." << std::endl; + << " The CA may reject your application if your expected period is too long." + << " The maximum validity period allowed by this CA is " << maxValidityPeriod << "."<< std::endl; size_t count = 0; - while (true && count < 3) { + while (count < 3) { std::string periodStr = ""; getline(std::cin, periodStr); try { @@ -102,16 +107,71 @@ exit(1); } -static void +static Name +captureKeyName(ndn::security::pib::Identity& identity, ndn::security::pib::Key& defaultKey) +{ + size_t count = 0; + std::cerr << "***************************************\n" + << "Step " << nStep++ << ": KEY SELECTION" << std::endl; + for (const auto& key : identity.getKeys()) { + std::cerr << "> Index: " << count++ << std::endl + << ">> Key Name:"; + if (key == defaultKey) { + std::cerr << " +->* "; + } + else { + std::cerr << " +-> "; + } + std::cerr << key.getName() << std::endl; + } + + std::cerr << "Please type in the key's index that you want to certify or type in NEW if you want to certify a new key:\n"; + std::string indexStr = ""; + std::string indexStrLower = ""; + size_t keyIndex; + getline(std::cin, indexStr); + + indexStrLower = indexStr; + boost::algorithm::to_lower(indexStrLower); + if (indexStrLower == "new") { + auto newlyCreatedKey = keyChain.createKey(identity); + newlyCreatedKeyName = newlyCreatedKey.getName(); + std::cerr << "New key generated: " << newlyCreatedKeyName << std::endl; + return newlyCreatedKeyName; + } + else { + try { + keyIndex = std::stoul(indexStr); + } + catch (const std::exception&) { + std::cerr << "Your input is neither NEW nor a valid index. Exit" << std::endl; + exit(1); + } + + if (keyIndex >= count) { + std::cerr << "Your input is not an existing index. Exit" << std::endl; + exit(1); + } + else { + auto itemIterator = identity.getKeys().begin(); + std::advance(itemIterator, keyIndex); + auto targetKeyItem = *itemIterator; + return targetKeyItem.getName(); + } + } +} + +[[noreturn]] static void onNackCb() { std::cerr << "Got NACK\n"; + exit(1); } static void timeoutCb() { - std::cerr << "Interest sent time out\n"; + std::cerr << "Interest timeout\n"; } static void @@ -123,10 +183,10 @@ } std::cerr << "\n***************************************\n" << "Step " << nStep++ - << ": DONE\nCertificate with Name: " << reply.getName().toUri() - << "has already been installed to your local keychain" << std::endl - << "Exit now"; - face.getIoService().stop(); + << ": DONE\nCertificate with Name: " << reply.getName() + << " has been installed to your local keychain\n" + << "Exit now" << std::endl; + face.getIoContext().stop(); } static void @@ -163,21 +223,27 @@ } size_t challengeIndex = 0; - if (challengeList.size() < 1) { + if (challengeList.empty()) { std::cerr << "There is no available challenge provided by the CA. Exit" << std::endl; exit(1); } - else if (challengeList.size() >= 1) { + + auto item = std::find(challengeList.begin(), challengeList.end(), defaultChallenge); + if (item != challengeList.end()) { + runChallenge(defaultChallenge); + } + else { + // default challenge not available std::cerr << "\n***************************************\n" << "Step " << nStep++ << ": CHALLENGE SELECTION" << std::endl; size_t count = 0; std::string choice = ""; - for (auto item : challengeList) { + for (const auto& item : challengeList) { std::cerr << "> Index: " << count++ << std::endl - << ">> Challenge:" << item << std::endl; + << ">> Challenge: " << item << std::endl; } - std::cerr << "Please type in the challenge index that you want to perform:" << std::endl; + std::cerr << "Please type in the index of the challenge that you want to perform:" << std::endl; size_t inputCount = 0; while (inputCount < 3) { getline(std::cin, choice); @@ -200,19 +266,23 @@ std::cerr << "Invalid input for too many times, exit. " << std::endl; exit(1); } + + auto it = challengeList.begin(); + std::advance(it, challengeIndex); + std::cerr << "The challenge has been selected: " << *it << std::endl; + runChallenge(*it); } - auto it = challengeList.begin(); - std::advance(it, challengeIndex); - std::cerr << "The challenge has been selected: " << *it << std::endl; - runChallenge(*it); } static void infoCb(const Data& reply, const Name& certFullName) { - optional profile; + std::optional profile; try { if (certFullName.empty()) { + if (trustedCert && !ndn::security::verifySignature(reply, *trustedCert)) { + NDN_THROW(std::runtime_error("Cannot verify replied Data packet signature.")); + } profile = Request::onCaProfileResponse(reply); } else { @@ -223,25 +293,31 @@ std::cerr << "The fetched CA information cannot be used because: " << e.what() << std::endl; return; } - std::cerr << "\n***************************************\n" - << "Step " << nStep++ - << ": Will use a new trust anchor, please double check the identity info:" << std::endl - << "> New CA name: " << profile->caPrefix.toUri() << std::endl - << "> This trust anchor information is signed by: " << reply.getSignatureInfo().getKeyLocator() << std::endl - << "> The certificate: " << *profile->cert << std::endl - << "Do you trust the information? Type in YES or NO" << std::endl; - - std::string answer; - getline(std::cin, answer); - boost::algorithm::to_lower(answer); - if (answer == "yes") { - std::cerr << "You answered YES: new CA " << profile->caPrefix.toUri() << " will be used" << std::endl; - runProbe(*profile); - // client.getClientConf().save(std::string(SYSCONFDIR) + "/ndncert/client.conf"); + if (!trustedCert) + { + std::cerr << "\n***************************************\n" + << "Step " << nStep++ + << ": Will use the following CA, please double check the identity info:" << std::endl + << "> CA name: " << profile->caPrefix << std::endl + << "> This CA information is signed by: " << reply.getSignatureInfo().getKeyLocator() << std::endl + << "> The certificate:" << std::endl << *profile->cert << std::endl + << "Do you trust the information? Type in YES or NO" << std::endl; + + std::string answer; + getline(std::cin, answer); + boost::algorithm::to_lower(answer); + if (answer == "yes") { + std::cerr << "You answered YES: new CA " << profile->caPrefix << " will be used" << std::endl; + trustedCert = profile->cert; + runProbe(*profile); + } + else { + std::cerr << "You answered NO: new CA " << profile->caPrefix << " will not be used" << std::endl; + exit(0); + } } else { - std::cerr << "You answered NO: new CA " << profile->caPrefix.toUri() << " will not be used" << std::endl; - exit(0); + runProbe(*profile); } } @@ -250,78 +326,103 @@ { std::vector> names; std::vector redirects; - Request::onProbeResponse(reply, profile, names, redirects); - size_t count = 0; - std::cerr << "\n***************************************\n" - << "Step " << nStep++ - << ": You can either select one of the following names suggested by the CA: " << std::endl; - for (const auto& name : names) { - std::cerr << "> Index: " << count++ << std::endl - << ">> Suggested name: " << name.first.toUri() << std::endl - << ">> Corresponding Max sufiix length: " << name.second << std::endl; - } - std::cerr << "\nOr choose another trusted CA suggested by the CA: " << std::endl; - for (const auto& redirect : redirects) { - std::cerr << "> Index: " << count++ << std::endl - << ">> Suggested CA: " << ndn::security::extractIdentityFromCertName(redirect.getPrefix(-1)) - << std::endl; - } - std::cerr << "Please type in the index of your choice:" << std::endl; - size_t index = 0; try { - std::string input; - getline(std::cin, input); - index = std::stoul(input); + Request::onProbeResponse(reply, profile, names, redirects); } - catch (const std::exception&) { - std::cerr << "Your input is Invalid. Exit" << std::endl; - exit(1); - } - if (index >= names.size() + redirects.size()) { - std::cerr << "Your input is not an existing index. Exit" << std::endl; + catch (const std::exception& e) { + std::cerr << "The probed CA response cannot be used because: " << e.what() << std::endl; exit(1); } - if (index < names.size()) { - //names - auto selectedName = names[index].first; - std::cerr << "You selected name: " << selectedName.toUri() << std::endl; - std::cerr << "Enter Suffix if you would like one (Enter to skip): "; - try { - std::string input; - getline(std::cin, input); - auto inputName = Name(input); - if (!inputName.empty()) { - selectedName.append(inputName); - std::cerr << "You are applying name: " << selectedName.toUri() << std::endl; - } + + size_t count = 0; + Name selectedName; + Name redirectedCaFullName; + // always prefer redirection over direct assignment + if (!redirects.empty()) { + if (redirects.size() < 2) { + redirectedCaFullName = redirects.front(); } - catch (const std::exception&) { - std::cerr << "Your input is Invalid. Exit" << std::endl; - exit(1); + else { + std::cerr << "\n***************************************\n" + << "Step " << nStep++ + << " Choose another trusted CA suggested by the CA: " << std::endl; + for (const auto& redirect : redirects) { + std::cerr << "> Index: " << count++ << std::endl + << ">> Suggested CA: " << ndn::security::extractIdentityFromCertName(redirect.getPrefix(-1)) + << std::endl; + } + std::cerr << "Please type in the index of your choice:" << std::endl; + size_t index = 0; + try { + std::string input; + getline(std::cin, input); + index = std::stoul(input); + } + catch (const std::exception&) { + std::cerr << "Your input is Invalid. Exit" << std::endl; + exit(1); + } + if (index >= redirects.size()) { + std::cerr << "Your input is not an existing index. Exit" << std::endl; + exit(1); + } + redirectedCaFullName = redirects[index]; } - runNew(profile, selectedName); - } - else { - //redirects - auto redirectedCaFullName = redirects[index - names.size()]; auto redirectedCaName = ndn::security::extractIdentityFromCertName(redirectedCaFullName.getPrefix(-1)); - std::cerr << "You selected to be redirected to CA: " << redirectedCaName.toUri() << std::endl; + std::cerr << "You will be redirected to CA: " << redirectedCaName << std::endl; face.expressInterest( *Request::genCaProfileDiscoveryInterest(redirectedCaName), - [&] (const auto&, const auto& data) { + [&, redirectedCaFullName] (const auto&, const auto& data) { auto fetchingInterest = Request::genCaProfileInterestFromDiscoveryResponse(data); face.expressInterest(*fetchingInterest, - [=] (const auto&, const auto& data2) { infoCb(data2, redirectedCaFullName); }, - [] (auto&&...) { onNackCb(); }, - [] (auto&&...) { timeoutCb(); }); + [=] (const auto&, const auto& data2) { infoCb(data2, redirectedCaFullName); }, + [] (auto&&...) { onNackCb(); }, + [] (auto&&...) { timeoutCb(); }); }, [] (auto&&...) { onNackCb(); }, [] (auto&&...) { timeoutCb(); }); } + else if (!names.empty()) { + if (names.size() < 2) { + selectedName = names.front().first; + } + else { + std::cerr << "\n***************************************\n" + << "Step " << nStep++ + << ": You can either select one of the following names suggested by the CA: " << std::endl; + for (const auto& name : names) { + std::cerr << "> Index: " << count++ << std::endl + << ">> Suggested name: " << name.first << std::endl + << ">> Corresponding max suffix length: " << name.second << std::endl; + } + std::cerr << "Please type in the index of your choice:" << std::endl; + size_t index = 0; + try { + std::string input; + getline(std::cin, input); + index = std::stoul(input); + } + catch (const std::exception&) { + std::cerr << "Your input is invalid. Exit" << std::endl; + exit(1); + } + if (index >= names.size()) { + std::cerr << "Your input is not an existing index. Exit" << std::endl; + exit(1); + } + selectedName = names[index].first; + } + std::cerr << "You are applying for name: " << selectedName << std::endl; + runNew(profile, selectedName); + } + else { + std::cerr << "Neither name assignment nor redirection is available, exit." << std::endl; + exit(1); + } } static void -selectCaProfile(std::string configFilePath) +selectCaProfile(const std::string& configFilePath) { ProfileStorage profileStorage; try { @@ -334,7 +435,7 @@ size_t count = 0; std::cerr << "***************************************\n" << "Step " << nStep++ << ": CA SELECTION" << std::endl; - for (auto item : profileStorage.getKnownProfiles()) { + for (const auto& item : profileStorage.getKnownProfiles()) { std::cerr << "> Index: " << count++ << std::endl << ">> CA prefix:" << item.caPrefix << std::endl << ">> Introduction: " << item.caInfo << std::endl; @@ -345,22 +446,19 @@ getline(std::cin, caIndexS); caIndexSLower = caIndexS; boost::algorithm::to_lower(caIndexSLower); + Name selectedCaName; if (caIndexSLower == "none") { std::cerr << "\n***************************************\n" << "Step " << nStep << ": ADD NEW CA\nPlease type in the CA's Name:" << std::endl; - std::string expectedCAName; - getline(std::cin, expectedCAName); - face.expressInterest( - *Request::genCaProfileDiscoveryInterest(Name(expectedCAName)), - [&] (const auto&, const auto& data) { - auto fetchingInterest = Request::genCaProfileInterestFromDiscoveryResponse(data); - face.expressInterest(*fetchingInterest, - [] (const auto&, const auto& data2) { infoCb(data2, {}); }, - [] (auto&&...) { onNackCb(); }, - [] (auto&&...) { timeoutCb(); }); - }, - [] (auto&&...) { onNackCb(); }, - [] (auto&&...) { timeoutCb(); }); + std::string targetCaName; + getline(std::cin, targetCaName); + try { + selectedCaName = Name(targetCaName); + } + catch (const std::exception&) { + std::cerr << "Your input is not a valid name. Exit" << std::endl; + exit(1); + } } else { size_t caIndex; @@ -369,75 +467,85 @@ } catch (const std::exception&) { std::cerr << "Your input is neither NONE nor a valid index. Exit" << std::endl; - return; + exit(1); } if (caIndex >= count) { std::cerr << "Your input is not an existing index. Exit" << std::endl; - return; + exit(1); } auto itemIterator = profileStorage.getKnownProfiles().cbegin(); std::advance(itemIterator, caIndex); auto targetCaItem = *itemIterator; - runProbe(targetCaItem); + trustedCert = targetCaItem.cert; + selectedCaName = targetCaItem.caPrefix; } + face.expressInterest( + *Request::genCaProfileDiscoveryInterest(selectedCaName), + [&] (const auto&, const auto& data) { + auto fetchingInterest = Request::genCaProfileInterestFromDiscoveryResponse(data); + face.expressInterest(*fetchingInterest, + [] (const auto&, const auto& data2) { infoCb(data2, {}); }, + [] (auto&&...) { onNackCb(); }, + [] (auto&&...) { timeoutCb(); }); + }, + [] (auto&&...) { onNackCb(); }, + [] (auto&&...) { timeoutCb(); }); } static void runProbe(CaProfile profile) { - std::cerr << "\n***************************************\n" - << "Step " << nStep++ - << ": Do you know your identity name to be certified by CA " - << profile.caPrefix.toUri() - << " already? Type in YES or NO" << std::endl; - bool validAnswer = false; - size_t count = 0; - while (!validAnswer && count < 3) { - std::string answer; - getline(std::cin, answer); - boost::algorithm::to_lower(answer); - if (answer == "yes") { - validAnswer = true; - std::cerr << "You answered YES" << std::endl; - std::cerr << "\n***************************************\n" - << "Step " << nStep++ - << ": Please type in the full identity name you want to get (with CA prefix " - << profile.caPrefix.toUri() - << "):" << std::endl; - std::string identityNameStr; - getline(std::cin, identityNameStr); - runNew(profile, Name(identityNameStr)); - } - else if (answer == "no") { - validAnswer = true; - std::cerr << "You answered NO" << std::endl; - std::cerr << "\n***************************************\n" - << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl; - auto capturedParams = captureParams(profile.probeParameterKeys); - face.expressInterest(*Request::genProbeInterest(profile, std::move(capturedParams)), - [profile] (const auto&, const auto& data) { probeCb(data, profile); }, - [] (auto&&...) { onNackCb(); }, - [] (auto&&...) { timeoutCb(); }); - } - else { - std::cerr << "Invalid answer. Type in YES or NO" << std::endl; - count++; - } - } - if (count == 3) { - std::cerr << "Invalid input for too many times, exit. " << std::endl; - exit(1); - } + if (!capturedProbeParams) { + std::cerr << "\n***************************************\n" + << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl; + auto captured = captureParams(profile.probeParameterKeys); + auto it = captured.find(defaultChallenge); + if (it != captured.end()) { + it->second = boost::algorithm::to_lower_copy(it->second); + } + capturedProbeParams = std::make_shared>(captured); + } + face.expressInterest(*Request::genProbeInterest(profile, std::move(*capturedProbeParams)), + [profile] (const auto&, const auto& data) { probeCb(data, profile); }, + [] (auto&&...) { onNackCb(); }, + [] (auto&&...) { timeoutCb(); }); } static void runNew(CaProfile profile, Name identityName) { - int validityPeriod = captureValidityPeriod(); + int validityPeriod = captureValidityPeriod(time::duration_cast(profile.maxValidityPeriod)); auto now = time::system_clock::now(); std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl; requesterState = std::make_shared(keyChain, profile, RequestType::NEW); - auto interest = requesterState->genNewInterest(identityName, now, now + time::hours(validityPeriod)); + + // generate a newly key pair or choose an existing key + const auto& pib = keyChain.getPib(); + ndn::security::pib::Identity identity; + ndn::security::pib::Key defaultKey; + try { + identity = pib.getIdentity(identityName); + } + catch (const ndn::security::Pib::Error&) { + identity = keyChain.createIdentity(identityName); + newlyCreatedIdentityName = identity.getName(); + } + try { + defaultKey = identity.getDefaultKey(); + } + catch (const ndn::security::Pib::Error&) { + defaultKey = keyChain.createKey(identity); + newlyCreatedKeyName = defaultKey.getName(); + } + + Name keyName; + if (newlyCreatedIdentityName.empty()) { + keyName = captureKeyName(identity, defaultKey); + } + else { + keyName = defaultKey.getName(); + } + auto interest = requesterState->genNewInterest(keyName, now, now + time::hours(validityPeriod)); if (interest != nullptr) { face.expressInterest(*interest, [] (const auto&, const auto& data) { newCb(data); }, @@ -461,11 +569,16 @@ << "\nExit." << std::endl; exit(1); } - if (requirement.size() > 0) { - std::cerr << "\n***************************************\n" - << "Step " << nStep - << ": Please provide parameters used for Identity Verification Challenge" << std::endl; - captureParams(requirement); + if (!requirement.empty()) { + if (requesterState->m_status == Status::BEFORE_CHALLENGE && challengeType == defaultChallenge) { + requirement.find(challengeType)->second = capturedProbeParams->find(defaultChallenge)->second; + } + else { + std::cerr << "\n***************************************\n" + << "Step " << nStep + << ": Please provide parameters used for Identity Verification Challenge" << std::endl; + captureParams(requirement); + } } face.expressInterest(*requesterState->genChallengeInterest(std::move(requirement)), [] (const auto&, const auto& data) { challengeCb(data); }, @@ -489,16 +602,23 @@ } std::cerr << std::endl; if (requesterState) { - requesterState->endSession(); + if (!newlyCreatedIdentityName.empty()) { + auto identity = keyChain.getPib().getIdentity(newlyCreatedIdentityName); + keyChain.deleteIdentity(identity); + } + else if (!newlyCreatedKeyName.empty()) { + auto identity = keyChain.getPib().getIdentity(ndn::security::extractIdentityFromKeyName(newlyCreatedKeyName)); + keyChain.deleteKey(identity, identity.getKey(newlyCreatedKeyName)); + } } - face.getIoService().stop(); + face.getIoContext().stop(); exit(1); } static int main(int argc, char* argv[]) { - boost::asio::signal_set terminateSignals(face.getIoService()); + boost::asio::signal_set terminateSignals(face.getIoContext()); terminateSignals.add(SIGINT); terminateSignals.add(SIGTERM); terminateSignals.async_wait(handleSignal); @@ -532,8 +652,7 @@ return 0; } -} // namespace requester -} // namespace ndncert +} // namespace ndncert::requester int main(int argc, char* argv[]) diff -Nru ndncert-0.0.5-1-gfae76c4dc/tools/wscript ndncert-0.0.6-1-g3e4818421/tools/wscript --- ndncert-0.0.5-1-gfae76c4dc/tools/wscript 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/tools/wscript 2023-11-17 18:39:05.000000000 +0000 @@ -5,18 +5,18 @@ def build(bld): bld.program( name='ndncert-client', - target='../bin/ndncert-client', + target=f'{top}/bin/ndncert-client', source='ndncert-client.cpp', - use='ndn-cert') + use='BOOST_TOOLS libndn-cert') bld.program( name='ndncert-ca-server', - target='../bin/ndncert-ca-server', + target=f'{top}/bin/ndncert-ca-server', source='ndncert-ca-server.cpp', - use='ndn-cert') + use='BOOST_TOOLS libndn-cert') bld.program( name='ndncert-ca-status', - target='../bin/ndncert-ca-status', + target=f'{top}/bin/ndncert-ca-status', source='ndncert-ca-status.cpp', - use='ndn-cert') + use='BOOST_TOOLS libndn-cert') diff -Nru ndncert-0.0.5-1-gfae76c4dc/waf ndncert-0.0.6-1-g3e4818421/waf --- ndncert-0.0.5-1-gfae76c4dc/waf 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/waf 2023-11-17 18:39:05.000000000 +0000 @@ -32,13 +32,13 @@ import os, sys, inspect -VERSION="2.0.23" -REVISION="7dbdd0b348178777c338c9a31f6218a5" -GIT="c0d5ac00f8315586cdcb227cef500329f00aaa44" +VERSION="2.0.26" +REVISION="bbbe549153f90c006795714355b81761" +GIT="ad7b733fc60852f77eff200b79e8b6f9562494d2" INSTALL='' -C1='#2' -C2='#1' -C3='#-' +C1='#-' +C2='#*' +C3='#%' cwd = os.getcwd() join = os.path.join @@ -168,6 +168,6 @@ Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY1a&#-Pe(,0M0b<{eЀ#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-:KVFv#2J6gۻt6[6MmF;}]]kZۺO[{ ٳn{nhݷeR=8v{2͹kCM|:[6opȾ#-Oc@QӀ#(ݝ`m뽤t}5i#-uݻG6{`)zSJ#-@E*R9#2$#-P:4UZz{Gzٽޙ^nɘjlZeJkGO#-۽e[޸g]wqRIom{{Oewe__=^= z:0%hYZk'v{[^{n`СxѬ6ZdTS#-( ̗USx#1eS{kqQ}k>3҉#-#-֖muԺ}{;]ym=}/zܡ}|+/ʹuqק oq+Qml[v-s]mӻ̓/{˻{n^c}juí:>aOѭňzioq۷Sk{=}foq1Wl^펯0>ܞO#-}]}ץx$#2H\Lk7kC.BA1:UޘnW]=]-#Bf#-#-wyD#-#-/zr;콙#6}{jwwFn;H{zg}wRSit0}3oD3޽{׺k|[DfzQr]]<}޲wa6{s^uqYăjVP{:we]׷^ xWeWtj㷛ӹzT#-4΢cm{w:8;\;x§uy͌ġ}^ztgЧ[vݵziΏ N{yw|#-9fյ#-וyq7ؕ#2ZHVݜۜ{4mL4U;۝Nꓥ×#13Xӽ1{Zo^;=Wޯ{ϻo F7>^ƻuy7;wwۏO㼣M_-]M&@#-@&FɣC@M?4Ҁ OSA䙈 M@#- LC$d#-#-#-#-#- bh2i15)*yOҞ)jڙ#-#-#-#-#-#- =R!h&bz{CJzzhCC@#-Gh#-#-#-#-#-"HBh#-@Fi1M=4#1#-#-4#-21#-Q@@#- zT)eO)Pd#-@#-#-U/5+1$ |jݦ&FLF(T$Gb#-wt+@3S+2g0.o#ciw/Oj! R0&d)dxʧ+.V0vʱ5>#xJ@ZxXDF串p ``#2Z+mb6mlUI E:N$F$rX 튔EKAPU 2(HS2d,fjf"h"m3Pc RiE6l(LA(jj5Fmi"4KD!HJh$#-5,h`i*m"dZ)e[D BHlLFfc5Pd#1)E %a-#Jmlm6VѦbK3!4D2ɶm4ԔlZl͔&b(*"iE2(6B#hhBI*"#2b6!PH,@2#$!X!fI# )ee3 i,E,ɱ,e!#FҚ`RdQI&&$%D4d$X&e- f Q͈&#ZMIea"IIX*!$J4H` !F(L3cFRjHb iM$"H[b2F̒3bdE(͑&U4Đ)HlX $iJd%Ȧb l XbjIeAF4)&beMY)2&iQ0IAF&R 1`dXLPF2fe-DLjE1#2Dfƅ#2TE"$$cc*iňHRf`Rͦ؈Djd2#2CDR4EM,ԩJJh6FLdȦY#)!H,UhM)i5Dc`LF4*4"mBi2E"d,ȂSd1J"L66X K&2* XR$cc1 2RXYJ&EM(ZhII$2ɲ#)E,ѥ15e"$4J5S4e214fDEh5),f)4,QiHQM(B4mtBT" 1Qح)M5-!j6MFDai2F`%Iȃ134d1))j6d ŋ S-,ɐ&TlHSe,RS#2lfjdEledJe6URd[dh Me-!TbCQFJVMdjEHkD$mbj6hZLQLM14-4m15[$XDRֲC!2k&*F$"i$Vԫ6)MRT,+SHҩdLbВMe#1MeebTi"BƈaL D#1 BѪ QC&M&M%DeE6RA"f%#2J6i i#6I#cd)SL#21#11$d#!4LQJdRP5BT4 JBj-"ͨQ)$ hRE0MIXE,-4) Ě4Yda, jE54ECF)6b)-F#!&@4ؒ2)6I RaId&1M(idF3D $(MF&LBM%$$EEbљl4i$Сd(6(Vid3(b$lD[FjQ&,( RjA B1R6JiB4ILjb2EJ*cI"ɢ)BԢ̔ISbQ*F*dlY5FŚQl+֓"[&*IL)Qa`(4Im$50A2`BJl͉*ed244QAQDjJŲX1h& "4ZYhj$Ze(Ml!j6Ƥ""R#l56*ŴjJUZ&mH5Q*TҬmFIQ f-F+iQQJVf*I52VœlѣXRcFѫmՊDʅDĔF*HhѱM$ژc$FضŦj1eRɶ jM5bh(ё#1Rjo;vlQTI$Im,l{OD&k7)*miE̴_V/JFgsm^v77^nu>m1E'VE7ڳK9a,K#2I$mA??^dg˯'mɌXH_74Ju8gBj?evjŧlNI-I勲rrX?l٩8QhZ!II #dMɍl~47wHLo\үRYA&HYeS\RJH#Mt#-c38#2Pܲ3znhlAL^,0qز w }ۙ4Yt\ɯ&)-)>dzo_VkA;][E0›Bd咬4Dϗ11؞j%?͹r,NN["M+{Ϟ͗7bRSo״1#- ֭l6w^lsES+t^;llOu6Pk+-ȦWȶu.nmBd<|s>6Lf=4vh_;k{۔Ft##1 HzeaۮhMu;GJ̔et9̏[oWt1iu#^sqrjł)G֓Ohƕiu=%MOM,X#2*C#-nҚ )p3ƾ@:?- 6rfLuBY[dl집^~+(2)iD1pq-k#œG'V65ף6&/z3(fV9=;PW}|YH)ɨ>~s=ۻ[&$EQHNBʝɺgFѣBRYm6GƂJ:[2ŔҠwKg~x\/]h٘IP#M+e#2i*Q]Tƈb1A9G,t>!klJ0TTMtehUbZLqW͕7e]iBTTQWL͑dDiGDPaLֶjϽ$![QJ+aCP;] I+^e5Z A~:F;KH~|>8499q#11K0܆%[ ;~㎙L*"sMՆS_mGSN^<=98Q94}Jr;}pu0!it?Xף]|r_4ꅙw`ҟfIC?,Y VԀpBy=-%mEa)3eF^,z*G p:a#[C./̗0t"m#1h5v*F]ǁzU2>l<p"G]dmmLZ$:#cG4Hi:#+PF:E&}ٞvC}X#1 `8FW_fSr#10(Nv3L~Y=g b<R:I2\q.{ϩ,8`=jP4ovpoB9dkrmǨEƽ]nC=6|Z5aSו)FswEw+^{5ZU|#2)˾cT @^ǡ*L yZۍo/[1É ̋e>ؾi[w25^/!*n4WOٿ.RYTNLYP:ZPPNm(7+]}}]Mi Ja>еTE”Tϕr~|Tj+aGR|(ES>lv߿[InC_Y>sչY$OknFx*F5e!ުy8O6S!jz@J[FSJ.]{#m* ҊV'؝j'6ʴOb\Rv/B>?\ Aq#-˨#҆ `098I /NP4QHXE':r-|74c>ގ7DYTN{NБ(<צk8S;?[u2 y=skCjJb)ج7îpv^/FZև|\TGX^rEԒμ/H"")C m쪤<ݥbRhrHطP*#1ͮS">0ȊŊTQΥ DFKύ=!tCDh,I YV˂¤X#14oW,#-@% h#󹚲^!)'.:o1>>!??7tWwrrsY,-|'U㯢;eFÃN=:$ Чej#ܯ1/q [kŢ;CT*D,L}uT;ng z%G6"IDo]9M;aA8Hcr#16 9܊SRϦ H+67ĶSb!ܭvჿRf(PS.O}y^=uv|1G] #1@yFluƜXȘZ1ƲCݵYxh֣DhTTNpCN% 9D9өuꢇmuVFzU/76=,cy^\`˳K|q+wpuI#Z=sazFK4CџF7v:ߒ_Ť\鵚_'eͿ|CidBn##lJ#2sR4ř"{r6uTG(4X Te; 1$V@dR#DT1Ԁ:9R(,4č3=f].ylcV~хb?g3d[ EX)c$E kc|dbrKNzXs3#2 +&j|#2xE\dymqKzvud#2/x@lj9ݶC<#1ӂf0⺒c ^];8ŋqVBnjuJf*D94̰+5OLI ,ڪn7'3#6Hqox6uga>~#1Usf]|У$IDmyȹև/-"QD@zmG1as=<'>(f7@adyQk#-EDL <Q9k-]=wZe"35%#2U2ߑh W5v물,Q#e|gPꔰbI$i#2't̅6#-'n_ON%w5йB6/Kٓ`B"6Ư /#dIzW% in-b;{#eJcfέVgWT$3Yc0kn)VnԬu3M%.l)kZC$3T^fn5#1sp{k\(ۭNl;S)rj̮?m/#1o1o8e:)do6hжFAxkh[hܦo6LlT'8EikGlبX?OL)sد*3ʝ/ykSq(ŨAHoh=KRoF.#Pd{pH.]Ũs÷#2]75hΕD.{%=W/F(5#;D9_y%0Ij[J3NMT9OxcHIeSy֚]"Vm\~[ >@uޯ-6h]d~Mr'߸X(׬(dogÇhiA^3lJYko첊LtZ(a85#1*!= !Ԫ#1ͪÃIΉ^aمXIȅOnd!\9N3t>{lxA(j=#2\ڶӑK48F@h#2fY5Zj֙c^bSߴqۋȤ&X HE^l-kUeľ6N{Ym{i(R)1P^ۖ\3kNJNbDghjw嶔: omr-R P$Qky qFٹL~{Ϫ|_,H}}:yo:&8~rPhʗ̰mߧ*}$Fʁ/<}ܐfH.'Uw|[>M=Æ{y$*j .##-aI3p#18h!ш 0#1f2wZpiWf,`R;/ G1h}4s2eҝ<`u^#1XNźiDbyg(t_iE-y.F/.=:I^NǤҜ4j~ou?vhĜU4KD[鷏e +#-4IC0`| +MɿF㧽jCIε{Oz4z\.TVwG8Ld*#-Kp##xMD|w"#1ŻP 4#-y՘~Pc4uXHqdn4ÓU?ׯo?h8싐H[\(( z :NUwSMH=rH.qQq|"J)?0բɤ[Mw4~^ @̢nҝ_fN`OQ ! ğL@ a#! #-$'oVw卣`vܻ #1cc 2#-@?ӌ#2Lo#-C"JHu(&.wU DR}U7##- TpTX O <1DGP気Wll-(#-VOqPo#2kC, ]%Þ,F9h}\}i }t|kRjcc;4Rf0SM>U\t)3/驟uOOc슻`0l$iwPk mK/2eOsrBd:[3#1qr,GG 7ϣP}x ^#7ئcѓ;Krvdjbg_zupmuQFP\$55AXBY9E\Fb>bi#1 AQݏ"s !!XM%FB(qF!@Yc=ƒ EyzyK8&di%#1bGn~LbJQ;f3t#-X1OF\(7)ɚ{O䭪@=W,]b"8#2Թ{  ^6kp[g* f=>Wi n|&ҕ2ԧ-`y=F5q{m ~e>{A4׮[H8ۨ9CK y'L&)#2#1˩kU9-ʯ((^Sq#|w m=+BE l<5#2Žib[#85S 4#2`#1~H5ٝ>:{?##-1s,9`Z'Ƀ2"ooNiƲ†17ƄNIwSnqMLYj0;Ƚ}xHmmy'Ujue;30Msmp>i$UBiY̨uPY֡1u5V!Uj: 2r4C,C Y(6HAh 5z ާNuCR}x# 0ud#-N_0efeRh,a#IfTQY{ԜKUkAuhM%I(k[F#23-b=cJ4y>3:2(uy!jN#8sy#1bn=>ty3 #C!L97[#2ġ̺-us.ܨQ' czcv!#V:;ɽ:w+coF1Tl[,#1ϟO5엟[ftn5<⬲me>%{q&=t8&abʆ?e,ٟ;%>3SSgC\*5% s5CsDc^8x=#2͖2_]Kv.wu`#G7Hۖ!?͞[LE|y_9^Ѹ01ЛiRH6#1SXSF(ʍ{3ۂbh4\JDq#-6 H N5Plxwj>0َ.Z4HFõW>UzJ);I0FJnԯh? v#1l < ņ+s=EP_mnnԼR˱!}E#2*2-¯2uAO7&#(d@@;gMeC*a t"}~};,dδР$|zkTj;Ͽ䥧`p+:a eI?h­#7͚yx/cZ4ۮ&*8BFzIS᎖bJ☦J(Bwʼb&a0eA2l0uX$qR"wHH>x 9XLs]_}"iУNy'#1&y<42@ 6K|[a#ۤɎ4$;>)͙n$"ص7 hz&`p.`cc[t.V"v/){l7nLice~ԉؘ̼|sb3ӎ7;5l+cBf#1]reZHQBU QCZpٲL8Z!'d8+S!9_TbD*l?f#1B[f)yw0}X`n#-&H:G֚'~(L2uM`IX_>'_v AOLj8+E^LYMn*AjJۖo`C/L7Dz[UMB!()o.-/íCQ?-d\>[Zi,QlFzZmId& 3eK8MlUQFҊhTI?'Xҿ߷8nzɆ8dmv ,PXTR* @I)R Wt/bkSWKF'×ܤ|i>:##29bnȤ(O0"Y4J:3#'&@(AU#1ү#-"aTUS!<v~Z{vFlF'Y(iAX!~S_/$~`r}jDC7گ.c8=cGo틀 9o/klU\kX8~(*q.Bƛg_],jwh~6~X Lovθ$mUHU#2z$?0c€Kޡpվ뭢j1P (+p`3er cRC~-:ݛmR](2t\_ >2. ** 1 =6qv Q}GMbq#c?1 =<(#2.0|^e#b)qPdL]A] $-)[q*FH tFK#TX#-صEVuke둇q?oHKb''%=-k(]ޠ~!:$5KVZg|g?bv$S{KTMxh25%}#-]ҀvHs@%ʼW $'}i#11 J TM_#-^.Lr>SκW('3 b+uih9b@bPB#-ZAB-#EED{ Rszy>}]{xKFb: UKDZ!DRoT:żWߟii٭7V}~L:wp(}GlT:}SRe}UKUz,`]5CAK)Z꯭޶Enf߷Xݳ-ՈX Zh.p?j[u<17k7!_rqiovsly喏t{vǃhТXKVo#2Y1>{0x=CO_K} qH#1Ϲ{3wO>*>>?Mr!< 5JmZ׺-gϯ]%m}0͆^=85U4ѵ|dm|-Z>cDv=o\C(ݱ˵c:$V3#-}ٮ])/P6r)9WvU΂^]>x7xۤѯO)1]YS3<ƿiQyaLˆ,ovQ6#1/_|mݳwn#2sCƺбvUǟMRD8;DXh[11it{?GxJx e^߸=ɇ6#=N8zU_۲6|P؊ﮟ^~nC(S;B/@`rwok/ؠ2@xx}2pU4dGZTR}iϊ_6\2t.m؞o7]g㥵m:?2wbv=#1jiUP#ee9|{7 ߚ+wz}I}`pwul|VJYXINpTX\ʟ7}fȩ@# ;)dj_*WX9ez}?ih]-8Om2ѪQ;1͋K~ZДQB099DX?|r^ag㛯#2ͽ.Qyv)~+?dZL9Y:;ufO~<o'/j?o5O/_^]_xwupKG1E,^^ͮBfN_ (3r<]U=x(\v5ct?5;k@D}S>j>qߏ|lԷ]#1B_7Gwٓק:!u}4'?-"c=Y(4|8#1{bVÿ˼p#-UhI_#Sjg?h)vFfxcغK[OU~3=[HmXn=}UVD}LO'}@/2A=-]_I$!%'i(յE/23kKwyoMե|Y1x|AagNcw6+8t ś|6hZv:>D*}T)8:JU/uhЂiG6}JiuZcӻMS-͑2fq`{s|O#-/`r_xvMK\,/iWz-X=jv$nۂM3B*=ԭT8j0X & #2Jf&c56*hi'oG|L1~+S<& =a5G%u޽NBu!alar(eA(u_OS88G^Gbh1"GGJfm IAH(TQ&Fը,MمUTU@J*fiQըϛL~YtdowA!E]FK#2njeu?#?t_ٰK=*}]1wO>`o;οל;#1]1s|VT&ꮗ|*[mecWj $ow.=~٧ѧQ,$AW#1#-p/QMjߓӫ'p~~z$^o_=ްeNk C7MPTԥ]My+5pog"mƿʯGuB,/uz%0=5hq_;cFmxUբEm(dR+Far*Cvҭ7z̥Q&5׭נt`ZONE1J Gakhh@e;n0SCUZڌF0DV7FpmdI7#2F5ېMȠ`B#1Qfbpuںl611fD3#2ΕE> ;Bx [ZXJc?#-\oK#2kSyhlbbeCN"GDtdHA(6ESh##2*1H!!'Khl#21$Jb H{ ~qa(NUT7Vc'u'Fh2_;y:?Ix_;O`QKthz}O\=g#**_uy"X{3\\QzA=ɰQJTh!<=2 H~R3MqOʽUj㪳WχCpe8~/ &~Cb|]gϹg|0#^U) Tnt؈mƈ#1= 7=7+k}PYN͓:1i}dGe)ua!\}1UMB_:/ֽR0ː*镞z!U($٬[heU4%#-u?їM%і( ݉YCFFdk>Ny~#{~hy^ ˢ$H#6̈=(R0 i KCۦR6kOZ,[W2ajViVEXf h#qTS3 (4Ej,&-/؈L{#[VuYo ߯@V5Ja jFʘYsdX1HȺ% jEfޭ:m6,鸙D63h2@PL&#-Z7rƣ-oFVF#1ut(%lҬeɶ*=SѣAݏn0C^e]w u끹(Ik V߅NМ#x8$tAb'~m :˓#1^uJH#/5BРhcŘd/YM8a#2y{sq8"2ӿb=4Ǐ?(xiBtz3?<bŅUO•T@/WHԎqqť%N9U]:R7Sq;Y:EA1!Dg{l=qzM7BvGlf" iHۍˆDCiޫzh㧼b WFQDOU)t&ϺM[1@|t>PALmENsHKK҃93Ŋ#2|kjmYۆXC~6?lY#2AT8p@Q}Ͷ3U\(L6860&Ì޳BLRr8;\;$3 #2|_Ĉam犁'W`=6V&sGMeuv7jHpm!& HHc,_>F:Ƶ-#-ƼP>QcV+UZL]IK*Nj;c`om;F2iJ-8Mk]+I(5#2#GFFK~6uQuvwPVM%*78(6]qq(5#1k[r+V;B'F)9;6TZfP;5KM1dፑXe '91#2A䓜lQHK]naZu `my&֤ȃ#1e{V6&-g[3hM_y*q=s^NII*JC1OGi/_~NX7Za8!U6#1}#1%dIF6$,5\m4b-TM^"R".f哽FrI5Q&㨇RVLCLe/ZFUB@␲||†LHT>D*}B`0&FtEs_`0c&/taNٽuçw/+#2Lj-,Sv~:Ki!>CЉgnqX#1ԏM_}biRYQTe*mX7Hr*L#27'm^YtNWd:/VRͲc pFiU|,nÄg* L(Ŕ:gMP_Okc֯ -[AJ6E;҃(;te4su %;Qc3X vdzksOJ%闕kMuӅƎO'0Ɋ:PjuO]hgqSgq='=5)7Q쬃X,cm|)M{ o1g%q/G_ݺݣL!Ե80]I 9G)+w%8#1FR/(?7sGTNnQ 9,=UDyΏZVۃ+ů=c][|#1U_1 28|.zߛ򯢍#1VۀqBlq@P<K28w01!Sb PWi]uܗ\p 08'h #-!!(#21VƍG+;hJ8*& >%RYpP+r y]A.X 9yos&ǡ8.}TusEZ!owp|Mb F8{`BtfθO2?=s7 ď^qBU>SP7x"ner(AF%6L[2;BZ#24d[pP{>ǿտ3t|G^nJwoYLDU;;Q vEx1 |ﻮ6]滷N+EIEbVR ([;_mtGIRN4ew\P݋5(>BޚNќTCpM_=x噺yB5ьNyLjoLk.˞N=_t&aY7ϒ 8y#2E6rhϬ^g[{7jhKѷC[-ux <jif#Yׂ1a\1M?KZQMvڟ;╏#27_f4Ɍq^nM2s=^3'?&(~ ]NcB9~Q8672{[uY4bG6‚HcA9:vßL ԅ?V/`"8ǀu飢M,ͱHʙ`Z)U̅.Cx=8!1ȣ 3"нĽvwbl -r<0Ywnr=ӆ;-=F0h\%B[iӮOU/;d&Gyݧ#1d#11YfCM|#1U`Ɏ"߇c0$1|Vޞf08# t EdV;99~ܻx)kǃٚORtK`ETjްQDT}mq߳2#25ε{Cjo(Vxfᯗ^Sސ\'R!ًhf;C;N* r4+2Ӄ>Q9\`>۬nqFד;7Z%XpvG&2U a/*tƭzA!T`A+%s,M#s뮊V-V>QET <ωՒ'lļpHUVoec3 ɴm ٬jwr jrGE/A!\jl>bE#2!si+UKgnM,M#2յ^}xBBHӔ,<>w9K#2M9շA#s`Oa~sP8s5鉏CFBΎ6|Z< z'#PF*0W{UF%6z~!/Kcjьs;ۉڕ|."vd$Gd]..EEp;94oOK|܅գ;L= ::(~%ڑHIj/W=}㩮0kO<%ŁOg)Uxmt0Kx*VfFVۛ[hr$޴:#CcDOtߚ"7Q0t>_at-b$LȂ߯ǭ:DX\愦5 6/-Į$Ҷ|ļkgX3qSghOF9;bOpoc'UD$ϕ-O``20j⚛?qMW[6zta*} 6EOmµ-^#2 ΩƐJUC6!H$oJwKRs>RaEnzSEˎ!O!$t|g@eٍ44ʖܴsj +$uT]u<:rNIKM$Wѝ0b$Qd0;5rе:MnsWXtF͍s#2{ZHA9&UU1},%gX'g(a!B$#1+j"8ϓ0o-e#29梩u|]Cu!qʞ!\M|~ފmq#~H#2<~H̲x!]=][J^TF!N#1r_ߧbS<U F5c o!uYe)gjێ㇛5&gPk;ZsÉDmyL?q^Ngդ:8덺5c"+-L2̮Mj# "DֳstjGLA#1:D&/Ϯ靶dVMreVѵK KM)U;,= xSg>O1]\Aع'j*hl/u|bw0 #23Qц*&R<XO, u`L_o٪LKRQ6hf2ngsT$Xr=ްRc^+ZmH% ?}$$n!i)#1nB#1 sT-(r"dF.oܱGKwڳGcۍA |{eպuh>$hU:f-r)p·f@p(@3y'"%k,J ͵MΗخsn4seM)g(eP\ k2;P G?QWhUW% M h&F&}#13Rd/alGN&X8m7fuAǹ]2ׇC&r[0g=bh7HOmUc꒴w@9RcTkimb :#2mjIf%ZlP{nZ}Qg䷷\љ~kvQZ!mNd[ƄVMrpo rH,#21C~iɃO]l'OOd Js_tHcs{5.ߡ%j{p2WUr#1!:֘@embTvaXRUU 7R*,ga.W40G<س2A#ۭ߿޸jgt[zLE;<Ϡu+s #2# < P{@dI?-UfRDcyeO}kR?*tl*8̵R$*/c 9n4aK&{0:D2iOL;-;9"WG][`,>7$9η9|a%tOE׏ꍷ~M#lFhқrMbf!\ACht[qv ^w7ڎh*fAs}51ZWB^JUM^p!L!ŸUӫEUe}+F!~0ckdT#2f=)h=Xfg~CA-En$HLM__Y=c'jN[:B*.)F(= 66M1@|]f.s۞(::l=~|Yw'ൔ8+=kӵ؋5j+k$3RLv+2HR+ rيmdYy:6as}9ڣ̈́k6"*rHE@#ިɦ:c7[7u 49ѹg f5C3v=!Zl\^2q6ɇt%,tFn_\T Rz |'+}loE}B;G 83h+(ju)9&Bul)@QpoOu-)* ç4Н셥^[?5!p99E}Jf#-4lVܴ AWX\д99kCvYބCXK uvFL$Px}DywP>o>x:b3`<9Ko,w="<DBN#2үE\D͋RNw^hB-L зUr7bQTxCs:i8]m#-ƜULfi#w)T[ǛYr~Ziq L$cfnj绣D-8}r!eg]0^2VOM*b7{b>|#1X،`! A (e\pr?>k/۔jɽDRq5,l:El7 "%U>wVn%8bPC(Cpt<4שU=}胠/`mk1B+6Xrsy |v_.2o$#1䲤W3&8fZX,eٽ>I hdYKon'%F/+Q Q`sqG5]nAq$s=>Ӭ?,; =>o}v"_ 4dRRZ"B_ЊMW0w9Mj~95'JbO5ohq`FZڮ@mmpkefõFctmFhMr k^Ga^#m#1#2ul*(%.QayFSA#-q3r9 1X}9#1rT<5]q~ʨ<B1F!#2Ha v#2$#-?@9GC'S^X $8@".@@>\4O72rH<P @rR=#-Sկ#-cG9xN\cQT<#29^ )Yʻ8)0~>F:s}؟:/>d*T:DYqqaE@RBBUSD;N=mulJ"0o"c' <%P#@q>ROA}#1h+ g($8PҀ5?]U#2'+s**#2>d҉n̡`W{{ (켬#ѭSp AP#1xJ#-|G&kMJ/H$wY8+Bb;B˖k  fSQ#-rP\ ܠXt@äB=TLdG&(r,#-!n`Eu؅,"jKA[&TE5),dEI:\tQPY#2/ace(lJ4.tXAİ"YT$*Gt#24(4*=`@PKޙ Wr5x]#1$ڛrn Ufߜ^{=j~zp)\uU2p️уVtde;68#16lTx4XC); +F3s ddOCdGV[kj:ɦ.%8L/U]^`NUΙy&\}天DE5MbӍXHj~ h_#-6uA &ǁ`;&$!S4f̔ŏ8$f EUTe@$hKmnx'takg=7%*vnT34_:"1"(p*dP!(+iןN#2jMH"Bvheϥ#2]ЛD\i*Hla54+qtvappX#10P{H6*i!`ԀFv5(nC}sI?7ʛYIߩ29OcBz#[0 m4#cdgvdzdb+NlS#-;K"U~+7eVJbսJ5o%ʵIF ,'3U_GϦIzF. F*(Qj6xIyz@+#1-7@Â\!{!r&x) 5ʲ@by&_6!GF )|;ǍC<'tsH-hE_[_ׂ@^wL)dx7O#մk5JsFT9WlQIIoWksTJ2@Z"Tlսﶵ/ر`̅$HU(:MtJ:k;zߠxv`#V&0B"#2@Y%(9ҎgL%#2VFE F 22U!7yqFtԁ=gff@(#2=LW#1N<ϳEMUc>G^1`BJC]x3/7vjRJe0TP Ӿj/@{|8aWoB"cɨ4 p~(!Cb=w-S-Oc%F=6ӶD)@m\#D=8~Q01C=rgBmV.(;. ݍb7ηsKf }>-voY-N{+|j}wN>Qζ{^E︸ه}QuNPѷ 9:|{,Im$>,$v!׻7B(\L* ;mB$ӗk]SJDy>%$U^ Pپ/ `;c Ra39x78 *Zh~ݔ1!4He:n tMDَ%}/곬t։ΎX* {'icLB"YPOi$!#-6><*]rm[♰#5ǯ\4\qeԮX=bPe~*ulD?]/fA~N}bl6ǐH$I?̶]̯D@cW]Kd7k3Ŧ,k^>RtCb<'|7]G(ʂDC(boO4inOUk,V;H#1PQ{ӷ'BΤRuڹiDi6mI)6־^sd 3G|7OZyVxaUd8΀`>,`^-j15V8;>eP#-P׍K-<*x,Ԕl~u7aX:0!!ޕ:#1v(ʼn3XnNG`b7&$g/a  cgĤKO?fPa@UѰ#Z~(:0`(@P#-}k;?ǃrOV Aҷ'v/eWȖx32}V'_§en{lp)qE;qNuY_܋;~{#-ŝDjd݊4 ߟd&-Vdt)FH)#1r<+#1n✼wڲD#uaHk!)'_·]%|_,Th?l( p[^x"i DO_o_KQQu'**4EE>oƕNp [UӋ8>+349#2Ă "w ^Z'ĺ2$#.]8L[Oߋ|dEP IC?|ss*}-{q$#Ig|;9cH0iS!?2P!mܡi)_?g?1k9 e5J]ԩڷu_4&BT*E:6&ΠC&#b`$X'KTAQeμ?7 #-Cstb$ ,L˖߭#-!%qYD-5lifɰa6C{ps?76>T(ŸD+ODK#2=Mcﶴ'#1YNʦ1EGݷe†zʩ KtDH@H!#)(rOΞ\^.X>Wg)F$'1P0/#ygesc&el?|4HDz ȹÆ^rU-]+UGANU'Y-|WK03pDO *H<\::8y`YwR?%춹\|d%7z,*>!СE@#--n&GJř[c)pHNΪ}&c>ҿG#2过 TK,.]O  {PhƐ,zJ#-#-s?P^O.XMe;eӰ ,G aEu={#-E ?rz;m^'祑zy%}#1C#-@2AwD2SwCs_̀@#-+d1-qX#-yŹ39#-{}7޻"#-΃j4BՁՌ1IPfģ2zvmC֞~eWOhxeCTg2 FC(>xBe4#-6UHBmǣFh6'&#-`2YUӜZ#2x;N!G7@A!BBd@7>uo!iv9ǝoT?݂=CX`۹u_*lX閬&3qԼL~2g#-f=3sZ7=*\}[Pt|<5Bl#-vU(/t#-Z~D?z̻u`O%jP_nb'^u#1mi_Mv2}c/@#-@b^|;[;eAb@6Fvc1uYW^.t5pxq*4B$3W^P#-;֢ϭpa#- w>b;@=0,)"-aR{Njz檴`ۨ]cRpkQSN褑掗.?_{Ӣ՜\ofQ} wZkV<ֽ3g܃Jm1LDU2wIJQUMy6؇^Ec҈[?[WYqfD.^!!yKyxCufҕ%u6>MKES0a#2(5q#2SP$G)\<`eCPTfPF82}7_ka`+ZXF#-ïc?eVǞ#2ߏ_X(9>Of2 }`ı!#1[-3+Z 隈0Rdi^QY|WDٵGOw}!)he6dv=6UZW+K -+h~X5J]vĺBIA#N~ڜr\1c2cG1V͙rJy{o=9t;:3y=X3;h(ɉ_}f8\[Ekv=A~+ |yLz䧈SW5ߖ,2X~K' ^/MNǕx#1}Q.oly$E{CmF7o̮h ߪAK=hj7Β?O9<6ڊmTZ}\ Xձ#2`]*'y0;͝8ޝ~zsCLH%ȵڝG}j{_]Fٜ_zv"lgH/Tӳl{l(N5bq ІS.t%nOf#XaJr{oi}}'#2ÒIBLy߸[ѫU/(beWHs_c9#2=*P#2Cm/{Jmی+X6k)F/=~Is*T< ,YZB#2'6y,/Y4Nq+~0l{|dD۟""N]8o4Y.E42r+ܦҾ1aN"{#kq۸A[mgTŮ#2`98.)Sx^.);κǦd񨟓0xiws!'B[=BWkIgM#G^ H:sX3kF(p KclϽX.!Ԅ#\o?!-sR/7'ywVު2&D14'ufDΩm&#2W|3V3'[}!cATkw|T"xwY1ˆ#2r@(h[To`!XQBr yg5ܪ{( mcA5c |l8>ьNWEݽ@Be#N<;_\ߵD{9-9ڄy9N] Z# pN)| z}x 8җwKނl<2$$QqNQA݈ԻL8;ol;Agq~F=)8q~>٣ρ7ʽ8uChM*ɆzN2/g/l?#13#1#ϤQ9ɇ/!G$;PP4=Z~xOs̛z^O;谉)d!7b>7m? k$~NǤlW>ϳ:I à\#0Wh<"/TyJ1]xi)ؼi'Sf8P`|Զ Q*AF}1$T"~$#1YqrA@f#-+ 54 =;p>޲)q,&IO# zeO$>hf) #1OxBDbE\yw;9q34Ƚ4otj;#2`Mjê#-~(T:~̏J7Z~X88iTbP]C9Pjg\d;$9* k34{tx: %#-,aebT:q#-jR}=jnCE#-}zT)AY`A#2K_Is6 P!DA&N0#2#zY*Mt tl,tgg2jbON@2q7&-#2SmLzP>:Q8w76NWU(~'q>D;#]jQ%x.-\mcЪjQR?#,3|;rџqeYj~ieu'ݘe;gTO sQАyA@Ǘ `Ic04mTԯmߓWIyC?hӶQBT#( 'U|R8^M#1ۖ<`?EJhDp͓èM<ӪnU[EmځA/o5\xj#1ȅЋe["Bc VǮhh$~5E Qg֥||BJD*2iy(ZOo{onX(u.R#ܕ‹>I-CQ{>oztm>:{+mb_\AR(]{bȤ^?YɄ&?fVls;ز8"#F{)G]cNWL?$#2׳IXL9<'휇( oT!ؠT>(SHV#2%夬1^#UeM73vnu _놩x/7n9p8gN"4fفBQ#158.Os;8{$e*[墫eVNۤnyYi,'d #1}(FOONN/8J&c޸A$]TG\ˈ2Ӝ~*Eۑ"c|T<2V3#x#2^ƠKYՎ7 CqՇzv~6Gy#Ze됝}K#2tDZ#1WLK犫[E=/+A6)~NZYh#nCDmO].7ֈ\Gtttrj-s1W(9|Ni(s#<$.zhyWVa(#-|)hfKR^Ϧ2J00`Si)#2W+KA0 <}#2#1s# $yWj@;l߯e"W;G73^+r5S#-^;<X@eVf#tҿ tñZA%9kbTΫ;ˁxMr>_s^#Sw%03ZEw/{U>)ZvV/$rDHXw_-TpRH(X.}*) [ׇ~^N扟?vǥT/lϽr]}Z#1ՎGRjpwe`"Y#RF0.dY Dr r9#VfV,v_!m]%,eXO%B*3k*L-tt`ԫ5j#2B 40ȅȡACkFnW_Oh0"g9%ws@,Mǝ9$WsH]$X6#2$LVΈ;Ũ"48Qw"z0]^LFӓ7.%bE.f">M~J'fUuD5dK}Vc*cBy-ciNdi>XlL,l#荳:Ε;"mnZUAeEMb'˵jۈ .;%CTp'a7)bGnȉIQ:<6[%z ?`ouJG0ga/yUU}p?S#-"(U(R,`-3_@|hIxP X  2H#1UB_Ke_T;EڍE.``ş[=^OnwXmY?P(a8 v 8#246p"f'.bW9}(&-AaBɞo}JR#1Z9wUN2kG{ܝgTa|}nA/4#1#+Q_a_w;,/r iC h\wے#-jP5u_zz?ђ kHt}הqV®q|t1f ws_߻whS삹"8%#2#-W2phOO+Yi8X52Q E:GL30}ۡ&!Ph4;!m%7i%>Ҷ_vv  0ƞA_wVkU,)rmn #-jv ҁ>V(IY >̗#2XPd@|<Nb#1? #1e%0)K~OJ^/78S(SR"哏wXke#1jhr Zq MdHUUNI$:ܹ#n1Lj{!Mx\?{̸=)2 o:22ӡfAivd2Kjm}?`}EB#/#-?>͏t'% .e\l|tAF c'ׅWJ#1gp'#1;t:^hD4d&B?#1sH4P"]n75^ܚю0#2DIy#1"`Q?\6ׅR?bRŇٜU^2IpdEM4DA&P4ܩF0à32l90;w H̪šR&_Pf5^ArC봐C`goX[FEjI"8(.d?;Aܷ}r9*P%q#2J?n0²~V8@fdn"EG;j1-.Xf]dmffT!>r[CTF2JD4Yy$IXLԖy-f7V`ax˨cX@z#1#1kz)nXd{uc@(Ƣf/o_wf59i$zD_Kș-2I}MXͿ$nqH;ܟ5ѓM"J*!NAW]*00(E,nʳ}Ehz}>9Sרx:޻F"*Y5Nhcbq{|s MCi ;-:]5B#2MLqg&I#1^j>y'W,DiĴAGWہzm6N_O?u;"/Sᒛo Xc#}:Ϲ6F+0?EN3v7(#27)DKf7p`y ywǑ#2T-{ ?{B>D~D#1g#1mS#1dOy!8{5W߀ԸcgUJ*JRDS|HhC>7#-٪YPP |ΔOnϛ\{Jhyé?Aԝ^I X-2b'ޤ CQKFA; Yf0k ܬxw]/v`G ,[xSh0#-#sՇ ]==s(Df>'#-9!>J]`@|ۼD:>"<#-$dj?zX"Dgxh=~N{3:yOE67rXhkiϣ$VA~೪u<dP e~-A@:DH?&@}E 启ɩo+X~[Ϻ U[bbFI$ _X&!V+ ]4d>?֯I>S;B:|!i9XQ=gǓk*.Z?|lØv8\: !^o;C##2_q+;O3>Ax]>@;sN90XŇ}w9u2il<>%YUң% ?S98νy8DX#WRq֡CJOg!yA$U,ן7m70F=lpa.x=eu&ڛzL*$ZЫFIwyN#1ː)]Av(Tt@>>Ph^o`v`u@<{ƽчKh )SjEy|~~=aR7Z'K{*"&$ ʤ_Hl9CUqb*ΛH#-obJ )*F=g|`sEri|̄O:oI/D5h?{mJ=P@^"rq3F;OA2AF POIdEԨOQHK5yK՜%OsgA (C(T->/0c>Z[Q`7S,:LJ#-ͻt~"PkLh_[H7r 7b\~bTej#-#1X f`~ kA*n#-@Y)dE".% F*AJ*"9 $8tM@FOM~aȚ >*sX1K-o*U/UڝNCmO`~lS3P٩bY():3 0bgZun{OЇfHz3JH+oPާkî6!FFoy[\VL6T& [w^yB#1Sl-a(bY_WXS,S=yWGO0dH>)g7FR9w7?f)A5w!b #D(̂` jC! _J9)ak1<(LntK~ZFB6h;7}q#-6R#2;#-y:Fygukۚ. 4l-*K첏QK'ov /I.{"~6)#2(߽`>'!'%I(;};w)ۧQ![fXq}G{B$?rVGt*3&jB!k6IVI$}z}wɺ;kR)@]C{pKHQAETϟ*)7{(GaDs'$/g^#-۵HT!6`HD{@=w{~GvdF;:RԟPCC'ϽSHS,a]cp%dþ bz \I^~{f#1ďf_#-PC2??RS?3adk4QDAJEQ;3Bo#2r=E +;TiX QXp#1QB%N$$"KC@RȁNi#2|z\jBP:mUR!qpb~wlj`\z @ #1g = ߟ{zBo. S#2 =]< nMGNΧ;I%h?ep#5vDH`ؑyGS@>3I.2IT9;Q:Kgק]n*Gە%9pc9Wmi;6^sT4:)i$mRz#CÙ:гFDUfUQ-"8;9NvӴ،"(%?nuPn)=#-_{#23׏L' II_kd#2)rĀLMf#-?J}On-4u#s#PIg?~{~-\T=]X&5ZMrcooh9osF`r^#1eq>#-!?UvoMxK0N|)MP L`vHieDLnfH#27oX!(4$H|<\ !`#;R`]L}ߝ#1,'>zJ뵇zM=?#- 2!K$zwa|N(#-50۷݄x'o=TkᴨHII!]^7:g#/z΋ma=jQQEV17\D$0R$r/OZC0?Ja'm =vvD#14-슴3gP`6 u.*U #&L@H-'bO׆\~ga"y|g։T7ոS^t(Mg)b?/(BoP~?#-ٖ׺HcW ɐ섉"2qjͰ{Jk02#-{;McGrVΖ F$q&󰨡Ƌ(q"d(9 P 8CƮ̩PO#cj"Xl?`{r1 R$`:\."Ax+0{ܖ?^9g؇ q?#-J._K_rv1d<7¹IV DHSt̶O)Nͷl#o/eRdȂ$Bem <9B:xjai*f)\a+*MC43Rt*Bcaj10~݈Q-1j * 1zS.3~W3.%Q=Z-r `٬< $(C>E~%3Oݺa!#2koΟ+gaU+`/C Q̒t:5U*9&tB(/|n#=$T"%嶜W.|'|k\Nt#-O΃Ͼu@TU`n-=@/"iJ<t͏"qF']~}|_Tj2ݢܴcCO~v[}6#2 #-@#1x#2 shl@VIGbT 7g;H=gPJޝbiCL3L\gfá?҇/ ?A7D$˗t̛d?'"CǗןO3h3n#W0&3uM6m#-<6J|Nk=[eԙLpu;f,2P]S}MBD|.Zw=ciw|šky!ҌT<#2;_+"6ƸPp=( vzw#1d/*ozx`FM_4CGC LT>1Ks4v~vzH~r֌%AUG@ C[ k/4s*>۵nAEcb%9#1= Wg q(V#2[ð˲mWQ4GJ b/UX'}ǣ邯8q`gkVppQG67/uZ6cS@~<@NkD0r>b?L{kw턋> &NxD|&j#2Q'}e8ԾB+CV.60PtCzEoAzƇB»(Fui~-?z\U)#1! (%mڇfu@u /ս̵ZTqЩa*cs³o:G3gm|2^s!ukISaٲ{;gq+޵ٜ4-[J=>./$ƽ4E5ۮcʱNlrvv鋂2w՘opʾwsws&e]]239b2ozkST!n"F&AklԐ)8ѬNZ{+tS#2PQ;T C#1q[GU?#2#-A>O񏠻Kюt#2!x|c? {$LgWr3]2+?N>wƛYSD['%%_2t>OC]u6fyF#2ѷYvfTX4W9UaHp2#-bjpZ"lҢDZrmB;V3d@.( #2/vg3ӋZbhD'Vzqh!8uAfHTX[FH/^~zv 0N#1A7#! =>cAQbx3 #2ueɫm(~#?c}T}ABH p^U%dg^IR2* h 4B^o HU2R3ͼLwY#1v򱵟yu~Ī%r}JJ65h)N2Ouk#-`TPtb}۰yQX+;{>¤\_gUgC怰m"d$qC{q[j4azͽz- g]xx3r&Hc`;wmϸmU ni锗r!˅`u.a[~r@C\,ycSySqL`,ݴG.v;LJ#1+ʉQ϶쳤%^!;]$4558G׳O hw߳Kzq]wcJV4{o:{c<-!u /9)z35z8AuhBR:?#1u 3tn(޷9c͏5[TжY(#2E)n[; 5rKxWhu/h׿gٸ8e3=fIs0G #<ǻᯇ;/qvdwaDKK#1]yLR}1s3oE(Dr#1^@+1(}Aèq3Ӽb =H͹}Q{Ft!hbׁv#`g f^2\<fhQimum<yQr.;|2R2o(nDJmeA/@uC313̏#1B*Xr{#2hMmn]d LF- lӀQbiRqytZ[#2lnl#-!y@Ynt#lr+7GZgHv߇veUIiemfutOx3=FChYCS8G+}95Gsid%#2nBih>IwP%,h ^4{+#2]nԍJ,!25.!bΒ[sHƶg6|kP奕 e#1dӒCG)AkotcC~^d#22h -npIF#2!تn+Tjegh5B@-7))ѭ@qh`4ucc.\,XzkP=V%O2(J(Hk2fdqDGhm䋠itv$H&f \Qn-#-6tUk@ptd4wECX`Xbu:+ς:@P{phȖ[!fDHDlbyV&7nn8ۀ6 Tu#1Y)t]#2o~M3v.VY)F.wrXJS}GYiñ2M}B:l7M(zQN%%9NF,#-gp8q[&ᨡrGX!"u.FD $UTl#1:lPΚ0M4 G8UyNWpq*#-;@!}0nW#-X chl3oJwd=GP$M;C#uW2}Zݑy[XW3 6ڽ{ }+,y;PRri(,}2yv#-}͛0{1.u=C#1/>DCxn|~ͤLEC[OٛaCٵ0$zBf>fۻt:Bz" H}z:D6?˭mdM'Xvc=oX`؝eGJU)noovz;((Q7puӵϯSuy ^SvΨ9omr.#2Hz!WQf[i'x A>Q{u[kS](f!qVQ%P CDzE@~}a"@&MR1+{wQ&[v$A%0"j@XW jVt>NJ6$$KZy}V]arn60Z X3鄍"#1{{0󕑕Y}>9Rra>7D_ J*A  { XL<&yg+E+sҼN2<4My*/"pD< bn=UB#-A!)#-"2p|DRD Xhd@N&9?0'IKp.3DJ> (FZR{aY[k@'Z54H I !bue>k"EJI1oWvzbQA%Qmy.nwW7,ݣ_.n^4E!QɎc`3#hdekzH!@mMs<$OPVNA;Cx^OoEnEEtVw6u/9xsLXCDo7 ɮ[q>.<|=۴v;C !ybhU=MhT#2@Z8.ȃE}YrQyWķh{RGVk;U}\; paQ.a#27( hYt2TBE7(뻅D@AA5a'LB(ZhVY+Vm͒f•2`TCQP)>Ͼx5A59#-$ Jp#-y^/Pa:iF\"od CN|.$ T#HqS $=zPB|#dwyA`☪ʢ`v#-Bz#? Q@ ,(UT2>F㫫dMN(^(m GoݦQO X1al!̠V- L_Q u'.sy@q$`fCs gx {e"@˿lN݄)2%́ovA%O'%F?o#(t#2#2ю@4B(ba5K0މ|PbN(#2C.278#2N'(AY{>w/<4nq}<->Wl?<G%#1ʼiNgeSb^ew7 1@Woo`@ 댋؇}em)M3hG&#޹0mݽijho#1#*8U*Ar_᛻5gl Ӡ m .9؁PYAbdDFOIQPy#-1>S"{9+5[whY2ƚ*#1(D>‌WwkCEX_tP Eby;7v/eӞpMPb7qBM=id0p5g] ;T:(#-K 2@#-#-#̂)R|s.ؿ3)^#-@dݶ(upxfP8!ٺ!+$#2 uaW n#-U+7 }zc1[ OyEߟ=N1\^:*^#&#17ҍU ;ְ:<=tjxv<(mY4%B0.Qۇ>5Zbǹ6#1ۦ:~xTq̐=4 K`ྛlɶvj ;z7~56|ZڷeǏrNZ #2A "ŀ)8Oho_+bsaצ#-YbdSdƁ#1ps{Il0 7B'>Z}PT>pr7#-J#>iI"&__t͎,KvpҼ=/4Z0C:퉩VU RM3۹+mP'Lrտ3еxWGP>r5{vq}=) B,5<{-CU}>̾~݆nfuxgsbX9Љ`=6Kٟ$Հ8i}H@m1׸fܱ1P4ls/Pv ȋUR"!>{䉜G3R)/dF ; Ϫf/ϩle @#~gf28sӡ66.5c^=䴁ǦM$';=Ӂs6KvuON9U$"Wq'?aYbt`ʇ+I:Q~}Yי"N(DﮗK\GN"ٛfn9vȦrv#-+R;C0_#-bo*F[fUiM{H'4'R>2TkxnE.ڀ"TcM7BJbJHE%(6J%! ΰy#2Xn%D(s:+#1cj+F_/fsabEx4WoחZk,d.lp:k;d&(`S'\DP ijMť1sJy" _ImUUSIw ND{6Qcn. #2#2`xP9㮮^*ŌP#"n46E$L߆@εL$8jͼkzy#2@؊Z&P*#-tZ9LQD^}XlhX"E3^utʦ*qϫqI!YiZQ'(oӓ7ZsX`#g:yz{^BФTM޻o=_HM˹sp:X^6M3H@ɜoddS?ճlr9nqKI#buȑ@-nM6Qx 3@CaJTKut )L;BH#pK5Qz 5Zt%^EA@@Ȝ_e{> < #̰$#-#2A׬,Ѥfb2l. #H`Ș`3KZ#caL7,d-%$6-5)LL BMD2+o.eAP#V*&/yh#1b,ddBk1X|# @\FEّʥi.e]vH]zoM!I8ZGY%"m|<}/7Sa$nqX#1)ac'HC-&#2TH4;Z@d #1hZA,rd8t-S#-fudܿ/0" `beKLsשw~uGK  pp7DYO(VMBsRFYYD*Ɔ4CA4S•bĜv"(SW;IE.B$x=>vH.cf!0ɻ/յ6*3!cE}4aDO L=4ķh<0dYC*;C #2M_a#-*Ijn(2_) +V[jF)6uJka=xfY]}6*IT/ YTL T,2xQ#2\zHc] ;mwfAbYHǒ3ʰiM'cѨ`)"?7} 7k4g4˓4Fvp.c4Hʽ :P-5Ԅ-SQ-e"Fnh/}]WDXS`(nF9`#2֮L?lV>-<2[%$/T3vKb&T"ĉ1e4FnH ޽.\wOLf<Z vy7xG9͋M8n2{ijՆ阄Јȡ$*$D Lp_KeYJ9-UAXI.#1ĥ qJDMCsAAa% fnh4QD7Mi)M`I`鉉e8QTR@i"m.]xjd%RhCAز ŀY+VəhyK|!wSS&sMH/&cVgPbf"QFЍc#;1\#1B@x~&,~Xw#2q#0:h%mKOTa_@Xʇ:xG`3EH".D$@jȜ}8K{k2r\R>FLv:)>#1w1(F^a:MID&)-tʗN, 2I+Fmk>)4+.b$리(˶?mS>^O㍳*0h'~;j0m[R[d`:[3#1t"KBu#1`zHKF`X,bdېEl#-Syv>JaBXPE#1&3ҒCEia#-J*:UR Xb#-YV#co0ߥ&%)P$ xa#$C~ێC]/0M*&9aT,=Yߍݗ'r) $:fzHJoO֜ 3lhtqts/⨌^v47C'ʸ#12<0ҩ+1`#2B`|@z>#2 )mh[^*5rն5kolj"D4.{Rt !(Vb_3JR#-~:xms5st>pF`#A)2idfRa-i jPM(Q,k߷(d֓IDV#2d3$fTPĥCwQE#2D)%%0AD(ɒ5Dd)24iLcIMEoG7ɷłdU50CoCa?yQ'q*7#2eG8LUea9c!q$K#1#14czK #-b3}N vSEF?E~&7birO'3 9lWj=ZU#:FS|w.vc [gxi'_}oPۊ2:#10/PW\e_%3#2't&1#235h6KL$&}?az-X6A)PSi<4TnqEF))$Sթ)ɥsۂmq9{+P~;SWSiOrāpbGBm@G ^blCvG;D!]o7+]vEѢb!LpIGkU1-.D/m>1Jx nkw%FrWMխ'kWATM?(-lf8XBfEԮycF0va}yG8\OxھMo%e $vQ+1-/YӇ:whג]mw3FWbTrCs/ pZ4!0xwn#1?;Ǿ]1gifPcQF &}h$0`E$ڏG@#2'M?'Nƭlv gy;;l}~1%Ƃq{êu*Hg/xT"$"#-5ϟY<e=ؔxW i=`%kTwzD M(-<6rAUٱRWմbDtue g>zd#1f#7j%CNhtX 9_ԝ^4tw}NIdY}LԦ&y>mёa\kR1-ֆٓTPz” Q.posYi30W>ًӤujfZf^lfԺHb84j(L"UkXP mΖL›WVMC3s}7LZRqkY.N0n y~Mlu_0E;STzK#2jf[CՙH#2'S;:ܱkuκ#1.S06{P#INVKΣQblP,lkC3'xLƠ}$ O3Ĉyr4߸Y:n 2Z1=]ƙGAӤ>#1Y5V\0N{d Ï,;$5ΌX!\u#140`,"du;m^3:/$g}n@upB텴ց=,Y?9@íRPɬ `vu?D]Qe=:MQR؇jll:TX0ai51zz⳶`z" v68ӕyxęe[wk38o[Ft8ElxX6#1 !?[Is)eZU9,PFvƇ593Hr5\sf ی{CQ|\d8}^I =7A֙Ս&1u&<= pkfM(}m%m@K#B RK]i-k$i"奅AAzaEPXiSW(m\*raEJXVo}m{#)!L j66g9㜕'ubJ]%4:߄:-f^3r (E&`F:P6D>idcRNLܢ7L:8FMb䦽dڨ#-@ie#2qu¥ǠP̧]bT.n 0M4vEí'#-.\ PN7szH ͝a&YN |ҢCgKћᣦ>u\y#1\+lŨ$d!L6m3 8I١ա#Y֋M!#2y}e3IC\Ć}@XPbއYN~| ohoS)AXգrLoƆ7׃uU6uublz)&2S8O/ R=:%)(#1ink-ff3kn]111yku8Yժ5WaG'TgMGYj#-L@9ik& SxN=[0mp2 !:H=Խ*mBpN 53Sii.To^M;ٚ,j6^oLyY=19ZoK2-ݥ/E[nrm3d}&Б "Gq,^ IodKeT}vuu4tP)QIIAJ=Xȁ tf'CJ"t@#-a݋OQOP/#2 m#t02,OsZO_A~I# #X#])*+#d0 -û#1ұp$4@gys( Ac4&RNR`25djfe24҂[%3j"ݚvVS%#2# i"23SdtvNI-"S#1['2`Rqf d5eR#1XsL%q~SlN)H=d'ϚUbx\Pu짒6у:8D Q prle K؃v,Sdw"6'{ UPffk`(!`hF80F nhg4g.3hn#1 jKUN bE6LJII4dpjs iu7.et]#1aL,44)3 WvXQv;%H`m#-NM,7:EF$#b ݼH "kMMȚ.QR kQaD6rRIՌ0KR-~u/oh$#-q;!tnI-oW[`+׶ *#-)vZ{rx1t?\,O;eU& P{%iOpq] u,ťkFw[.k*Í#|8XE0@Cq*G\'IeKZrCCk! 47a7u% exnX!Ս#-M9\SymB<8rbH$c(B%H湩LY7U#-Bȼ<Ev B~=i!0Z!&k!kkJJ2ir!D="M>YrPzͪW/*r1veMlO/nNTx2X(ҘUN#-q?^#-9 !r(`DZEz(|L' /܍^8d?7'lOɎ[2!gfiZLPOB0@cK.'Bh M1ȅ4bޢUJh.9\ZRgH#4"j4x1"[󷗐\b^zo$k~{zVDlUcFkFIDL`3-lX}?D(Tv'x{yiZ*w)]q&A.ú#-;=N Dׅ %cX"+Q@G(m[6n;ݎǪI8K֠0`׫@.H_,{#-"F+Ƞ#;EDWz#1 )-b"bkh#1MM.-WnNs&;4*D4cfBFbIU %@B-Ab#1ThN]Xn6+F(-Gp0~]l&Lp;v$wgFb 9E$P&5ъHVV#1>d\mKf98J .q=}l7;VƄAB|PD¤A6۷Wk 1!5tE,i2D͌DĹ!@8#-ZPRIMQZ#2#2T#]`3myG̪F߶[^%5XQ(0HtS#1SC5meZ?0nAlA]fsҘG"쬻]vVōQmXV˵--S4> OMq*12V!5?KZ)}W[㥳W n5VVuVoj9l9VW"ɽ4M2eZR,Ni6PۡԵQ*J"hd65ɄM$iLi[xR@e0qfmG#1I{tprDSNDIeRn.h͸t8P1mN."طg͢)]Q1&܈h5)a=`C8ۧ E2±J|&-M 315ędgU3d0%Ol ɲ!ej^Nen&=\dV\C]sУl ( g/!Ňt51b H6yZJMKy5Zmbwgyɘ ;LR$ȁ)qT" P i@dDr@lBZ**Hvv%mX]# ֏o˭/r0_Fi0oNA\-je2+@RDd5᧷]:#=4\Txn7:ۆ݄B.DU9q:c|bZ C&$B`w?6lx3ݟbL8o\rEDSXLloht=7bw#--(~0T/c]Oͭ'(qPeCB߬* K`FmD5dQ-$ʣ$YC-Y&5j&M)m&6-")Y,JU,ځJ3񮰦՚f6cLIQSDȭJmB_si^ݻ*b(2#2fҌ֤֔1UZ}.D߻d"X+[l$Mk[ZZ4TRyxRZm6PMXmm&vթmb];t݋f͵1hd2Avw 꾔n5I1:P Ya%6hH9˦G#22u#-I_P[R(KP}Fݗ75#1N|•QRgJ pM28E}`~E1Sޒqݶӳ\x zUjV7bbM<Â3l(nؑe?R>B.r)gpl@ҫYsߡv&p*/P.F©)i[cB}6Ƴ& x%g87rͨ Ζ_#1F&Y#P̵}a‘X%WK{{vb]#-mU2N"QRR+r'8#2t("2! D"'6ܿ)nZ2f,"O|dV#-Y;+s8e%Pn#k͕{,cf3z&[C>" t+zy4 MS`,;`Ib"rYE%"qZ"BY.µbm"F1ˑ#21AQ)#2T `h{CRMn @M zu6Sp:hPXA# /,"An"!LbqK U"IL*c)#1qHߥI #--Un.s%J!##1!6N 8n#1f=#F+GJ7gxןϙ^3pgx0'M;}{7%1nn{zC0 X#2(-AXAKꭢlkX?o:Qb I0RA":捱4q)䓝3#2VZ|]j[|4Uj[mrkiQIf!`v2b7 5o,I+*e@ ^!R5_Zb A!B7[i#2|zE+>Va8ƑnbHhT ILdш*WϞً~#2_JS XIS ~2&¤id-,0d URMJJO-DVCCpP!0=p[Ca׮u36Af.KшU Tr-oy!31EAN'#2 ^ʝS4Z5$&N8GQVZ`J~N~UD=9J^P'e\fzLݭ_Pp@EBC-*8J&&M1@eFdPA ?_ F]p%@SK^#1k(XNt#-Ą$y@L=hG McsLC}8U3kJ%ؔއ$TB;Dt6|Qx~+^#1촴QwNlM6&W$wDI9:ŵ.DP]l#-<(%V"^LYv1v35{u< ʫڻB&;@Ci: @rfP\/Q#-(ukP(#H4 PEx`PduQŢP.,ե&ӎ76ZmYw5O}IHR*ZDI4uԛ3U1"kHADL78\N\UT)I3h`nz3D By1gaxs!*oZh/!ߪ&{׼Gۡ>|8׋ u#]F\h̎NRGewh?ؘ 8Ҏ7yzAZW)PNM;{p݌<1!"ON-вMdRf>уA,uOZ!BӾ%٣H?;IfA9yr6-2 ެBdcJUTJ˭kORӻqHM\^K̵yɷ,Q@EM,9$CQSnI܇D䀌˳C|gNw\i0;Ċv@=5~Ľ/kY͐RO͡)6ֽZ5dګ#0 X*JLl#--BhF2-jFU4ii.mĖFb6ʷ#"k_Tx\%PF _ʴUKim1QPآ#Ҕ<omxZ?_&v"sxa\b(x뫁G˹tͦciD!(Uh5#1CBcSɵPލe'X5Zޣ$: eeFޫ}9`:d[#1$,Ŭ S8g叿#-0#2t\ m :{FaA$@*Gla\~S`;,7DY D_& m&F#1=Il0}#2G'k:@ PHQ+# pf!#1#2,<㷨exOࢬI|9QPsB"HC]f\^ިoG\3шYPG8r@~a:B@8Aτ))O_P'[_diݵmfA**TE`(^#PD$B "5 f}Nk3.?d[̅#2-X#u'QX6yV!&(u7$칡ZXNڪJ-R(ZJEp(]F&+?W.,K ry@_ |m{|A4m W%"!('R'y"HI:Nϳ"JQH!H֚?kXɦK: XR#2gG-4tHFh*>#ZeC@ h2^rV#-" AM$QC ]B;DiW;gLHv#bYdȂL[R2 ޡL -F̬FBjcUFQT|Zjhŷ"#1T*ZQ@`Z{5ʱmo&ͥF01rǶ,#jIeD3a--`Rd 7 6ȫ#2p´XCU&X9:#1mĕmbݝۚ\ՍwE并+oR[(6dF*‘^6jLgI1N,f0AΤ]Ya#M\5;wCH\rCXtjֶeaݪj8*HQĻ}k`ӹhud 0>[mҤTE{LȚMf75jh41#CՉt1 ו{ޯxxhϚ#2=ao'i(KB $,V_ͫNn^kcReWmiQ&KźJƱEUu+FsuKM5JZ7V-J *?d}Xw#2l ,b$"?xX#1*)g]#1_w䆇IZ(R~vJF2A#1A HݺhfӦζO>~h71?|rxު$*ЅAQ_;u)Rʮk6̈+XlTzŊ$hFdLDh#2KX10Ԛ&ijqUڽ$#-(E=a#:-J˨MI`(b!vb"05#2\#-$CL,e$/,ipq#بȳ6S #20+$?e8L2oXr[l'~@C4Kfg;L@zi}%d]ZNt{vFBq.*.2j&C8 N#8b޺**>a(#-naH#1\{@lz.;t aW$-RU(" JTcj׼Uk)WQ5!!x#-n;x1.Wt~@Ÿ^L!}ύ8!w$GgtĹiZ{%=4fka5n*y0Hb! 1bd:\7$H0Јh#&Č0a`(mJb9'z*$IhTUGNG@7n6a(tzV30@K EJjh(naj-5! `eߣf4pl s/S<0gQUɳn HěGGk RıO쒀i/I v|JQM e`#26HJK34OAD1ͷ><'13p,q5FLrgf?BY2"Gv&$lOsgR}'֞>٠뎦J?nAΠ_ $TXEefjkijMhS_WDhS-,#1[̵6EbcmVYZ̶ZťTڙU&ت4d-O}o]9nbB&W=N_ʔBT:j]Mk\#2.*Y?| 2)dX <'@7Q#Q#2j,@ 2#-x$XObw2O*(4*#iʣ@(d~DBB|\1lC$N#-Nq@J"#-|@'D6#y!Ą[AE*@?<[DnHƕ=O0b<ǁzn߻ ]Y$4#;ZpF) e+Sx2.e4ff`8G/.*3T4.k#2|oTDg2E/Zvo:ZkZB.q.lOa#-'C7y#- b6]4 Y$kAB#2PP68`nV;Մcj"$44I$0l,X*xOtl(!~E?egled~\}qOOM j6OrNK\`ΦVMtGF*ADQ=vm>m}&Jz#-nsvn4 9+S: L }~jwM*6iVMu"PX;1LOϕ3M#-2O03f ~N4㔥014׾HB8.7AT!eR"{n#1fMkߋ&7#CӀX["$c+ i%cax}LKIqF1kVy0fLUi bj@Qdta-Ԋ*cuhF&(}#-DByւ))QyzRX&^:o7#1LPSB7/C0Ђ#2Ĩ(B\iU9xAzMlA":aAzKk۫tP%1T$I͠O5'K:!oZw_ޝeo{6V*LD4aJ66(- 4#2L#2S}g؀x#15tԲP)/p-CEd*ciMH\fg'%E ͐ RYX"A@P ay{)to.S%!wy#2Ј,U#2AHCz]2%ـG95@c#1.)cY+[aal!<2%}#2ʟKiR\er(z~ݚ#-HRfmlT*(4#1rc}g%׿.jrI]iARPet[uAjYAH$+4G?#-rHʼnɛ 7#-cpvvGʃMQRx:"ɜAۗɢGS,-ZA/aV]TSĺ+njB~9Br,1rsU:qs~1}ik_:yr-[PPY)$gC#-PR@1BC"+J#-R*&·ǼL#-Y;]=s#-#1|l2ۄ#2+߮0O4y :y#-2@,"ryhQ'gRD{weV[Z#-]~4l2li8H~>f<ԩ  )lWty]bMɶ&K/`BDTJ1 Pguf `ƅݾH< (6W1`Gob ƀHX jD2g›w{o]ecwE.鼚\2/q*WA`H&UcVQrQsw[j8Pr"*=}ֳVBM̥9up;M>9e%V>qtJ+T-d1 vI{!WoUx6ѩ«ȶ #1Iൾkq{!@]0 vǁ]traX jP"7 #r# \p88DY!lb}:rØŌX=^û&a26#1`qKh <陂)fiMA=ȥ{}[bH$/unv_]Nv;52<{O3_=8[ C2&ͽgPTGUGႝ!p6=}#2N0~"b!d%]-.JZʝ0O^A0>wi:sy䃉eSZȾ$3o/:Qrܥ,o ;#wH_ąOarL #`„U*78O/rL;LzQ,r r&6HM>kͽ#1v#1#2Z/~[~7T!~[%iΣuX,XFLu$,EE ``ؠ'bH0!iLPofK>D A!.2ʐq|]/cMJ@)7lLTV &#p"P TVSmnںd-4W)vo,•5[-my77Q @JV5 9[\$9k)&_Rnl )uITUa!})vo+jM͎3 cƃa-S# ɜ#1 tCCbE,4#1qA#-D| smxṇ#vez{4#1x&red!dgrׁpERz 7c-笯W 1$o<ΓƾWv4U#1@yJF?Fk&1 p]bA!HTU_EQ!GKK ȡW?So_?Wp_buE|Le w>]#2rlv,6JB#1klL/H~Y{@Z$*mk_z.#mlAAX Zip0];`R×4fpx\bdXwZ5(b#?XDy%FMM#.:J\E#+<:^RI'Z fsͻŶˆ9h솹"& ̌PiL <[ hoG 4:{-6/Cx,Fxܽ:[mt!p<ˡ1JL#23LHIҶהe՚_{sHh >,#3-ؒn7B*yjCd??%O?]!hwn[43`tb̐j=4=zlWvƓս6KBjlſߵ뮘% gRDJ_=Vdף:]D)ceE. "1@5eB\_`bD*Vm5#2 Ď=V3.DFq#1 ̪MZPvgKS1xb @qI0MT B(Obzۜ`Xd3vT;O.-R<[VczQGXfx:-2wûk8Mr,Z#-f/  #291>?mu۩~A{6nQ >t6vSVS?}Re&1T*]hفuEFD :#2 MIڍBILlaRapPiґ33Xhk6,hB_{*#1QfZ&2K-ȃ\5:#`dq=[GihʘD#-H11#C@ghk}##1^ȟ`TEDB,#-5ҮoȳdJUΓ#͆M3P6R7WH*llJPh9وAZ 2)AvX==Ycn#1dZS#1 }-B`8aO#1'2m67#&Y`ģ[#-eaM x#2]nՊzE!jr8i͙KFvq>V%<5fуδ.i\6Ɩٍ1*<1#2XPCt 6 m#1S2#F5AzhgP;[n#Y ݡ%}畅1s{xxqҺ>5ƆŦy9a]2bɆy08aL$ZoMjIN)x֤0I\VXiP*ةB-Ml&`űxa#}=oK!wB]8d YV[pVo&|%}<ᆐE@ h B#2#1.P) D)D ]@0DFA!DPHF*(.Ӣa(Sd&O, H?,?wlBFԈڭ..Yiaz,lc]̓MBbA1H4id8w&GiJ;])˛ǟװ$ק!Ɋge[ØTDI]hŒa0ȟ;6wme>-kJ뇣+fUq K&b]s4I$kΉ#2/hH;.E#2>a#2&6g-c!ɊzL)+j֥!lOez!\phq}0U-[QKo>tԘHD^"F#26 P@$Y ,I_?m%n\?ynE}T (X@/1O1'd#2dŭͪ[%BNRX3#(a(Iƕ" p ɑ? \+td̓5m\X>*˼UDF>\ɫlͬ'9ƗTnU)1a cmlթJ q#2D3VkTchT…I%TBBb2#-f!oU)/XF. #-->Fac#2 =dۘ׳;D!y?Adsa`s8#1V3̙M Qf5%ݻO ;m}魪[_VpUH M<>D uPQt?PE }>WɇEsTTcNA@~[-]>߭Lxm(p6?M&8oÆUQK3Du4n#2 Јa8 zbg3/|:0}l!) 4&!QU14YE#-غ"Xt,B"#-L˲DC4b9&K`ȰyD_ 5{/=@J;>HשT79lޭ`gĪt$Uf>'|v~U*5tUݶ[k#2 p#2P#1/s}VUWYILd?-팋"/-k ;F\pҎ^CbSydM$Rxvd͌7A+scwלɸG:+H^-TsMye*ёm]6ѩKnSfY,+wwtnݙ\KFs*:ʋFV)#Dl#-#1R {#-#H"@w4P={ !z?UܒE}ym+PTSuN) o=PЯ]~&(!R~j(?/^gy>#-8XF1TXĆѹP"Td*?V0u9YL@dՊաKC-ozj#2RT,D) `:qLϗ;Jf 1%fRr.#2hR֫!#vmA!>EՁ@:1E!$|gsќ,hZG?P wfi*Oa|F#1IY%#2Kuunn+\KX a„%0(D@#1M[TlԄW@#2#1c0f+M#11UaPQmyEW*KrI,a2;6*슙'XPU|I*U`?-$.*j#-.fl$_VѦXiFCv$(S#2AhLHĒcZ*ޭ˩p h$ l6 WR~.cGMED!#2C̬=abVEDAՉ`=aPP&iP4`D$5K>#14~0y17NjPR #1TEU9?L2H1Lһz횷-Wo5-PE:YՉE'WØkt;s ,SS!DMp:6sd)#2ŴBV8冃_La9g;!KdKv0$I#L+ǃQS1쐊В_1l#-qWCg#]Gx|PJl+ݦyhuL#!OWhw܃#2ڍwOT@GiӀ(dT@B" =4Uc"C DC!QN٢e)vž^!,O\wF#2),3FZmߙsaS`~g}uX_V)uA_dEQ{8KYopV]L51t\z0#-HhTwء[r摷1(Ծ+Ѯm{sU#2b8b}DۿyTj^kVغy$LdUC#1xYLmiq)38R#1D#-adƍ-!c?xHW b :ti gx(<NDZ9nah0}$Fwdp6f/qP\LSp,ڶ+hB㜏cCԿؚ>=SE5&1++#OBHl1c$ [#1lֱZc&F*iv>ݐȲsH;3d:H4Bē6>h1ӵ)旷zͳ`_WI7]J3d-^϶9@Ѽ#13]7@Xݥ_s#KK0:7#1} ;cIJqLB X ٌ#1 E]/gMd.H[4B!@6R}h#-몚AKӿ=ervډU ~Q<8ndwp?O>3O,WT.YW/vm/k|ͩ`zRPDAm"#- IQLsԱtY&ah$KISqKP*HcsO>]L25mY8^nc~x:VfG_#2eI[$[֫p~F/֛Ɓm!`*Ĉ2n4mY_̄n!ª;B`@UPAoY@knxy}N${ŏ C! N~AdY&I#-|R4{(}@?+#-Q$D$H(w/Wڃp炤{k=iS#-qcwKz6L[ .6<>wr1rqI@Y24bϩ4A^E9V@vR (IL~Uqrrϳ"zn9(Y6{:s+ZQ;CU,|1X#-j{[c=uj:@7<l;Ѕ1V9\־~ee$^zAFJZ#wm hFDtbYC`iИ 56;R8z((#13ͷŸy#1hc_"(\ELgCF|DXcK|Wɑv`?T']uawJo)`T%xCzNv\NUggpok 8\AruX1vŸ%z4 )Cr=~V !/ *T9I#uLoxW8KhB`ӂz6FRXpC=ء$Ed}ީ#Pb *$B!LhC^>d*))Q[RjΘ~U .'Yv_%FA}h%>;_i :\rWtM]F-5K5iJD$P"l[H#-:.k/ :| 2 MZ#-HWz'@&љFT3f ($ƉF"4ҤdhRl2*#22rsdٓ0I)h#tJ';iDW#2 Ål8D#iDg DC#- 5!0%?a=L!ʕ5NA Se*(ehUjQ77ZV9?/mL4'[LA4#/]g1.`-MUNB7TRznO"20<515MvU.l!4c'kaHNJƃ:~PRp" ~u0Ϲƴe&lux$L&wΣ>d*#1IOވYlJ2Ri`RZbfڼTkTmguJm7WZyCk3[qcYC#21&1;/B*%*FIjdL`F֊ɩ5U\hJ#9quW:^ ʃ!YR Eirèi-knqH܉.c.&ڦNwF\MZ 00#2Tr!S`jGHFC"FJۊ7*xІb Bw3SSeq"4&+oE^˼!yYEU{y! ڣ`3 DCd#8Bm68#$[ji6iLj0ҚccX(0jlOCrXQ:UJKvaeuB#1'XQ*FHT+evinJ*[%TaU`ң .j%)Oq*A)V("ۦ#1QcSOyl2-\s<64a7n1+Z5Z dd LF:pcc4 rF{f,l%DpIm:(hjJ#1膚,őB0qx8B ټ1CԔ*8ۘ0O#24LJQ%G:81`KVQM*-й( tI 5G gF d# _D y3#2ئm#emVBDiv8HRS\Pɑ&UC-f +rׯ;j񬻪^B#2cFt-R8x#1dӃ bhT"m#1,,)FRQ7fl%7kb:QaXfi/I(0bQHD5qen#1n`LnDDayQ]~gZoq,(WRfV#1]#-czƢ*"CQ_6$&p@V[m2"ijcwN`H\Tj\0 b\c(P#2$,IQAdT!P[A`h#-i&Ċ*=7wowLZWB~)G"#1R!5'#-DmE Sf6Y2huVYD?@JxzItثi܎<2,P ;#2?~Xz!7Gj0Or@D#- B$b1RzE KzS|a4'7]>8J\ B,I%}sT`(ic;4ACGedX5#]]6])ZE\Ca`9=4HMi$?Rː:Ss].ZBfAADG8޺/kaoQBfڛgy,kd7/͊򷆷 u 0|^z ӡl+ }gDTB9O׉ex(a(Cȑ%gA&h?~_ذXPė(W!&š9kڇ2@^˿˼P8~O#-loo+-We}5f[~ZG]$,]p 7O`!y$Px}i1m~'> 31_it[rz#1#-e#2T7wMk-:O>8NY["5rPGb ,"*KgԝPکIUFWαUVXi3v%e!]5F7:DukXE$P!HhCPUIJ3X[Ei-ͭؖշ2#1FmmVCXۛIʭ͝r,mj Q+j,KpD8É9睂z" (ȣ3^k|31+0xzEu$B(o4Di({D #2oªy?(#2׿"E]$2PR(V ʯJaHJIBHD$>?#}P+,bd25 0#-#m۵hkk-):y&B]ޅEU~,ImJD?>Q #:Gp= xuv[V %(z"eyR#-;f_O` HrVMt_eH(z큛#-2"e5m)jflJPK$eICjJ+6RS&ѡJDZmEiS*V-h6BM#2f}5Ik2l# )mT|/ X"U#2qC)`G po27b4yR@TҀ"Q1n6FAX1-a"#2@`i KQ#2)Xh2TQ@T`H|B@}ŝRЀKD]t;Z~^S-("!bB chC7@M%_ B3~/gZ\E*W"@/nllkTE&0f?Su44 -d!\ܢXhD(AL\"Uc3E8R(6U#n> ;T4O9pfW[.JfP]rѵhZp:·Z?H "̑ƴ#1C6ʔe퇴 l"!Ge*"fGlb+u=XQQKF{8SWR)fh&"jUR.UwN$O$;B,<*&dr# X}vt~;/X~ pPzvL_s7N^B}ixp犢}=Ip訨hD S#ҋ` M͆jm_GWq r]ڣ]u\ʨJ\?x#Mf$r@mqWofh1'ZV')bQ$覓st .m)MF')]۳z]⚭]N3nN4{yKP>7$NHlal;vء)=#2{3~ܿuj-|Ju>̱g֓N-R㳨CA:*n謄y*^GW{K2MRzT.nn_dƛ=c1:gHE&~ulEGӊ-u,87CC(!r&Sq\Ԯ>c&A31o#1\pׇGcLJxcȻ5$CgrsByo,pѾa& q_6z707%k9]Fb|++d]oQx[qwb73vXqXic0t&PK3bgV #1G0MS{ )N?~HjG.PPt1fJeީtvь3z8Q>8ğئ`Wb4wbAd,KKXn] p6#5M36r8E&<#1Kx^C8StB:Q G4U%5C4qҀ#1aSfTl!C)Op`{H^+kfBx\-hSn(9$88rbP(Ul  "#1+ a}S߽8] ub("rG>27q:J5SACl3d眽c+*fA.f]UM0򆳔(j}M`#-@AJժifyw5\ [k,*2pLWʕ3J}vdymI8:ms],?(vYLTGT1Rғ߬sQB(J"jiߌE*~s؁l[Oh> 8!kJچ#1rrqaw~B`8]>*tBT<3E9[F/KEZyނkdvxwuJkZ-;hF|km^Qء~%@vhiVkQ-29ys83D^[)Xd;2ѝcMXu鉻\!jm;vW#1`5eOn=LsGcF:Wqг!w;۞%Ѫ`Nq^%3Ͷ FoLɣ[(sq yH﷌yN#yfˬx)oX1ibn X=#16msxfkF:COHŒ\LfO6t֜hĕf#upJY ǓA,Pd$)*2ʠ+ƞw$Kȑ`B&n1n9Q5MƤXKLh \T{g@Njw[ z$z1iPRɀd4ĊaүJabnF;ۯc/MJie܌F!!XֵLp\y'[#1jǩ9eohDQ%\T548/6YC.(-T2L-~[Zv0;ĬRͅ@e C(Lq4ƌ{⮱W;Q Uq9YTW24)Yzل˩D M#1Xm)v #t]M95JG(#2Qg1=릞DuB4gAƣPh *P FSC .-yd6r@ehȩQFL`sU"05C7c.ۺ#2~OB)0lSI眖!ѬPѴMT6+<Ѳfc*)`0ހcL"Nr#1VFq#BZmWX h3B։339QUo)Ӿ*7=!ҳm4@=[GW( (X׫A_-$u)#14b78cMA@=6alS!vH#10ՌKE}V ƙ2R͝jhT H<ՂZm3slֈZ F ֦)I@ZXpED+k1#1ec׃3#2Qf1l3cU{ʪv̇#TJ;Ku#1s獰~է+M 0"a'C!ҖTbА<>˦$?3)*vc65p#1![v~A=ޢ})i8YX Ll#2Hx?@k@PVb#1 ~ . F#-1g&ssfq?4Ny䣬PlR<gnj߿}wW܍7tɄE*#U*229шv{+Üz/Q757EvQD,P'<:zCFO"|ނa ^HdM6RUڦRե5d#2#h(6((WCf#-5y">_ߟ$!#%jkl(2cƐ-jV)lڱIFؒhТ*6C64ԍ)6IF)!%DhcM5-!LR"MZIa$dA$$#-᧑\@z=C؆BeAvW~$!Ȁ';8]f%vX*%-H@ڈ!l6I$lmHvg VSzkpC%߁(2#21XMwoMv[ãrF"U1 w.;*U)C:Gf?#1M4(@s/ O&@X#-66RZƏkMJH΅!Q0B(U*%.w#!GJjj4?½%gp*#4F~]ht%b9I-`䉱0l-L rB+@6lxRXZLH/>I-C4K*#2yo:koi^RoL60M0Q;3PQ. a#-!`СGLԥAy6OY̕tYlStI-M솑RQb75hۚ݅&bnZMvG.!`3g6#xmbJ%L&XC=hhN3đAB R]P #2' BE ȷ$$GM`s#-M4L)\tn)iXHl1+~gØ3z#1z$#!?"d셄o cpu:Q 5T:o`CHO"Y0Lmmjfa~'#-}89<~|$0e>1TD9^$͊{dS#-t '[v2>c35\'#1#-R*lfL~;n>#-9My~_,mI@}^ľTCR'߂\tO823>^u!6yd*hHaeMwg#-9C{3ecg|x_#-O/NB#EA/U\ye)^=sDR1*);(r ȶd#e}(imX;Kxvmw aDU]0?LNUL4240S8&#-!M&vfD@@5) H!QTNr$|rkFt I4CGt+.@N28dB#2(Zi9Ѐ^À4.}KCd\TURۧ=#1ۭJnֽ,rHn`"iM,"qn$B!0l GH`ژ0:8Ã9I*$m65D0HRq:6ހҡo^{oGn(]uGF`&-t3ZL+t0e^~UoY8;f'a)2m,$P\(fZRV2љrY#1&Ƅ˒b9dnaS^M) WMH;5M.0S9A'X\t.|BiYs(tR0^F $cR 1ñ+;`Vb}2!垈HFhz8̫ <8%8g2٣ *pH̍X3W5kyt!|߼g#a[&&7 %&3`Ħ,>9n,-˜dִzcvTBRBY7[t` #1p+,'щL׀g}ix$3@̷gnՠ#2@RhZF6#12+Us (CBB1^jÃC;amSA\ koL2CgFl\U:3M7 a1(ouY![l&].\uLL&抨65#-ha`1evRS_;$0#2h89玽n> 0!fCnq #2fT,.0fhL2 4TR)"'I1mcjE= %@ ABD`j*PEx~ޭy|~Yj!LqJPi#Z0T1`aMrdzWԢѵ|0m_89IC5F#1x3b[:V#-5Z*=/A7C9w(^Ed#-88T"k%Ԡvh'VaǮhޚd<Upw%Fdw*De"diې?#2Q-#1y菼"Țdߓ#XtݼqOɭ+pD1PC5h¢Br _fn[4W۝xnhyYg-1չ[-VE쏱Q*2RG0#-l uߓCK )PBGdUS"1BThJ D79 n7p#2VX"#˻[$2IEMNbD6fH $H06B#2AvaHXi#ԭ! F@z&<dPi[nRx h1EC04Vfȳ"88OWgl}a1xlxֈAt[>M1#1XFFLR|1<#P$xähU+D2S#26&I(,$% \7ړmO6s}vs속UFUS֦,E~٠5DgԚ=d,X[#2q`wvܵ}JeVK1#2#[3>ӅӔd#1 8P%=H)y䐁#2ӕ.LHz(Řmw~6+F+{skʌʸ0')HH2DOmڊJ-fִ&4"*8","b$P`W@؍" `P#-ᗫ"B!tD"&wy'M.ovdF Ba#-D{x0}lUbp4ڬ("*H b;]})I"7L#-9p]bbNtJ1F/NEJZ!(…<@A#1*m ēaY.UZ((Gj$Lbr K^+nRQd,*K4g#1P3:$#1G\dhK Hnh\Bjmvr<òwȂ.jϿ`,0Chk}3PJ2P_d^ciSmqQ ` M b϶e-!5,*,C脊#2`1640h_M s8XXXpr2?ǃ$Dža_CCX.2ۑFnn9\r33nĤΦ(W#2{OзRk/zhL#7PPugt$TSа*P)C:Dq%YޯzF+d#2#"DmͤԒLbJPU[hMT֘mZ+WtDb(w#1=Ŋy~x(êpDP\绞mH"Ϩف|yunm{;&DTS#-! FC>m`QSkA#2642D(~4E{jrl-<aI"j=L8Ք]b|色W77gE&;jeК5|q!:#2j7NU\я¶5FVR፶f܀#-PYkb- V, &ֆN$ XD_@2K1YAبփZbZagfLeeaNO.HH&dW5H@R#2"?dH8#-<'XwՋĺexTNO !##CG4*X:'PW3I% QQo ˴${҆@iD rH:O3{bG?Fsclm#1n9-T<DuAf/me^_M`HddxL4"D`F*$m$2l*%t#2,FrC-VmQQjR)H#2m'c{3꾐 SԠtd#-(Iu#=!a IH,MW5NAFcSQUM4mN%/kx&ޚU^5JZ{F9FSȈStPo~illk YThّV*e';IJTvRhE@$DMWy>癇\'!Pd2ː۶ 1> h"99|OXc}^V{TxJj@O7<`Lo!P(b1HBY?4 A mjdHE:@P^!BSlh("!$hD&ړJMRvWPkA)#1J.))VEea_Ҩ~xgb1H>3iQ%dBٚN9_6 "\Ho6 r^?8}2h.>(!E#-jHyԍ0-SLJdj)lbMo~6aQe@ ]Me!T4* :#B&zqF`8j|.wة"!d #1CThJe $"J<ogvҲ6~@с#1KQL5V:]|1Iw$ȫmiGl3"LD9`7Q}hoofSE*[l+kkkmMoJ*-7feW\ݵsȳjƚ&wWw[IchV@U#1V$JN.CrDMi^חjMIUZuuyZJ2e-yvE3[.5)fE7ւ[i ٢ 4.TM>`hٞ-S$#2|Q1zLfPRe A#1Ic#-`ptNX46p,_qB"<;z1Xovoq6)uF{q!nx#cԉS-٬M0F0fa됧aݷA(^&7I5:Spٶ/t.N@]ÃkWIYw]*O]ocr-8ː(Ģqf󂛔kLZԒ`^Cim^h{ӴMȁBnYBG5D+mwĝujWeoMs)MEG+nr?$ W#1#-X%8֐C,SNa67@@KR-)! LUBQdj"*v+VmAbKmc4҈/6t[FWNbU#278pt6'G#2#160aL;<-,E ))Z fa]x !y#-Pl~&Nx~'F1BG_p + 9C"2V#1~OaH#2OKB*?@Y4ʂG(٤7T;(W&JB{sp{S3XzcfBlT&_#-#-{'gw~?_; ]>g|U_?m?~a /GP+AYw) #2GV)N*"x V̋6#2@*ȤRh`RUwQ$&f|Vʷ #-QݐjB ML^D m#2xeBIvܙ !B۩?Ǒ^A:03nBGql·er=CLC2*ڼPQ`$0XHtA#1hwH ×;dފvlx3iZlrݗ,6P*f,$IHb=y 1#-厄3?#10u)zבA!gZtlJw{"ur_L Ոh3^$i IqacFU1].`AHIz{p`#-fֱJd#Q "IEqO~ۘ\Z4ncP4@b,)1wN|7jf#-s2k.\*PdGh_ͅ ID$ R ַ#-` S{gbX1d_'y;; *.#1#1:Q:FB LjJlkY3[FmDŅS_;ڪ|:t!")ȇ UKQVJ/.꽞;T5 ӣ_[Ms#k5ULF"%^>i:o&؅h<0wPaP>#-!\jnQ05噫w#-aJN3R5/bhɎf٨$>,5Ez«n$#Ykz͖є7cn/ʴhŶ[ƼV#1o1h6+zQjަT6{UZwv m#-\9!Ȥ`AS#$^YS\@v;\nYdSw!q!4n5«ŏTs#2P=atؼh5,H㘥=Z=HGc/2 (zPȢŊr;%#2TXY-Z-_qbl5@OY E$#-\~Yh6E!xuzz珔emZ~׬@o{bEx?#- 0q5I@QdpہN*,= h *[a-]٠W՘gϣ~ 4Nx}QS=>Rbo=O}?Ghg̤RsSK ~lYVo{c-:iz*4 3 q%>pDy?p:Wo)}5\.2`þG6;hVV?9bo1yl%xFDm9 0rV&HK}<bEV!쐖OwT8ǧx)91CoDzZ_0?iO?0)„~wP +#BZh91AY&SY#-b#%Pe((00b*#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%﮾y÷*h5tink4:y=l}kS^Y+ۮѶ'W. v]ۘou޽t{ݳR7[=fQ#%MY=uӥg۪<|*{E0[{#%#%#%#%(t#%}@l˘)z;{^f[V#%\:=:zM#%!J#%^J #%P)=yG۸LĻ»:rQ-ۉJkkts#%[؞}7['{Ԟoowz|nݮmQ9{}}#-{#-!zbi*2jtws`uҺn7`lRDSFSր(#%R #*e{7y;[LJWW}#%(} m_]H}wq@{3`:#%k1G6ꖲ޼+s]ۺ=\wq҅;8ݵbWtwnn6]9ۊt]NKbM2­4ux.٣c>^#*Jf^ljڵslޏ^ze^xw4un@{^vN#%PTT s Z핷vn;짼y.vm%zWt;j{̤\c#%r#%#-,#G2E6vtk8ևڕhvZFz}>暭3e\+49>Jk#*=^pkܞ}>^io#nl{];_n绲>]7n:](E({uwy2nuʯꔌwSq#%}jT1ғo\ms#-Һc6n־d{ϭe(z:\Gml=:h[=t#-CeG#%ʸ{{Rڔ#--^]fzxݺ{]êj7w[t٥ ([M'vݲ.=1{Xzk>xaf|ё ;'gn#* k/s72շ{;tOt#*O񳆚 #%L#%#%4h2'jP2)&!A#% !ɡiIL@4ɡ#* #%#%#%#%D!jmO @MO$Rz)'o#t+ʆXxfZv20"M8'%ݳf‹B#-JL`I"lLhch z:t˽=*/Yϱg5r 61YeS\RJH##*۠։I^<|[#7#-`)f k{lS(R;)L7ac,/%'{8h'kb(ShR(Jg64T\م´hc72|OwsٹK(F KFEcZi(RAsnCQW6bMQ[ Q@U'4|P[Lmːukޕ?T y2FmrꠊXOUԆ\2IH#rxהq$DaJ2W4j&wܣrJ2IG}[l2sNY M{!ch43leƉe#-(ZM?SF4K̆+TRnzieTqP:@0ltՀIK51ha2dnQި͑6GQ{nZAMũJ^ݵ0٪ͼMFx|6Cl#(h&<ν!e &uoK`'nnF!9 #c*w&CbۆfR6z5\tO]J̱e$):4RGx_*uW,Z6LT >]dX_Ŕ+ d 69#*"ƛ7:cXfLR:uѳ=Abĭ^JDѧF\m0zpa58Oe_+TTQW#* ^41X?aٚ" L#-^5acR|;+A(e#*2R)trRdJr墶/]^Ȅfd{]nݏ14&z)1h6_cPi-i>v0d`4Eh;RZ?C2bfV}AY>ζ졑}]_Ӆ"˜,6m)JDChzQLUiN-wլХ²SE#-lQ MZd%7yUXTF.կ)wQ6z/ńޅiTV'Vl$#*.TKD{8:n89lXtA)\s,#V.!}yUƻbV*F2<,"#(̍)cwMWu@hkZJueJ*B#DTҾڣvW/ ߿%!4`,Ҡ\wՒW,nHrh|l>ܷdA9:_bj~ٺkѳu)ҫ‡'#-'&)^Zֻ8(Q̊R{JxF!:fsĝtN3ZH;x° gɵv;dfoȑ(^#Dž8cL@}'2_۝^E`dn$g esZ=]~Hَ x~07UXW)vT9 0D*?V1ښH3uvB㤇Dlq}|\i:t^ϕ`uMS^9<-Npp?',0#+GӔƜ-:Qٙh>A8{ƔL|"=`k\dfF5F ^aw:9dkrlhmqwq{t##*c'ն|z9Ѯak1gd#"O^~:hgh66'x0N EB#*mA8Eܻ&"nOB4ߏ#-?Ugwu `oUȴziiA);փÆm#1SB_:$nkS4}̆j2}<{|tD$)z:}׮|_!7/N7u1J*Bgi;oWWO5}wSV%T4fH*#I96D&7pp=i<$0 1,}>LT0$4ԻSTBQnCCh^VFTOv8a%c? ^ާ&.xKhG?i #28tzPs~+H08L}S#7E"/5`"1FJUzVL'v}8"<*)Ϗ`}+mnΛr<צk8S]柭q:iӼ*n(I .۵3"lwl;^S,Fg?9}R<ž\KEԒμ/H"")C m쪤<=ۘFO#^ hcmuU6G+A;Efe;\CYB2#*t4fRDb)\:'?֏MHe(+vtL영:]ƶ& :F[mݙh&-#-cz~<˩nG=S#.oO粗0xKD|!Y5HqA[kvv=D8^;[(/c#~?I ᳏X[c_s c:*!@2,S.\XJ `5zXF|) #-^rUP[7. s#-b7sC\~kWڃw=Iwb8QURfw|P9+*4"ᛉefQ 9&3yM y-_UiNャg הl\Kpp?c¡.}jZ?f.dV#%R_nuJ 4^E^j@mG%t杚F0p'5 #-F0T٧ܠ1Ħηf SʽwlbXa֮tƩ!aܻleC>3<Ҥ.jP \UpCr8]ҋEDžqluƜXȘL4{MGNڸ[$ս֮?ze8T!_lj% 29_R㋫muVFz薙;6{p:.?Z'FcZtw aĭߣ%U?tvsQGMV#*lj9;0#%Ct8FP#+t T.1bUУۚR4CL~-t7~"sUԺދ\g.rQtc1&^.u"%L'}CѷoVjpo' }Q48nqUG_p2Tk5PݥNqh)(R,Qk]R5xݹN"W\uFƬ$%1(|ogF7ē*I%"9JW%:p)pe=OQIo#%'mGN&w>9,Z7w?87(O2'ϥk[_۶N=P+^!TAN1Zhb;{#eJcdٷ=;NΨHf:`-vF~-Ũ#zs_ŧ;n-n:w",rץvơn2PWȰQ,-(8$<ŜUeq>k>bmf\rVFF0K@0럯JVF8W}>26p|{yej>nD1X틞yUT{;EңF-ULȳި"C|20t,(i5#*VG$Z>@WSq]sVTBg1A N zd`, Յ,gڨ skƐPosi#-@^mлIOq1=rӺI7*a#W҃Uz+fy޿-/wWL1ִ KL' ZZ#*#*'ns'ҠKbC(:Q(H* 'R#*<$Fʁ/U(K3!ßxsl.|IWONcnA0 h#%Ԇ(4=9 `(e􁿍5 ML_f-]3_QX:8K!Ӳu:b-,^atoO,] طM(W^uktպ/ '=Nt=`ZhVAG,A\m:I9inߺ,٢Sr9Tak 9Z_>! 4q=2 L:c&8SmEj9+r9jN@RfV9[\((vP#%6=v.8*Y`D,U GE櫜OжL5Rz#%7ce#3N<_0"?#*<ҿq"/t0)e = ^OeG&FO,*I^\x#%xn 0No$BD=ʽt@rܤf8xxWUQH]Hm#-8G$)~nqMH6mx{5y}~ꎟ^ BowD{Ju}u;xurХ6Cf׹ E@#% $e~B%l_Q2tX#a͙hzn?r/ѤGq#%/8?[ͫsrf!L7Ȣ)>~7i#-vQ}>B":]wϯH=PY :ng)=~9;"aɿI{a܏v%]5^&0(=?ƚG.#% h:`L#%7; V^|PFlO*31*bGoCdt&~0ԲlmS,V_7$&Hܾfse{:UuDZAeT]?<`mFc<ṅgoڦћ;Kv(-EYS:CkY><}CaFp:=?H lk6"/O怳sޡȏx`H#*~|ɀ`i~k&f2lETz|<].?/v#* -G8?j>;32 I[GS}1'1tk-rmX #*aa%zoJ&(7qTX$}ツgCD #*]pAb*t#*?rdI|wzZ!}gwJ2s#*Vsb S.k㧨e[}x뷩+S#%!;F4LwP~S)}o{zsN5^1t?cB'$;DZ8&#*{ZLA_xӎ ?!TڕTF,j8F's2.;FsRI}pc紳*.7%^tB:z73|ФFQjޮJH%GNV|cۡYq˛o}c+<([AOT_C\M#*x j.l#*#*l<2xe~߻f57A!_x?cc+kl70,Ox(E&IXVg7C#*uXԧFB$#*1_2"?a8k$ic!Q2˻w͋-*",1Par郭cHGGT>n%3U6EwTO6Ƙڱᝑzl_o`8hx&#-,d^yz#*/7f`Y#-h=#%j#*q!, 1 ( `2Sm[~ǿ/ms>[U)1Jh?-νyXj\gRZPuʋ0BeBv(3 ޫ]1 &{2լ);wc1:s2”n*D#G85A١tWcP42j,e 4hVKD"e͜=Iȼ]7>JYbSQRp#- Ź8zN?Thsꆄ4#*Pll;f2,I,؇F]0\bPVf]K]\K*n0TtX^=ݢ)HH╎ãobѣU$2 s4z;%O8b|{q&=t8&ab#*poc aў~_B"B)=ֳU1\(t=5Af#%O裃{k .m@BeuшDwk2wwZx4st͹hBlN"eoWǕii4&j"#*TVQ`f{pLM LA`i#%{@([7ϟq& aD$l;)QII2Pu½zȃ^JZp54?5.Ql.6Lj/77j^$$?rsK}E#-*2-¯2uAO7&#(@@P{hw;tZ2 wHXR'Ӽ^RLM#-c*ɵsl؅E.y#-?3cE . 3^~\Y{1~١~["9Mb.hgUbg)pK1K%CqLS`t%o1z%օhHԱ4 a)c#-|G x#%CEirıł6PÐ&$Co#6/.:_~H㶺 _4bW-_y^{ 6ƙbњ#- 2%Y2jb=@wU,hY8tH#-l *B#%YFk}+O۩lPVz삗hŊFi^u2tSX.#mH!U#ԀW#%R፩`0!0Nͳ?Um@^y"~IǽޛkTLϲCEn+^ͅ78<=#-*r^m#*C X.Y/:6'wny;5L[^kS"dhK-]Ta?[;|-?> j ްHwlߢ7  cmx,y>s4Qvv31h7f坸8 t0 pAd@Y"tzpV)U1#%l]N)].C=tچ@}?GU%X*;`5b"Rp;pCǍ`yQZ(A+#%Zı`q38$>^?'d"!=abGEk R7ʷܾƟ":.Mm#*2! Gyנ\hJ#*G:P#*u8.,A,;(O4#-ѸZ1r>!TNV&9Wt#%AߌfjQ\D3m˓*DbdPiOI_n[E8>׶UN2~$#-ԘxQSOv(O ^` !xK>ي^~7^^gw@#%OuZ7=;yeCi7kMͦK:\(6bZ*U=|}ͽלkFěQ[nmͶ鶚#-DP@K*5th,~ݣiւͪi wR刴X=GG #*|hh+ۋ #hG?_̽+Cͯ y8T9i|2>0PI8\z24W)A3+<;d<+Dsq_rx eyvӫP |#*/۵+,7=u]:}o<-g_\~ӳٴYcؾdX8 #$y˰otIKw[̀xJ!cfRF,5F;JS?O}9a.Gc@#-\QJ(bv 9.uUԕ,-_AGg(ԛ>4P](J@J]˿ۡ\1p#-|GĨǭ;7$JKݻzT(#=NS6hEN3FZj([nr#*s|K#*Mð[W>D&)0,}#%dmv ,PY^4snUp5t?e`ѩW)">OŽy)#%Sigd(@̌m_.eAU#*d(@bfF2~-CpMK05`[efQ(G ==[?s8kVB+珃7X30~vF(,>_*8 jPOjCMHy5g„P!j1T,jq<_#%*0QM72(8{5FX%2C3RbaQ!?Ki#*ݝ($*Rh!0VJdͭ.*S'"7f|>~ # }_W,u3Zn2&?w= uzú~ԍ_Hw~f iϜ/4>ܯ?2[t8L0u+9c#\&W͢B'_$OggM w'Mw-˼rή6||$lu#*05f/sDcsU e8*ak#P#-X?i7 LCQCab/<|peh&v';3 ,(ɦ^ *Ja:P>0L?FݍnC2>%28FsS_ܢB*Cy+e{A[eX,W[NԝzE5$Ihց5#Jn '[OAaCxH^^J'i#*1 ۩ TQ_#%igG#<,,$p_KCI%._6X_̈·ث8P(`7IP AS$!9%.l_Ǟai`hOSA:Aʹh D(Q@mJKb#%R~J1A@QDHx3ˮ糫S7Q;yuU:Tbk,<~9-`]F,ƒEܹk޶EoOw߰oK-y^yጱp`?s|MvPdž&<k}қȴuW?Ûۯӣ?)^˖zOtyM #%ݾ}OTV=:-t$O_ @2O߽E{t;/>~O㊛$%CyY1[3:^mYs}*tϯOlQƌ4u~skdt9U[_F׳Dnc[zwJwF.w;WZ|ch㟵p묲&#*JNoݕ4lS8|9~1.`sbys֮i[xٞ ww=!/2cTտ<Ͷ(Ga2|lNᯛ?Vbo~ߒ3z^P?+Wv=882m!A~:^9(`:l%rkͳgc\3ЯMt{W/~U=~ENu#%&@x+϶3gU9g}'?O~Nݣend)W7 ~9yg74?!(=ࢄ`s.r BPrxgcͫT*I&BcG#z=;o#%EQ:HW/(A#-/1HLg׭׿{:To'9|OZO.c3kc瞧;1Or"ճeoײ9 R'/{w:,p>]=aem)Gр#L8|Ed#-}[>q~ߴRnCW<9U}e|`;D|;mjwsZu.ʾv..[e+ow2/&_[❴}`YBA<;OTc㘾b7g:GSk`{E=$fw ɑ+|j/sTJ0C$3$KI? EbCd_W]tM+я#*3ٗ_ h ]֎ iP۔#*/HvqIlmLEo<́\I,t9KP)ѷ=?0M;aa4't0x-cs4P?#`%Z҈牓F\e`ӕ&H,7|g"wrEX{Z,8'CNJt]_3T*}u)8:I>_!(FMtr϶]]5ο _ҤrvNy&L,|a.vG~H#&/0q|U:mee;Uǟ-k6W^Of/0G8uE]8~q$+w?ۢϯ/٢G`D!VDžI\:ʶUdvV{#\s h8D ܂$: ǟ?uz%<HYE6*뎿g@VB:Nȓyu{zsZ\yz/u#-ԷSOɽF.nmWsOvV~S4[% Ey_XR>ސ/yqxq{۝6V*j"2J)k !etu^y)dFTXbQXCvY͕=9DcDQQ" \YJg>0s1-RDalZ-A#-2AnI&`R1nB#-)F 6`A4 {Mcm#dL00ꨴq23HZ7KkJXZkưym97FPPRDPSQ!4}ZěEs"@Pg4U]FFZF1Vtf |AI$ *Ztz z]z'z>ewruFPV/o(P:r}i{F[Nkׇ=prx$eEKwbofH|h \=ɬ[ 5XxH9~]5}G[vlьY7#"= >+#*P^_S]y$$%#-ק#*nhab;>B#`_\֌A#-́A%=ETnOu6!Fj U]7hFXv%e Q??gðh% աͽZv5یmY%5fƤ͠B%2LKDhkGL 7CN]d;u`.C(MTq^ }a{{uт.b_y׮4Q'A-[~WE;p3D8)!\,XK{}!R@!#-Tv<=/9#%:t0gjH#Wb C,ƛ x_ge703*?4`.ss#*h'~Q]ՇJtzA35DFXYm1bҪ~ hi ^1nEtH7^ZQ"TO#-Ԉ$dHm745>CF=ۼ1m@{b(oN*y4hE5 #-.01.79t;q_Glf" iHۍˆmDC<޻}r|zEj x*U P^Ȭ:#- aΰ* -+Agc@ <{{ǾKQσ\Ɗ]uq׼PqcX0 Fymuن-2xt[dXHQ"qVN#BDcMxQe8ֻo3f`qM%Z8& eXPXJe@0ߝ+#%hg~a8Lf%ӜsE2.lm8V62ءVKςj ]d#ttp@Vi%v mb'!P7ŁPӌ+grX̑FGFP2-c+P)2H`c'22&;v)b#;w:c6s5n<-ͭops#4V~4C"?ieV&Q߽lCmUo[ѷٟn>ƌ0ːz U![iKxJsQ|n5]yͶ1q}3Q&䊕uKq{Z$-Lcwd; .gh#i^x{5lfƢb$Mf2Pgb>5ejl&؅ (C~6?سVbq1f;Ģ2lf&V866-k<3z! 1I(D⌄Fkąxv#*poq6㍼Pá$9QÿUCF#-~aCۜM:౾S(;e2ם15WhM~ZԺx9VUV*Q)ǎ*;c`omFqN ! /v獦\IAƆQ#-:26#*];C⍶볺o1,QG7dR닌`'AXnZkZ:1MWG̜ٲ4rb<@.6bscdNf)LkBÐf$ RW[`XVC6n]H'mjM 0y_gcbkAߦu6Ir W 7s~t8trK9S!mD;[iG  rqB17מm[^J"N'"1$@XdL)iSnjm3ί\.0*I-7D:b"c-6yc4N*!NmHY{X>[\˿:owۚ]h#*[V8L4?I#*|qhPpjPRkPUD/aXO I4MgW,_Aֺ-`O0mB&坹`7R>5(|>Jʍ2XM,s Box7^|Y#Π.&\0ۥI&SINWd;=/fRͲcR\sk[EOt& {M鑨E?`y-vvfYcjP޴#*F#Eweyy1yK^K/+#-֛a%1 B#&(>C9ы0$-Ftǃ(LS4ﴡͨpf YPAه3TtWÿVnv{#jO':~!4 *dt{Bw~mP1bYcN=9M/PH '0>"<#%YΏ7b+rlmvk3wA~LkI/1&X^P&D3$,;.:#ГW;ɯ`zdTlQ݇b:΄aM:wfMt z6#v(|_e ȡ]jR5Z9lTk$Zzd%#*M?;K\382֥LsM ' I+ %ܻGRd=^Y1$ yA#Gi@'.s' 彧XOm-&$sl()4 &<(>eXuaz> L:- @~p^Dqן^Q&Onfʙ`4S|  h]Zg8dkژ{lI#IhEn{zvX>\x`71ߔŃ6Q1F0h\%B[iӮO_Wǁֹ#*y$[#Ꮌnths2x|XoZf ꎲ)m|3 LJvrrOGo$ܻx)kbeA=zn#-gyO{)f1r!TgqvT\diVd>ϧ|F沆EDx>VAE,dDQhtsc%[R f{3*Ak٨_:tQm|(xkK|u}uJŠBӔ~1U$BO3:_D혗ߎI#-Cռ2MV^:|S:0jx#o~ /+/gq1$7jScSLkYsZۤ9J_zIۢ͟ 8<ޜSAͽai;fF/H})S⟇~~#>KGw)[k"Qzʧ(Xy}&r~տsnG?F(3R+qnu-Nϱd,mtӷۤy#YE M‹Q+`\zq;U><)H戧)|[Pf|fNԭaaݓ wldz `TQGdyL7_q oF9#|c/="4WG(lV3"idG#o9i ),SyUy6c}H|%x<'#-:%[nmozk FG!r85mEn>)pCy9-b$LȂ߯ǭz! ]kДơإؙRĚVϓ7Zln5`U,- 0x0^W،C#*? dX?mBM#%R3. s'ʲām^b*hR5Q GQ]ucd:FNx*=$5UgOtV-ez)mCtMѝٺVݖZC#-& s;UHVRtaE}Vb#*Wذ+E_2TEkjpU:'20kѮMLi>繢 wd~N2 {C(%U1gXn-ssܕ;X]V8AmV#-y#*.8p82v9\l}|l1kYJx8X^sFw|smFK)! @c6V)Qn%l%SNΖ}/eppy(s->ӞkdZHm`.ql ޮ/![cnB>mtdmzfSshxj~ueqtk $ƾ6\uz̹&jioPۯ Z6A4I]x%ֶ/Ġ>,Ӈ\q #-3u nTgvbb'[s΅d&gV6=<yZ/rftmyi-jYt:(Һ)9GUM )'8p9=DR4)vBm%*gu#-]QЋĕ.լ)tlvIޢ-Κκm#-m2 U֍PsF婱3{N,͗1ڶ?Y#\>WM}63t}$11Bws#*'sXo7hbeuj2OM#`F\ `8X%lw7յ-QdYpQGꃅte].*~fTxx] 75X),o:}4a]{19q{}*UdfYZK4`'UV7USDyHsl/e*6;VYfeKy3"d=C#*Y^}|sda? r}MVݳ;`Dk岏m+T Q[/=7O~Ϫ\Ҭf#*#*P Rl(TEE&XǎUW_:#[ se\f8pj]sJU`HQdF#*jL[al@}tY_ XFۄ۰Z\.5,IW{VG)څ"$u‰++⭴'d-d- rcU^͑#*M˃Jj [9阒[J4|mZk%[v-\)Et~:\(+{]Y"];34?Tzv x5R"H>o/,Ud28#*.aͿX5 L=ljb#*.}/yw9d`YVPn6S\bѤ=ЬM;&0m9ѪzZC1N+g#*#E-_+g~O?_Ń~OY'75*=fL~xV-h#-iLQ*Ra ٨Qau8',V @2co-!%gX7h1 UEi’7ntq͇5KV#*n*xs56B(HT{z+O[yx%$[4w.UG$~X/Xss?'i9/15BK`ꬄ,ij{k7f9ӫrhʅÏ:/f//xN;TǯOmuW<ߍ3+#-w)̴6)0'Ez"\,z4⃞14ZFH) E5+ct,jz]77Mt0äBA:'[J!:B`-uc {L NQľ#-ʦъjWy 6KJÈG'2^4ߓB¦N#*NAS;s=CHH\I7Q1QWV窾7/zͶ><7o׈|BeRglZٶ.oɤXIb)s9c-mJɖ,Μ6=`DŽr]x{ZJ~r$$o!j{+#*@%i#-:_ tӏ""LtZW#*0TuB4G6N:Rv0<#-fygoK#-  i IȧN6s%Ηڮso5=ٷP9mĽ~Q#%e(۟1dSz 0{}YSY"%Pʡ4H^ вP i!~6ql%b= ] uPDժdV ttکY#*\/@v0ZqWPrY$5ƒx*}BepUe00݋nt]4(]4uZ6 պh7WBFZߝ(r}\Elt sIޟjtH[~7l#7I2]_{kB+&}trX,#-1C͟jISe~i{W{hdWk 8D e5}8۷`2Yok?;x.Y轵1]|Xʓ<+eSvpbr޿}[k]'湚kGL,R<{[yt_h9S;G8ؒۗ5Eqpyszg7hAn؆Uض#*W~-dvsw#%QglcrgzkK lwn]鯏"Bdp#*bu<~k1qO7)*#Sg7_ĉ:rW*z@WysA/q=nBhtRߌM~\s $q]C&v>NC"JHr lx.R$ڸGE>Vzקkj3pV>JH\>3oy}'nuP+1ѮS|j-dx~]J]bKI{e*Hړluէ@br2"Z"@46p+b@}w}Dšt"򐰑k!{mD2.%H/<{xOW#-$ۺ驕]EH/$AaXKxڻN5qBmo4OZaQIkg@2x_9/-ˡ{ >,|ji"r!~?]Y%zfʭգ>ʤ(-Jnw;mDQGoz~z5Zw ;! fR@=ğii8cm3(kl)ֵn%bRf%ER1#3$ᢜ)T;vkm† #*dEʪ"al($8&JLm~#*ҵc'K Q@T0RrM(e?v0~qٶ,- : D C(]!A6˳6^HH [m,Z"OǮc甩ĜTp*{~P#-9d7ĵB*@!&`9lVR$9>Umx3-?ף2Nr^9P#sݘ\%k1EP%^lkaK,M%%$%X^)Js 'qÚˋFR8BB#-\E&e.\ګodC0`P#- 6Dp7E]X{lmc#*[9'rXԫy*)$-}Xu/f2<͸#%m~)TH)aX'!⢮A4 G)z贁*U'N3gVS1E9&{~ȸZ*:6;EܐGpWgJP\0F$àkJz]FS6< L7/>ãcVB0ka }>A͌"$&zǭol]m穉;yanjX#- a#-QĂ??0lu8'.E8pL/Ls&sHvDs:W&"ݥL0$ݾTڤP[EFiV#-nei#*#%Sv FT,vH&T93jIȀ#N.rkZv=taDl3Z$P#*mT(sݰ=% b(P&B:߸turk[TeqXÑ@:rݑup7 #^IID߱0#*#*P\pYё*tMTћU%=hPYb|:k5T%JB#-Q ť+vNݫ<3)VT~9sker'PFTӏwzf#[5Rgi~4F߲z%P#CrMe=ֽz=l0dVA>;bϰ*P< GDsx2r:ȚJ 'ݰݙC U\V?wo>7!)w9?3K>^p$֯iM1Ke5j)Q9(d.@P,: a!f*&PC#}K#%[h00p A 0P)`?\eDSaIc"*H%#҂PҥvoGl!i@TMK`K%LmJYHBn|D3!A,PpMz'Ij]à HX'u* byy2;76l.Co=ho.cW#*Tsν9(%0Yɖw"!A EևXgQ&ޢ{{A\9gEpH 66Kmҹ$tpV"m9Hj`PF0PR(V npm"&w&$m9zE贈Ip)\uURp!eҡ`|-7JqQobabIuI\4C'U&އdGvaOqT%1uĖxì쮇n.jm5M㪲Ӻ/1c{G4/382zۭd! h_#%6uHC 82{ɸıX%hɢA0L*Y3%1cy/}jF`UUFTF?,狼 滍h#*7X&)wD5ŕDX"@6@$EPB(SY4f Ϸ\#*vZD1TNF#kg҇MG#-]ЛD\i*HkNxhW{ "#HDQ,&̒T,J3鲎#-6 (nC}ʛYIƹ"5a29O[Bzw3ޘ0 m4#23xXb+NlS#%;K"qJݕ[*V[+Xվlmkkf0 H0Ll'dD#_IzF.MF*(Qj6x"c+Z<:~p-F'cEAܛφe|8ȅ3p+4 /)I YIdX#%G@lpCoYlkjq02/iᢅ9;4hCpjQ$RJK_#-#-LƕEWb"OvfX@{lε|MnVMcmݫ{Ҷׯ_bZB C$LGۗГ|}vNޠw Ť*^UɵO%!7\/{~ymLQ5U(xQHPUzŃD#-) XQiyyUҒTCs#-`:wEqO' kz5+L9 gT6#48CNhNn}jqAg6ОO_UI(HdL릩1d^#-7z#L@8Ѱ-dgwӊyყ=t0YK7K:9kŘ$ 8#*tfFE r5ӟͺq 2r"n捙*)#-VdM"hY9J#%1AV ?(mT|84䏬M @m3F2YQbԇzB\4Qʫ#-aEl 5{N\IX(%#-&2J#*q]69`((ӟ3vwW,Fq';cSK)EwWG瑔|o-lo>mggqQnG<@{40SΩFtUt}=:$JjhJiAKY3$6!PxQ6Mfz#YA#*c4|KsϿ'btC|S|}zwdOz΄ڬ\4Qw>i- /1Ɗ8)AA,#"81B]@MuhzӪew{fm4sh/lq{.6]^CO+FǼ&`gFibL\ |X-fw!׻7B(zmt(ڄ.;}T0s3XfX%߼02NdHRA`h4g#4#EP[$<,gRqaw_@{IHm#*ԯvrl;hHP1!|64ˊ)%#-!O·sCSJ wEצ-QX"ƖKQ.1 X~Bڸ䥑Eb0#-UCh#-v~^A~N}it lϬtSgUJVzܥ#-@mMl(P$v枲!2CTxOnP39z0Qv:heCvw[L; !}O76jfl&&Scw[{m鍯4inRLdK;n}#*6 7FIt5#%@Lxp㥽#*F&ǶmK2G9n:y@4C^4=,RQUsɻ#-U)ա!ޖNagW#*r徻(A:ꎜoLIW8 J_ù]8&r1vgĤKO~<'̡$e7ӻ;c5vkP1.vwFGuИCdPğP#**V{qrI=p`҇b}6l#%d#%{,P)qEJAi(⧘@ M7H&iϨz~|>%9|L<%Mımf_>$}QI3aK3#%Onr^?Ŧ/(!laH*2AH@^P1/~ `qM$Y}.bKaQՅ!Z#-Qr1ל@7=#*?X?mxYn8 $0 _?l)ܳ*!4%dkc9Ʋޭt-r"1|QBxV$G}yeyh0@ /Ylf1;- #JDOIc|1m7BJ#3T7=z)OS(9x. bBMY6-Gʯ^vnPy3c&?g'a6OYJ2ilHS;D!V ;>Bg0 }]Q@tj٩AQß^무Ka=wz1}i%;S2\T`m(Wl g+5W5:?ko 7 &z<"z$4(CÙb#* Gov}֟*-/V?:~M+nHWc dB'`!(~<.+:dӭ,m$WPD&d\~/OD8&إBE&?:sUWIkV DE NZV@L}rpubmg^1̉>&#-n*H<\9h^=#*Ӹxj(1.*bObq1ǟ!#-Ųbm{OZG㿣-FC]ByLJHW:оJ,߀r&0$A ax._C)=<7)綌Ǫg|e!׸?^V-qQ<wf 䁣(c;YBGHG#*4W5<>Rj ahr{T@#%` __#%4\w疔à0%k1h=>>|t a>93{pӸmxg~qRh>>jNm" #% %h -ApZQ}>H|'@ݻ]: "#)4B:#6߇ҁ$ʱ`u깷ݐ<N2`xȞj+^rh;b9QAM[?%c7\WF{T6/\C4a:>&v>t>W נ9#%ciYi1g6׳iJJd 5n3u}4.Zxf#!C=;]l+Q#% uuA'z?#՞feӳWO%).Axs:#*:zo#%A{U 6ś#%Q:#%낃:Pc\0]\X2ؠ#%@=x#*l?^]9i82 eTV?1eޚvsATTȚtw `#-O0#%KAޮy}\%$P>6~vSr`ԢzyK]_?2h?}9FED+1u8ZGT3:ɓƥk?ZG|Uqws!IO(i/#-= qbxw|Uy'uI&^zF+z6^>褑掗.?_{Ӣ՜\ofQ} wZk8Bi{)5`h ,+;%d!&S"*ޙ;n4mFTGm6{\)@vqyf[hq}da'RڬăT0rz:ier}c4km8^yz#*TMbUy`[hX£2D|rgGasch+bXF#%)N#%(W#*4ak x8xm+bC,[sfVA5#eNbx!{|]SBQPT%. ,.Wh#-MJ&[, /.tCAc^gqΗuɤH"bw+f̹ ww#-{o=9t;:3y=X3;h(ɉ_f8\[Ek*λ\Ꞡx?lf}T|Lz䧈SWN~  c{2-a#%q!(OdE#-Ǖx#*}7 V|<}[~paNꇦq]Bՠ/8kU,sV$1M!#-5SzgU$J ACxmOj)QjRND:e9,j؅0.ӕ_bGoN=9OP(5(I~'r-vQsZb!|dJP3r)0i6UN_DCNۼ͌>~1+b68ߍt[i(@$IyNe.hEN t[PJܜsJPPbT;gJQm(,!ɘΛӘy?s 𒋗nE[12BL91ATd]f|^s'M [e6:D>8ZbX=;~l0zOɢ;ip5ɧiޝM&#Hq& ICo/|JLJ%Hʔ2 IȐe<:r%.ȩ=U̒úZ,\4G[+HAUdӥX@} j,dEd|`O9|Z1r)rMأOK;)`g0hŇ(U7tq~{_ۏ][\u>YCgTk2z;jwtrm*e}(bq|j'Y:d=g7+8cӎ?oP?w bDfN`4]>:*C_*z(VtAYHE6[jj9h. lc3~>,#-NjBSwBX7Đܭq~߮:F]z"pgTw+e>e>0ִ#-HvOԈ5hd!^ zrm#%H$N"ªK\#-<8?KxTP@)0qlPJD% J}@䪛s#%1)b&[_*Q:)$/Kyi6+vS7sgd-Jp8W]| z}x8җwKނE~xeKHI:NQ}`j]&6hz!C-n:(Ap\,#xwgX@O,%pRUWSRV-CIGP=i6^BHvΡVis܅ fYa咍Y IBQL6+ bOR.&{yu!;u,\x|!3RzԁeIsfi \;pĸ"@ A T|`xJ"da#%RBL{Ta.TxYkuS#*^9y2ᄣ*R\,Y XAbsq^sWPnf[HKA#I YolvQ\{?9MDZ.z>"*/#-0#%~z`υ՟_TNloc2QE{O`=<jA p 9ÒqȤX".ZCrgvqE_)IM|^օDP?UTҫNz73G#*5KY3rǀG4"(b"`jWS;u5o= 'ПΚ:۽Ǐ7OϨnW>s;I ˨^#0Wj<"O8-3JraR$#-KQA9+Wځ#-6H.^}Q$T"~O>skϋ#-0>#-#-o%i#3\/?JPlIO zeW$? in) _`9ʌH7N^\kt#%P`H#*U< D>mR1+7I UvjSA(C8ԞVK½צ9~zDsmr)DY[@A>8똿8 ,=lJg_#%UYDA 88K?3( ԡJ #%N UEF[̃8) D $`})yXY:@e63sD5K' 8x_)=(({;P'+wV_?|f@,]Ts,O > dRkЪkRgZ[qմ)F4Us3\YA}9PpM3l=نSq\%OI5] #(G@]AtFWS&n0;Cc(Jw>o=qxk6nӪi>KJ#%To}#`5M8_PM\ǭYuwn@sˏ6l~DNPVmH!|]74 R8T~_tw- #*rpaVw!աv~#- 0+c~#*@Zxǜa’Of<Z.?N#x2%#n:g] 5|[OT`ψDKP%y)UAD씁2L82Zi+ :8y] =\`zN}ˡ9)hޢ9ХtGewIzYkl:LҤ/nM[#*)M:gAP _r}:-7,+B;]!il4z>WBG;3ۿd[s[A/{ 隱≙6\{-Ksz\'qFndn V,~܉bwzxv-7e*ٹL 7m,ҍR^/뗛ouǨsoG"DC#-"QIۆa`8@!6[zH<3Ǩ¶28e`2!=}d sJomJ)a@9#*n?%}埣_+&=O oR>H8}ggZvK%LtۙWSP!c_6I*<$[#*zFz=/ PK#-'}B5&v}*W$#%NwI<^-on]I7#~3+]K{#R Lӥt/xPCTQ4U.$a LKz5j`$&!R [6x唄0 4-#%t_OA{lA2aj:;%㖣C Wv7gG0iE$iUHEe.CEQHJׇ;% GL!`{~eg@fѣ7a#b'iG+W Vj3D ijF?ģtx."Qsًs %(teFzX#*kqcm3=;K}qg 3'jh۳i9^D6{PSS&ڎPOt:qz¶kT#*,3!s(PiP+37oWaSp0"09^%&A2GOI0z SꍲٯkY,oŝѲZf68 =5DY=YPRpru06fڹ$H__!>W$RvmOW_|C_}c"UFsPhrS.W*y ;{!Z*oaQBh?D_3aͳGC٠l!i \DX#*"_/VϡT{ p vNBkc6ʲR#-LǺ{a:ha8+1 [DWw΂{GC;SLC#*83d2%֋PW|qﲈ-ضshCEj"Nk#-: ptL\mCFتs7 ϯLl#*hX60!*m V[.q`vŬ6ܶd 5-{EE=Ϣa}U#-Է\oLWODgWd zmCA|;&Bʲg(9ǑA|(D.LKkn|ʸ%6?իg}٠^GBvܿ:P(>#-PK->VJتiHcJdDtݔ(8'D@`{~AwZ-xOV9f~^^PCghӉNg㻷sIP۴s;_oW~R; >MYT*Bs)S0e(}-|ГxP", zrJ*bS%԰W?G]6%#%j#%4Іgy{q>]ߡaNMpJG G1eӵ__TA3Ciܗ ;]ݼx4/y,/UN/,!nP=`Wx.uk^nmþf^"9 #%)Xey~=?GWrkmd#*P6}unT`}ۡLCPh4;!m%7itKKP}kiI{C.^/C#`A_.֪-X Nx8JH#%#*BCi#*Naj+]XC۲4)<!f=%=>Azw`s^'_oܶOJ;j0#[GumȨ7RYdł#-0r\siJ0!dQC;hCNㆢͩ8x5&±;&vyZcXK<BEZuO}{|VC=BUނx͉]s7,2W Op>=Пc~9ـH_6Wa$Uk_pv*ZeTr:F̛Vϭ;[6;٭oۘh`ƚZ#*H)OϹ940{>*6@⚊@l}nG. ɑ:0XȶQ_r L 4!GX33_ݣ};e! B;t8A/<,$'i=gǠCO[r_)nQTU0P0v;.u3?pt.Y?w,zc VK`+1n֜tgx-$e9UU5>Ri 9>)yrl#gLר;:`K-G CrT۱#*ՂKjm}?X} ?q~GNt@K}xgi#-%X-Yu>lCݭ@/>fMM7iz齭/lh+2ޓZl8|??t b{BX/81a:C[ʵ= @o*-^ՍQhqC.k,hs`#% N!&G7G&C@]Ms`C!AtCw}#*4M9fة1s9@ eKQ}! d:5#%jO=fq/ʃJTgy$ D 7.$A)8M#%Ó_F<tXfUXSQ#-3d6!}员i uM^3*G@@C`*d~O~8@#*\?%$LT+%]=zI)2۶ lc`?Nd+ z,e3D6FOZ*r#*>r[CTF2JD4YxI$f)1S.5%fjY镘G^20txyCCZފin2#*ʂReX2L]j-[ko`#%cAP~_jrX7:!_#&wh)E7ɴ87 ^fİx1)@)2>7Ѯaw4jN+ļNc!J3@Z#-(had O2!#;<=l&X]}w#%Hj Q (Ň鷎89#% O?7% 쒺K{xy~}LJf1WA,hh_qcȹg>R{s-6shpȡD?rL"4o~ô>#*ARc'߿?AC<#)@#*zLIF`E=|:kzC{#*9Vxx)?ݻzVN}"Ȥd |s3#%@O@&=nx''ajo8vQO>޲?yd+'QLȟ9^ҳU65h|CZ }m lئjI'kU@l#*#%Y +R)'ڊvhVGg(k#-KNjh6aӿ1OA#-8W*mIX&p!;[X_3c$e#C{΀v0r`9Qjss2(0d$al;~F#*pD51QGKp2nD,pKU4pṬ*蘑)Q6i43]ԦӺIF)B;4LJ0ZmWRo}}"Cٻs,C#- [lϱ:pn\HP΢=%V XeRkӠ#%wg=wC>#%Ì@<{Kh )SjEy|>_}~̾aR7Z'K{$bTQRP#*(j68`:m _ \*TU!*ξ;v|SN(?&}D)%#*m!q$xCoP%!i>/ҝZ/Fn≥ʵ1#-57y'3?͚.8㺔J;Dt_&ZWzz,a/TJK,#-}:X,,X!*B/1tZ-o8YBu{Cz?wVD!V7r 7"1.?4bTej#%#*Xᅥ f`~ kA*n#%|#-5b!! ɀ +$T#U Dt] !9#%tMCL?}yn?C?aȚ >?:suX+Abk !:@@#%k)$SS%;(l,K%'Yw#Af R!֝[ْ!Ҭ_3J^筫î6!FFo(*+#* +-מeЃe P1?s`{?W۟IzA}O0>{#-zLg6;R9w7?fSd?A6;REF#%P .d֔p:E(ҎJXZO%/ z%^<ݼ#%Q8vOAxEiy)]'fr0Yø3qC ӊCGa=#%y:+Fy'ukۚ. 4l-*EQK'ov /̤ė=đ?`ipQ޲T?' z*zt,m'osR_$I B-312a/ |=UIv7C3f!@.* ˂^B#*tUO&e2HQ 2 @6RqMp$"=P:ʒ3xvڏ0<&;:vuDE$!?L> >OoW#\iO uǀ,+I99|AYAIgs=Hc@d1E6hn0$|}Y\E P d3##~e.24z;qCQB"JUZ*18T)jr=E +;TiX QXhM&aIӅ$$"׻e E JIp}n40ǯKe,%ZE),'xX,xǨ<#*#%~б~ y=#%w^ށl}KET‚gӞJuOw`^i#$v#%fv vo99$%H7ژHI"ub@`ޛ[G=PU#-&'a #%(BՉqfx4E8'`f,ђdȏ9YO()Ax$lI*CC2%(/^uɇl+/5浦q64cwpΠzU*dqEtOQiNK֡l8t;Kc;ˏf󁌞zHn6rVJaM#%8&BAu˪pYv3Ӯ P ʀq9pc9W7i;3@iCS`:)i8jK$D`s8^=z(ws'Zz ]ȐJʪ%[9g#)vYB<}TZO@vFsѝxTтA'Oj3%r[ (.g(ON 4-`|̡^$PPo}3<@z}}Mg?OzԞ~-\*T=]X&"$$^࣭:U:c -WL4(xDQcXY:;u?6:'<)υ8)n쬦#-Gv@v9}fڎHX!(5$H|#*C0UEɟ,`#:_#%ޛu/IW?϶C #%UEH%QJXVH}F #*۸F#?m#-3  u7lE%mh?W4~4G4Bֆ6k(Po/zU1(~: nx<"4 bL=a^ѧ3B C ."4==g3qFP~1}}GqLն%"!PRxN<<`'"2%57sX|`Z$"X뻻+wޕl(#%cBa7NυqλŽ"EZOQ(4#*تm ]hn6AnfuOŎWWBSA]O_D.?CPY?SCU*9ոS^t67>w!y>J;~S#'Æ碠Ƭo@L\ Hz-.N2_q=5HiP΀t{ɒԛO}AKuV7VBK.B4i0B X  !W}(EvU%ȑ9~߁Bۥk ϴm`X>rs F ҳ#*{zNO\qK_FcyޖyIV DHSh['y۪yKX"K(J#-+5]NxsCڳZ #-#-+Aᩅq#SߡQM!C[1׈0?ff(#-<0>,՛3~W3.%Q:#-[h%n@?`٬\#*"~!VC"yP?O۶,y"{2[W?V-qU+`/C Q̒t:NU+?Ͷ6xׯgUX'c}ǫ邯߇oʊ9ɿX2h۔Es+[ ?&#q#-UA󁌣gomP{t$X| #i@^#?<%nQ!;IaN/#- #*9_G#*SP4T9TXYz%zGW^:iYM~rG7 QK2_c{lAnLL#-#*՛ qJnqRrJNxVtěu'Yq3#*г>Kx7i3Jc;6S/glu#{ֻ3\b#%xiG30h\BPOZCi_3M;;tQ1̸#f?r˷`<0'w1#*h]ݶX@sJxLMvU૔6wozJJbh4=]B 7᙮u9VQ*8Bب=FمR9ߊ] a 7< λI$ #%!|?'~z2#Qf[;H9kX[45+[_SEpS]ֱ^V:ti?UNt3-mU6K2IL,K:S^b,&W­| v&w(Q@ VhDKHI$0,ţ:&f9.9nca_݄zݭjQ)@*@KFDl6mJ!QJ#z"})Buv·w;@po W=7yf\N  'cM>Jk?f\*f>OBb!cdo"XqB6p~#];Y8Lu}c!tǠpÅ" qSo=c8N%a|=RV}y6M{ vl@ t8n1Bۊ3!Kd^6lTV$հX;4 wMH8yzV"Q_O;mrn%0YeH)Y0R!Ny٫(Fz *00a#5R-#sra}Y#ˠF zzf*6H`صI괺0V'\@rDT$bO~OكK< (OaC}Sp>g-Ya` [^di#-u_}^Î_^w¥~"&lZwPOsn!6UX=L 6c@U lHp6Ч~F?m(4,CY*UIWIM:*``8au^CC0#*sˑ8U^;p4a9w83o ip:BsR5#-),ct=ovekr96Y5Pe\`wP#Ҿ,d7@2̢=tb_#-:vo4'io4.W\Ca#Nț !MYٔ˺l0̂D(()p`DRAZ,x@i]8@!b+Qa\Uy#*vKIօ!;8i8Gw]VT"'e)%\ YNpO)ެY#%'Ϸf!Nxr=7[GQ_TFME賷p|ǵ*ODNUT-Qk$F؋+Lm- A+Q ˫&aA@BF+\E,?oif4ENļlGWBBQhЪIl*Iڈ|b-]vmxj7(56=4#%` FΝǰ{;#*U?Sϡc$@4[.s@X6LXr8_ Vڱ␧t$/c|%AIZ8 'qwZkZv!rP^O^~Lb6$?ϰ4p Ǐroʬb^@35#*n1ɰ݄-h]XgQ^Rv1P+\/#pBg+d5?]J#*H%{IAxyQ$ E8lxEl2ft_)z!!{~n0OY<5p0;&h7Zuo*S)tie50HmohFq9-r35T!$BfTjt82rWCͅ2{rLc:#%ؽ4= c+-h#%0[ChlrK.k /hgٸ8e3=fIs0JC>|y㯎d7⅄DI$C ň_ѷ$'336R* JxMjzרQ#%nŠ٨&D.3֝HAd]#b`_޸@K >Anp׈_--qî;-)9DFr('eCh{~&m57=PK$vd+m^10XO+I#*!eaJdMcΝ:w߯<Ǟ2NzTxBB663-{uičv,y0hBan#*JB@ZoS~"SgZƀ,B@"lݬslyeˑe1>LR%rКiEzI#*p&L̜5WHH#*t'I'GS&%,(@,+}:׻KXSrƛ@|pzPTΰa_S=3NP>F.hm"-#-0#*C#*Ͱ.[N^U4Pi;8ФB";CQ(2HC~$ґDrTq8 v2}&;ӹ,HIݧqvQz}7]p&lB:3pM1dN¥ LsdY `5ƭ0kd588H3!Oșa`r83kfsMɮͻ[ˆ 395xXCzj3\Ih}ʀ%Nn_~L DHDf |oRoYI85&Vt |:M}-nfz p#{C"#aCi#*W7ҹBAeeʋiڄ]hǾU_Xm($MπS<{#%4D7W\De_ww+cjV#-Ij`(I/ >јsQ1T&a0.0Xoph%/M͕di07T#X($hݑ&ht@/(ܯYTRIu_wetE2[F'_/m֜n]*x$v_a=Hc!CA?u_S&# R{* ?\Y6;i\#LAh)ZJChaw'<,Z4@,00Gkd$*8#%NIjޚp#*1|Ad+^Qs}v#%ۦyup< bn=U+W-KIfҤ[;P;DTDRA #%$E b0SO0~W}. Q(q#-(f%P]m2.X\` #*#*бyge>k"EJI1oWvzbQA%Qmy.n9\++G$N4B %Z eG:[[+?6HkoܮF !p%#*3,o:"*{2{0/i\;PqB>,@:z{gyib^-8f|ߙ5+a=~bP:)?g_>J'0;<x,SSwZ[x+~_j7AVD}U c%&N(^(m GoݦQOL_TҀrcF3Q#2#|'Ws0C>c)(vV]4#TmB J&phk|VH$-RswTzIkl(2 .pzvuۼ64Q2T]EsNeX5ּntC $Ē6>D9ArP3w9'xAS4A#%UPbMs#%HT"|&<}Lz(4Gw?*Ӊ{4#*o/F2Woo`@ 댋iSn <P:BAHutߒq#-\F>aF(>Z{ŌaHcLFU5s#Y,7vk:.!B)2zDcf6 g'*Tg`#%!`` ]3Jz"!ҳU]?}7;8?z w2/U#-HNWhP+a^z3|W dp11Rz$ DAtA@rI&]8#%zA3d4>&nuzae_K)#*f0#%@d|uxfP8PU֒;PX{1 Rx \0'&Q)8+#-B`蘬,Y֙6PPA܎~D!kq#-FJeK.vxs8f:^6lGnl;o}Bʺ<_fFWȽ()Ѽxʖs97 μgj#*6W)ٱwcSnũڌK`e)[Z5DH#"ob#l.kbz> Nl:}x ,Ly !s7k}٣9! ϖ*CJ#pA۶Ш*RzoէC,K2<*Ax? r~~D#-dôÄ͆fYP`t]wϜ0b7P5EQ$#% EB1D#-or sH:q7?_'wѸ۫uohؖ$F4"Xmk͒g-lkq@77#*7,|yܱ1P4ls#8FȋUR"!>8Vcgr[I iIgp1z@R_#*LAaoށ^u}yp#*9! X*%6N=Lo{nxwC]fmؗa(ꞎ6HDv/gaqyC4T9_GRIjtԍȏ#^d8_ *w߶$;Wj# m6ȦN9{;7Vu $d#-)^GKUM+MVbFlK);rXJ[Q}tR5 #-01FBhJhȅ咗i˹6QKdPHB+ m0d A* 1 H;ȣk#-lؘŜ{lNy!m݊&I@:H"%Uɬ_2>l!$|(@#- =qP$ .Cg]`Qb*Аa}2$Jΰy,&5B#*Ʉka ej oE=߽:8~#%WwlC0k1POT7#Q-ʳ{[馼{FCdpPPT؂4@D@Q*R1j1*dVAJHE #-,մS0#*O#%҄;mgB0I!, sh#*;5o$ξuuL&lll8f[v-У_IrCl⧡_גܙiio|1_"$H տyehgw'`v2OAڌ3#-bYz$VW l7bXO#-H9lLz~Kd*Exl֙0vq*,G(P?]r6d4MXE֊"gьf:JST{З+}:#-jMek-FD'n;:k3հ#-?j^ V%[j;ie.x/n&jټ^5&eҊ}z7et HQTyհIk`k;6u812I!g>#-#*#*\a>pPCi|@`!PU4:AM`&}X4Мmd(oYEu0DzZ'[y3#*Bg #-ZOah! -yyzʽkVFQ L)XhۖInݝ7ߛ@pJ|$)y7a ,P(!PI,ڳm=MȒ䵒~ɹ$lq-`UN=ۉhY*^-S܏C%56rK,Y"\8X,gB& IEU"1PzNki}ܙ;>׬7>}bQ#%<(tʕUI,ɥ/0" ^&T0}zJa;Y36k/E7VKumEӅXH]egHU2"j^d"#%5yR̕82HzN}G x+ԑjt mM =E萍>aDO+L{io&p<#*yR < &D)5EzRCc6rr#*4/ BnJ㱳89g CB)xuvcmka=xfY].2[(hNG}.\@PC>gƉˆW#*e2C OramS@ioUly#9#-)SFR*E(}6yg.0]g[kɚz0^Pf*"$dT&Y)Er0 ]k,l1#*k׿{S8*Aѕy0`-"DzyCr6dՅh0 jO]7VzBXuuS$ŗXhY /zTIq>ΐL vhca7&s{Goώ)>ԞK"kbۼHL|0SJѰx@Ġ6[`ܧ&eQdvjϰc\ϗ|8lJ?2sZ9ߞڥ:[VԵ;"i"C}'k%ȳKBװ&;CKgro7e8`nw^tGѸ#*2(b!Bi'<4%6aH-Tt1G04ѠM#%$4:?)%< #-5Ġct31J#*0 䡿g6{df %@;*G11rP#*lj7oaHIL !\aU6#/` (I .@z[}uk4R}-ZkFZƵs[smUb4.%@B0QMXQ'i#-T0#-#%(QY4{z4k_L$ əLҴ2JRiMQi3P~۩0 TM5(d(֖5۔2kI$S+f2bf0FU1)D;K""aIIe 5A2Pj-*4Q03F2d1Mb&Q4)#-j Fb#*4f,#-M;o6{jq@="> -, 6V"Q[T呡){ŒL7ZSYX`%GNX`I-~e CF7)PF#7! NE#-cN\pYZ^ˎ- *J#:GC[gq:ι_Iny2_'B>#*J1\GA6cFUW\e_3#-'tɲ#-35h6I$AUg^3_ [~n_l|Ntk70ר%3z5RE2 gfsoo=;þZžWZ|HyMs!2jTX ] vM+7.#3,PYn/^!ڪi_+rm-t[m*m6|#%0(7JwvgL}cǎHTFc8--ߣ8g^|4=%򨦦 fcBT}z{N03ͯ#*AAOSM#-8P½WJRt#*8L{0snm_&ɂ2;mLe~Kt+(q5. l6+1*S䇽^RJ vEnu7%4OFOfI Y|FIj5mwN݄o>ZRG0Lq0 nG@#-'l '=FNƭj'}#% +I֐!8RqW,Cu2ce:8AVVR,hBۄnOQdS҂,]>q;;c#*Y5V\0>xɚAwHkٝ2c s4W7x=u3BOF~wXF1](8[Ohe :5% NM`Bb x$D"&dJu6aoY>}c0篩>+[ QΜ$.vވYu|TzR3΀tmVelB7cXڇ3|6'Fv(n%:dJLjT]GK$:;1d9LJnMlɠBek0,##%^N:82%pd"^!\(2CAl(jS }5l7l-K a+a[*ɾU쌦!2(1 ٞ'rT;CCI̕O,K+Thu(v[=M&f#*51LPMYzt$#-&pU Skɕcۜ#*+33vc+Cp͹k({O/ R=:%)d75Tzэ̳5(q.<8跎P8έ)(mWligKd99hXbdhg mƇ3,+Fuꤾ(5`pGE5(n45Uhkc7ь7Ӄ2qY59(4y0:q#*'wgZ! E@{W{=ng|C41zGJwLM!6Dr+xST)P# F"h1TFLh\ҔqpP6.fѤɽCbBM$˕cik#TM9rAByΖRlYt73`p ,(ДgXml.@!lym߮p0&22G6Bz0ݱulJDCst11ʬxnBa &7#ʪpp wMiҌՒ`#*!iX!337a-F, M2x$AP8&#-av3̺Eہ0u23iTp#&wM6e&wUdq[#*;Ma-00CAFGԩ[&hɚVK]m_ͼY$R6@x3;iSp…#JDJ>L:W׶7Uѫ`ϼf1I#%"vC"P!(PJP 0&@0.#%8ȃb>2@ ❲}(=ĭ6dpq}85rh2n(beomdCIbna<\s'M.i4c`md2o݄d`V׈mq. #*::ޠ g|69#G'`{P=9IrbH$cHBխUӍ;[_Zh0EPRsXbgbDLǯB$Ґi#-PMD&({ ln;~aEU* l*#%vc[˛ ?BXS u@]˰xC SjwpA#%$3CH)ˬ9jQ0)0#%xnZzɑp`y?se}6|{/۸9VM' 1%HU#-ջ!#x e Lfk M1ȅ4bޢUJhtpavΑs.7 ӖFiE#*%4xD6[󷗐\b^zo$5MǸ$b-Z+DD6 2Ѩ-c|uhڅh!qh v)o_Lbs#*2wb"kDFI0#% #EV-ރ}W2Qi<&kְE4t/V*4E-E"g)VL"@!۾;#-NGM-eu}M4S50$yةB#*BHPI)vL L*,K@#--JYMn0.!j9Ì&m#*qSP9R'p@R$F"-PAĂFLƴ0wsLw߄TwKJ22ov^_&A> t]A9!R9{)r=h.1-FoREh- nQȚp8cɜEN).$6 cSbY2#5zPg#%QeH)p g#*d.Z¡QY/_C1$@4:"MЀ2~>cQsHk̯es#k'`DQ.Fno[Dda2.>L4a :v}_yMƎk$𹆰.""(oE8a)k[Gjjhql"I8Dd9:Wfv`ǓȧcBsHKV -@B-Aben@Hdհ,39|#*21#%}' Rdǂ.OSFI%]`Sc`T&JH0cVSB#-xuXc5AGԜeք¹òKO-4hwvMKgYYڷ4$ ta(aeB2۫AK-n##-aT"#-`Phf`i Hnq]сדc=A(ՙZ(ިGd3my5$Bљ3+ӊE'M=Ft6+Oka v<`!zd!#-3Ty$Q0LH5[9bE%$BDS4>LGf/RRr>=!8P_P'لӢe8L(LM(/ Uzr i̱UeNTd:cWtn6NI:RUJ4D$)gҁZmP#-v #I#*߹ JYFWBXlF.l @vL#*Nո N:) *H"k#*4Kw=ì ڽF#-#*zMNI{EA@#*-#r,!3`ZHtQ CfIW!T}d$g> #pYlӗ͖|I*/X*|Mi4 OZ0ICȀ@T A#%_?gƺ[ɊN8IXXXs>?d&1qL&CTZeϿyH}{E!dtN+-bD`,.h#%RH$úeiiArg#*CɀQE$J]6-vk4ݮSW6{yMWKmE#-R*fb)6qJ)AX-E#%$A 6cKLKi%-S6ka4R6`"JZʢm%(JHQ)M"jLѰ1ba[(ɉ,#*#*Fڔ#*EJlJdJIT6ڋYв2RcAIJddԳU6j"FRͩ2HZԳY2hҒZSl&H T*ERK{k]6liDA D,%؁mT[Qi Bpy:՞:Ý#DP#*t;[v]q娂#@ =`\唢jdC^o""y8iΒTa#-GnF=-Q$!ǦEk=+1Hbϓ4#-.<}]]^.ڙ-L YU7rRJ*RA,7ɂ|Z͐qw]$a",$аz`sF ,ڶSL ,.v=kJ #%TJTQ[lS0@T)AT'߶5640^+9mEWown(h5q"6kQ8R6q0rD8L;N#%R#%.#%XƪoUr$%I1)VFn)#"#*dN.ryCX/ו՝X1a{.f,/1ƨq!p:~{L=eJSKlֵ#*~f:Oӫn@\xy=}oKo:{ٻƔ=5e=S@[*rYE%"qZ"BY.ʃ/ݻ!ZeF6HF1FsUIcX #UgKF#a)FIʹqbaqV ՑMVp ijFsba#-"0[Kq,TCD.XUTƺ]$ *ZqrrHǭc1cF+ 0Fq;&*6XYJ*#x`]/hbݫ]rH|~ v*K7ݹ}nc)45- #-w|0%mŧ֪žwf.+jHdN܏Xux=ӻ)pM1H##ıTZIL0Uh9;%D(Yzwܰj.FiؒMC21R.oݑ3(z]#-!1A@̥KXڵާ)xݸ$#-JbeK˱]Q@EM:i!(˩]nI܇D䀌˳Caӱk"v11ҹW%x_u ٢;&ekZhՓjP:U2df`h#-D eZr*i4]6rbK#bm~ҭňƔTr(A1V$ hZ|K\#-vX 2ipu&Y B(AKPitΨ &BJ#%p,H-ሪ0Qld5BpĥO?^婰nXG%x$ECU9l#*a쑵ja~opi&,&2_ܺnjMwv"B) JZ#%MݾW`amTk7YDy V#*|='>X=PAC VTm|>',PgVE!@XZ@?E3~X#%H;\ZTt|ƒFHT7goi#**<@H,D#Y#*J}6LףAHw3E:`bɷhOMa݃<@hsOoA@TTi$!Y(c^]~×Ȅ# |!MQM>`s;/"\vz<ȠV6%[N[m]6bEJ@hFŘ#-#% .HMC&]fehajWF#-CxۍfBą_u#u'QXryV!&'(u7$칡ZhNڪJН2ZPQ!>'0*LV~>>\XN^9eP=gBl :MtU&\?xn^c9E3H0\fGVvI11LZ81;QbiLH>#%F9[DAP*h['z^$IhTUGs'#z"F^B6M3w4FEJjh(naj2(a7jn:jp Q򝦛[p5K} lDu!$@CNJuf4pG&in@c#%Dy-! *A m*g{)YTT}Ą Ou~Rdk#*bn(:C #%v.Ja+cEv56<#C#=Τ!'_XZzC]#%\v(|A‑[["ك RRdLŵ5&ƴdkJA&2jS2fJي%jZmek2,EZQM5Yim`PI$bfH_0`QBφtZ"j#-W G)%6ZSmQkkݛWT`6ھ}a ;S'7Q#Qs6(iomi$]ȹJtwPFBD2*4eQaH Y`12O"f.! 'ˁpQۼaS!F@xSt0$)P#%D#*jtCh~#%d{kؕtmW*PDnHƕ=O0bn&@T#%1UC32B*6'ۥL!s.SKff#rr?K B'N$N(izɓ$^Bh#-9ֵ"7x!PA@8-Wc(6`#%E#*ɇ$8;&k#*ji]((w#-3P#m`LӰ*bl.TxQC~Ji"VqE? >h^p̶a4 u #-|m!/ٯ9vo:)yH{9Mf੝?x܇&orהܸܸKAɅ>/LuڵnK0oC*Lk/Xy>U"qޚ WSyyyme%JYu$qrCL*v#-HLM{~0Bϖ&TR9h r=; WdD^1,ea$ `l/;J `] Z|-c/P#*3U2Kd e#-dR c*1Av'cֱ$HLy#%McMTPSΣis2I`pj{gXGxˉDpaE@1ɁvnTQ"Q@qsfbS"==:Xd"waA"a#-(%#*h"! "SBA 8(E z>T. Qv#%~f`]jdO$dG !ETBGU4I^sFlj|َb`61߲m雰P)}{xפm\5&+$l) )koQBsd)!H7T(dV%ȵTV5zJ]6QdP5ĈQT)#DQLEFzZ8`DHbz $mqII2ܨ@xsLK@DKD}E\x6ʆҹrȠmIv4D?y$($4#*q[BqK ]F[Cl &Me֔ je 5VWE[$FBq~#enʼnɛ 7#%&xgD5NmA LF#*iegѝًOםwQFizUUq.Jۚ7=5_ʭ5sxŔ`0KegvsXjp;#*t@Jy,@U)[U-SKo/"P ,Sd&·Ǽ֙=#%#%uEvϼDp7o S-@ۏvxۭG^as !Z{|OD$5Byht9Yn @j8=#-gggT52kӴ?>q6!=n&y'S͵]u^`&ƒbjĈ&=ɶ&K/J5JeQ(XAx!B[.J#*7#-J(H/&!j2+CCU-0 3U;i]<(nm봬nZe1j}!q.% eLL9@kK-m*h`B"ZKɶ!cC^=(Mj+(lYCYy nOD65! I2^ 8kh$SB9XݸSw6CĠ2b[1Ywo_lfs%35I KU8T\TX흀]9 )islnz" B&i4c8p|'D/!|<#- xeR#-@'!yYY+bed5Jk_r+Ob%!HSz`}:crQsw[j8@D#%Y!PCJr9Q,5QEv>jU` ۽l(8<m^I&)CZYya<5L$'#-0{\Y^==Iӎ1+!#%ZQ4$n@u8$#;pޔgBlw7h4 3uHro(:cft#*1%')|eVط>f c}7;Q篑-Wa]m #%G$/F%J" z/v]gPٱJS.Ƿ{Ia4m1SCKbĦ#A12xbpLmfh3oa16mgsM%T*1^t.Y/ρ#*]ɏX;|M!#`„U*278Oxmcl}=)nc}a{D{j;<+wvsG]`m $JBA-^HZ8539Q00G#%0\lT"Ŧ k ab!A1L$AuEηsRݛmñ{4X", ZqMLOvBLZt]*I0nV*Rpڋޟ,)߷߮"#B}LIbUؘ^2#-Y{Z$*mk_t=j\h6#% -@80ޢx#*NP1hm0z·to2Q9x"iѸtSH˥@~_4P"3kt( #+<$:Pfg<mݳ#- Ф#*K =L^# sƃjo4l^6X"03zt錢cK#-eИl %&LHay>X2r#*^4HAYeȱG#-L9u<rIm$_eUlգ+c!3İ92 s様gsI;`!-h#%0,a֟^IA^SчBUYxPy5K#+ց?a@;"#-WO33˚ƽ9#-uAIjP*6/Ekm[cE&k ȣ"(Z QTEJfك]Ύ3 D`;HPF8nU^ ZH1> 9 rX^!BQIY[r#%j6 'pS!mAHkcakc$a/ܽ#-!w'm;_#%H##*OBهT7iEpܑpt?ǝ:?6L:6e\<&#%~@q^=v-|#-FCC[|C0a#*ڳͪB |@2JBjlſL"%rc42СPw+U%UIA I?zfx&qKT]" yKJdK-˄TH Z#-6?{_ HM"uA#8ČCfUH&P;|c/cc05@a#`1b2TPW8|p:gT9~#-쩔;w\Zy;rʭǤ(vUEZdvq2,Z#%f/  #-91>n<|bIf*Oaٴ>ڴ>:D&*TVk06αTHȐ>gAQє4)8{QB@jx5*@ TTPN .봤mL-#*uō#%KeA, TP$Ie#p`24KRq 1 7h5PQ8:5*bTa$9"ƴK hMi⍭نqUd?-ŒʨRk\ަE%ZRt묙RһOSnzn] !a{IL#AQ^wyy+^x֛52lf61jI$Цc%fdleyuM;sW$n4\כ&-C1o{=ɦj#*)#*66 (zo4KMlTeL̐#*AvX==Ycn#*dZ #*}Vj$qy&i@r$5LcK` 1iyV*BkR,5B$!JX6fY-V-uSB1\$b-f%`#FC U6Di6ѳmJ+dEaFb7H0lAD-ea.5DRDnf;jc ߿.q#Q7! nL@67v:VW ˉԆHG#*2l QΞ%7fޔy *n`x{5<&1dx8+skh gjF V4.#%cFٍ1*<#-XPZ1XTs2G;M.jg$%0l#s&>?wYiYp;zX_f_᎕r)2B1Ehƾ!Bi*H^"/qbܑӖB-6ҡUIRZ8١ᇸiyXcKYCHHhqad W*Vc(3*ߢM<ᆀJQ@1҈ nouh˷a#%a t@h`!XNKM@9=av ok4Q.tNDܭ..t`U0!TiLz4o8dJߟ\uB^-yuUZ:q/&#F;K#{{Nˏt{4z6\ȥfZy%8x|=:&j-sٻqFb!$-ۓټ讼6q#-B#-Uӣlduoj,#-, ɘm$G/Kl!]Wn^t8/z}0D9EFm`M#*1L(qC&?#p8=G+(~YךnxW#*M,=6ͪ#-z&ݕ2eCsOdMk! '+F?͈t׈0D+M}=~S.>9#*` +$ PȊaDl- u%>Ȣ*$ɣb#-'QQ]yl%O­]4B2$"I\ri[=F*6m\k[W+O^AQqJ@ 1-~vo*(^{\]-kۋ36h "`!|d5T.=s#~A 0vVKkl1&k%WqwH\y:ҳ=.j2Ap3ix-eFER+7v*fJF-XͅZ!`0RDIUPXŀQ#%i3*DPrb{޸3Pa$nc^F1gG݃WS9r7ksR2qewcvtLci9#%US@$ANI! !DS-3AT>gj2?? d^)N?O#%s?Tc$hc!=NM>:8 ~C8=g-#-\ PB.QP(̅ p߇ NUQK3~#*D4D5M&ްؙwí֪cBb[IAP#*Re+Hh#%"3H#d6 TL2~`ߵ}i*OuR5}6Ml#vF7F*9g 'zT#%lo1dwsmj]bIC#- p#-P#1/q(m@Te0 6~[WEywˣ.ZiG]/!)̼Vl&FljG1PA!%Aa~5yv]M#CXQeU64jBXq"U^߁ (҃b[ᘑON2LlE"7u(71hb)#-5zk[zVґfȶeQ@LF* \VLRD(Wb!V)D0|OC6&qx#-#%lAMVV-jEl@HCv_B:Amf5fԍ-2f(IB@: ώIRjKL[_$V! 7LwSk` ӜTb4#%#%5$hjQtz=]?5'#-AԵi#%3C,8j~x*m1dHĚ#*R]|d$RICΜts.8/!O#ŚNGxi X#"X$QOPM$q #*n!GLF„%0 0#nb+5!*"Ԁo3PƆ٘\.RH AL R$Љ! @X(&eIvlI$T8‚OoPʫ3p~$.*j#%.fw =DBfE#-Yf$AABIHGX #KT-ѺZƞ^v6L4~y17NjPR #*TEU9?׺؍+E3JӶm5 0lAjB)$b+CAZ؛4'S(H|Qkǜ>ضnk1mոgCAUa9gvC0#L`6v6BY2LpP4ºx553n] %cdX;wȵ/*+Xin97͚abxpyjtPO;QRH:px ꈦT]jnʪ3!V!#-'lg2;_#%z/7'xs#*C =~=Y$T>KAdQa76|# :1E7 xQOB_ұNJ#-$6vmƷK+.ދX|QD%y:nkA^/MPf#*KFZ[^"#-p~my#*Lũ%C畱.IPDbS*[P4\)#%c0cFYmVЅ$جO{(7#*t4qahc!d$p#*2`\щh"٭b1ǢM&T}!c<;UocwFGD!bIE^Yl RyiΔK޴l:İc{n}>h%p喯c`(7k񿎲<-0x"kHˌLKSxݷݐb:oZ46c8CgB~QMAR2kxNFD1#,b!,bAKՖ̆@ޅpzeWQV-#%#-#*U0ݦ`JY.YW/nm/k|ͩ`zRPDAmJ[~W;w_bLG8 y@FFR#-"ӡG};l86L[VN"%*gu`P3_#-eUZUY9ƮA"5-/JInA1" sMÕ5vw+Q|\vaRkZWԨ3En_RaqbժNMxG"]#%;J5]c02 JplTg!@EDKڅ; #*X0&˖fuw-*z-paG/7 >?#-R ܁ѹƙUcH<-ݧQ2$v+J t#-z1% ޵:q,4US7VDLC'hWy던G^MA0{{Lu3wh*}_v CHy1P{ bA$_ #%$PG!G/` *$$""@ Cyzx*G^ԙ @E8 G$zv|\@>P|p|sGټwx֚H#*,݀9ݫjxdы>Ey#-,7rHP!**?Į}S{9!GqGɯ;ro3t9YP&7lw*)0B'|@1#-F x5SmZl0'ˏA(y\/bJZ1@q+ZO3+)"SaDd[-@'Ӗ jGF(5@L?J!o8lї%Rࢦf4Z6#-iN[dFz(cCQ*pM;V6}^L4/H_l|s{og?mRJ>!#*;_s;[QU¤Rt:`#AĄIдIU|)+1MpAH!V5FX$~#s^`«Ln39/.)oo+%ˍ4!0t#%gGCϮ,BE#-j0b]d=BdV"6ݖc!emVmamtoU9 Vc%azy߱r6oI]ք@CI H/*r>çf[W a<zŪFšRH2ex؝A47H ak-y//g-{Yl-4#*e7 MguJm7WZYFBi(5.\D0L1Xa(8 ! c5C<"HdD>85KwV}UjMixW.,5mk[skəPd+1BD#*t;p ɢ.x*U7 j'F\\1HM3M]0EpB2m@k3P!BJD3rz#U%\mpXoE+mq#*Ŵ&ʱ5ٸhIU\Pe*B6IHVmDFSjH-M#* @E9)Q EA0lqGIc,jm1a4Ʊ7 "P`ՁYP؞AuʫN(6m`$#-aqhN<ێxIhi6TJT-1 0F6 !:ԯ@7q7*p]Eښ.%PH4Fٖd[Wz3c&G,6ц9Hv.rjh2[]@:jАuii&I"倎R<96"2Hv@ވiiY#ڋ'c"fጒ$֪piRiNiV^j0R;`1;#81`KCIƨ&\EfFHdEUA<c:1&0#*bb XsȱTn61)[) Y#*L!ILsC#&DT DiQ^s\5ڼk.2#-cF5Tp[|pb|ɧ Aء)"*K7Q=1AuFg. d'̓4ǸjN0fc4LXi h"-\{Edzy[[:0&AGSF"#*NV0ڼ.Zoq,([W|B̭~92#%DUb\/dV(j`DM1MR qbq1l*R#-*A6,`L.b#- ĹPHX(ȩ0`#*#bbX0$RD2#%pT* wpݼOy]1j2Z]]-:zӹ+_ |%-MLX0"jChқM#*Uuw /~ *(!"g#%H,[TF5!~4QZL9ܬUIhM3YڭiXm^$}JJr:r<66`QT"N+`Y/5Q\aH%7VC'*븱wvcc[)KדKxe1;Z2$#-4Q*~(qf滦\bl!J5$P"6 lt_ #*&mvwƺޏvʼnˍ{*;#*/'e3Rpdg`:@{]رjk%R;G>ӡWA+p#*gT'f@&NgIĹkDvnIY<x b4wfC/X,^bK\ًىpNu>};PsS̻C#-?ݬ O#*.}s-X>/S0Awy/7ALcPM=AW֝d礇Tz3/(w(*`b#%M}kYnФx T$bh9q/6ύbk _֗X "?6UQU95j#*&t0cZb8z1Vڋ #|"r:q5tlh,"k疽$U.Fɽ+d{5b[wV-pTk9[5d55\USݮEmA5W+ƫ ᆈq;e݇s;|O3 r(̭4ٌę7{d-"C|E hMCD@I;7#*h9<*/#~`}{B!t@7I,|;&O 0~^%hi#-&"FP7D"W((6C#`a#%ȠH #- $E(Y$u%ہ#-L{#-$#%#%FY#Fllj_#*GP#%O xuv[dѠ_pm 4oqk#%;f_H1"#-xzY7C} t{u ؇|$((E36I%(PXť2Ѥ١%)II)hХm6ձjZVjZ52b֋ci5[Ϻ<׮ sEM*H} l#H888cx (ƣʒBI;Z:PLJR!ۍN1'רus>X> *5yex3ƴgi6.ݙRHdyqM(#%9߽`n=u0bY|u\km '#*w;ju=s*\q*4]pX΀~GF + G˶g@-~c7;in4@$9 SĪjU L{p!&״k`"qEkviCEp=)؋k1(pq001q^9I,Q￿9jl'I!g7tVBZS/ yo#߫%&sX*77/cMr!|+NIFQtc]K=dʇhe3߿ĹW5+iųXɄtLFW5r?n&ܬ=蕇8`FV;/Ow*7:,GàE7,$. Ljڙbt6[6+g#*/+6kSb1Ĭh 0 N8u<[Fb/FL7dgyMQDZE_w8 %yxF4^٨"D:]|gR֓Q{>TdL\{a@Aq=0{Z1Vg3\uNq]Agjw}:'yOܞ}cJWjM3C#*Ƙ FT]ht;,KR|(tW+Ol=(IѼFz{FqzC50˾%Lpojs>NΌH˕.YyEc}]%`v؍豋xuӋL8FejqM@Č B7^CC'#*Rpڜ(ICx ȓX͎Hż9c'X梄QU6ETӑ#-,Teñl|dpC^[Pa'߁P):>{iv}:m!*k(Ŀȅ7@|̴5Qj1].JkZkN21D cʗ/ n,Z ;,JFJZioޜŹ'L.x[CUZ=2*-FO&s;a~MN|0,<"kC`Saމy;K\r.7 )sf\\=#@У۞R}( 9UI#*Bx#%G7vcd&ssfq~;X-T,Ǚہ#wIܘD_w@y9;1cUS-Icuyzw, h#![kHポ"!0wQ;$ZC{Z+mٶ2/.kںoj֠#-*#-5CY'BB{?vҾv`Qb#%kl(2cƐ-jV)lڱIFؒhТ*6C64ԍ)6IF)!%DhcM5-!LR"MZIa$ƭ`#%O"f{Q#-{їKwMֽ4nf*,z:xZ9|TGKHZK#%* >'ahf6ؽLKnR&#*s^;EB@Xlݼq7v[ãr(Q7^I$. kz4Aoz$jEկ~G[-ꖧ5}֭Q-m1ƏkMJgvBT,%wE$fP#-?fE6 )f.#*>P‹P1n~YtjcC82V)#fH,AXiEh'2Ԗ1b ϒKbʩB5aS]{Jxh̥lYpxcn. V#-8 ddV$F%71TD9^3bf#*~Nβ{-_`dFz3IQJpl"еGi5fL~n>#%9D: >Y!""" 4Gz j$|O L-}'5>7e1 i٥Uta nʘxg#%=Yl{`0ƿƟG~<D~TH%K<+"VGNh\%B:teC>Evk%+zCOClǹ kE,3̈́JSuUtD19Wώ<8GeS:ch` ` ?UK'9aNT]˹XF `HԤHV\0dpc#*҄QFi9Ѐ^À,ώb㳹NH:ipXƑ$@n1#*s٨mЈlA)DJeCtA$rIvE)t=! oextDR,ӡ[#-#*&im4$Ƒh ik[ yI4RB.e]o4{qY_$ˑьvvGʸH(و:@k#-삷!RAV,ᇉ$E(Ho!@3k9Ç_'LYЫz P Miqm"vnmS#$P\(fZRV2љrY#*&Ƅ˒b9dnaS^M) Ub6A:Ne;mKI1LZ)hIcgirެ m O-e23LFiIƥ{@bsqWEh4PdC=DeY,9$$φM"24#VT^)aN黧̛OIӻR-qu!h5e .W+#-U*fA.Sz$d B"9r-Y_gb`+w9t95ih+BFčfqYs<:kWL#**\\ζpq:ù_Z`ppN(7)>Q \8Z+k͛#- MArXʬF@]vv#-ԱNSu p/-`bV讒 9v5fl28%[16M&\q4柁I 5VIhy:7.mpQ.΂:f8D(˙pt#*kM'2qҶ41C0%!lz;yy9kt ƠrjΑofd'=Zb>ϗ!|Fa[&&7, VJMjfLX|rX[[#51 -kzmpTBRBY8AWa С0'SDM0Ӱ.;Y!%e;tVP%(fcn8,풵W0o 1#%Ri"4Djݖf|H'#*P#*ћZ6l*k0#*o)9Qvdr&dt嫢+aۗ֡#* LӴ^PQXgaat&nɦG Y.\տ%7VGT1DMjC:X͈(ѳt`L51#*0$p$#*"GɆd ocIWJjwݳ<]nhF[hVBf8.*n09O;q(hھXJ n#-r? ndfh&[:XdQK$.C!r6}ifB#%le\TQ!D}"#%UJv $vh'VaǮhޚd<Upw%Fdw*De"diZM3܆tGV}dM2d4tݼqOɭ+pD1PC5h *l#-9smk祝rI[;L> _Z#-$U=eNj>:Ι3| #%!'.LH dYCUREK (i"pd&|󁛪fMj"6·"RC&J-jwn#*H $H06B#-AvEF THF 8iQ)3 ǃ#I#*aS5'T8ڡ,uU,ĢA;wC x-!=PDg#0Մy1I[h@bt㹆2"VĮL+ؚ$p ߿Et Ng. -< w>9CF*SUyfB#-~"p3&#- 2p^#-0e,0yɩ5،͉ۖըqYT/1!8]7uvb#%omH|=OPcnk$ D¹KogkLY~]h/$0-,V"$%3Y ס3u!?&Ի%EVۭmkJBmRmEa$TpDXEnYq qMT#*U#* `ApB$6"P#%ߗ"F!tL"CoUՁ'M.ovdF a#%BD>aTUÌ?X$ ,Sˍx}#nޠ.<y8:><5#*fG 4aePM2Xl+%ʫEEyveۜ^+nRk&5fՋ̵BYd8ku 3@ԨJf*R&ۙ`9Z('w(oCK I'dкnci vM;١ΝF=w;187AQW$#%-r"/~$$ڲ3W:*'õnq5 ;ڡ7` @#%-P4&tMI,|zO0P'x(e[̅УJM**rD9vV`ReH?J{OзRk/zhL#7`zm(< }Q:#%M4EiC:Dq%(~zI>ȭ(^W/$jIie1Y%(-VŬbE5&߭qCFfN!y8U#06wFD8St@;ϩ#*#*޾ΰU,N=lEW6}=xǗPvF 'SOy#-d@9Rg=PϾJ#Zp#VF撁%`I#-o6^ڜKm5Cvx6٦S3 @047 G˹,O6PjaWDM.\$haH$e뫊>gn8َ@li&mn*.hea['\^:eFs;04F e#-7к{TT-VS+"9o ,ŪK,5c.- 6>^#-#*R/?ek<:#%PK"Ȑq!2=$zWZ,U!f,ʹB{C<@R#-dXȥ)E4jF}W9h55EQL_T06o@ DLJFEŅ"6wT$"'|%zw\V(ѳ#T- O^wZ66j-h䈚fWy=癇fD_lB=1T,+BB!$XC5q@sAsd&Q|OP1Ǻ+~W) Rti1;3s&#0C6TǭbՒ#*"aOz@P^!BSjtiIiݺMjjM6P&'ME¨FR(+\/*SRu.:ΕC^4$pÇL5W4&cĴL-]_K#-CiXD;^X؃|}avg!~D=EMBc?=?ί%4SD۸`X(N<}_l3໶wUHmoTkWmF-ڔ5F6XXQHD'Ȕy1VY) ~_m`Q#%CF\a\RaW,pJ_iU㸪6!ZQ?K-2!RvcY0(%Ak[[jhKzͅvӻ-wvWyuMɭW%Ef̪뛶vm^]Dl*dJlkyCE*pR$0p)o"o%i^ˈ֥4SRezV]^y֫EemK^wrݮe˦Mrn{^_" 0 hFрtA?`וUfjbɸ} FB`hٞ-Sm\[Y[Fh&X#-rɃb vΐ\M4Z`\](\=$޽Sل#-DV>:yn؆b)uFB'" t2cԉS:ghg { }^&f#-v}yD"8QH%poRcxka#%#*N$aTjIf6".a, 8;x_j>V]J[ދbx܆q&8&Z8A-E*8ʩpXס̤FȒ`>wEu 3OC0FW%#-DGTqf󂛔F 0TR'4^4=&@ՄBn@ x&lgפ9/]!p3 % T"jJ, nYƀrR&`씹 X ziő&N3\BCJ2f!T#*omyǂNV Rud\Y<89gWnFw[{}tٲ'p2R}Z TЊ9F!B2}(#-@J|erPհPȢŊr;QJaDF#-b2bsbzB$H9#|=jr;|_h2YiS,A<7@&_<&^|YY?yB-6sӟ#-"h~'v?o2mH_Oivy&VufuǓ;Oo dK7N+D?[2D?{'6#%*Qv%?%na f.PmwGg2_!/DGDRM!((~Y]N*pMp9SCD 29xFa,o4N*%+i(jR3:Ŋ/˯%k,=z l B|֯VAKJC#IBtʺBg/}XPp)„mP #<== -#-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEECzlystnjLqtCPS4PIr4MYv+/pUgFAmG5Pz0ACgkQIr4MYv+/\npUiZBA/+PH1uI+xWcT67oY7I5kcHxmriqgaV2vskG9AdNzPIRgJ24xYNR6KcZVDD\nrPiKjAfo2YgdLyglXxYttEg7t1QTXZDLnEf/19AoCwyQDqf+GstOtmEEYVRluavW\nwykb6FfR/ivth3UZ9Qb2ZREIQq3CpKfoz+IcHTECrGTbQEmUtR9pBTxhne2K0Bgo\nTz+aIIMePIVBn/8rRwja59D1P4PabvkaSGwyGtX2wdeDIhJnN3Ak8RoBw/xm+rpG\nCyZVLks6m2LFqN8MvJacytKlaNTgGN+QIjYlEDqQispyxi2TKOMl9hThQ8ZrYByl\nHaLnVGJgqJJpvnsymUORRRUtNYrKw+tIpizwjCltLBf1eFk2M62qCZaNr3aRvKKz\n9X1i4tQFKqSfZeKxeAo6C/AnnTQ5y4FWvkjDveZZmAe5x9LCoJ+5DjXQaEBC9X/x\nl7RhvMnrXF5Bhom67DaHaemc9Ul2qlPD8Q2LjgQbis74TCGtXtb2dWorGtsVtOMt\nSc/9hAizDp2Hw0O0d+dqFHJ45VrVYgk0tmIwOQMAeSGKtqggzajkfxUQIDGUMbRR\n43FYzxp8YcdVtQ0XVsj0/8VejiDct5YNSJLY2glxhBt6yHrNHdxBCgP5eOggErOL\nSs8mHWtqaglcLRchx6+6HgfemqCiERxmZAvTtzsWW/0eBYRbn6Q=\n=BMpA\n-----END PGP SIGNATURE-----\n +#-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEjH6y+TsMRfVzL+XRG6xXHc13IpUFAmTW3xEACgkQG6xXHc13\nIpUBBA//X7EWVmGwG3rZIELAPfIre2RAFutwAtZwi10FhwtSSPcQDUW0oMgYgbea\ny8tCMpLXEPpPC2OQQhoxjHrMHu+xZwwKE419fBZjzHCbJ3HCfKa/K8zkJfRhFQgI\nnR4FxsCvWrV7RVoqKucmocGAsoTy4BAuPPo86cQSiAK+baHwP3Oo1O3zedXoXaBc\nJoNO1Fl6ldGSDh4xrcxonF+YomjWXgCy1Vplh2SD97c1SKREMtVpls3u0hUUg00q\nOXqfWT6MSdrAGq1zGTGWVl6/SSiFTlkRQhJfaNlA4uRdfYx37x4wuUhI1+OQpbyM\n6qs7s4pvbLG8t9tMB1onAJW7K92VkYxNjP67AeXuKD1HK9aJFw5xmPa3oIS9Cxly\nFAdoXSB99Y1QPNOgyPHqY0qzWiRyKK3melcsGE3CniKqQUosuzeNzagTY9hN0YwU\nB2DYQX6Yar7VLbaHROtk7KPs0nOnDgEezQZ0ZNdEbJXGv16n3s7QGD0w/e3tI3Ow\nC5vaBIGC/7iwoNmw0+iu//Cczw9Vjl1Ga5o++cpA2KCpwFB6/XC9OjEp3TEuPUf5\nn0p5BLFk3BsrhfGhui/mE+Q08LEFjX1yvWl6zyPEa0Gb9al+scK4uco12MslIV6H\n3GS7C2mrZVe8Mdjquttk1o/OSm7QlZ5Ej3aOlilbhiT6D3p2L1Y=\n=4SRH\n-----END PGP SIGNATURE-----\n diff -Nru ndncert-0.0.5-1-gfae76c4dc/wscript ndncert-0.0.6-1-g3e4818421/wscript --- ndncert-0.0.5-1-gfae76c4dc/wscript 2022-02-17 21:20:48.000000000 +0000 +++ ndncert-0.0.6-1-g3e4818421/wscript 2023-11-17 18:39:05.000000000 +0000 @@ -1,44 +1,56 @@ # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- -from waflib import Utils import os +from waflib import Utils VERSION = '0.1.0' APPNAME = 'ndncert' -GIT_TAG_PREFIX = 'ndncert-' def options(opt): opt.load(['compiler_cxx', 'gnu_dirs']) - opt.load(['default-compiler-flags', 'coverage', 'sanitizers', + opt.load(['default-compiler-flags', + 'coverage', 'sanitizers', 'boost', 'openssl', 'sqlite3'], tooldir=['.waf-tools']) optgrp = opt.add_option_group('ndncert Options') optgrp.add_option('--with-tests', action='store_true', default=False, help='Build unit tests') + optgrp.add_option('--without-tools', action='store_false', default=True, dest='with_tools', + help='Do not build tools') def configure(conf): conf.load(['compiler_cxx', 'gnu_dirs', - 'default-compiler-flags', 'boost', 'openssl', 'sqlite3']) + 'default-compiler-flags', + 'boost', 'openssl', 'sqlite3']) conf.env.WITH_TESTS = conf.options.with_tests + conf.env.WITH_TOOLS = conf.options.with_tools - conf.check_cfg(package='libndn-cxx', args=['--cflags', '--libs'], uselib_store='NDN_CXX', - pkg_config_path=os.environ.get('PKG_CONFIG_PATH', '%s/pkgconfig' % conf.env.LIBDIR)) + # Prefer pkgconf if it's installed, because it gives more correct results + # on Fedora/CentOS/RHEL/etc. See https://bugzilla.redhat.com/show_bug.cgi?id=1953348 + # Store the result in env.PKGCONFIG, which is the variable used inside check_cfg() + conf.find_program(['pkgconf', 'pkg-config'], var='PKGCONFIG') + + pkg_config_path = os.environ.get('PKG_CONFIG_PATH', f'{conf.env.LIBDIR}/pkgconfig') + conf.check_cfg(package='libndn-cxx', args=['libndn-cxx >= 0.8.1', '--cflags', '--libs'], + uselib_store='NDN_CXX', pkg_config_path=pkg_config_path) conf.check_sqlite3() conf.check_openssl(lib='crypto', atleast_version='1.1.1') - boost_libs = ['system', 'program_options', 'filesystem'] - if conf.env.WITH_TESTS: - boost_libs.append('unit_test_framework') - - conf.check_boost(lib=boost_libs, mt=True) - if conf.env.BOOST_VERSION_NUMBER < 106501: - conf.fatal('The minimum supported version of Boost is 1.65.1.\n' + conf.check_boost(lib='filesystem', mt=True) + if conf.env.BOOST_VERSION_NUMBER < 107100: + conf.fatal('The minimum supported version of Boost is 1.71.0.\n' 'Please upgrade your distribution or manually install a newer version of Boost.\n' 'For more information, see https://redmine.named-data.net/projects/nfd/wiki/Boost') + if conf.env.WITH_TESTS: + conf.check_boost(lib='unit_test_framework', mt=True, uselib_store='BOOST_TESTS') + + if conf.env.WITH_TOOLS: + conf.check_boost(lib='program_options', mt=True, uselib_store='BOOST_TOOLS') + conf.check_compiler_flags() # Loading "late" to prevent tests from being compiled with profiling flags @@ -59,13 +71,35 @@ conf.write_config_header('src/detail/ndncert-config.hpp', define_prefix='NDNCERT_') def build(bld): - bld.shlib(target='ndn-cert', - vnum=VERSION, - cnum=VERSION, - source=bld.path.ant_glob('src/**/*.cpp'), - use='NDN_CXX BOOST OPENSSL SQLITE3', - includes='src', - export_includes='src') + bld.shlib( + target='ndn-cert', + name='libndn-cert', + vnum=VERSION, + cnum=VERSION, + source=bld.path.ant_glob('src/**/*.cpp'), + use='BOOST NDN_CXX OPENSSL SQLITE3', + includes='src', + export_includes='src') + + if bld.env.WITH_TESTS: + bld.recurse('tests') + + if bld.env.WITH_TOOLS: + bld.recurse('tools') + + # Install header files + srcdir = bld.path.find_dir('src') + bld.install_files('${INCLUDEDIR}/ndncert', + srcdir.ant_glob('**/*.hpp'), + cwd=srcdir, + relative_trick=True) + bld.install_files('${INCLUDEDIR}/ndncert/detail', 'src/detail/ndncert-config.hpp') + + # Install sample configs + bld.install_files('${SYSCONFDIR}/ndncert', + ['ca.conf.sample', + 'client.conf.sample', + 'ndncert-mail.conf.sample']) bld(features='subst', source='libndn-cert.pc.in', @@ -73,23 +107,6 @@ install_path='${LIBDIR}/pkgconfig', VERSION=VERSION) - bld.recurse('tools') - bld.recurse('tests') - - bld.install_files( - dest='${INCLUDEDIR}/ndncert', - files=bld.path.ant_glob('src/**/*.hpp'), - cwd=bld.path.find_dir('src'), - relative_trick=True) - - bld.install_files('${INCLUDEDIR}/ndncert/detail', - bld.path.find_resource('src/detail/ndncert-config.hpp')) - - bld.install_files('${SYSCONFDIR}/ndncert', - ['ca.conf.sample', - 'client.conf.sample', - 'ndncert-mail.conf.sample']) - bld(features='subst', name='ndncert-send-email-challenge', source='ndncert-send-email-challenge.py', @@ -99,6 +116,12 @@ if Utils.unversioned_sys_platform() == 'linux': bld(features='subst', - name='ndncert-ca.service', + name='systemd-units', source='systemd/ndncert-ca.service.in', target='systemd/ndncert-ca.service') + +def dist(ctx): + ctx.algo = 'tar.xz' + +def distcheck(ctx): + ctx.algo = 'tar.xz'